/* =====================================================================
QuoteResult.jsx — pantalla de resultado de la cotización.
===================================================================== */
const QR = window.Cotizador;
function StatBig({ label, value, sub }) {
return (
Cotización incompleta.
;
}
const platform = QR.PLATFORMS[state.platform];
const rec = QR.recommendPlatform(state.projectType, state.scale);
const wasRecommended = state.platform === rec.platform;
const deposit = Math.round(quote.mid * 0.5);
const internal = QR.internalEstimate(state, quote.mid);
const buildSubtotal = breakdown.filter(it => it.kind === "base" || it.kind === "platform").reduce((a, it) => a + it.amount, 0);
const extrasSubtotal = breakdown.filter(it => !it.kind && it.amount !== 0).reduce((a, it) => a + it.amount, 0);
// semanas hábiles aprox
const weeks = Math.max(1, Math.round(time / 5));
function copyShare() {
const url = shareUrl || (location.origin + location.pathname + "?cotizacion=" + quoteId);
const done = () => { setCopied(true); setTimeout(() => setCopied(false), 2000); };
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(done, done);
} else {
const ta = document.createElement("textarea"); ta.value = url; document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); } catch (e) {}
document.body.removeChild(ta); done();
}
} catch (e) { done(); }
}
return (
{/* Cabecera */}
{readOnly ? "Cotización guardada" : "Tu estimado está listo"}
{QR.PROJECT_TYPES[state.projectType].label} en {platform ? platform.label : "—"}
Folio
{quoteId}
{/* HERO precio + tiempo (banda navy) */}
1 ? "s" : "") + " de trabajo"} />
{/* Split construcción vs. extras — evita leer el total como "precio de la página" */}
{extrasSubtotal > 0 && (
Construcción del sitio
{QR.fmtUSD(buildSubtotal)}
Servicios adicionales
{QR.fmtUSD(extrasSubtotal)}
)}
{/* Plataforma recomendada y por qué */}
Plataforma: {platform ? platform.label : "—"}
{wasRecommended && Recomendada }
{rec.reason}
{!readOnly && (
onEditStep(3)}>Cambiar
)}
{/* Desglose colapsable */}
setOpenBreakdown(o => !o)} aria-expanded={openBreakdown} style={{
width: "100%", display: "flex", alignItems: "center", gap: 12, padding: "18px 22px", background: "transparent",
border: "none", cursor: "pointer", fontFamily: "var(--font-sans)", textAlign: "left",
}}>
¿Qué incluye este precio?
{openBreakdown && (() => {
const buildItems = breakdown.filter(it => it.kind === "base" || it.kind === "platform");
const extraItems = breakdown.filter(it => !it.kind && it.amount !== 0);
const includedItems = breakdown.filter(it => it.kind === "included");
const buildSubtotal = buildItems.reduce((a, it) => a + it.amount, 0);
const extraSubtotal = extraItems.reduce((a, it) => a + it.amount, 0);
const Row = ({ item }) => (
{item.kind === "included"
?
: }
{item.label}
{item.amount === 0 ? "Incluido" : (item.amount > 0 ? "+" : "−") + QR.fmtUSD(Math.abs(item.amount))}
);
const GroupHead = ({ children, subtotal }) => (
{children}
{subtotal != null && {QR.fmtUSD(subtotal)} }
);
return (
Construcción del sitio
{buildItems.map((item, i) =>
|
)}
{extraItems.length > 0 && (
Servicios adicionales (opcionales)
{extraItems.map((item, i) =>
)}
)}
Incluido sin costo
{includedItems.map((item, i) =>
|
)}
Estimado central
{QR.fmtUSD(quote.mid)} USD
);
})()}
{/* Compliance / siempre incluido */}
Compliance legal incluido en toda cotización
{/* Vista interna — SOLO equipo Sell-U, nunca en la vista compartida del cliente */}
{!readOnly && (
Vista interna · equipo Sell-U
No visible para el cliente
{[
{ k: "Horas de producción", v: internal.hours + " h", s: "Fijas para " + QR.PROJECT_TYPES[state.projectType].label.toLowerCase() },
{ k: "Costo de producción", v: QR.fmtUSD(internal.cost), s: internal.hours + " h × " + QR.fmtUSD(internal.rate) + "/h" },
{ k: "Margen estimado", v: QR.fmtUSD(internal.margin), s: "≈ " + internal.marginPct + "% sobre " + QR.fmtUSD(quote.mid), pos: internal.margin >= 0 },
].map((m, i) => (
))}
)}
{/* CTAs */}
{!readOnly && (
setShowCal(true)}>
Agendar llamada
setShowStripe(true)}>
Bloquear cupo con 50%
setShowEmail(true)}>
Enviarme esta cotización por email
{copied ? "¡Enlace copiado!" : "Compartir cotización"}
)}
{readOnly && (
Crear mi propia cotización →
)}
{!readOnly && (
Empezar de nuevo
)}
setShowCal(false)} />
setShowStripe(false)} deposit={deposit} />
setShowEmail(false)} />
);
}
Object.assign(window, { QuoteResult });