/* =====================================================================
steps.jsx — los 7 pasos del wizard. Cada paso lee/escribe el store
vía props. StepShell estandariza encabezado y ritmo vertical.
===================================================================== */
const C = window.Cotizador;
/* Encabezado de paso */
function StepShell({ step, title, subtitle, children }) {
return (
Paso {step} de 7
{title}
{subtitle &&
{subtitle}
}
{children}
);
}
/* Sub-pregunta dentro de un paso (label + tooltip opcional + control) */
function SubQuestion({ label, tip, tipLabel, children, hint }) {
return (
{label}
{tip && }
{children}
{hint &&
{hint}
}
);
}
const grid2 = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: 14 };
/* ---------------------------------------------------------------- PASO 1 */
function Step1({ state, setField, density }) {
const icons = { landing: "panel-top", sales: "shopping-cart", funnel: "filter" };
const tips = {
funnel: "Un embudo guía al visitante por una secuencia de páginas (captación → oferta → checkout → upsell) midiendo la conversión en cada paso.",
};
function choose(type) {
if (state.projectType === type) return;
// cambiar tipo invalida escala y plataforma dependientes
setField({ projectType: type, scale: null, platform: null, recommendChosen: false, platformConfirmed: false });
}
return (
{Object.keys(C.PROJECT_TYPES).map(key => {
const pt = C.PROJECT_TYPES[key];
return (
choose(key)}
icon={icons[key]}
title={pt.label}
desc={pt.desc}
density={density}
/>
{tips[key] && (
)}
);
})}
);
}
/* ---------------------------------------------------------------- PASO 2 */
function Step2({ state, setField, density, openContact }) {
const scales = C.SCALES[state.projectType] || [];
const noun = state.projectType === "sales" ? "tu catálogo" : state.projectType === "funnel" ? "tus embudos" : "tu landing";
return (
{scales.map(s => (
{ if (s.contact) { openContact(); } else { setField("scale", s.value); } }}
title={s.label}
desc={s.desc}
badge={s.contact ? "Hablemos" : null}
tone={s.contact ? "accent" : null}
density={density}
/>
))}
);
}
/* ---------------------------------------------------------------- PASO 3 */
function Step3({ state, setField, density }) {
const order = ["recommend", "wordpress-elementor", "wordpress-woo", "shopify", "webflow", "framer", "nextjs-react", "php-laravel", "clickfunnels-systeme", "carrd"];
const rec = (state.recommendChosen || state.platform) ? C.recommendPlatform(state.projectType, state.scale) : null;
function pickPlatform(key) {
if (key === "recommend") {
setField({ recommendChosen: true, platform: null, platformConfirmed: false });
} else {
setField({ platform: key, recommendChosen: false, platformConfirmed: true });
}
}
function confirmRec() {
const r = C.recommendPlatform(state.projectType, state.scale);
setField({ platform: r.platform, platformConfirmed: true });
}
const recPlatformLabel = rec ? C.PLATFORMS[rec.platform].label : "";
return (
{order.map(key => {
const pl = C.PLATFORMS[key];
const isRecommendCard = key === "recommend";
const selected = isRecommendCard ? state.recommendChosen : (state.platform === key && !state.recommendChosen);
return (
pickPlatform(key)}
icon={isRecommendCard ? "sparkles" : null}
title={pl.label}
desc={pl.desc}
tone={isRecommendCard ? "accent" : null}
badge={isRecommendCard ? "Sugerido" : null}
density={density}
/>
);
})}
{/* Panel de recomendación */}
{state.recommendChosen && rec && (
Nuestra recomendación
{recPlatformLabel}
{rec.reason}
{state.platformConfirmed ? "Plataforma confirmada ✓" : "Usar " + recPlatformLabel + " →"}
setField({ recommendChosen: false, platform: null, platformConfirmed: false })}>
Prefiero elegir yo
)}
{/* Hosting / licencia */}
{(state.platformConfirmed || (state.platform && !state.recommendChosen)) && (
setField("hostingOwnership", "client")}
icon="user-round"
title="A mi nombre"
desc="Tú eres el dueño. Setup +$80 USD."
density={density}
/>
setField("hostingOwnership", "sell-u")}
icon="building-2"
title="A nombre de Sell-U"
desc="Nosotros lo administramos por ti."
density={density}
/>
)}
);
}
/* ---------------------------------------------------------------- PASO 4 */
function Step4({ state, setNested }) {
const cs = state.currentStatus;
return (
setNested("currentStatus", "hasWebsite", v)}
options={[
{ value: "no", label: "No" },
{ value: "migrate", label: "Sí, quiero migrar" },
{ value: "redesign", label: "Sí, quiero rediseñar" },
]}
/>
setNested("currentStatus", "hasDomain", v)}
options={[
{ value: "yes", label: "Sí, lo tengo" },
{ value: "no", label: "No" },
{ value: "unknown", label: "No sé dónde está" },
]}
/>
setNested("currentStatus", "hasHosting", v === "yes")}
options={[
{ value: "yes", label: "Sí" },
{ value: "no", label: "No" },
]}
/>
setNested("currentStatus", "hasBranding", v)}
options={[
{ value: "no", label: "No" },
{ value: "partial", label: "Parcial" },
{ value: "complete", label: "Completo" },
]}
/>
);
}
/* ---------------------------------------------------------------- PASO 5 */
function Step5({ state, setNested }) {
const co = state.content;
return (
setNested("content", "productPhotos", v)}
options={[
{ value: "none", label: "Ninguna" },
{ value: "some", label: "Algunas" },
{ value: "all-pro", label: "Todas profesionales" },
]}
/>
setNested("content", "copy", v)}
options={[
{ value: "client", label: "Yo los doy" },
{ value: "sell-u", label: "Quiero que ustedes los escriban" },
]}
/>
setNested("content", "videos", v)}
options={[
{ value: "none", label: "Ninguno" },
{ value: "has", label: "Tengo" },
{ value: "needs-production", label: "Necesito producción" },
]}
/>
setNested("content", "languages", v)}
options={[
{ value: 1, label: "1" },
{ value: 2, label: "2" },
{ value: 3, label: "3+" },
]}
/>
);
}
/* ---------------------------------------------------------------- PASO 6 */
function Step6({ state, toggleInArray, density }) {
const tips = {
crm: "Un CRM organiza tus contactos y automatiza correos (ej. HubSpot, Mailchimp). Útil para dar seguimiento a clientes potenciales.",
analytics: "El Pixel de Meta y GA4 miden quién visita tu sitio y qué hace, para optimizar tu publicidad. 'Server-side' lo hace más preciso.",
};
return (
{C.INTEGRATIONS.map(ig => (
toggleInArray("integrations", ig.value)}
title={ig.label}
desc={ig.desc}
density={density}
/>
{tips[ig.value] && (
)}
))}
);
}
/* ---------------------------------------------------------------- PASO 7 */
function Step7({ state, setField, density }) {
return (
{C.SUPPORT_OPTIONS.map(opt => (
setField("support", opt.value)}
title={opt.label}
desc={opt.desc}
badge={opt.price === 0 ? "Incluido" : "+" + C.fmtUSD(opt.price)}
density={density}
/>
))}
);
}
Object.assign(window, { StepShell, SubQuestion, Step1, Step2, Step3, Step4, Step5, Step6, Step7 });