//--- Imports --- import { getOnlineUserCount, getUserList } from './service/chat.js'; import { getProducts, sendOrder } from './service/product.js'; import { login, existsTable, guestLogin, getTableFromUrl } from './service/auth.js'; import { createGlobalLoader, showGlobalLoader, hideGlobalLoader } from './utils/loader.js'; import { updateProgress, claimReward, getProgress } from './utils/progressBar.js'; import { showError } from './utils/error.js'; import { addHistoryRow, setupShoppingCart } from './utils/shoppingCart.js'; import { hideGUI, showGUI } from './utils/gui.js'; import { smartSearch } from './utils/searching.js'; import { getUserData } from './service/user.js'; import { getComment, COMMENT_TYPES } from './utils/get_comment.js'; import { imageObserver } from './utils/observer.js'; // --- Variables Globales --- // --- User Data --- let userId = -1; let userName = "Invitado"; let chatUserName = "guest"; let userTable = null; let userToken = null; let chatWebsocket = null; let isGuest = false; // --- Products & Cart --- let Allproducts = []; let cart = []; let itsEmpty = true; let cacheMode = false; // --- Configuration --- const favoriteCategories = ["Shop", "Pizzas Familiares", "Pizza Medianas"]; const productPriority = { "Shop": [6, 12, 163, 168, 15, 665, 1], "Cervezas": [17], "Coctelería Gin Klein": [655], "Pizzas Familiares": [27], "Pizza Medianas": [2] } const userColors = [ "text-red-500", "text-red-600", "text-rose-500", "text-rose-600", "text-orange-500", "text-orange-600", "text-amber-500", "text-amber-600", "text-yellow-500", "text-yellow-600", "text-lime-500", "text-lime-600", "text-green-500", "text-green-600", "text-emerald-500", "text-emerald-600", "text-teal-500", "text-teal-600", "text-cyan-500", "text-cyan-600", "text-sky-500", "text-sky-600", "text-blue-500", "text-blue-600", "text-indigo-500", "text-indigo-600", "text-violet-500", "text-violet-600", "text-purple-500", "text-purple-600", "text-fuchsia-500", "text-fuchsia-600", "text-pink-500", "text-pink-600", "text-gray-500", "text-gray-600", "text-zinc-500", "text-zinc-600", "text-neutral-500", "text-neutral-600", "text-stone-500", "text-stone-600" ]; const pulpas = ["Frambuesa", "Mango", "Maracuyá", "Piña", "Tradicional"] const bebidasPisco = ["Coca Normal.","Coca Zero","Pepsi","Pepsi 0","Ginger Ale zero","Ginger Ale","Sprite","Tonica Canada dry"] const bebidasGin = ["Tonica Pomelo", "Ginger Beer", "Coca Zero","Pepsi","Pepsi 0","Ginger Ale zero","Ginger Ale","Sprite","Tonica Canada dry"] const bebidasWhisky = ["Tonica Canada dry", "Coca Cola", "Coca Cola Zero", "Pepsi", "Pepsi Zero", "Sprite", "Ginger Ale", "Ginger Ale Zero"] const bebidasRon = ["Coca Cola", "Coca Cola Zero", "Pepsi", "Pepsi Zero", "Sprite", "Ginger Ale", "Ginger Ale Zero"] const productsWithVariety = { "480": pulpas, "79": bebidasPisco, "80": bebidasPisco, "81": bebidasPisco, "84": bebidasPisco, "85": bebidasPisco, "360": bebidasPisco, "378": bebidasPisco, "379": bebidasPisco, "957": bebidasPisco, "912": bebidasPisco, "612": bebidasGin, "697": bebidasGin, "92": bebidasGin, "93": bebidasGin, "106": bebidasWhisky, "108": bebidasWhisky, "363": bebidasWhisky, "109": bebidasRon, "110": bebidasRon, "111": bebidasRon, } //--- Elementos del DOM --- const productListElement = document.getElementById("productList"); const cartItemsElement = document.getElementById("cartItems"); const cartTotalElement = document.getElementById("cartTotal"); const emptyCartTextElement = document.getElementById("emptyCartText"); const checkoutButton = document.getElementById("checkoutButton"); const originalCheckoutButtonText = checkoutButton ? checkoutButton.textContent : "Finalizar Pedido"; const cartCountElement = document.getElementById("cartCount"); // --- Chat Elements --- const chatMessagesElement = document.getElementById("chatMessages"); const chatInputElement = document.getElementById("chatInput"); const chatForm = document.getElementById("chatForm"); const userList = document.getElementById("userList") const onlineUsersElement = document.querySelector("#onlineUsers h4"); // --- Reward Elements --- const rewardBtn = document.getElementById('rewardBtn'); const rewardModal = document.getElementById('rewardModal'); const closeRewardModal = document.getElementById('closeRewardModal'); const closeSuccessRewardModalButton = document.getElementById('closeSuccessRewardModal'); const cancelRewardBtn = document.getElementById('cancelRewardBtn'); const acceptTermsCheckbox = document.getElementById('acceptTermsCheckbox'); const claimRewardBtn = document.getElementById('claimRewardBtn'); const rewardContainer = document.getElementById('rewardContainer'); // ID NECESARIO EN HTML //--- Inicialización y Configuración --- async function initializeApp() { createGlobalLoader(); showGlobalLoader("Conectando..."); // 1. Obtener Mesa const urlTable = getTableFromUrl(); console.log(urlTable); if (!urlTable) { showError("Mesa no especificada en URL."); console.warn("Mesa no especificada en URL."); hideGlobalLoader(); return; } userTable = parseInt(urlTable); if (userTable < 0 || userTable > 1000) { alert("Mesa no válida."); console.warn("Mesa no válida."); window.location.replace("/"); return; } // 2. Verificar Cache const isCacheValid = await checkCache(); if (!isCacheValid) { // 3. Guest Mode try { const guestData = await guestLogin(); userToken = guestData.token; isGuest = true; userName = "Invitado"; userId = 0; // ID temporal para guest // UI Guest if (typeof showToast === "function") { setTimeout(() => showToast("Modo invitado. Inicia sesión para más funciones."), 1000); setTimeout(() => showToast(`Mesa: ${userTable}`), 2000); } } catch (error) { showError("Error crítico de conexión."); hideGlobalLoader(); return; } } // Configuración UI según estado updateUIForUserType(); // Inicializar Componentes if (!isGuest) { chatUserName = userName.split(" ")[0].toLowerCase() + "_mesa" + userTable; initializeChat(); initializeWebSocket(); } await initializeProducts(); await renderProducts(Allproducts); setupSearchListener(); setupTabSwitching(); updateCartDisplay(); // Solo cargar historial si es usuario registrado if (!isGuest) { setupShoppingCart(userId, userToken, userName); } setupBasicListeners(); initializeLoginModal(); showGUI(); hideGlobalLoader(); } function updateUIForUserType() { const loginBtn = document.getElementById('openLoginBtn'); const logoutBtn = document.getElementById('headerLogoutBtn'); // Nuevo botón header const userNameDisplay = document.getElementById('headerUserName'); // Span del nombre const historyTable = document.getElementById('historyTable'); // Tablero de historial if (isGuest) { // UI GUEST if (rewardContainer) rewardContainer.classList.add("hidden"); if (loginBtn) loginBtn.classList.remove("hidden"); if (logoutBtn) logoutBtn.classList.add("hidden"); if (userNameDisplay) userNameDisplay.classList.add("hidden"); if (historyTable) historyTable.parentElement.parentElement.classList.add("hidden"); } else { // UI LOGUEADO if (rewardContainer) rewardContainer.classList.remove("hidden"); if (loginBtn) loginBtn.classList.add("hidden"); if (logoutBtn) logoutBtn.classList.remove("hidden"); if (userNameDisplay) { // Mostramos solo el primer nombre para ahorrar espacio userNameDisplay.textContent = userName.split(" ")[0]; userNameDisplay.classList.remove("hidden"); } } } function initializeLoginModal() { const sessionModal = document.getElementById('sessionModal'); const loginForm = document.getElementById('loginForm'); // const logoutBtn = document.getElementById('logoutBtn'); // Ya no usamos el del modal const openLoginBtn = document.getElementById('openLoginBtn'); const headerLogoutBtn = document.getElementById('headerLogoutBtn'); // Nuevo botón header // 1. Abrir modal (Guest intenta loguearse) if (openLoginBtn) { openLoginBtn.addEventListener('click', () => { document.querySelector("#emailInputContainer").classList.remove("hidden"); document.querySelector("#pinInputContainer").classList.remove("hidden"); const tableInput = document.getElementById("tableInput"); if(tableInput) { tableInput.value = userTable; tableInput.readOnly = true; } // Ocultamos logout del modal si existiera, limpiamos UI const modalLogout = document.getElementById('logoutBtn'); if(modalLogout) modalLogout.classList.add("hidden"); document.querySelector("#loginTitle").textContent = "¡Bienvenido!"; sessionModal.classList.remove('hidden'); }); } // 2. Funcionalidad Cerrar Sesión (Desde el Header) if (headerLogoutBtn) { headerLogoutBtn.addEventListener('click', () => { if(confirm("¿Estás seguro que deseas cerrar sesión?")) { setCookie("userToken", "", -1); window.location.reload(); } }); } // 3. Submit del Login (Igual que antes) loginForm.addEventListener('submit', async (event) => { event.preventDefault(); showGlobalLoader("Iniciando sesión..."); const fd = new FormData(loginForm); const email = fd.get('email').trim(); const pin = fd.get('pin').trim(); if (!email || !pin) { showError("Completa todos los campos."); hideGlobalLoader(); return; } try { const data = await login(email, pin, userTable); userToken = data.token; userName = data.name; userId = data.id; isGuest = false; setCookie("userToken", userToken, 7); updateProgress(data.reward_progress || 0); sessionModal.classList.add('hidden'); initializeApp(); } catch (error) { console.error(error); hideGlobalLoader(); } }); // Cerrar modal click afuera sessionModal.addEventListener('click', (e) => { if (e.target === sessionModal && (isGuest || userId !== -1)) { sessionModal.classList.add('hidden'); } }); } // ... (Resto de funciones WebSocket y Chat igual, solo se ejecutan si !isGuest) ... function initializeWebSocket() { if (isGuest) return; // Seguridad extra if (!chatWebsocket) { const wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; chatWebsocket = new WebSocket(`${wsProtocol}://${location.host}/api/chat/ws?token=${userToken}`); chatWebsocket.onopen = () => { chatWebsocket.send(JSON.stringify({ type: "join", username: chatUserName })); } getOnlineUserCount(userToken).then(count => { onlineUsersElement.innerText = `${count} Usuario${count !== 1 ? 's' : ''} en línea`; }); chatWebsocket.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === "message") displayChatMessage(data.username, data.message); else if (data.type === "join") newUserInChat(data.username); else if (data.type === "leave") userLeftChat(data.username); else if (data.type === "mentioned") { if (data.username && data.username !== chatUserName) return; showMentioned(); } else if (data.type === "ping") { chatWebsocket.send(JSON.stringify({ type: "pong" })); } } } } function initializeChat() { if (isGuest || !chatForm) return; // ... (Código original de initializeChat) ... chatForm.addEventListener("submit", (event) => { event.preventDefault(); if (chatInputElement.value.trim() === "") return; chatForm.querySelector("button").animate( [{ transform: 'scale(1)' }, { transform: 'scale(0.9)' }, { transform: 'scale(1)' }], { duration: 200, iterations: 1, easing: 'ease-in-out' } ) sendMessage(chatInputElement.value.trim()); chatInputElement.value = ""; }); let debounceTimer; chatInputElement.addEventListener("input", () => { const lastWord = chatInputElement.value.split(" ").at(-1); if (lastWord.trim().startsWith("@")) { clearTimeout(debounceTimer); debounceTimer = setTimeout(async () => { userList.classList.remove("hidden"); let users = await getUserList(lastWord.trim().substring(1), userToken); users = users.filter(u => u !== chatUserName); users.push("IAKlein"); userList.innerHTML = ""; users.forEach(user => { const userItem = document.createElement("button"); userItem.type = "button"; userItem.onclick = () => { const inputValue = chatInputElement.value; const lastWordIndex = inputValue.lastIndexOf(lastWord); if (lastWordIndex !== -1) { chatInputElement.value = inputValue.slice(0, lastWordIndex) + `@${user}` + inputValue.slice(lastWordIndex + lastWord.length); } userList.classList.add("hidden"); chatInputElement.value += " "; chatInputElement.focus(); }; const color = getUserColor(user); userItem.classList.add("list-user-name", "cursor-pointer", color); userItem.textContent = user; userList.appendChild(userItem); }); }, 300); } else { userList.classList.add("hidden"); clearTimeout(debounceTimer); } }); } // ... (Funciones sendMessage, userMentioned, displayChatMessage, etc. iguales) ... async function sendMessage(message) { if (chatWebsocket) { if (chatWebsocket.readyState !== WebSocket.OPEN) { chatWebsocket = null; initializeWebSocket(); } await chatWebsocket.send(JSON.stringify({ type: "message", username: chatUserName, message: message })); let mentions_repeated = []; await setTimeout(() => { const mentionRegex = /@([a-zA-Z0-9_]+_mesa\d+|IAKlein)/g; const mentions = message.match(mentionRegex); if (mentions) { mentions.forEach(mention => { const username = mention.slice(1); const exist = mentions_repeated.find(u => u === username); if (exist || username === chatUserName) return; mentions_repeated.push(username); if (username === "IAKlein"){ sendMessageToAI(message); }else { userMentioned(username); } }); } }, 1000); } } function userMentioned(username) { chatWebsocket.send(JSON.stringify({ type: "mention", username: username })); } function showMentioned(){ const chatButton = document.querySelector("#chatIcon span"); if (chatButton) { chatButton.classList.remove("hidden"); chatButton.animate( [{ transform: 'scale(1)' }, { transform: 'scale(1.5)' }, { transform: 'scale(1)' }], { duration: 300, iterations: 3, easing: 'ease-in-out' } ); } } window.hideMentioned = function(){ const chatButton = document.querySelector("#chatIcon span"); if (chatButton) chatButton.classList.add("hidden"); } function getUserColor(username) { const hash = [...username].reduce((acc, char) => acc + char.charCodeAt(0), 0); return userColors[hash % userColors.length]; } function displayChatMessage(sender, message, time = undefined) { let realtime = `[${time ? time : new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `; let messageTemplate = chatMessagesElement.querySelector("#chatMessageTemplate"); if (!messageTemplate) return; let messageClone = messageTemplate.content.cloneNode(true).firstElementChild; messageClone.querySelector(".chat-message-text").innerHTML = message; messageClone.querySelector(".chat-message-time").innerHTML = realtime; messageClone.querySelector(".chat-message-user").innerHTML = sender messageClone.querySelector(".chat-message-user").classList.add(getUserColor(sender)); chatMessagesElement.appendChild(messageClone); chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight; } function newUserInChat(userName) { let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate"); if (!userTemplate) return; const realtime = `[${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `; let userClone = userTemplate.content.cloneNode(true).firstElementChild; userClone.classList.add("text-green-600"); userClone.querySelector(".chat-message-time").innerHTML = realtime; userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha unido al chat`; chatMessagesElement.appendChild(userClone); } function userLeftChat(userName) { let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate"); if (!userTemplate) return; const realtime = `[${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false })}] `; let userClone = userTemplate.content.cloneNode(true).firstElementChild; userClone.classList.add("text-red-600"); userClone.querySelector(".chat-message-time").innerHTML = realtime; userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha ido del chat`; chatMessagesElement.appendChild(userClone); } function sendMessageToAI(userMessage) { if (userMessage === "") return; const messages = Array.from(chatMessagesElement.querySelectorAll(".chat-message")).slice(-10); let formattedMessages = messages.map(msg => { const sender = msg.querySelector(".chat-message-user").textContent; const content = msg.querySelector(".chat-message-text").innerHTML; return { user: sender, content: content }; }); chatWebsocket.send(JSON.stringify({ type: "ai_message", username: chatUserName, messages: formattedMessages })); chatInputElement.value = ""; } //--- Manejo de Pestañas --- window.switchTab = function(targetTabId) { // BLOQUEO CHAT PARA GUEST if (targetTabId === 'chatTab' && isGuest) { if (typeof showToast === "function") showToast("Inicia sesión para usar el chat"); // Opcional: abrir modal login const openLoginBtn = document.getElementById('openLoginBtn'); if(openLoginBtn) openLoginBtn.click(); return; } const targetButton = document.querySelector(`[data-target="${targetTabId}"]`); if (!targetButton) return; if (targetTabId === "chatTab") window.hideMentioned(); const active = document.querySelector(':not(.hidden)[data-tab]'); if (!active) return; const activeIndex = active.dataset.index; const to = document.querySelector(`#${targetTabId}[data-tab]`); if (!to) return; const toIndex = to.dataset.index; if (activeIndex === toIndex || transitioning) return; const buttons = document.querySelectorAll('.tab-btn'); buttons.forEach(button => button.classList.remove('active')); targetButton.classList.add('active'); performTabTransition(active, to, activeIndex, toIndex, targetButton); }; // ... (setupTabSwitching, performTabTransition, setCookie, getCookie sin cambios) ... // Tab switching variables const animation_time = 200; let transitioning = false; function performTabTransition(active, to, activeIndex, toIndex, targetButton) { active.style.height = "100%"; active.style.width = "100vw"; to.style.height = "100%"; to.style.width = "100vw"; to.style.zIndex = "1"; active.style.zIndex = "0"; transitioning = true; const otherTabs = document.querySelectorAll('[data-tab]'); otherTabs.forEach(tab => { if (tab !== active && tab !== to) { tab.classList.add('hidden'); tab.classList.remove(`animate-[slideLeft_${animation_time}ms_ease-out]`, `animate-[slideRight_${animation_time}ms_ease-out]`); tab.classList.remove(`animate-[slideLeftIn_${animation_time}ms_ease-out]`, `animate-[slideRightIn_${animation_time}ms_ease-out]`); } }); to.classList.remove('hidden'); if (activeIndex < toIndex) { active.classList.add(`animate-[slideLeft_${animation_time}ms_ease-out]`); to.classList.add(`animate-[slideRightIn_${animation_time}ms_ease-out]`); } else if (activeIndex > toIndex) { active.classList.add(`animate-[slideRight_${animation_time}ms_ease-out]`); to.classList.add(`animate-[slideLeftIn_${animation_time}ms_ease-out]`); } setTimeout(() => { active.classList.remove(`animate-[slideLeft_${animation_time}ms_ease-out]`, `animate-[slideRight_${animation_time}ms_ease-out]`); active.classList.add('hidden'); to.classList.remove(`animate-[slideLeftIn_${animation_time}ms_ease-out]`, `animate-[slideRightIn_${animation_time}ms_ease-out]`); transitioning = false; }, animation_time); const title = targetButton.dataset.title; if (title) { const mainTitle = document.getElementById('mainTitle'); if (mainTitle) mainTitle.textContent = title; } } function setupTabSwitching() { const buttons = document.querySelectorAll('.tab-btn'); buttons.forEach(btn => { btn.addEventListener('click', () => { const target = btn.dataset.target; window.switchTab(target); }); }); } function setCookie(name, value, days) { const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString(); document.cookie = `${name}=${value}; expires=${expires}; path=/`; } function getCookie(name) { const cookies = document.cookie.split('; '); for (const cookie of cookies) { const [cookieName, cookieValue] = cookie.split('='); if (cookieName === name) return decodeURIComponent(cookieValue); } return null; } function formatPrice(price) { return price.toLocaleString("es-CL", { style: "currency", currency: "CLP" }); } // ... (Funciones de Productos, renderizado y carrito permanecen igual. addToCart usa isGuest para determinar funcionalidades si fuera necesario, pero por ahora todos compran) ... // (initializeProducts, createCategories, renderProducts, renderProductsWithAnimation, addComment, addToCart, removeFromCart, calculateTotal, updateCartDisplay, processOrder) async function initializeProducts() { Allproducts = await getProducts(userToken); } // [Incluir el resto de funciones de productos y carrito aquí, sin cambios, ya que funcionan con userToken sea guest o user] async function createCategories(products) { let categories = new Set(products.map(product => product.type || "Sin categoría")); categories = Array.from(categories).sort((a, b) => a.localeCompare(b)); for (const category of [...favoriteCategories].reverse()) { if (categories.includes(category)) { categories = categories.filter(cat => cat !== category); categories.unshift(category); } } if (!productListElement) return; const categoryContainers = categories.map(category => { const container = document.createElement("div"); container.classList.add("category-container", "mb-8", "p-4"); const title = document.createElement("h2"); title.classList.add("category-title", "text-3xl", "font-bold", "border-b-2", "pb-3", "mb-4"); const lastChar = category.charAt(category.length - 1).toLowerCase(); const pluralSuffix = ["a", "e", "i", "o", "u", "á", "é", "í", "ó", "ú", "p"].includes(lastChar) ? "s" : lastChar === "s" ? "" : "es"; title.textContent = category + pluralSuffix; container.appendChild(title); const productList = document.createElement("div"); productList.classList.add("product-list", "space-y-6"); productList.id = `productList-${category}`; container.appendChild(productList); productListElement.appendChild(container); return { category, container: productList }; }); return categoryContainers; } async function renderProducts(products, groupInCategories = true, searchTerm = "") { if (!productListElement) return; const template = document.getElementById("product-card-template"); if (!template) return; productListElement.innerHTML = ""; let categoryContainers = []; if (groupInCategories) { categoryContainers = await createCategories(products); } else { categoryContainers = [{ category: searchTerm, container: productListElement }]; } if (products.length === 0) { const noProductsMessage = document.createElement("p"); noProductsMessage.textContent = "No hay productos disponibles."; productListElement.appendChild(noProductsMessage); return; } for (const { category, container } of categoryContainers) { let productsInCategory = groupInCategories ? products.filter(product => product.type === category) : products; if (favoriteCategories.includes(category) && productPriority[category]) { productsInCategory.sort((a, b) => { const aIndex = productPriority[category].indexOf(a.id); const bIndex = productPriority[category].indexOf(b.id); if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex; if (aIndex !== -1 && bIndex === -1) return -1; if (aIndex === -1 && bIndex !== -1) return 1; return 0; }); } if (productsInCategory.length === 0) continue; productsInCategory.forEach(product => { const clone = template.content.cloneNode(true).firstElementChild; clone.querySelector(".product-type").textContent = product.type || "Sin categoría"; clone.querySelector(".product-name").textContent = product.name; clone.querySelector(".product-description").textContent = product.description; clone.querySelector(".product-price").textContent = formatPrice(product.price); const image = clone.querySelector(".product-image"); image.dataset.src = product.image const addBtn = clone.querySelector(".add-to-cart-btn"); addBtn.dataset.productId = product.id; addBtn.addEventListener('click', (event) => { const productId = parseInt(event.target.dataset.productId); addToCart(productId, event.target); }); container.appendChild(clone); imageObserver.observe(image); }); } } async function renderProductsWithAnimation(products, groupInCategories = true, searchTerm = "") { if (!productListElement) return; const template = document.getElementById("product-card-template"); if (!template) return; productListElement.style.opacity = "0"; productListElement.style.transform = "scale(0.95)"; setTimeout(async () => { productListElement.innerHTML = ""; let categoryContainers = []; if (groupInCategories) { categoryContainers = await createCategories(products); } else { categoryContainers = [{ category: searchTerm, container: productListElement }]; } categoryContainers.sort((a, b) => { const aIndex = favoriteCategories.indexOf(a.category); const bIndex = favoriteCategories.indexOf(b.category); return aIndex - bIndex; }); if (products.length === 0) { const noProductsMessage = document.createElement("p"); noProductsMessage.textContent = "No hay productos disponibles."; noProductsMessage.style.opacity = "0"; noProductsMessage.style.transform = "translateY(20px)"; noProductsMessage.style.transition = "opacity 0.3s ease, transform 0.3s ease"; productListElement.appendChild(noProductsMessage); productListElement.style.opacity = "1"; productListElement.style.transform = "scale(1)"; setTimeout(() => { noProductsMessage.style.opacity = "1"; noProductsMessage.style.transform = "translateY(0)"; }, 50); return; } let animationDelay = 0; for (const { category, container } of categoryContainers) { let productsInCategory = groupInCategories ? products.filter(product => product.type === category) : products; if (productsInCategory.length === 0) continue; if (favoriteCategories.includes(category) && productPriority[category]) { productsInCategory.sort((a, b) => { const aIndex = productPriority[category].indexOf(a.id); const bIndex = productPriority[category].indexOf(b.id); if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex; if (aIndex !== -1 && bIndex === -1) return -1; if (aIndex === -1 && bIndex !== -1) return 1; return 0; }); } productsInCategory.forEach((product, index) => { const clone = template.content.cloneNode(true).firstElementChild; clone.querySelector(".product-type").textContent = product.type || "Sin categoría"; clone.querySelector(".product-name").textContent = product.name; clone.querySelector(".product-description").textContent = product.description; clone.querySelector(".product-price").textContent = formatPrice(product.price); const image = clone.querySelector(".product-image"); image.dataset.src = product.image const addBtn = clone.querySelector(".add-to-cart-btn"); addBtn.dataset.productId = product.id; addBtn.addEventListener('click', (event) => { const productId = parseInt(event.target.dataset.productId); addToCart(productId, event.target); }); const productCard = clone.children[0]; productCard.style.opacity = "0"; productCard.style.transform = "translateY(20px) scale(0.95)"; productCard.style.transition = "opacity 0.4s ease, transform 0.4s ease"; container.appendChild(clone); imageObserver.observe(image); setTimeout(() => { productCard.style.opacity = "1"; productCard.style.transform = "translateY(0) scale(1)"; }, animationDelay); animationDelay += 50; }); } productListElement.style.opacity = "1"; productListElement.style.transform = "scale(1)"; }, 150); } window.addComment = async function(productId) { const cartItem = cart.find(p => p.id === productId); if (!cartItem) return; const comment = await getComment(COMMENT_TYPES.LIBRE, [cartItem.comment]).catch(e => console.error(e)); if (!comment) return; cartItem.comment += ` ${comment}`; } window.addToCart = async function(productId, buttonElement = null) { let comment = ""; if (productsWithVariety[String(productId)]) { comment = await getComment(COMMENT_TYPES.DESPLEGABLE, productsWithVariety[String(productId)]); } const product = Allproducts.find(p => p.id === productId); if (!product) return; const cartItem = cart.find(item => item.id === productId); if (cartItem) { cartItem.quantity++; if (comment ) cartItem.comment += `, ${comment}`; } else { cart.push({ ...product, quantity: 1, comment }); } if (buttonElement) { const originalHTML = buttonElement.innerHTML; buttonElement.textContent = "✔ Agregado!"; buttonElement.disabled = true; setTimeout(() => { buttonElement.innerHTML = originalHTML; buttonElement.disabled = false; }, 300); } updateCartDisplay(); if (typeof showToast === "function") showToast(`${product.name} agregado al carrito`); }; window.removeFromCart = function(productId, removeAll = false) { const itemIndex = cart.findIndex(item => item.id === productId); if (itemIndex > -1) { if (removeAll || cart[itemIndex].quantity === 1) { cart.splice(itemIndex, 1); } else { cart[itemIndex].quantity--; } } updateCartDisplay(); }; function calculateTotal() { if (!cartTotalElement) return; const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0); cartTotalElement.textContent = formatPrice(total); } function updateCartDisplay() { if (!cartItemsElement || !emptyCartTextElement || !checkoutButton || !cartCountElement) return; cartItemsElement.innerHTML = ""; cartCountElement.textContent = cart.reduce((sum, item) => sum + item.quantity, 0); if (cart.length === 0) { cartCountElement.classList.add("hidden"); emptyCartTextElement.classList.remove("hidden"); checkoutButton.disabled = true; itsEmpty = true; } else { cartCountElement.classList.remove("hidden"); if (cartCountElement && itsEmpty) { itsEmpty = false; cartCountElement.animate([{ transform: 'scale(0)' }, { transform: 'scale(1)' }], { duration: 300, iterations: 1, easing: 'ease-in-out' }); } else { cartCountElement.animate([{ transform: 'scale(1) rotate(0deg)' }, { transform: 'scale(1.2) rotate(180deg)' }, { transform: 'scale(1) rotate(360deg)' }], { duration: 300, iterations: 1, easing: 'ease-in-out' }); } emptyCartTextElement.classList.add("hidden"); checkoutButton.disabled = false; cart.forEach(item => { const cartItemHTML = `
${formatPrice(item.price * item.quantity)}