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 = ` `; 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 = `
${escapeHtml(cat.name)}
`; 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 = `
${item.image ? `${escapeHtml(item.name)}` : ''}
${escapeHtml(item.subcatName || displayCatName || cat.name)}

${escapeHtml(item.name)}

${escapeHtml(item.desc ? item.desc : '—')}

$${fmt(priceFor(item))}
${cta}
`; 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 ? `
${escapeHtml(itemName)}
` : ``; el.innerHTML = `
${thumbHtml}
${escapeHtml(itemName)}
${money(unitPrice)} c/u
${detailLines.length ? `
${detailLines.map((line) => `${escapeHtml(line)}`).join('')}
` : ''}
${money(itemTotal)}
${row.qty}
`; 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 = `${escapeHtml(item.name || '')}`; } 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(); })();