/* ============================================================ ATUKASA SUSHI — Componentes ============================================================ */ const { useState, useEffect, useRef } = React; /* ---------- helpers ---------- */ const fmt = (n) => n.toLocaleString("es-DO"); /* ---------- placeholder de imagen (sin fotos reales) ---------- */ function DishImage({ product, kind }) { const tile = window.ATUKASA.TILE[product.cat] || ["#1F3B34", "#15302B"]; const letter = product.name.replace(/^[^A-Za-zÁÉÍÓÚÑ]+/, "").charAt(0).toUpperCase(); const style = { "--c1": tile[0], "--c2": tile[1] }; // Buscar imagen real del producto const allProducts = window.ATUKASA.PRODUCTS || []; const found = allProducts.find(p => p.name === product.name || p.id === product.id); const imgSrc = found && found.images && found.images[0] ? found.images[0] : null; if (imgSrc) { return (
{product.name}
); } return (
{letter} foto
); } /* ---------- stepper cantidad ---------- */ function Stepper({ value, onChange, min = 1 }) { return (
{value}
); } /* ---------- rail de categorías ---------- */ function CategoryRail({ cats, active, onPick, countByCat }) { return ( ); } /* ---------- tarjeta de producto ---------- */ function ProductCard({ product, onOpen }) { const startPrice = product.variants ? product.variants[0].price : product.price; return (
onOpen(product)}>
{product.images ? {product.name} : } {product.badge && ( Array.isArray(product.badge) ?
{product.badge.map((b,i) => {b})}
: {product.badge} )}

{product.name}

{product.desc}

{product.regular && RD$ {fmt(product.regular)}} RD$ {fmt(startPrice)} {product.unitNote && /{product.unitNote.replace("por ", "")}}
); } /* ---------- modal de producto (variantes + modificadores) ---------- */ function ProductModal({ product, onClose, onAdd }) { const variants = product.variants || null; const customizable = !!variants; const hasSides = !!(product.sides && product.sides.length); const [vIdx, setVIdx] = useState(0); const [side, setSide] = useState(hasSides ? null : undefined); const [mods, setMods] = useState([]); const [qty, setQty] = useState(1); const [imgIdx, setImgIdx] = useState(0); const [sideError, setSideError] = useState(false); const images = product.images || null; useEffect(() => { const onKey = (e) => e.key === "Escape" && onClose(); document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, []); if (!product) return null; const allMods = window.ATUKASA.MODIFIERS; const toggleMod = (m) => { setMods((cur) => cur.find((x) => x.id === m.id) ? cur.filter((x) => x.id !== m.id) : [...cur, m] ); }; const base = variants ? variants[vIdx].price : product.price; const scaleFactor = variants ? (variants[vIdx].porc === 8 ? 1 : variants[vIdx].porc === 12 ? 1.2 : 1.5) : 1; const isWednesday = new Date().getDay() === 3; const scaledMod = (m) => (isWednesday && m.id === "empanizado") ? 0 : Math.round(m.price * scaleFactor); const modsTotal = mods.reduce((s, m) => s + scaledMod(m), 0); const unit = base + modsTotal; const total = unit * qty; const handleAdd = () => { if (hasSides && !side) { setSideError(true); return; } onAdd({ key: product.id + "-" + (variants ? variants[vIdx].porc : "u") + "-" + (side || "") + "-" + mods.map((m) => m.id).sort().join(""), id: product.id, name: product.name, cat: product.cat, variant: variants ? variants[vIdx].porc : null, side: side || null, mods: mods.map((m) => ({ ...m, price: scaledMod(m) })), unit, qty, }); onClose(); }; return (
e.stopPropagation()}>
{images ? (
{product.name} {images.length > 1 && ( <>
{images.map((src, i) => ( ))}
)}
) : ( )} {product.badge && ( Array.isArray(product.badge) ?
{product.badge.map((b,i) => {b})}
: {product.badge} )}

{product.name}

{product.desc}

{hasSides && (

{product.sidesLabel || "Guarnición"}

{sideError ? "¡Elige una!" : "Obligatorio"}
{product.sides.map((s) => ( ))}
)} {customizable && (

Tamaño

Obligatorio
{variants.map((v, i) => ( ))}
)} {customizable && (

Agrega toppings

{mods.length} seleccionados
{allMods.map((m) => { const on = !!mods.find((x) => x.id === m.id); return ( ); })}
)}
); } /* ---------- línea del carrito ---------- */ function CartLine({ item, onQty, onRemove }) { const modNames = item.mods.map((m) => m.name).join(", "); return (

{item.name}

{item.variant ? item.variant + " porciones" : ""} {item.variant && (item.side || modNames) ? " · " : ""} {item.side ? item.side : ""} {item.side && modNames ? " · " : ""} {modNames} {!item.variant && !item.side && !modNames ? "Sin extras" : ""}

onQty(item.key, q)} /> RD$ {fmt(item.unit * item.qty)}
); } /* ===================== CHECKOUT ===================== */ const DELIVERY_FEE = 100; // RD$ — exento de ITBIS const CARDNET_ENABLED = true; // ← cambiar a true cuando Hansel confirme el Acquiring Code /* estilos del checkout (autocontenidos, no requieren tocar p2.css) */ (function () { if (document.getElementById("atukasa-checkout-css")) return; var s = document.createElement("style"); s.id = "atukasa-checkout-css"; s.textContent = [ ".ck__field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}", ".ck__label{font-size:12px;letter-spacing:.08em;text-transform:uppercase;opacity:.75}", ".ck__input{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);border-radius:10px;color:inherit;font:inherit;padding:12px 14px;outline:none;width:100%}", ".ck__input:focus{border-color:#e02424}", ".ck__input--err{border-color:#e02424;background:rgba(224,36,36,.08)}", ".ck__err{color:#ff8a8a;font-size:12px}", ".ck__pay{display:flex;flex-direction:column;gap:8px;margin-bottom:14px}", ".ck__payopt{display:flex;align-items:center;gap:10px;border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:12px 14px;cursor:pointer;background:rgba(255,255,255,.04);font:inherit;color:inherit;text-align:left;width:100%}", ".ck__payopt.is-on{border-color:#37d67a;background:rgba(55,214,122,.10)}", ".ck__payopt:disabled{opacity:.45;cursor:not-allowed}", ".ck__radio{width:16px;height:16px;border-radius:50%;border:2px solid rgba(255,255,255,.4);flex-shrink:0;position:relative}", ".is-on .ck__radio{border-color:#37d67a}", ".is-on .ck__radio::after{content:'';position:absolute;inset:3px;border-radius:50%;background:#37d67a}", ".ck__paysoon{margin-left:auto;font-size:11px;opacity:.7;border:1px solid rgba(255,255,255,.2);border-radius:99px;padding:2px 8px}", ".ck__cards{display:flex;gap:8px;align-items:center;padding:6px 14px 0 40px;opacity:.55}", ".ck__cardbadge{display:inline-flex;align-items:center;justify-content:center;background:#fff;border-radius:4px;height:20px;min-width:32px;padding:0 4px}", ".ck__rows{border-top:1px solid rgba(255,255,255,.1);padding-top:12px;margin-bottom:12px}", ".ck__row{display:flex;justify-content:space-between;font-size:14px;padding:3px 0;opacity:.9}", ".ck__row--total{font-size:17px;font-weight:700;opacity:1;padding-top:8px}", ".ck__free{color:#37d67a;font-weight:600}", ".ck__back{background:none;border:none;color:inherit;font:inherit;display:flex;align-items:center;gap:6px;cursor:pointer;opacity:.8;padding:0;margin-bottom:4px}", ".ck__back:hover{opacity:1}", ".ck__success{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:34px 22px}", ".ck__success-icon{font-size:46px}", ".ck__success h3{margin:0;font-size:22px}", ".ck__success p{margin:0;opacity:.85;font-size:14px;line-height:1.5}", ".ck__ordernum{font-size:13px;letter-spacing:.1em;text-transform:uppercase;opacity:.7}", ".ck__senderr{background:rgba(224,36,36,.12);border:1px solid rgba(224,36,36,.45);border-radius:10px;padding:10px 12px;font-size:13px;margin-bottom:12px}", ".acct{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.05);color:inherit;cursor:pointer;position:relative;flex-shrink:0;font:inherit;padding:0}", ".acct--on{background:#e02424;border-color:#e02424;font-weight:700;font-size:16px}", ".acct__pop{position:absolute;top:calc(100% + 10px);right:0;background:#161213;border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:14px;min-width:240px;z-index:90;box-shadow:0 12px 30px rgba(0,0,0,.55);text-align:left;cursor:default}", ".acct__pop-name{font-weight:700;font-size:15px;margin-bottom:2px}", ".acct__pop-line{font-size:12.5px;opacity:.8;line-height:1.45;word-break:break-word}", ".acct__pop-member{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:#37d67a;margin-top:8px;font-weight:600}", ".acct__pop-del{margin-top:12px;background:none;border:1px solid rgba(255,255,255,.18);border-radius:8px;color:inherit;font:inherit;font-size:12px;padding:7px 10px;cursor:pointer;opacity:.8;width:100%}", ".acct__pop-del:hover{opacity:1;border-color:#e02424;color:#ff8a8a}", ".ck__club{display:flex;align-items:flex-start;gap:10px;border:1px solid rgba(55,214,122,.4);background:rgba(55,214,122,.07);border-radius:10px;padding:12px;cursor:pointer;margin-bottom:14px;font:inherit;color:inherit;text-align:left;width:100%}", ".ck__club.is-on{border-color:#37d67a;background:rgba(55,214,122,.13)}", ".ck__clubcheck{width:18px;height:18px;border-radius:5px;border:2px solid rgba(255,255,255,.4);flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:1px}", ".is-on .ck__clubcheck{background:#37d67a;border-color:#37d67a;color:#0b0b0b}", ".ck__clubtxt{font-size:13.5px;line-height:1.45}", ".ck__clubtxt b{color:#37d67a}", ".ck__clubmember{display:flex;align-items:center;gap:8px;color:#37d67a;font-size:13px;font-weight:600;border:1px solid rgba(55,214,122,.4);background:rgba(55,214,122,.08);border-radius:10px;padding:11px 12px;margin-bottom:14px}", ".club__divider{display:flex;align-items:center;gap:10px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;opacity:.65;margin:18px 0 8px}", ".club__divider::before,.club__divider::after{content:'';flex:1;height:1px;background:rgba(255,255,255,.15)}", ".club__phone{display:flex;gap:8px;margin-top:8px}", ".club__phone .ck__input{flex:1}", ".club__activate{background:#e02424;border:none;border-radius:10px;color:#fff;font:inherit;font-weight:700;padding:0 18px;cursor:pointer;flex-shrink:0}", ".club__activate:disabled{opacity:.6;cursor:wait}", ".ck__club{display:flex;gap:11px;align-items:flex-start;border:1px dashed rgba(55,214,122,.55);background:rgba(55,214,122,.07);border-radius:10px;padding:12px;cursor:pointer;margin-bottom:14px;font:inherit;color:inherit;text-align:left;width:100%}", ".ck__club .check{margin-top:1px}", ".ck__club-title{font-weight:700;font-size:13.5px}", ".ck__club-sub{font-size:12px;opacity:.8;line-height:1.45}", ".ck__clubon{display:flex;align-items:center;gap:8px;border:1px solid rgba(55,214,122,.45);background:rgba(55,214,122,.08);border-radius:10px;padding:11px 12px;font-size:13px;color:#37d67a;font-weight:600;margin-bottom:14px}", ".club__phone{display:flex;gap:8px;margin-top:10px}", ".club__phone .ck__input{flex:1}", ".club__activate{background:#e02424;border:none;border-radius:10px;color:#fff;font:inherit;font-weight:700;padding:0 16px;cursor:pointer}", ".club__divider{display:flex;align-items:center;gap:10px;opacity:.55;font-size:12px;margin:18px 0 4px;text-transform:uppercase;letter-spacing:.08em}", ".club__divider::before,.club__divider::after{content:'';flex:1;height:1px;background:rgba(255,255,255,.18)}", ".ck__mode{display:flex;gap:8px;margin-bottom:14px}", ".ck__modeopt{flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:12px 8px;cursor:pointer;background:rgba(255,255,255,.04);font:inherit;color:inherit;font-size:13px;font-weight:600;transition:border-color .15s,background .15s}", ".ck__modeopt:hover{border-color:rgba(255,255,255,.3)}", ".ck__modeopt.is-on{border-color:#e02424;background:rgba(224,36,36,.12)}", ".ck__modeopt-icon{font-size:22px;line-height:1}" ].join(""); document.head.appendChild(s); })(); function CheckoutPanel({ items, subtotal, member, notes, onBack, onDone, onSuccess }) { const saved = (() => { try { return JSON.parse(localStorage.getItem("atukasa_customer") || "{}"); } catch (e) { return {}; } })(); const [phone, setPhone] = useState(saved.phone || ""); const [name, setName] = useState(saved.name || ""); const [address, setAddress] = useState(saved.address || ""); const [email, setEmail] = useState(saved.email || ""); const [joinClub, setJoin] = useState(false); const [pay, setPay] = useState("cod"); const [pickup, setPickup] = useState(false); const [errs, setErrs] = useState({}); const [sending, setSending] = useState(false); const [sendErr, setSendErr] = useState(null); const isMember = !!member; // ya pertenece al Club (este dispositivo) const clubActive = isMember || joinClub; const delivery = pickup ? 0 : (clubActive ? 0 : DELIVERY_FEE); const total = subtotal + delivery; const validate = () => { const e = {}; const digits = phone.replace(/\D/g, ""); if (digits.length < 10) e.phone = "Escribe un teléfono válido (10 dígitos)"; if (name.trim().length < 2) e.name = "Escribe tu nombre"; if (!pickup && address.trim().length < 8) e.address = "Escribe la dirección completa de entrega"; if (pay === "card" && !email.trim()) e.email = "El correo es necesario para pagos con tarjeta"; if ((joinClub || pay === "card") && email.trim() && !/^\S+@\S+\.\S+$/.test(email.trim())) e.email = "Ese correo no parece válido"; setErrs(e); return Object.keys(e).length === 0; }; const confirm = async () => { if (sending) return; if (!validate()) return; setSending(true); if (typeof fbq === 'function') fbq('track', 'Contact'); setSendErr(null); const customer = { phone: phone.replace(/\D/g, ""), name: name.trim(), address: address.trim(), email: email.trim() }; localStorage.setItem("atukasa_customer", JSON.stringify(customer)); window.dispatchEvent(new Event("atukasa:customer")); try { const res = await fetch("/atukasa-checkout.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ customer, payment: pay, pickup: pickup, club: joinClub, notes: (notes || "").trim(), items: items.map((it) => ({ name: it.name, qty: it.qty, unit: it.unit, detail: [ it.variant ? it.variant + " porciones" : null, it.side || null, it.mods && it.mods.length ? "+ " + it.mods.map((m) => m.name).join(", ") : null, ].filter(Boolean).join(" · "), })), }), }); const data = await res.json().catch(() => ({})); if (!res.ok || !data.ok) throw new Error(data.message || "No se pudo registrar el pedido"); // el servidor manda la última palabra sobre membresía y total const srvMember = !!data.member; const srvTotal = (typeof data.total === "number") ? data.total : total; const srvDeliv = srvMember ? 0 : DELIVERY_FEE; if (srvMember) { localStorage.setItem("atukasa_club", JSON.stringify({ name: customer.name, phone: customer.phone, joined: Date.now() })); window.dispatchEvent(new Event("atukasa:club")); } // — Cardnet: solo redirigir si el cliente eligió tarjeta y Cardnet está activo if (pay === "card" && CARDNET_ENABLED && data.payment_url) { window.location.href = data.payment_url; return; } const num = data.number || data.id; let msg = "¡Hola Atukasa! 🍣 Acabo de confirmar mi pedido *#" + num + "* en la web:\n\n"; items.forEach((it) => { msg += "• " + it.qty + "x " + it.name; if (it.variant) msg += " (" + it.variant + " porciones)"; if (it.side) msg += " · " + it.side; if (it.mods && it.mods.length) msg += " + " + it.mods.map((m) => m.name).join(", "); msg += " — RD$ " + fmt(it.unit * it.qty) + "\n"; }); if (notes && notes.trim()) msg += "\n📝 Notas: " + notes.trim(); msg += "\n👤 " + customer.name; msg += "\n📍 " + customer.address; msg += "\n" + (pickup ? "🏪 Recoger en local (General Cabral #90, San Cristóbal)" : "🛵 Delivery: " + (srvMember ? "Gratis (Club Atukasa)" : "RD$ " + fmt(srvDeliv))); msg += "\n💵 Pago: " + (pay === "cod" ? "efectivo al recibir" : "tarjeta"); msg += "\n\n*Total: RD$ " + fmt(srvTotal) + "* (ITBIS incluido)"; onSuccess && onSuccess({ number: num, msg: msg, firstName: customer.name.split(" ")[0], phone: customer.phone, club: srvMember, justJoined: srvMember && !isMember, pickup: pickup, pay: pay }); // payment_url redirect already handled above } catch (err) { setSendErr(err.message + ". Inténtalo de nuevo o pide por WhatsApp."); } finally { setSending(false); } }; return (
{/* ── Modalidad ── */}
① ¿Cómo quieres recibir tu pedido?
{pickup &&

📍 General Cabral #90, San Cristóbal · Mié–Dom 5–10 PM

}
setPhone(e.target.value)} /> {errs.phone && {errs.phone}}
setName(e.target.value)} /> {errs.name && {errs.name}}
{!pickup && (