/* ============================================================
ATUKASA SUSHI — App principal (Rail layout)
============================================================ */
const { useState, useEffect, useRef, useMemo } = React;
const REVIEWS_DATA = [
{ name: "María A.", rating: "★★★★★", text: "El mejor sushi de San Cristóbal, sin discusión. El crispy chicken lo pedimos cada vez que vamos.", time: "Hace 2 sem." },
{ name: "Carlos M.", rating: "★★★★★", text: "Nunca pensé encontrar sushi así aquí. El tempura roll con queso Philadelphia te cambia la vida.", time: "Hace 1 mes" },
{ name: "Ana L.", rating: "★★★★★", text: "Lo mejor que tiene San Cristóbal. Los dumplings son perfectos. Volvemos siempre.", time: "Hace 3 sem." },
{ name: "Lucia P.", rating: "★★★★★", text: "Primera vez comiendo sushi y ahora soy adicta. El crispy con plátano maduro es increíble.", time: "Hace 1 mes" },
{ name: "Roberto F.", rating: "★★★★★", text: "He comido en la capital y el de Atukasa está igual de bueno. Calidad real en San Cristóbal.", time: "Hace 2 m." },
{ name: "Diana V.", rating: "★★★★★", text: "El delivery siempre a tiempo y el sushi en perfectas condiciones. Muy recomendado.", time: "Hace 1 sem." },
];
function ReviewsTicker() {
const all = [...REVIEWS_DATA, ...REVIEWS_DATA];
return (
{all.map((r, i) => (
{r.name.charAt(0)}
{r.time}
{r.text}
))}
);
}
function App() {
const { CATEGORIES, PRODUCTS, BUSINESS } = window.ATUKASA;
const [active, setActive] = useState(CATEGORIES[0].id);
const [modal, setModal] = useState(null);
const [cartOpen, setCartOpen] = useState(false);
const [loyaltyOpen, setLoyaltyOpen] = useState(false);
const { user, login } = useLoyalty();
const [items, setItems] = useState(() => {
try { return JSON.parse(localStorage.getItem("atukasa_cart") || "[]"); }
catch (e) { return []; }
});
const [toast, setToast] = useState(null);
const scrollRef = useRef(null);
const sectionRefs = useRef({});
const lockSpy = useRef(false);
useEffect(() => {
localStorage.setItem("atukasa_cart", JSON.stringify(items));
}, [items]);
const countByCat = useMemo(() => {
const m = {};
PRODUCTS.forEach((p) => { m[p.cat] = (m[p.cat] || 0) + 1; });
return m;
}, []);
const cartCount = items.reduce((s, it) => s + it.qty, 0);
const cartTotal = items.reduce((s, it) => s + it.unit * it.qty, 0);
const goToCat = (id) => {
setActive(id);
const cont = scrollRef.current;
if (!cont) return;
lockSpy.current = true;
if (id === "promos") {
cont.scrollTo({ top: cont.scrollHeight, behavior: "smooth" });
} else {
const el = sectionRefs.current[id];
if (el) cont.scrollTo({ top: el.offsetTop - 8, behavior: "smooth" });
}
setTimeout(() => { lockSpy.current = false; }, 600);
};
const onScroll = () => {
if (lockSpy.current) return;
const cont = scrollRef.current;
const top = cont.scrollTop + 80;
let current = CATEGORIES[0].id;
for (const c of CATEGORIES) {
const el = sectionRefs.current[c.id];
if (el && el.offsetTop <= top) current = c.id;
}
setActive((a) => (a === current ? a : current));
};
const addItem = (item) => {
setItems((cur) => {
const found = cur.find((x) => x.key === item.key);
if (found) return cur.map((x) => x.key === item.key ? { ...x, qty: x.qty + item.qty } : x);
return [...cur, item];
});
setToast(item.name + " agregado");
setTimeout(() => setToast(null), 1800);
};
const setQty = (key, q) => setItems((cur) => cur.map((x) => x.key === key ? { ...x, qty: q } : x));
const removeItem = (key) => setItems((cur) => cur.filter((x) => x.key !== key));
const clearCart = () => setItems([]);
const now = new Date();
const isOpen = BUSINESS.openDays.includes(now.getDay()) &&
now.getHours() >= BUSINESS.openFrom && now.getHours() < BUSINESS.openTo;
const mapsUrl = "https://google.com/maps/place/AtuKasa+Sushi/data=!4m2!3m1!1s0x0:0xaed0104586fa4d07?sa=X&ved=1t:2428&ictx=111";
return (
{/* ── HEADER ── */}
ATUKASA
SUSHI
San Cristóbal
{isOpen ? "Abierto ahora" : "Cerrado"}
setLoyaltyOpen(true)} />
setLoyaltyOpen(true)} />
setCartOpen(true)}>
{cartCount > 0 && {cartCount} }
{/* ── LAYOUT ── */}
{/* HERO */}
The Dominikkei Experience
El sabor de
sentirse bien.
La fusión perfecta entre Japón y República Dominicana. Pide en segundos desde tu celular: elige porciones, agrega toppings y ordena por WhatsApp.
{/* SECCIONES */}
{CATEGORIES.map((c) => {
const prods = PRODUCTS.filter((p) => p.cat === c.id);
return (
(sectionRefs.current[c.id] = el)}>
{prods.length > 0 && (
{prods.length} {prods.length === 1 ? "plato" : "platos"}
)}
{prods.length === 0 && c.id === "promos" ? (
Los miércoles son de todos
Empanizados
gratis.
) : prods.length === 0 ? (
%
Próximamente
Estamos cocinando nuevas promociones para ti. Vuelve pronto o escríbenos por WhatsApp para conocer las ofertas del día.
) : prods.map((p) => (
))}
);
})}
{/* FOOTER */}
Visítanos
{/* Dirección + GPS */}
{/* Reseñas Google (reemplaza Delivery) */}
{/* Redes */}
{BUSINESS.ig}
Instagram · Facebook · TikTok
{BUSINESS.web} · Precios en RD$ con ITBIS incluido. Imágenes de referencia, reemplazables por fotos reales.
{/* BARRA MÓVIL */}
{cartCount > 0 && (
setCartOpen(true)}>
{cartCount}
Ver pedido
RD$ {fmt(cartTotal)}
)}
{/* LOYALTY MODAL */}
{loyaltyOpen &&
setLoyaltyOpen(false)} member={user} />}
{/* MODAL */}
{modal && setModal(null)} onAdd={addItem} />}
{/* DRAWER */}
setCartOpen(false)}
onQty={setQty}
onRemove={removeItem}
onClear={clearCart}
user={user}
onOpenLoyalty={() => setLoyaltyOpen(true)}
/>
{/* TOAST */}
{toast && (
)}
);
}
ReactDOM.createRoot(document.getElementById("root")).render( );