let WEB_MODE = normalizeWebMode(window.__WEB_MODE__ || {});
let SERVICE_STATUS = {
orders_enabled: WEB_MODE.orders_enabled,
message: WEB_MODE.message
};
function normalizeWebMode(data) {
const mode = (data && data.mode === 'catalog') ? 'catalog' : 'orders';
const deliveryEnabled = !(data && data.delivery_enabled === false);
const pickupEnabled = !(data && data.pickup_enabled === false);
const ordersEnabled = mode === 'orders';
return {
ok: !(data && data.ok === false),
mode,
orders_enabled: ordersEnabled,
catalog_only: !ordersEnabled,
delivery_enabled: deliveryEnabled,
pickup_enabled: pickupEnabled,
checkout_enabled: ordersEnabled && (deliveryEnabled || pickupEnabled),
message: String((data && data.message) || ''),
reason: String((data && data.reason) || ''),
updated_at: data ? data.updated_at : null,
updated_by: data ? data.updated_by : null,
};
}
function webModeAllowsOrders() {
return !!(WEB_MODE && WEB_MODE.orders_enabled);
}
function webModeMessage() {
return WEB_MODE.message || 'Los pedidos online no están disponibles por ahora.';
}
function deliveryEnabled() {
return !!(WEB_MODE && WEB_MODE.delivery_enabled);
}
function pickupEnabled() {
return !!(WEB_MODE && WEB_MODE.pickup_enabled);
}
function methodEnabled(method) {
return method === 'delivery' ? deliveryEnabled() : pickupEnabled();
}
function enabledMethods() {
const methods = [];
if (deliveryEnabled()) methods.push('delivery');
if (pickupEnabled()) methods.push('pickup');
return methods;
}
function syncWebModeBodyClasses() {
document.body.classList.toggle('webModeCatalog', !webModeAllowsOrders());
document.body.classList.toggle('ordersClosed', !webModeAllowsOrders());
document.body.classList.toggle('webModeOrders', webModeAllowsOrders());
document.body.classList.toggle('webModeDeliveryPaused', !deliveryEnabled());
document.body.classList.toggle('webModePickupPaused', !pickupEnabled());
}
function showWebModeBanner(message) {
const msg = message || webModeMessage();
let banner = document.getElementById('serviceClosedBanner');
if (!banner) {
banner = document.createElement('div');
banner.id = 'serviceClosedBanner';
banner.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
background: #dc2626;
color: white;
padding: 12px;
text-align: center;
font-weight: 800;
z-index: 9999;
box-shadow: 0 10px 24px rgba(0,0,0,.22);
`;
document.body.appendChild(banner);
}
banner.textContent = msg;
banner.hidden = false;
}
function hideWebModeBanner() {
const banner = document.getElementById('serviceClosedBanner');
if (banner) banner.hidden = true;
}
function applyWebMode(data) {
WEB_MODE = normalizeWebMode(data);
SERVICE_STATUS = {
orders_enabled: WEB_MODE.orders_enabled,
message: WEB_MODE.message
};
syncWebModeBodyClasses();
const btnDelivery = document.getElementById('btnDelivery');
const btnRetiro = document.getElementById('btnRetiro');
if (btnDelivery) {
btnDelivery.disabled = !deliveryEnabled() || !webModeAllowsOrders();
btnDelivery.setAttribute('aria-disabled', btnDelivery.disabled ? 'true' : 'false');
}
if (btnRetiro) {
btnRetiro.disabled = !pickupEnabled() || !webModeAllowsOrders();
btnRetiro.setAttribute('aria-disabled', btnRetiro.disabled ? 'true' : 'false');
}
window.ClientePlusWebMode = { ...WEB_MODE };
try {
document.dispatchEvent(new CustomEvent('clienteplus:webmode', { detail: { ...WEB_MODE } }));
} catch (_) {}
if (!webModeAllowsOrders()) {
document.querySelectorAll('.addBtn, .cardQtyBtn, .cfgBtn, .card__add, .btn-add, .productAdd, #productModalAddBtn, #checkoutBtn').forEach((btn) => {
btn.disabled = true;
btn.style.opacity = '0.42';
btn.style.pointerEvents = 'none';
});
const cart = document.getElementById('floatingCart');
if (cart) cart.hidden = true;
if (typeof closeDrawer === 'function') closeDrawer();
if (typeof closeProductModal === 'function') closeProductModal();
showWebModeBanner(WEB_MODE.message);
} else {
document.querySelectorAll('.addBtn, .cardQtyBtn, .cfgBtn, .card__add, .btn-add, .productAdd, #productModalAddBtn, #checkoutBtn').forEach((btn) => {
btn.style.opacity = '';
btn.style.pointerEvents = '';
});
hideWebModeBanner();
}
if (typeof syncUI === 'function') syncUI();
}
let webModeLoadInFlight = null;
let webModePollTimer = null;
async function loadServiceStatus() {
try {
const res = await fetch('api/web_mode.php?_=' + Date.now(), { cache: 'no-store' });
const data = await res.json();
applyWebMode(data);
} catch (e) {
try {
const res = await fetch('api/service_status.php?_=' + Date.now(), { cache: 'no-store' });
const data = await res.json();
applyWebMode({
mode: data && data.orders_enabled === false ? 'catalog' : 'orders',
message: data && data.message ? data.message : '',
delivery_enabled: data && data.delivery_enabled !== false,
pickup_enabled: data && data.pickup_enabled !== false,
});
} catch (_) {
applyWebMode(WEB_MODE);
}
}
}
function refreshWebMode() {
if (webModeLoadInFlight) return webModeLoadInFlight;
webModeLoadInFlight = loadServiceStatus()
.catch(() => {})
.finally(() => { webModeLoadInFlight = null; });
return webModeLoadInFlight;
}
function startWebModeAutoRefresh() {
if (webModePollTimer) return;
webModePollTimer = window.setInterval(() => {
if (!document.hidden) refreshWebMode();
}, 7000);
window.addEventListener('focus', refreshWebMode);
window.addEventListener('pageshow', refreshWebMode);
document.addEventListener('visibilitychange', () => {
if (!document.hidden) refreshWebMode();
});
}
function activateServiceClosedMode(message) {
applyWebMode({
mode: 'catalog',
message: message || webModeMessage(),
delivery_enabled: false,
pickup_enabled: false,
});
}
console.log('PAGODIRECTO2_APP loaded');
const menu = Array.isArray(window.__MENU__) ? window.__MENU__ : [];
const localsCfg = window.__LOCALS__ || { default: null, locals: [] };
const DEMO_LOCAL_FIXED = 'maipu';
const FINE_MEAT_CATEGORY_NAMES = ['cortes finos', 'carnes finas'];
const COOK_POINTS = ['A punto', 'A la inglesa', '3/4', 'Bien cocida'];
const state = {
method: 'delivery',
localId: DEMO_LOCAL_FIXED,
cart: new Map(),
addr: '',
activeCategory: '',
};
const productModalState = {
mode: 'create',
rowId: null,
itemKey: null,
item: null,
qty: 1,
};
const fmt = (n) => Number(n || 0).toLocaleString('es-CL');
const money = (n) => `$${fmt(n)}`;
const $ = (id) => document.getElementById(id);
function isPublished(entity) {
return entity && entity.published === undefined ? true : !!(entity && entity.published);
}
function onlyDigits(s) {
return String(s || '').replace(/\D+/g, '');
}
function fieldValue(id) {
return String(document.getElementById(id)?.value || '').trim();
}
function validateName(v) {
return String(v || '').trim().length >= 2;
}
function normalizeCLPhoneTo569(raw) {
if (!raw) return '';
let s = String(raw).trim();
s = s.replace(/[^\d+]/g, '');
s = s.replace(/^\+/, '');
s = s.replace(/^0056/, '56');
// ClientePlus CORE:
// Formato interno único para WhatsApp Chile: 569XXXXXXXX.
// Si el input usa prefijo visual fijo +569, el usuario puede ingresar solo 8 dígitos.
if (/^\d{8}$/.test(s)) return `569${s}`;
// Si el usuario escribe 9 + 8 dígitos.
if (/^9\d{8}$/.test(s)) return `56${s}`;
// Si el usuario escribe 09 + 8 dígitos.
if (/^09\d{8}$/.test(s)) return `56${s.substring(1)}`;
// Si el usuario escribe formato completo.
if (/^569\d{8}$/.test(s)) return s;
return s.replace(/[^\d]/g, '');
}
function validatePhone(v) {
return /^569\d{8}$/.test(normalizeCLPhoneTo569(v));
}
function escapeHtml(str) {
return String(str ?? '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function nl2brSafe(str) {
return escapeHtml(str).replace(/\n/g, '
');
}
function ensureWarn(msg) {
const warn = $('warnBox');
if (!warn) return;
warn.hidden = false;
warn.textContent = msg;
}
function ensureWarnHtml(html) {
const warn = $('warnBox');
if (!warn) return;
warn.hidden = false;
warn.innerHTML = html;
}
function clearWarn() {
const warn = $('warnBox');
if (!warn) return;
warn.hidden = true;
warn.textContent = '';
warn.innerHTML = '';
}
function ensureOrderOverlay() {
let overlay = document.getElementById('orderSuccessOverlay');
if (overlay) return overlay;
overlay = document.createElement('div');
overlay.id = 'orderSuccessOverlay';
overlay.setAttribute('aria-hidden', 'true');
overlay.style.cssText = [
'position:fixed',
'inset:0',
'display:none',
'align-items:center',
'justify-content:center',
'padding:18px',
'background:rgba(0,0,0,.58)',
'backdrop-filter:blur(10px)',
'z-index:2147483647'
].join(';');
overlay.innerHTML = `
✓
Pedido enviado
Tu pedido fue recibido correctamente y ya quedó registrado en el sistema.
Estado
Enviado con éxito
Siguiente paso
Revisa tu WhatsApp para continuar
Esta ventana se cerrará automáticamente en unos segundos.
`;
document.body.appendChild(overlay);
return overlay;
}
function showOrderOverlay(message) {
const overlay = ensureOrderOverlay();
const text = document.getElementById('orderSuccessOverlayText');
if (text) text.textContent = message || 'Revisa WhatsApp para confirmación.';
overlay.style.display = 'flex';
overlay.setAttribute('aria-hidden', 'false');
document.body.classList.add('noScroll');
}
function hideOrderOverlay() {
const overlay = document.getElementById('orderSuccessOverlay');
if (!overlay) return;
overlay.style.display = 'none';
overlay.setAttribute('aria-hidden', 'true');
document.body.classList.remove('noScroll');
}
function setCheckoutReadyPulse() {
const btn = $('checkoutBtn');
if (!btn || btn.dataset.readyPulseDone === '1') return;
if (btn.getAttribute('data-state') !== 'ready') {
btn.dataset.readyPulseDone = '0';
return;
}
btn.dataset.readyPulseDone = '1';
try {
btn.animate(
[
{ transform: 'scale(1)' },
{ transform: 'scale(1.02)' },
{ transform: 'scale(1)' }
],
{ duration: 220, easing: 'cubic-bezier(.2,.8,.2,1)' }
);
} catch (e) {}
}
function autoScrollToClientFieldsIfReady() {
return;
}
function resetClientFieldsAutoscrollFlag() {
const clientCard = $('clientCard');
if (!clientCard) return;
clientCard.dataset.autoscrollDone = '0';
}
function maybeAdvanceFromNameToPhone() {
const nameEl = $('clientName');
const phoneEl = $('clientPhone');
if (!nameEl || !phoneEl) return;
if (!validateName(nameEl.value)) return;
if (String(phoneEl.value || '').trim()) return;
setTimeout(() => {
try { phoneEl.focus({ preventScroll: true }); } catch (e) { phoneEl.focus(); }
}, 80);
}
async function postOrder(payload) {
const url = 'api/guardar-pedido.php';
try {
const r = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const text = await r.text();
let data;
try {
data = JSON.parse(text);
} catch {
return { ok: false, error: 'Servidor devolvió no-JSON', detail: text.slice(0, 500), status: r.status };
}
if (data && typeof data === 'object') data.status = r.status;
return data;
} catch (err) {
return { ok: false, error: 'Error fetch', detail: String(err?.message || err) };
}
}
function pullAddr() {
const el = $('addrInput');
if (!el) return;
state.addr = String(el.value || '').slice(0, 160);
}
function collectSectionItems(cat) {
const items = [];
for (const item of cat.items || []) {
if (isPublished(item)) items.push(item);
}
for (const sub of cat.subcategories || []) {
if (!isPublished(sub)) continue;
for (const item of sub.items || []) {
if (isPublished(item)) {
items.push({ ...item, _subcatId: sub.id || '', _subcatName: sub.name || '' });
}
}
}
return items;
}
function getFeaturedItems() {
const items = [];
for (const cat of menu) {
if (!isPublished(cat)) continue;
for (const item of collectSectionItems(cat)) {
if (!isPublished(item)) continue;
if (item.featured) {
items.push({ ...item, catId: cat.id, catName: cat.name, _sectionId: 'destacados', _sectionName: 'Destacados' });
}
}
}
return items;
}
function getRenderableSections() {
const sections = [];
for (const cat of menu) {
if (!isPublished(cat)) continue;
const next = { ...cat };
next.items = (cat.items || []).filter(isPublished);
next.subcategories = (cat.subcategories || []).filter(isPublished).map((sub) => ({
...sub,
items: (sub.items || []).filter(isPublished),
}));
sections.push(next);
}
return sections;
}
function getLocalCfg() {
const id = state.localId;
return (localsCfg.locals || []).find((l) => l.id === id)
|| (localsCfg.locals || [])[0]
|| { id: 'maipu', name: 'Chilenazo Maipú', address: '', whatsapp: '56942851261', delivery_fee: 0, free_from: 0, min_order: 15000, eta_delivery: '35–45 min', eta_pickup: '15–25 min' };
}
function setLocal(id) {
state.localId = id;
try { localStorage.setItem('chz_local', id); } catch (e) {}
renderMenu();
syncUI();
syncWarnings();
}
function priceFor(item) {
const l = getLocalCfg();
let p = Number(item.price ?? item.base_price ?? 0);
if (l && l.price_overrides && l.price_overrides[item.id] !== undefined) {
p = Number(l.price_overrides[item.id]);
} else if (l && l.price_factor) {
p *= Number(l.price_factor);
}
return Math.max(0, Math.round(p));
}
function flattenMenu() {
const all = [];
for (const cat of menu) {
if (!isPublished(cat)) continue;
for (const item of cat.items || []) {
if (!isPublished(item)) continue;
all.push({ ...item, catId: cat.id, catName: cat.name });
}
for (const sub of cat.subcategories || []) {
if (!isPublished(sub)) continue;
for (const item of sub.items || []) {
if (!isPublished(item)) continue;
all.push({ ...item, catId: cat.id, catName: cat.name, subcatId: sub.id || '', subcatName: sub.name || '' });
}
}
}
return all;
}
const allItems = flattenMenu();
const itemByKey = new Map(allItems.map((i) => [`${i.catId || ''}::${i.id || ''}`, i]));
const itemById = new Map(allItems.map((i) => [i.id, i]));
function itemKeyFor(item) {
return `${item.catId || ''}::${item.id || ''}`;
}
function setYear() {
const y = $('year');
if (y) y.textContent = new Date().getFullYear();
}
function renderSkeleton() {
const wrap = $('menuWrap');
if (!wrap) return;
wrap.innerHTML = '';
for (let s = 0; s < 2; s += 1) {
const sec = document.createElement('section');
sec.className = 'section';
sec.innerHTML = `
${Array.from({ length: 6 }).map(() => `
`).join('')}
`;
wrap.appendChild(sec);
}
}
function renderChips() {
const chips = $('chips');
if (!chips) return;
chips.innerHTML = '';
const sections = getRenderableSections().filter((cat) => String(cat?.name || '').trim());
if (!state.activeCategory || !sections.some((c) => c.id === state.activeCategory)) {
state.activeCategory = sections[0]?.id || '';
}
sections.forEach((cat) => {
const btn = document.createElement('button');
btn.className = `chip${state.activeCategory === cat.id ? ' chip--active' : ''}`;
btn.type = 'button';
btn.textContent = cat.name;
btn.dataset.target = cat.id;
btn.onclick = () => {
setActiveChip(cat.id);
renderMenu();
};
chips.appendChild(btn);
});
}
function setActiveChip(catId) {
state.activeCategory = catId;
document.querySelectorAll('.chip').forEach((ch) => {
ch.classList.toggle('chip--active', ch.dataset.target === catId);
});
}
function isFineMeatItem(item) {
const cat = String(item?.catName || '').trim().toLowerCase();
return FINE_MEAT_CATEGORY_NAMES.includes(cat);
}
function buildStructuredNotes(row) {
const lines = [];
if (row?.cookPoint) lines.push(`Punto de cocción: ${String(row.cookPoint).trim()}`);
if (row?.note) lines.push(`Notas: ${String(row.note).trim()}`);
return lines.join('\n').trim();
}
function truncateCartText(value, max = 15) {
const clean = String(value || '').trim().replace(/\s+/g, ' ');
if (!clean) return '';
return clean.length > max ? `${clean.slice(0, max).trimEnd()}…` : clean;
}
function readVisibleNotes(row) {
const parts = [];
if (row?.cookPoint) parts.push(`Punto: ${truncateCartText(row.cookPoint, 18)}`);
if (row?.note) parts.push(`Notas: ${truncateCartText(row.note, 15)}`);
return parts;
}
function addToCart(itemKey, options = {}) {
if (!webModeAllowsOrders()) {
alert(webModeMessage());
return;
}
const item = itemByKey.get(itemKey) || itemById.get(itemKey);
if (!item) return;
const needsCustomization = isFineMeatItem(item);
if (needsCustomization && !options.skipCustomization && !options.cookPoint) {
openProductModal(item, { itemKey, mode: 'create' });
return;
}
const qtyToAdd = Math.max(1, Number(options.qty || 1));
const existing = state.cart.get(itemKey);
if (existing && !needsCustomization && !existing.cookPoint && !options.cookPoint) {
existing.qty += qtyToAdd;
} else if (existing && !needsCustomization && (!existing.note || existing.note === (options.note || ''))) {
existing.qty += qtyToAdd;
} else {
const uniqueKey = needsCustomization || (options.note || '') ? `${itemKey}::${Date.now()}::${Math.random().toString(36).slice(2, 7)}` : itemKey;
state.cart.set(uniqueKey, {
itemKey: uniqueKey,
sourceItemKey: itemKey,
item,
qty: qtyToAdd,
note: String(options.note || '').slice(0, 140),
notesOpen: false,
cookPoint: options.cookPoint || '',
});
}
syncUI();
}
function cartQtyForSource(itemKey) {
let qty = 0;
for (const row of state.cart.values()) {
const sourceKey = row.sourceItemKey || row.itemKey || itemKeyFor(row.item);
if (sourceKey === itemKey) qty += Number(row.qty || 0);
}
return qty;
}
function firstCartRowIdForSource(itemKey, reverse = true) {
const entries = Array.from(state.cart.entries());
const scan = reverse ? entries.reverse() : entries;
for (const [id, row] of scan) {
const sourceKey = row.sourceItemKey || row.itemKey || itemKeyFor(row.item);
if (sourceKey === itemKey) return id;
}
return null;
}
function removeOneFromSource(itemKey) {
const rowId = firstCartRowIdForSource(itemKey, true);
if (!rowId) return;
dec(rowId);
}
function cardActionHtml(itemKey) {
if (!webModeAllowsOrders()) return '';
const qty = cartQtyForSource(itemKey);
if (qty > 0) {
return `
${qty}
`;
}
return '';
}
function syncCardControls() {
document.querySelectorAll('.addWrap[data-item-key]').forEach((wrap) => {
const key = wrap.dataset.itemKey || '';
if (!key) return;
const qty = cartQtyForSource(key);
wrap.innerHTML = cardActionHtml(key);
wrap.classList.toggle('addWrap--active', qty > 0);
const card = wrap.closest('.pcard');
const priceEl = card ? card.querySelector('[data-card-price-for]') : null;
if (priceEl) {
const item = itemByKey.get(key) || itemById.get(key);
const unitPrice = item ? priceFor(item) : Number(priceEl.dataset.unitPrice || 0);
const visiblePrice = qty > 0 ? unitPrice * qty : unitPrice;
priceEl.textContent = money(visiblePrice);
priceEl.classList.toggle('price--cart-total', qty > 1);
}
});
}
function inc(itemId) {
const it = state.cart.get(itemId);
if (it) {
it.qty += 1;
syncUI();
}
}
function dec(itemId) {
const it = state.cart.get(itemId);
if (!it) return;
it.qty -= 1;
if (it.qty <= 0) state.cart.delete(itemId);
syncUI();
}
function removeItem(itemId) {
state.cart.delete(itemId);
syncUI();
}
function setNote(itemId, note) {
const it = state.cart.get(itemId);
if (!it) return;
it.note = String(note || '').slice(0, 140);
syncTotals();
}
function setCookPoint(itemId, value) {
const it = state.cart.get(itemId);
if (!it) return;
it.cookPoint = COOK_POINTS.includes(value) ? value : '';
syncUI();
}
function toggleRowNotes(itemId, forceValue = null) {
const it = state.cart.get(itemId);
if (!it) return;
it.notesOpen = forceValue === null ? !it.notesOpen : !!forceValue;
syncUI();
}
function cartCount() {
let c = 0;
for (const it of state.cart.values()) c += it.qty;
return c;
}
function cartUnitPrice(row) {
const item = row && row.item ? row.item : row;
if (!item) return 0;
if (item.type === 'combo') return Math.max(0, Math.round(Number(item.price || 0)));
return priceFor(item);
}
function calcSubtotal() {
let total = 0;
for (const row of state.cart.values()) {
total += cartUnitPrice(row) * Number(row.qty || 0);
}
return total;
}
function calcTotal() {
const subtotal = calcSubtotal();
const local = getLocalCfg();
let deliveryFee = 0;
if (state.method === 'delivery') {
const fee = Number(local.delivery_fee || 0);
const freeFrom = Number(local.free_from || 0);
deliveryFee = (freeFrom > 0 && subtotal >= freeFrom) ? 0 : fee;
}
const total = Math.max(0, subtotal + deliveryFee);
return { subtotal, deliveryFee, discount: 0, total };
}
function renderMenu() {
const wrap = $('menuWrap');
if (!wrap) return;
wrap.innerHTML = '';
const sections = getRenderableSections().filter((cat) => String(cat?.name || '').trim());
if (!sections.length) return;
const cat = sections.find((s) => s.id === state.activeCategory) || sections[0];
state.activeCategory = cat.id;
const section = document.createElement('section');
section.className = 'section section--activeCategory';
section.id = cat.id;
section.innerHTML = ``;
wrap.appendChild(section);
const renderCard = (rawItem, displayCatName) => {
const item = rawItem.catId ? rawItem : { ...rawItem, catId: cat.id, catName: displayCatName || cat.name };
const card = document.createElement('article');
card.className = 'pcard pcard--compact';
const cardItemKey = itemKeyFor(item);
const cta = !webModeAllowsOrders()
? ''
: ((item.type === 'combo')
? ``
: `${cardActionHtml(cardItemKey)}
`);
card.innerHTML = `
${escapeHtml(item.name)}
${escapeHtml(item.desc ? item.desc : '—')}
`;
card.addEventListener('click', () => {
if (!webModeAllowsOrders()) return;
openProductModal(item, {
itemKey: cardItemKey,
mode: 'create'
});
});
const addWrap = card.querySelector('.addWrap');
if (addWrap) {
addWrap.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const actionEl = e.target.closest('[data-cart-act]');
const act = actionEl ? actionEl.dataset.cartAct : 'plus';
const key = addWrap.dataset.itemKey || itemKeyFor(item);
if (act === 'minus') {
removeOneFromSource(key);
pulseEl(addWrap);
return;
}
addFromCard(key, addWrap);
});
}
const cfgBtn = card.querySelector('.cfgBtn');
if (cfgBtn) {
cfgBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
openCombo(item);
});
}
return card;
};
if ((cat.subcategories || []).length) {
(cat.subcategories || []).forEach((sub, idx) => {
if (!sub.items || !sub.items.length) return;
const subHeading = document.createElement('div');
subHeading.className = 'sectionHeading';
subHeading.style.fontSize = '18px';
subHeading.style.marginTop = idx === 0 ? '6px' : '18px';
subHeading.textContent = sub.name || '';
section.appendChild(subHeading);
const grid = document.createElement('div');
grid.className = 'grid grid--compact';
section.appendChild(grid);
for (const rawItem of sub.items || []) {
grid.appendChild(renderCard({ ...rawItem, subcatId: sub.id || '', subcatName: sub.name || '' }, cat.name));
}
});
} else {
const grid = document.createElement('div');
grid.className = 'grid grid--compact';
section.appendChild(grid);
for (const rawItem of cat.items || []) {
grid.appendChild(renderCard(rawItem, cat.name));
}
}
}
function renderCart() {
const list = $('cartList');
const drawerSub = $('drawerSub');
if (!list) return;
const items = Array.from(state.cart.entries()).reverse();
const count = cartCount();
if (drawerSub) drawerSub.textContent = `${count} ${count === 1 ? 'producto' : 'productos'}`;
if (items.length === 0) {
list.innerHTML = `
🛒
Tu pedido está vacío
Agrega productos desde el catálogo para continuar.
`;
return;
}
list.innerHTML = '';
for (const [id, row] of items) {
const el = document.createElement('div');
el.className = 'cartItem cartItem--justo';
const detailLines = readVisibleNotes(row);
const itemTotal = cartUnitPrice(row) * row.qty;
const unitPrice = cartUnitPrice(row);
const itemName = row?.item?.name || 'Producto';
const imageSrc = String(
row?.item?.image_thumb ||
row?.item?.thumb ||
row?.item?.thumbnail ||
row?.item?.image ||
row?.item?.image_full ||
''
).trim();
const thumbHtml = imageSrc
? ``
: `🍽️
`;
el.innerHTML = `
${thumbHtml}
${escapeHtml(itemName)}
${money(unitPrice)} c/u
${detailLines.length ? `
${detailLines.map((line) => `${escapeHtml(line)}`).join('')}
` : ''}
${money(itemTotal)}
`;
el.querySelector('[data-act="minus"]').onclick = () => dec(id);
el.querySelector('[data-act="plus"]').onclick = () => inc(id);
el.querySelector('[data-act="remove"]').onclick = () => removeItem(id);
const editBtn = el.querySelector('[data-act="edit"]');
if (editBtn) editBtn.onclick = () => openProductModal(row.item, { mode: 'edit', rowId: id, itemKey: row.sourceItemKey || itemKeyFor(row.item), existing: row });
list.appendChild(el);
}
}
function setMethod(m) {
if (!webModeAllowsOrders()) {
state.method = methodEnabled('delivery') ? 'delivery' : 'pickup';
} else if (!methodEnabled(m)) {
const alt = enabledMethods()[0] || 'delivery';
if (alt !== m) {
ensureWarn(m === 'delivery' ? 'Delivery está pausado por el local.' : 'Retiro está pausado por el local.');
}
m = alt;
}
state.method = m;
const bd = $('btnDelivery');
const br = $('btnRetiro');
if (bd) {
bd.classList.toggle('is-active', m === 'delivery' && deliveryEnabled());
bd.disabled = !deliveryEnabled() || !webModeAllowsOrders();
bd.setAttribute('aria-disabled', bd.disabled ? 'true' : 'false');
bd.style.opacity = bd.disabled ? '0.45' : '';
bd.style.pointerEvents = bd.disabled ? 'none' : '';
}
if (br) {
br.classList.toggle('is-active', m === 'pickup' && pickupEnabled());
br.disabled = !pickupEnabled() || !webModeAllowsOrders();
br.setAttribute('aria-disabled', br.disabled ? 'true' : 'false');
br.style.opacity = br.disabled ? '0.45' : '';
br.style.pointerEvents = br.disabled ? 'none' : '';
}
const hint = $('methodHint');
const addrCard = $('addrCard');
if (hint) {
const local = getLocalCfg();
const minOrder = Number(local.min_order || 0);
if (!webModeAllowsOrders()) {
hint.textContent = webModeMessage();
} else if (!deliveryEnabled() && pickupEnabled()) {
hint.textContent = 'Delivery pausado. Disponible solo retiro.';
} else if (deliveryEnabled() && !pickupEnabled()) {
hint.innerHTML = `Retiro pausado. Delivery tiene mínimo ${money(minOrder || 15000)}.`;
} else if (!deliveryEnabled() && !pickupEnabled()) {
hint.textContent = 'Delivery y retiro están pausados por el local.';
} else {
hint.innerHTML = `Delivery tiene mínimo ${money(minOrder || 15000)}.`;
}
}
if (addrCard) addrCard.style.display = (m === 'delivery' && deliveryEnabled() && webModeAllowsOrders()) ? '' : 'none';
syncTotals();
syncWarnings();
if (m === 'delivery' && deliveryEnabled() && webModeAllowsOrders()) {
const addrInput = $('addrInput');
if (addrInput) {
setTimeout(() => {
try { addrInput.focus({ preventScroll: false }); } catch (e) { addrInput.focus(); }
}, 80);
}
}
}
function syncTotals() {
const t = calcTotal();
const subEl = $('subtotal');
const totalEl = $('total');
if (subEl) subEl.textContent = money(t.subtotal);
if (totalEl) totalEl.textContent = money(t.total);
}
function validateCartCustomizations() {
for (const row of state.cart.values()) {
if (isFineMeatItem(row.item) && !row.cookPoint) {
return `Selecciona el punto de cocción para ${row.item.name}.`;
}
}
return '';
}
function syncWarnings() {
pullAddr();
const warn = $('warnBox');
const btn = $('checkoutBtn');
if (warn) {
warn.hidden = true;
warn.textContent = '';
warn.innerHTML = '';
}
if (!btn) return;
if (!webModeAllowsOrders()) {
btn.disabled = true;
btn.textContent = 'Pedidos online no disponibles';
btn.classList.add('cta--blocked');
btn.classList.remove('cta--ready');
btn.style.opacity = '0.42';
btn.style.pointerEvents = 'none';
const footer = $('cartFooter');
if (footer) footer.style.display = 'none';
return;
} else {
const footer = $('cartFooter');
if (footer) footer.style.display = '';
btn.style.pointerEvents = '';
}
if (!deliveryEnabled() && !pickupEnabled()) {
btn.disabled = true;
btn.textContent = 'Entrega pausada';
ensureWarn('Delivery y retiro están pausados por el local.');
return;
}
const count = cartCount();
const subtotal = calcSubtotal();
const addrWrap = $('addrWrap');
const addrHelp = $('addrHelp');
const addrVal = fieldValue('addrInput');
const addrOk = addrVal.length >= 6;
const headerCount = $('cartCount');
if (headerCount) headerCount.textContent = String(count);
if (addrWrap) {
const show = state.method === 'delivery';
const card = $('addrCard');
if (card) card.style.display = show ? '' : 'none';
addrWrap.classList.toggle('invalid', show && count > 0 && !addrOk);
if (addrHelp) {
if (!show) addrHelp.textContent = ' ';
else addrHelp.textContent = addrOk ? 'Perfecto, llevamos tu pedido ahí 🚚' : 'Falta tu dirección.';
}
}
const nombre = fieldValue('clientName');
const telefonoRaw = fieldValue('clientPhone');
const nombreOk = validateName(nombre);
const telefonoOk = validatePhone(telefonoRaw);
const customizationError = validateCartCustomizations();
let msgs = [];
let ctaText = 'Enviar pedido por WhatsApp';
if (!methodEnabled(state.method)) {
msgs.push(state.method === 'delivery' ? 'Delivery está pausado por el local.' : 'Retiro está pausado por el local.');
ctaText = 'Elige una opción disponible';
}
if (count === 0) {
msgs.push('Agrega productos para finalizar.');
ctaText = 'Agrega productos al carrito';
} else if (state.method === 'delivery' && subtotal < Number(getLocalCfg().min_order || 15000)) {
const minOrder = Number(getLocalCfg().min_order || 15000);
const falta = minOrder - subtotal;
msgs.push(`Mínimo ${money(minOrder)} para delivery.`);
ctaText = `Te faltan ${money(falta)} para Delivery`;
} else if (state.method === 'delivery' && !addrOk) {
msgs.push('Ingresa tu dirección para delivery.');
ctaText = 'Ingresa tu dirección para Delivery';
}
if (customizationError) {
msgs.push(customizationError);
if (ctaText === 'Enviar pedido por WhatsApp') ctaText = 'Completa las personalizaciones';
}
if (!nombreOk || !telefonoOk) {
msgs.push(!nombreOk && !telefonoOk ? 'Completa nombre y teléfono válidos.' : (!nombreOk ? 'Completa tu nombre.' : 'Completa un teléfono válido.'));
if (ctaText === 'Enviar pedido por WhatsApp') ctaText = !nombreOk ? 'Ingresa tu nombre' : 'Ingresa un teléfono válido';
}
const hasIssues = msgs.length > 0;
btn.disabled = hasIssues;
btn.textContent = ctaText;
btn.classList.toggle('cta--ready', !hasIssues);
btn.classList.toggle('cta--blocked', hasIssues);
btn.setAttribute('data-state', hasIssues ? 'blocked' : 'ready');
if (hasIssues) {
if ((state.method === 'delivery' && !addrOk) || !fieldValue('addrInput')) {
resetClientFieldsAutoscrollFlag();
}
if (state.method === 'delivery' && addrOk && (!nombreOk || !telefonoOk)) {
autoScrollToClientFieldsIfReady();
}
btn.dataset.readyPulseDone = '0';
btn.style.opacity = '1';
btn.style.filter = 'saturate(.82)';
btn.style.background = '#9b6b13';
btn.style.color = '#111';
btn.style.cursor = 'not-allowed';
} else {
btn.style.opacity = '1';
btn.style.filter = 'none';
btn.style.background = '';
btn.style.color = '';
btn.style.cursor = 'pointer';
setCheckoutReadyPulse();
autoScrollToClientFieldsIfReady();
}
}
function getClientData() {
const nombre = fieldValue('clientName').slice(0, 120);
const telefono = normalizeCLPhoneTo569(fieldValue('clientPhone'));
pullAddr();
const metodo = (state.method === 'delivery') ? 'delivery' : 'pickup';
return { nombre, telefono, direccion: state.addr, metodo };
}
function cartToItems() {
const items = [];
for (const row of state.cart.values()) {
const itemId = row.item.baseId || row.item.id;
items.push({
id: itemId,
nombre: row.item.name,
precio: cartUnitPrice(row),
cantidad: row.qty,
notas: buildStructuredNotes(row),
});
}
return items;
}
function openWhatsAppLink(url, popupRef) {
if (!url) return false;
try {
const u = new URL(url);
const phone = u.pathname.replace(/\//g, '');
const text = u.searchParams.get('text') || '';
const finalUrl = `https://wa.me/${phone}?text=${encodeURIComponent(text)}`;
// USAR popup existente (evita página en blanco)
if (popupRef && !popupRef.closed) {
popupRef.location.href = finalUrl;
try { popupRef.focus(); } catch (e) {}
return true;
}
// fallback si no existe popup
const win = window.open(finalUrl, '_blank');
if (win) {
try { win.focus(); } catch (e) {}
return true;
}
return false;
} catch (e) {
return false;
}
}
async function handleCheckout() {
try {
await refreshWebMode();
pullAddr();
syncWarnings();
const btn = $('checkoutBtn');
if (!webModeAllowsOrders()) {
ensureWarn(webModeMessage());
return;
}
if (!methodEnabled(state.method)) {
ensureWarn(state.method === 'delivery' ? 'Delivery está pausado por el local.' : 'Retiro está pausado por el local.');
return;
}
const cliente = getClientData();
const totals = calcTotal();
const count = cartCount();
const customizationError = validateCartCustomizations();
if (count === 0) {
ensureWarn('Agrega productos para finalizar.');
return;
}
if (customizationError) {
ensureWarn(customizationError);
return;
}
if (!validateName(cliente.nombre) || !validatePhone(cliente.telefono)) {
ensureWarn(!validateName(cliente.nombre) && !validatePhone(cliente.telefono) ? 'Completa nombre y teléfono válidos.' : (!validateName(cliente.nombre) ? 'Completa tu nombre.' : 'Teléfono inválido. Usa 569XXXXXXXX, +569XXXXXXXX o 9XXXXXXXX.'));
return;
}
if (!/^569\d{8}$/.test(cliente.telefono)) {
ensureWarn('Teléfono inválido. Usa 569XXXXXXXX, +569XXXXXXXX o 9XXXXXXXX.');
return;
}
if (cliente.metodo === 'delivery') {
const local = getLocalCfg();
const minOrder = Number(local.min_order || 15000);
const addrVal = (document.getElementById('addrInput')?.value || cliente.direccion || '').trim();
const addrOk = addrVal.length >= 6;
if (!addrOk) {
ensureWarn('Ingresa tu dirección.');
return;
}
if (totals.subtotal < minOrder) {
ensureWarn(`Mínimo ${money(minOrder)} para DELIVERY.`);
return;
}
}
const local = getLocalCfg();
const payload = {
local_id: local.id,
cliente,
items: cartToItems(),
subtotal: totals.subtotal,
delivery_fee: totals.deliveryFee,
total: totals.total,
};
if (btn) {
btn.disabled = true;
btn.textContent = 'Guardando pedido...';
}
clearWarn();
const res = await postOrder(payload);
if (!res || !res.ok) {
if (res && res.web_mode) {
applyWebMode(res.web_mode);
}
const backendMessage = res && (res.message || res.error || res.detail);
ensureWarn(backendMessage || 'No se pudo guardar el pedido.');
return;
}
const waUrl = res?.whatsapp?.url || '';
const codigo = res?.pedido?.codigo || '(sin código)';
state.cart.clear();
syncUI();
closeProductModal();
closeDrawer();
clearWarn();
if (waUrl) {
showOrderOverlay(`Pedido ${codigo} enviado con éxito. Revisa tu WhatsApp para continuar.`);
setTimeout(() => {
hideOrderOverlay();
}, 3200);
} else {
showOrderOverlay(`Pedido ${codigo} guardado correctamente.`);
setTimeout(() => {
hideOrderOverlay();
}, 3200);
ensureWarn(`✅ Pedido guardado: ${codigo}.`);
}
} catch (err) {
console.error('[handleCheckout] error', err);
ensureWarn('Error JS. Revisa consola (F12).');
} finally {
const btn = $('checkoutBtn');
if (btn) {
btn.disabled = false;
syncWarnings();
}
}
}
function openDrawer() {
if (!webModeAllowsOrders()) {
alert(webModeMessage());
return;
}
const d = $('drawer');
const o = $('overlay');
if (d) d.classList.add('drawer--open');
if (o) {
o.classList.add('overlay--open');
o.setAttribute('aria-hidden', 'false');
}
}
function closeDrawer() {
const d = $('drawer');
const o = $('overlay');
if (d) d.classList.remove('drawer--open');
if (o) {
o.classList.remove('overlay--open');
o.setAttribute('aria-hidden', 'true');
}
}
function syncUI() {
renderCart();
syncTotals();
syncWarnings();
syncFloatingCart();
syncCardControls();
}
function syncFloatingCart() {
const bar = $('floatingCart');
const total = $('floatingCartTotal');
const btn = $('floatingCartBtn');
const label = $('floatingCartLabel');
if (!bar || !total) return;
if (!webModeAllowsOrders()) {
bar.hidden = true;
document.body.classList.remove('hasFloatingCart');
return;
}
const count = cartCount();
if (count <= 0) {
bar.hidden = true;
document.body.classList.remove('hasFloatingCart');
} else {
bar.hidden = false;
document.body.classList.add('hasFloatingCart');
total.textContent = money(calcSubtotal());
if (label) label.textContent = `${count} ${count === 1 ? 'producto' : 'productos'} · Ver pedido`;
}
if (btn && !btn.dataset.wired) {
btn.dataset.wired = '1';
btn.onclick = () => openDrawer();
}
}
function pulseEl(el) {
if (!el) return;
el.classList.remove('pulse');
void el.offsetWidth;
el.classList.add('pulse');
setTimeout(() => el.classList.remove('pulse'), 220);
}
function observeSections() { return; }
function openProductModal(item, options = {}) {
if (!webModeAllowsOrders()) {
return;
}
const modal = $('productModal');
const title = $('productModalTitle');
const sub = $('productModalSub');
const price = $('productModalPrice');
const cookBlock = $('cookPointBlock');
const select = $('cookPointSelect');
const notesToggle = $('productNotesToggle');
const notesHint = $('productNotesHint');
const notesPanel = $('productNotesPanel');
const notesInput = $('productNotesInput');
const qtyMinus = $('productQtyMinus');
const qtyPlus = $('productQtyPlus');
const qtyValue = $('productQtyValue');
const addBtn = $('productModalAddBtn');
if (!modal || !title || !sub || !price || !select || !notesToggle || !notesPanel || !notesInput || !qtyMinus || !qtyPlus || !qtyValue || !addBtn) return;
const dialog = modal.querySelector('.productModal__dialog');
let media = dialog ? dialog.querySelector('.productModal__media') : null;
if (dialog && !media) {
media = document.createElement('div');
media.className = 'productModal__media';
const head = dialog.querySelector('.productModal__head');
if (head) {
dialog.insertBefore(media, head);
} else {
dialog.prepend(media);
}
}
if (media) {
const modalImageSrc = item.image_full || item.image || '';
if (modalImageSrc) {
media.hidden = false;
media.innerHTML = `
`;
} else {
media.hidden = true;
media.innerHTML = '';
}
}
productModalState.mode = options.mode || 'create';
productModalState.rowId = options.rowId || null;
productModalState.itemKey = options.itemKey || itemKeyFor(item);
productModalState.item = item;
let existing = options.existing || null;
// Si el producto ya fue agregado desde la card, el modal debe editar esa línea,
// no crear una nueva. Esto mantiene sincronizados cantidad, precio y notas.
if (productModalState.mode === 'create' && !existing) {
const existingRowId = firstCartRowIdForSource(productModalState.itemKey, true);
if (existingRowId && state.cart.has(existingRowId)) {
productModalState.mode = 'edit';
productModalState.rowId = existingRowId;
existing = state.cart.get(existingRowId);
}
}
const needsCookPoint = isFineMeatItem(item);
productModalState.qty = Math.max(1, Number(existing?.qty || options.qty || 1));
title.textContent = item.name || 'Producto';
sub.textContent = item.desc || '';
cookBlock.hidden = !needsCookPoint;
select.value = existing?.cookPoint || '';
notesInput.value = existing?.note || '';
if (notesHint) notesHint.textContent = 'Ej: Indica aquí tus alergias o preferencias. Ej: Soy celíaco, sin wasabi, menos sal...';
notesInput.placeholder = 'Ej: Indica aquí tus alergias o preferencias. Ej: Soy celíaco, sin wasabi, menos sal...';
notesPanel.hidden = !(existing?.note);
if (notesHint) notesHint.hidden = !notesPanel.hidden;
notesToggle.textContent = existing?.note ? 'Editar indicación' : '¿Alguna indicación especial?';
addBtn.textContent = productModalState.mode === 'edit' ? 'Guardar cambios' : 'Agregar al carrito';
const refreshModalPrice = () => {
const total = money(priceFor(item) * productModalState.qty);
price.textContent = total;
qtyValue.textContent = String(productModalState.qty);
};
refreshModalPrice();
notesToggle.onclick = () => {
const open = notesPanel.hidden;
notesPanel.hidden = !open;
if (notesHint) notesHint.hidden = open;
notesToggle.textContent = open ? 'Ocultar indicación' : ((notesInput.value.trim()) ? 'Editar indicación' : '¿Alguna indicación especial?');
if (open) notesInput.focus();
};
notesInput.oninput = () => {
if (!notesPanel.hidden) return;
notesToggle.textContent = notesInput.value.trim() ? 'Editar indicación' : '¿Alguna indicación especial?';
};
qtyMinus.onclick = () => {
productModalState.qty = Math.max(1, productModalState.qty - 1);
refreshModalPrice();
};
qtyPlus.onclick = () => {
productModalState.qty += 1;
refreshModalPrice();
};
addBtn.onclick = () => {
const cookPoint = select.value.trim();
const note = notesInput.value.trim().slice(0, 140);
if (needsCookPoint && !cookPoint) {
ensureWarn(`Selecciona el punto de cocción para ${item.name}.`);
return;
}
clearWarn();
if (productModalState.mode === 'edit' && productModalState.rowId && state.cart.has(productModalState.rowId)) {
const row = state.cart.get(productModalState.rowId);
row.cookPoint = cookPoint;
row.note = note;
row.qty = Math.max(1, productModalState.qty);
row.notesOpen = false;
syncUI();
closeProductModal();
return;
}
addToCart(productModalState.itemKey, {
cookPoint,
note,
qty: productModalState.qty,
skipCustomization: true,
});
closeProductModal();
};
modal.classList.add('show');
modal.setAttribute('aria-hidden', 'false');
document.body.classList.add('noScroll');
}
function closeProductModal() {
const modal = $('productModal');
if (!modal) return;
modal.classList.remove('show');
modal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('noScroll');
}
function wireProductModal() {
const closeBtn = $('productModalClose');
const modal = $('productModal');
if (closeBtn) closeBtn.onclick = () => closeProductModal();
if (modal) {
modal.addEventListener('click', (e) => {
if (e.target === modal) closeProductModal();
});
}
}
function wire() {
const cartBtn = $('cartBtn');
const closeBtn = $('closeDrawer');
const overlay = $('overlay');
if (cartBtn) cartBtn.onclick = openDrawer;
if (closeBtn) closeBtn.onclick = closeDrawer;
if (overlay) overlay.onclick = closeDrawer;
const btnDelivery = $('btnDelivery');
const btnRetiro = $('btnRetiro');
if (btnDelivery) btnDelivery.onclick = () => setMethod('delivery');
if (btnRetiro) btnRetiro.onclick = () => setMethod('pickup');
const addrInput = $('addrInput');
if (addrInput) {
const onAddr = (e) => {
state.addr = String(e.target.value || '').slice(0, 160);
syncWarnings();
};
['input', 'change', 'blur', 'keyup'].forEach((ev) => addrInput.addEventListener(ev, onAddr));
}
['clientName', 'clientPhone', 'addrInput'].forEach((id) => {
const el = $(id);
if (!el) return;
['input', 'change', 'blur', 'keyup', 'paste'].forEach((ev) => el.addEventListener(ev, () => {
if (id === 'addrInput') state.addr = String(el.value || '').slice(0, 160);
syncWarnings();
}));
if (id === 'clientName') {
el.addEventListener('blur', maybeAdvanceFromNameToPhone);
el.addEventListener('change', maybeAdvanceFromNameToPhone);
el.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
maybeAdvanceFromNameToPhone();
}
});
}
});
const checkoutBtn = $('checkoutBtn');
if (checkoutBtn) checkoutBtn.onclick = (e) => { e.preventDefault(); handleCheckout(); };
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeDrawer();
closeProductModal();
hideOrderOverlay();
}
});
wireProductModal();
const successOverlay = ensureOrderOverlay();
if (successOverlay && !successOverlay.dataset.bound) {
successOverlay.dataset.bound = '1';
successOverlay.addEventListener('click', (e) => {
if (e.target === successOverlay) hideOrderOverlay();
});
}
}
function init() {
setYear();
renderSkeleton();
renderChips();
requestAnimationFrame(() => {
renderMenu();
});
if (state.localId !== DEMO_LOCAL_FIXED) state.localId = DEMO_LOCAL_FIXED;
setLocal(DEMO_LOCAL_FIXED);
setMethod(deliveryEnabled() ? 'delivery' : 'pickup');
applyWebMode(WEB_MODE);
syncUI();
wire();
}
window.addFromCard = function addFromCard(itemKey, el) {
if (!itemKey || !webModeAllowsOrders()) return;
pulseEl(el);
addToCart(itemKey);
};
document.addEventListener('DOMContentLoaded', async () => {
await loadServiceStatus();
init();
startWebModeAutoRefresh();
});
(function () {
const form = document.getElementById('contactForm');
const toast = document.getElementById('toast');
if (!form) return;
function showToast(msg, ok = true) {
if (!toast) return;
toast.textContent = msg;
toast.classList.remove('show', 'bad');
void toast.offsetWidth;
toast.classList.add('show');
if (!ok) toast.classList.add('bad');
setTimeout(() => toast.classList.remove('show'), 3000);
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const fd = new FormData(form);
try {
const res = await fetch('contact_send.php', { method: 'POST', body: fd });
const data = await res.json().catch(() => ({ ok: false, message: 'Error' }));
if (data.ok) {
showToast(data.message, true);
form.reset();
} else {
showToast(data.message || 'No pudimos enviar.', false);
}
} catch (err) {
showToast('No pudimos enviar. Revisa tu conexión.', false);
}
});
}());
let __comboItem = null;
let __comboSelections = null;
function itemsFromCategory(catName, includeUnpublished) {
const cat = (menu || []).find((c) => (c.name || '').toLowerCase() === (catName || '').toLowerCase());
if (!cat) return [];
const items = cat.items || [];
return includeUnpublished ? items : items.filter((it) => (it.published === undefined) ? true : !!it.published);
}
function openCombo(item) {
__comboItem = item;
__comboSelections = ((item.config && item.config.groups) || []).map((g) => ({ title: g.title, pick: g.pick, items: [] }));
}
function closeCombo() {
__comboItem = null;
__comboSelections = null;
}
function pickCombo(gi, pid) {
const group = __comboSelections?.[gi];
if (!group) return;
const idx = group.items.indexOf(pid);
if (idx >= 0) {
group.items.splice(idx, 1);
return;
}
if (group.items.length >= group.pick) return;
group.items.push(pid);
}
function updateComboUI() { return true; }
function addComboToCart(item, selections) {
const key = `combo:${item.id}:${Date.now()}`;
const selLines = (selections || []).map((g) => {
const names = (g.items || []).map((pid) => (itemById.get(pid)?.name || pid)).join(', ');
return `${g.title}: ${names}`;
});
const note = selLines.length ? `Combo\n${selLines.join(' | ')}` : 'Combo';
const pseudoItem = {
id: key,
baseId: item.id,
name: item.name,
price: item.price,
type: 'combo',
};
state.cart.set(key, { item: pseudoItem, qty: 1, note, notesOpen: false, cookPoint: '' });
syncUI();
}
/* CLIENTE PLUS / category state tabs */
(function(){
const getSections = () => {
return (typeof getRenderableSections === 'function' ? getRenderableSections() : [])
.filter((cat) => String(cat?.name || '').trim());
};
const ensureValidActiveCategory = () => {
const sections = getSections();
if (!sections.length) return;
const exists = sections.some((s) => String(s.id) === String(state.activeCategory || ''));
if (!exists) {
state.activeCategory = String(sections[0].id);
}
};
const centerActiveTab = () => {
const tabsInner = document.getElementById('categoryTabsInner');
if (!tabsInner) return;
const active = tabsInner.querySelector('.categoryTabs__link.is-active');
if (!active) return;
const left = active.offsetLeft - (tabsInner.clientWidth / 2) + (active.offsetWidth / 2);
tabsInner.scrollTo({ left: Math.max(0, left), behavior: 'smooth' });
};
const updateArrowState = () => {
const tabsInner = document.getElementById('categoryTabsInner');
const btnLeft = document.getElementById('catScrollLeft');
const btnRight = document.getElementById('catScrollRight');
if (!tabsInner) return;
const maxScroll = Math.max(0, tabsInner.scrollWidth - tabsInner.clientWidth);
const desktop = window.matchMedia('(min-width: 769px)').matches;
if (btnLeft) {
btnLeft.style.display = desktop ? 'flex' : 'none';
btnLeft.style.opacity = tabsInner.scrollLeft > 4 ? '1' : '0';
btnLeft.style.pointerEvents = (desktop && tabsInner.scrollLeft > 4) ? 'auto' : 'none';
}
if (btnRight) {
btnRight.style.display = desktop ? 'flex' : 'none';
btnRight.style.opacity = tabsInner.scrollLeft < (maxScroll - 4) ? '1' : '0';
btnRight.style.pointerEvents = (desktop && tabsInner.scrollLeft < (maxScroll - 4)) ? 'auto' : 'none';
}
};
const updateStateTabs = () => {
const links = Array.from(document.querySelectorAll('[data-cat-link]'));
if (!links.length) return;
ensureValidActiveCategory();
const activeId = String(state?.activeCategory || '');
links.forEach((link) => {
const linkId = String(link.dataset.catLink || '');
link.classList.toggle('is-active', linkId === activeId);
});
centerActiveTab();
updateArrowState();
};
const scrollToMenuTop = () => {
const topbar = document.querySelector('.topbar.topbar--dark') || document.querySelector('.topbar');
const tabs = document.getElementById('categoryTabs');
const menuWrap = document.getElementById('menuWrap');
if (!menuWrap) return;
const offset = (topbar?.offsetHeight || 0) + (tabs?.offsetHeight || 0) + 8;
const y = menuWrap.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top: Math.max(0, y), behavior:'smooth' });
};
const bindStateTabs = () => {
const links = Array.from(document.querySelectorAll('[data-cat-link]'));
if (!links.length) return;
links.forEach((link) => {
if (link.dataset.boundClick === '1') return;
link.dataset.boundClick = '1';
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = String(link.dataset.catLink || '');
if (!targetId) return;
state.activeCategory = targetId;
if (typeof renderChips === 'function') renderChips();
if (typeof renderMenu === 'function') renderMenu();
updateStateTabs();
scrollToMenuTop();
});
});
const tabsInner = document.getElementById('categoryTabsInner');
const btnLeft = document.getElementById('catScrollLeft');
const btnRight = document.getElementById('catScrollRight');
const scrollAmount = () => Math.max(180, Math.floor((tabsInner?.clientWidth || 0) * 0.72));
if (btnLeft && btnLeft.dataset.boundArrow !== '1') {
btnLeft.dataset.boundArrow = '1';
btnLeft.addEventListener('click', () => {
if (!tabsInner) return;
tabsInner.scrollBy({ left: -scrollAmount(), behavior: 'smooth' });
});
}
if (btnRight && btnRight.dataset.boundArrow !== '1') {
btnRight.dataset.boundArrow = '1';
btnRight.addEventListener('click', () => {
if (!tabsInner) return;
tabsInner.scrollBy({ left: scrollAmount(), behavior: 'smooth' });
});
}
if (tabsInner && tabsInner.dataset.boundScrollState !== '1') {
tabsInner.dataset.boundScrollState = '1';
tabsInner.addEventListener('scroll', updateArrowState, { passive: true });
}
window.addEventListener('resize', updateArrowState);
updateStateTabs();
};
const originalRenderMenu = typeof renderMenu === 'function' ? renderMenu : null;
if (originalRenderMenu && !window.__cpRenderMenuWrapped) {
window.__cpRenderMenuWrapped = true;
renderMenu = function(){
ensureValidActiveCategory();
const result = originalRenderMenu.apply(this, arguments);
bindStateTabs();
updateStateTabs();
return result;
};
}
const originalRenderChips = typeof renderChips === 'function' ? renderChips : null;
if (originalRenderChips && !window.__cpRenderChipsWrapped) {
window.__cpRenderChipsWrapped = true;
renderChips = function(){
const result = originalRenderChips.apply(this, arguments);
updateStateTabs();
return result;
};
}
window.addEventListener('load', () => {
bindStateTabs();
updateStateTabs();
});
document.addEventListener('DOMContentLoaded', () => {
bindStateTabs();
updateStateTabs();
});
})();
/* CLIENTE PLUS / sticky offsets sync */
(function(){
const topbar = document.querySelector('.topbar.topbar--dark') || document.querySelector('.topbar');
const tabs = document.getElementById('categoryTabs');
const tabsInner = document.getElementById('categoryTabsInner');
if (!topbar || !tabs) return;
const syncOffsets = () => {
const h = Math.round(topbar.getBoundingClientRect().height || topbar.offsetHeight || 0);
document.documentElement.style.setProperty('--cp-topbar-offset', h + 'px');
};
const inicio = document.querySelector('.headerNav__link[href="#top"]');
if (inicio) {
inicio.addEventListener('click', (ev) => {
ev.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
syncOffsets();
});
}
if (tabsInner && tabsInner.dataset.boundWheelTabs !== '1') {
tabsInner.dataset.boundWheelTabs = '1';
tabsInner.addEventListener('wheel', (e) => {
const canScrollX = tabsInner.scrollWidth > tabsInner.clientWidth;
if (!canScrollX) return;
const delta = Math.abs(e.deltaY) >= Math.abs(e.deltaX) ? e.deltaY : e.deltaX;
if (!delta) return;
tabsInner.scrollLeft += delta;
e.preventDefault();
}, { passive: false });
}
window.addEventListener('load', syncOffsets);
window.addEventListener('resize', syncOffsets);
window.addEventListener('orientationchange', syncOffsets);
document.addEventListener('DOMContentLoaded', syncOffsets);
syncOffsets();
})();