/* ===================================================================== app.jsx — Cotizador Sell-U integrado dentro del layout público. Sin barra top propia · sin altura 100vh · navegación inline tipo doc. ===================================================================== */ (function () { const Cz = window.Cotizador; const QUOTES_KEY = "sellu_quotes_v1"; function loadQuotes() { try { return JSON.parse(localStorage.getItem(QUOTES_KEY) || "{}"); } catch (e) { return {}; } } function saveQuote(id, state) { const all = loadQuotes(); all[id] = { state: JSON.parse(JSON.stringify(state)), createdAt: Date.now() }; try { localStorage.setItem(QUOTES_KEY, JSON.stringify(all)); } catch (e) {} } const STEP_TITLES = ["Tipo", "Escala", "Plataforma", "Estado actual", "Contenido", "Integraciones", "Soporte"]; /* ---- Barra de progreso compacta (chips) ---- */ function Progress({ step, canGoTo, onJump }) { return (
{STEP_TITLES.map((t, i) => { const n = i + 1; const isDone = n < step; const isCurrent = n === step; const reachable = canGoTo(n); return ( {n < 7 && } ); })}
); } /* ---- App principal ---- */ function App() { const store = window.useQuoteStore(); const { state, setField, setNested, toggleInArray, goTo, reset } = store; const density = "regular"; const [error, setError] = React.useState(null); const [showContact, setShowContact] = React.useState(false); const [showGuestModal, setShowGuestModal] = React.useState(false); const [submitting, setSubmitting] = React.useState(false); const [guestForm, setGuestForm] = React.useState({ nombre: "", email: "", whatsapp: "" }); const stepHeaderRef = React.useRef(null); const isAuth = !!window.SELLU?.isAuth; React.useEffect(() => { if (window.lucide) window.lucide.createIcons(); }); React.useEffect(() => { setError(null); // Scroll suave al inicio del wizard cuando cambia el paso if (stepHeaderRef.current) { const offset = stepHeaderRef.current.getBoundingClientRect().top + window.scrollY - 90; window.scrollTo({ top: offset, behavior: "smooth" }); } }, [state.step, state.submittedId]); function canGoTo(target) { if (target <= state.step) return true; for (let s = state.step; s < target; s++) { if (!window.validateStep(s, state).ok) return false; } return true; } async function submitToBackend(state, quoteId, guestContact) { const csrf = document.querySelector('meta[name="csrf-token"]')?.content; const url = window.SELLU?.cotizarUrl || "/cotizador/enviar"; const quote = Cz.calculateQuote(state); const time = Cz.calculateTime(state); const platform = Cz.PLATFORMS[state.platform]; const payload = { _token: csrf, tipo_proyecto: state.projectType, plataforma: platform ? platform.label : state.platform, paginas: Cz.pagesCount(state), addons: (state.integrations || []), mantenimiento: state.support || 'ninguno', precio_estimado: quote ? quote.mid : 0, urgencia: 'cotizando', whatsapp: (guestContact && guestContact.whatsapp) || state.whatsapp || '—', notas: JSON.stringify({ folio: quoteId, tipo: state.projectType, escala: state.scale, plataforma: state.platform, recomendada: Cz.recommendPlatform(state.projectType, state.scale)?.platform, hosting: state.hostingOwnership, estado_actual: state.currentStatus, contenido: state.content, integraciones: state.integrations, soporte: state.support, rango: quote ? { min: quote.min, mid: quote.mid, max: quote.max } : null, tiempo_dias: time, }), }; if (!isAuth && guestContact) { payload.guest_nombre = guestContact.nombre; payload.guest_email = guestContact.email; } const form = new FormData(); Object.entries(payload).forEach(([k, v]) => { if (Array.isArray(v)) v.forEach(x => form.append(k + '[]', x)); else form.append(k, v); }); try { await fetch(url, { method: 'POST', body: form, credentials: 'same-origin' }); } catch (e) { console.warn('Cotización guardada local pero no se pudo enviar al servidor', e); } } async function finalizar(guestContact) { setSubmitting(true); const id = Cz.makeQuoteId(); saveQuote(id, state); await submitToBackend(state, id, guestContact); setField({ submittedId: id }); setSubmitting(false); setShowGuestModal(false); } async function next() { const v = window.validateStep(state.step, state); if (!v.ok) { setError(v.error); return; } setError(null); if (state.step < 7) { goTo(state.step + 1); } else { if (!isAuth) { setShowGuestModal(true); return; } await finalizar(null); } } function back() { if (state.submittedId) { setField({ submittedId: null }); return; } if (state.step > 1) goTo(state.step - 1); } function editStep(s) { setField({ submittedId: null }); goTo(s); } function restart() { reset(); } function submitGuest(e) { e.preventDefault(); if (!guestForm.nombre || !guestForm.email || !guestForm.whatsapp) { setError("Completa nombre, email y WhatsApp para enviar tu cotización."); return; } finalizar(guestForm); } // ── Resultado ── if (state.submittedId) { return (
); } // ── Estimado dinámico (preview en el footer) ── let estimate = null, estimatePrelim = false; if (state.step >= 2 && state.projectType && state.scale) { const q = Cz.calculateQuote(state, state.platform || "wordpress-elementor"); if (q) { estimate = q; estimatePrelim = !state.platform; } } const StepComp = [Step1, Step2, Step3, Step4, Step5, Step6, Step7][state.step - 1]; const stepProps = { state, setField, setNested, toggleInArray, density, openContact: () => setShowContact(true) }; return (
{/* Barra de progreso */}
{/* Step actual */}
{/* Footer fijo con CTA */}
{error && (
{error}
)}
{estimate ? (
{estimatePrelim ? "Estimado preliminar" : "Estimado"}
{Cz.fmtUSD(estimate.min)} – {Cz.fmtUSD(estimate.max)}
) :
}
setShowContact(false)} /> {/* Modal contacto guest */} {showGuestModal && (
{ if (e.target === e.currentTarget && !submitting) setShowGuestModal(false); }} style={{ position: "fixed", inset: 0, zIndex: 300, background: "rgba(20,24,42,.4)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20, }}>

Recibe tu cotización

Te enviamos el detalle de tu proyecto y un especialista te contacta en menos de 24 horas.

setGuestForm(f => ({...f, nombre: e.target.value}))} required /> setGuestForm(f => ({...f, email: e.target.value}))} required /> setGuestForm(f => ({...f, whatsapp: e.target.value}))} required />
{error &&
{error}
}

¿Ya tienes cuenta? Inicia sesión para guardar todas tus cotizaciones.

)} ); } window.CotizadorApp = App; })();