/* data.jsx — sample data, helpers, catalog */
const CATALOG = [
{ id: 'landing', name: 'Landing Page', unit: 'pieza', price: 18000, sub: 'Página de alta conversión · Hasta 6 secciones · CMS opcional' },
{ id: 'corp', name: 'Sitio web corporativo', unit: 'pieza', price: 35000, sub: '5 a 8 páginas · WordPress · SEO on-page incluido' },
{ id: 'ecom', name: 'Tienda en línea', unit: 'pieza', price: 55000, sub: 'WooCommerce o Shopify · Hasta 100 SKUs · Pasarela de pago MX' },
{ id: 'seo', name: 'Estrategia SEO mensual', unit: 'mes', price: 6500, sub: 'Auditoría, on-page, link building y reporte mensual' },
{ id: 'ads-setup', name: 'Configuración Google Ads', unit: 'pieza', price: 8000, sub: 'Estructura de campañas, keywords, copies y conversiones' },
{ id: 'ads-mgmt', name: 'Gestión Google Ads mensual', unit: 'mes', price: 5500, sub: 'Optimización, reportes y ajuste de pujas · No incluye presupuesto' },
{ id: 'maint', name: 'Mantenimiento web mensual', unit: 'mes', price: 1800, sub: 'Actualizaciones, respaldos, soporte técnico y velocidad' },
{ id: 'copy', name: 'Copywriting y textos del sitio', unit: 'pieza', price: 8000, sub: 'Hasta 6 secciones · Tono adaptado a la marca · SEO-friendly' },
];
const STATUS_LABEL = {
draft: 'Borrador',
sent: 'Enviada',
accepted: 'Aceptada',
rejected: 'Rechazada',
expired: 'Expirada',
};
/* HELPERS */
const fmtMXN = (n) => '$' + (Math.round(n)).toLocaleString('es-MX') + ' MXN';
const fmtMXNshort = (n) => '$' + (Math.round(n)).toLocaleString('es-MX');
const fmtDateLong = (iso) => {
if (!iso || iso === '—') return iso || '—';
const d = new Date(iso + 'T12:00:00');
return d.toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
};
const fmtDateShort = (iso) => {
if (!iso) return '';
const d = new Date(iso + 'T12:00:00');
return d.toLocaleDateString('es-MX', { day: '2-digit', month: 'short' });
};
function computeTotals(quote) {
const subtotal = quote.items.reduce((s, it) => s + (it.qty * it.price), 0);
let discount = 0;
if (quote.discount?.type === 'pct') discount = subtotal * (quote.discount.value / 100);
if (quote.discount?.type === 'fixed') discount = quote.discount.value;
const taxable = subtotal - discount;
const iva = taxable * 0.16;
const total = taxable + iva;
return { subtotal, discount, taxable, iva, total };
}
/* paymentSummary — produce a short "50% / 50%" or "40% / 30% / 30%" label */
function paymentSummary(quote) {
return quote.schedule.map(s => `${s.pct}%`).join(' / ');
}
const LogoMark = ({ size = 26 }) => (
);
const Brand = () => (