/* ============================================================
APP — estado global, carta, scrollspy, backend de tienda, montaje
============================================================ */
const { CATS: APP_CATS, FEATURE: APP_FEATURE, RICE: APP_RICE, DRINKS: APP_DRINKS, HOURS: APP_HOURS } = window;
function scrollToId(id, offset = 110) {
const el = document.getElementById(id);
if (!el) return;
const y = el.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({ top: Math.max(0, y), behavior: "smooth" });
}
function TopBar({ onOrder, scrolled, isOpen, onPdf }) {
return (
);
}
/* ---- Plato estrella destacado ---- */
function FeatureBlock({ onOpen, store }) {
const f = APP_FEATURE;
const cat = APP_CATS.find(c => c.id === f.catId);
const av = itemAvailable(store, f.n);
return (
{!av && Agotado}
{f.tag}
{f.n}
{f.d}
);
}
function App() {
const [cart, setCart] = useState(() => {
try {
const raw = JSON.parse(localStorage.getItem("do_cart") || "[]");
// migra líneas guardadas antes de agregar tamaños/precio por rango,
// reconstruyendo from/to/weights desde la carta actual por nombre de plato.
return raw.map(l => {
const match = (l.name === APP_FEATURE.n)
? APP_FEATURE
: APP_CATS.reduce((found, c) => found || c.items.find(it => it.n === l.name), null);
return {
...l,
from: l.from != null ? l.from : (match ? match.from : (l.ranged ? l.unitPrice - (l.drinkExtra || 0) : undefined)),
to: l.to != null ? l.to : (match ? match.to : undefined),
weights: l.weights != null ? l.weights : (match ? (match.weights || null) : null),
drinkExtra: l.drinkExtra != null ? l.drinkExtra : 0,
size: l.size != null ? l.size : null,
};
});
} catch (e) { return []; }
});
const [store, setStore] = useState(loadStore);
const [modal, setModal] = useState(null); // {item, cat}
const [drawer, setDrawer] = useState(false);
const [admin, setAdmin] = useState(false);
const [pdf, setPdf] = useState(false);
const [active, setActive] = useState(APP_CATS[0].id);
const [scrolled, setScrolled] = useState(false);
const special = useMemo(() => getActiveSpecial("auto"), []);
const promo = useMemo(() => getWeeklyPromo(), []);
const isOpen = isStoreOpen(store);
useEffect(() => { localStorage.setItem("do_cart", JSON.stringify(cart)); }, [cart]);
useEffect(() => { localStorage.setItem("do_store", JSON.stringify(store)); }, [store]);
// abre el panel admin con #admin en la URL
useEffect(() => {
if (location.hash === "#admin") setAdmin(true);
}, []);
// scrolled state for topbar
useEffect(() => {
const onS = () => setScrolled(window.scrollY > 80);
window.addEventListener("scroll", onS, { passive: true });
return () => window.removeEventListener("scroll", onS);
}, []);
// scrollspy
useEffect(() => {
const obs = new IntersectionObserver((es) => {
es.forEach(e => { if (e.isIntersecting) setActive(e.target.id); });
}, { rootMargin: "-45% 0px -50% 0px" });
APP_CATS.forEach(c => { const el = document.getElementById(c.id); if (el) obs.observe(el); });
return () => obs.disconnect();
}, []);
const openModal = (item, cat) => setModal({ item, cat });
const addToCart = (line) => setCart(c => [...c, line]);
const order = () => { if (cart.length) setDrawer(true); else scrollToId("menu"); };
const openItem = (name) => { const f = findItem(name); if (f) openModal(f.item, f.cat); };
// agregado rápido (cross-sell) con opciones por defecto
const quickAdd = (item, cat) => {
const inc = item.includes || cat.includes || {};
addToCart({
uid: "L" + Date.now() + Math.random().toString(36).slice(2, 6),
name: item.n, catName: cat.name,
unitPrice: item.from, ranged: !!(item.to && item.to !== item.from), from: item.from, to: item.to, drinkExtra: 0,
weights: item.weights || null, size: null,
qty: 1, rice: inc.rice ? APP_RICE[0].label : null,
drink: inc.drink ? APP_DRINKS[0].label : null, notes: "",
});
};
const onPromoCta = (p) => { if (p.itemName) openItem(p.itemName); else scrollToId(p.catId || "menu"); };
// "Quiero probarlo": agrega automáticamente los platos de la experiencia
// (el plato estrella + las sugerencias del chef) y abre el carrito.
const tryExperience = () => {
const picks = [];
const fcat = APP_CATS.find(c => c.id === APP_FEATURE.catId);
if (itemAvailable(store, APP_FEATURE.n)) picks.push({ item: APP_FEATURE, cat: fcat });
APP_CATS.forEach(c => c.items.forEach(it => {
if (it.suggested && itemAvailable(store, it.n)) picks.push({ item: it, cat: c });
}));
const inCart = new Set(cart.map(l => l.name));
const toAdd = picks.filter(p => !inCart.has(p.item.n));
toAdd.forEach(({ item, cat }) => quickAdd(item, cat));
setDrawer(true);
};
return (
<>
setPdf(true)} />
scrollToId("menu")} onScrollTo={scrollToId} onOpenItem={openItem} onPdf={() => setPdf(true)} isOpen={isOpen} />
{promo && }
scrollToId((special && special.target) || "menu")} />
scrollToId(id, 112)} />