/* ============================================================ PUBLIC · carta para clientes ============================================================ */ const CARTA_THEME = { aromas: { accent:'#955427', deep:'#74401C', tint:'rgba(149,84,39,.10)', icon:'coffee' }, restaurante: { accent:'#B0512C', deep:'#8C3D1E', tint:'rgba(176,81,44,.10)', icon:'utensils'}, bar: { accent:'#7A3243', deep:'#5F2433', tint:'rgba(122,50,67,.12)', icon:'wine' }, }; const themeFor = (id) => CARTA_THEME[id] || CARTA_THEME.restaurante; const accentVars = (id) => { const t = themeFor(id); return { '--accent':t.accent, '--accent-deep':t.deep, '--accent-tint':t.tint }; }; function Seal({ brand, size }) { if (brand.logo) return logo; return GC; } /* ---- product row ---- */ function ProductRow({ p }) { const { money } = useStore(); if (p.active === false) return null; const hasVariants = p.variants && p.variants.length > 0; let priceNode = null; if (p.priceText) priceNode = {p.priceText}; else if (p.price != null) priceNode = {money(p.price)}; return (
{p.photo && {p.name}}
{p.featured && } {p.name} {!hasVariants && priceNode}
{p.desc &&
{p.desc}
} {hasVariants && (
{p.variants.map((v, i) => ( {v.label} {money(v.price)} ))}
)}
); } /* ---- carta view ---- */ function CartaView({ carta }) { const [query, setQuery] = useState(''); const [activeCat, setActiveCat] = useState(carta.categories[0]?.id); const sectionRefs = useRef({}); const theme = themeFor(carta.id); useEffect(() => { setActiveCat(carta.categories[0]?.id); setQuery(''); window.scrollTo({top:0}); }, [carta.id]); const q = query.trim().toLowerCase(); const filtered = useMemo(() => { if (!q) return carta.categories; return carta.categories.map(cat => ({ ...cat, subcats: cat.subcats.map(s => ({ ...s, products: s.products.filter(p => p.active !== false && ((p.name||'').toLowerCase().includes(q) || (p.desc||'').toLowerCase().includes(q))) })) .filter(s => s.products.length) })).filter(cat => cat.subcats.length); }, [carta, q]); const scrollToCat = (catId) => { setActiveCat(catId); const el = sectionRefs.current[catId]; if (el) { const top = el.getBoundingClientRect().top + window.scrollY - 78; window.scrollTo({ top, behavior:'smooth' }); } }; useEffect(() => { if (q) return; const obs = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) setActiveCat(e.target.dataset.cat); }); }, { rootMargin: '-78px 0px -70% 0px' }); Object.values(sectionRefs.current).forEach(el => el && obs.observe(el)); return () => obs.disconnect(); }, [carta.id, q]); const countActive = (cat) => cat.subcats.reduce((n, s) => n + s.products.filter(p => p.active !== false).length, 0); return (
{carta.kind}

{carta.name}

setQuery(e.target.value)} placeholder={`Buscar en ${carta.name}…`} /> {query && }
{carta.categories.map(cat => ( ))}
{filtered.length === 0 &&
No encontramos resultados para “{query}”.
} {filtered.map(cat => (
sectionRefs.current[cat.id] = el}>

{cat.name}

{cat.subtitle && {cat.subtitle}}
{cat.subcats.map(sub => { const prods = sub.products.filter(p => p.active !== false); if (!prods.length) return null; return (
{sub.name &&

{sub.name}

}
{prods.map(p => )}
); })}
))}
); } /* ---- portada ---- */ function Portada({ brand, cartas, onOpen }) { return (
Antofagasta · Chile

{brand.name}

{brand.tagline}. Tres cartas, una sola mesa: cocina peruana y criolla, cafetería de especialidad y barra de autor.

{cartas.map(c => { const t = themeFor(c.id); let count = 0; c.categories.forEach(cat => cat.subcats.forEach(s => count += s.products.filter(p=>p.active!==false).length)); return (
onOpen(c.id)}>
{c.kind}

{c.name}

{c.categories.length} categorías · {count} productos
Ver carta
); })}
Horario
08:00 – 23:00 hrs
Dirección
{brand.address}
Teléfono
{brand.phone}
Correo
{brand.email}
); } /* ---- info modal ---- */ function InfoModal({ brand, onClose, onReserve }) { const socials = [ brand.instagram && { icon:'instagram', url:`https://instagram.com/${brand.instagram}` }, brand.facebook && { icon:'facebook', url:`https://facebook.com/${brand.facebook}` }, brand.whatsapp && { icon:'whatsapp', url:`https://wa.me/${brand.whatsapp.replace(/[^0-9]/g,'')}` }, ].filter(Boolean); return ( Reservar mesa}>
Horario
{brand.hours}
Teléfono
{brand.phone}
Dirección
{brand.address}
Correo
{brand.email}
{socials.length > 0 && <>
Síguenos
{socials.map((s,i) => )}
}
); } /* ---- reservation modal ---- */ function ReservaModal({ brand, cartas, onClose }) { const { addReservation, toast, sendMail } = useStore(); const [f, setF] = useState({ nombre:'', telefono:'', email:'', fecha:'', personas:2, local: cartas[0]?.name || '', comentario:'' }); const [done, setDone] = useState(false); const set = (k,v) => setF(s => ({...s, [k]:v})); const valid = f.nombre.trim() && f.telefono.trim() && f.fecha; const submit = async () => { addReservation(f); const fechaTxt = (f.fecha||'').replace('T',' '); // 1) correo al cliente: recibido, pendiente de aprobación if (f.email) sendMail({ to:f.email, subject:`Recibimos tu solicitud de reserva — ${brand.name}`, message:`Hola ${f.nombre},\n\nRecibimos tu solicitud de reserva. AÚN NO ESTÁ APROBADA: nuestro equipo la revisará y te enviaremos la confirmación.\n\n• Fecha y hora: ${fechaTxt}\n• Personas: ${f.personas}\n• Local: ${f.local}\n\nGracias por preferirnos.\n${brand.name}` }); // 2) aviso al administrador if (brand.email) sendMail({ to:brand.email, subject:`Nueva reserva pendiente: ${f.nombre} (${f.personas}p)`, message:`Nueva solicitud de reserva (pendiente de aprobar en el panel):\n\n• Nombre: ${f.nombre}\n• Teléfono: ${f.telefono}\n• Email: ${f.email}\n• Fecha/hora: ${fechaTxt}\n• Personas: ${f.personas}\n• Local: ${f.local}\n• Comentario: ${f.comentario}\n\nApruébala o recházala en Panel → Reservas.` }); // 3) alternativa por servidor (PHP), si está configurada const ep = brand.reservationEndpoint; if (ep) { try { await fetch(ep, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(f) }); } catch(e){} } setDone(true); }; const waLink = () => { const msg = `Hola ${brand.name}, quiero reservar:%0A• Nombre: ${f.nombre}%0A• Fecha/hora: ${f.fecha.replace('T',' ')}%0A• Personas: ${f.personas}%0A• Local: ${f.local}%0A• Tel: ${f.telefono}${f.comentario?('%0A• Nota: '+f.comentario):''}`; return `https://wa.me/${(brand.whatsapp||'').replace(/[^0-9]/g,'')}?text=${msg}`; }; if (done) return ( {brand.whatsapp && Confirmar por WhatsApp} }>

Gracias, {f.nombre}. Registramos tu solicitud para {f.personas} personas en {f.local}.
{f.email ? 'Te enviamos un correo confirmando la recepción; ' : ''}aún está pendiente de aprobación y te contactaremos para confirmarla.

); return ( }>
set('nombre',e.target.value)} placeholder="Tu nombre" /> set('telefono',e.target.value)} placeholder="+56 9 …" />
set('email',e.target.value)} placeholder="tucorreo@mail.com" />
set('fecha',e.target.value)} /> set('personas',e.target.value)} />