|
@@ -1,6 +1,7 @@
|
|
|
|
|
+//--- Imports ---
|
|
|
import { sendMessage as serviceSendMessage } from './service/chat.js';
|
|
import { sendMessage as serviceSendMessage } from './service/chat.js';
|
|
|
import { getProducts, sendOrder } from './service/product.js';
|
|
import { getProducts, sendOrder } from './service/product.js';
|
|
|
-import { login } from './service/auth.js'
|
|
|
|
|
|
|
+import { login } from './service/auth.js';
|
|
|
import { createGlobalLoader, showGlobalLoader, hideGlobalLoader } from './utils/loader.js';
|
|
import { createGlobalLoader, showGlobalLoader, hideGlobalLoader } from './utils/loader.js';
|
|
|
import { updateProgress, claimReward } from './utils/progressBar.js';
|
|
import { updateProgress, claimReward } from './utils/progressBar.js';
|
|
|
import { showError } from './utils/error.js';
|
|
import { showError } from './utils/error.js';
|
|
@@ -8,28 +9,64 @@ import { addHistoryRow, setupShoppingCart } from './utils/shoppingCart.js';
|
|
|
import { hideGUI, showGUI } from './utils/gui.js';
|
|
import { hideGUI, showGUI } from './utils/gui.js';
|
|
|
import { smartSearch } from './utils/searching.js';
|
|
import { smartSearch } from './utils/searching.js';
|
|
|
import { getUserData } from './service/user.js';
|
|
import { getUserData } from './service/user.js';
|
|
|
-// --- Variables de Usuario ---
|
|
|
|
|
|
|
+
|
|
|
|
|
+// --- Variables Globales ---
|
|
|
|
|
+
|
|
|
|
|
+// --- User Data ---
|
|
|
let userId = -1;
|
|
let userId = -1;
|
|
|
let userName = "Cliente";
|
|
let userName = "Cliente";
|
|
|
|
|
+let chatUserName = "prueba_mesa43"
|
|
|
let userTable = null;
|
|
let userTable = null;
|
|
|
let userToken = null;
|
|
let userToken = null;
|
|
|
-// --- Datos de Productos y Carrito ---
|
|
|
|
|
|
|
+let chatWebsocket = null;
|
|
|
|
|
+
|
|
|
|
|
+// --- Products & Cart ---
|
|
|
let Allproducts = [];
|
|
let Allproducts = [];
|
|
|
let cart = [];
|
|
let cart = [];
|
|
|
let itsEmpty = true;
|
|
let itsEmpty = true;
|
|
|
-let cacheMode = false
|
|
|
|
|
|
|
+let cacheMode = false;
|
|
|
|
|
|
|
|
-// --- Categorias Importantes ---
|
|
|
|
|
|
|
+// --- Configuration ---
|
|
|
|
|
+const favoriteCategories = ["Shop", "Pizzas Familiares", "Pizza Medianas"];
|
|
|
|
|
|
|
|
-const favoriteCategories = ["Shop", "Pizzas Familiares", "Pizza Medianas" ];
|
|
|
|
|
-
|
|
|
|
|
-// --- Historial de Chat ---
|
|
|
|
|
|
|
+// --- Chat History ---
|
|
|
let chatHistory = [
|
|
let chatHistory = [
|
|
|
- { role: "system", content: "¡Hola! Soy tu asistente en Biergarten Klein. ¿Te gustaría una recomendación de nuestras cervezas artesanales?" }
|
|
|
|
|
|
|
+ {
|
|
|
|
|
+ user: "name",
|
|
|
|
|
+ time: "00:00",
|
|
|
|
|
+ content: "Hola Mundo"
|
|
|
|
|
+ }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const userColors = [
|
|
|
|
|
+ // Rojos
|
|
|
|
|
+ "text-red-500", "text-red-600", "text-rose-500", "text-rose-600",
|
|
|
|
|
+ "text-orange-500", "text-orange-600", "text-amber-500", "text-amber-600",
|
|
|
|
|
+
|
|
|
|
|
+ // Amarillos / dorados
|
|
|
|
|
+ "text-yellow-500", "text-yellow-600", "text-lime-500", "text-lime-600",
|
|
|
|
|
+
|
|
|
|
|
+ // Verdes
|
|
|
|
|
+ "text-green-500", "text-green-600", "text-emerald-500", "text-emerald-600",
|
|
|
|
|
+ "text-teal-500", "text-teal-600", "text-cyan-500", "text-cyan-600",
|
|
|
|
|
+
|
|
|
|
|
+ // Azules
|
|
|
|
|
+ "text-sky-500", "text-sky-600", "text-blue-500", "text-blue-600",
|
|
|
|
|
+ "text-indigo-500", "text-indigo-600",
|
|
|
|
|
+
|
|
|
|
|
+ // Violetas / púrpuras æ
|
|
|
|
|
+ "text-violet-500", "text-violet-600", "text-purple-500", "text-purple-600",
|
|
|
|
|
+ "text-fuchsia-500", "text-fuchsia-600", "text-pink-500", "text-pink-600",
|
|
|
|
|
+
|
|
|
|
|
+ // Neutros variados
|
|
|
|
|
+ "text-gray-500", "text-gray-600", "text-zinc-500", "text-zinc-600",
|
|
|
|
|
+ "text-neutral-500", "text-neutral-600", "text-stone-500", "text-stone-600"
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
-// --- Elementos del DOM: Productos y Carrito ---
|
|
|
|
|
|
|
+//--- Elementos del DOM ---
|
|
|
|
|
+
|
|
|
|
|
+// --- Product & Cart Elements ---
|
|
|
const productListElement = document.getElementById("productList");
|
|
const productListElement = document.getElementById("productList");
|
|
|
const cartItemsElement = document.getElementById("cartItems");
|
|
const cartItemsElement = document.getElementById("cartItems");
|
|
|
const cartTotalElement = document.getElementById("cartTotal");
|
|
const cartTotalElement = document.getElementById("cartTotal");
|
|
@@ -38,41 +75,50 @@ const checkoutButton = document.getElementById("checkoutButton");
|
|
|
const originalCheckoutButtonText = checkoutButton ? checkoutButton.textContent : "Finalizar Pedido";
|
|
const originalCheckoutButtonText = checkoutButton ? checkoutButton.textContent : "Finalizar Pedido";
|
|
|
const cartCountElement = document.getElementById("cartCount");
|
|
const cartCountElement = document.getElementById("cartCount");
|
|
|
|
|
|
|
|
-// --- Elementos del DOM: Chat ---
|
|
|
|
|
|
|
+// --- Chat Elements ---
|
|
|
const chatMessagesElement = document.getElementById("chatMessages");
|
|
const chatMessagesElement = document.getElementById("chatMessages");
|
|
|
const chatInputElement = document.getElementById("chatInput");
|
|
const chatInputElement = document.getElementById("chatInput");
|
|
|
const chatForm = document.getElementById("chatForm");
|
|
const chatForm = document.getElementById("chatForm");
|
|
|
-const aiLoadingIndicator = document.getElementById("aiLoadingIndicator");
|
|
|
|
|
-const chatSuggestionsElement = document.getElementById("chatSuggestions");
|
|
|
|
|
|
|
+const userList = document.getElementById("userList")
|
|
|
|
|
+// --- 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');
|
|
|
|
|
|
|
|
|
|
+//--- Inicialización y Configuración ---
|
|
|
|
|
|
|
|
-//#region --- Inicialización y Configuracion ---
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Main application initialization
|
|
|
|
|
+ */
|
|
|
async function initializeApp() {
|
|
async function initializeApp() {
|
|
|
showGlobalLoader("Cargando productos...");
|
|
showGlobalLoader("Cargando productos...");
|
|
|
|
|
+ chatUserName = userName.split(" ")[0].toLowerCase() + "_mesa"+userTable
|
|
|
|
|
+ initializeChat();
|
|
|
|
|
+ initializeWebSocket();
|
|
|
await initializeProducts();
|
|
await initializeProducts();
|
|
|
await renderProducts(Allproducts);
|
|
await renderProducts(Allproducts);
|
|
|
setupSearchListener();
|
|
setupSearchListener();
|
|
|
updateCartDisplay();
|
|
updateCartDisplay();
|
|
|
setupShoppingCart(userId, userToken, userName);
|
|
setupShoppingCart(userId, userToken, userName);
|
|
|
- initializeChat();
|
|
|
|
|
setupBasicListeners();
|
|
setupBasicListeners();
|
|
|
showGUI();
|
|
showGUI();
|
|
|
hideGlobalLoader();
|
|
hideGlobalLoader();
|
|
|
-
|
|
|
|
|
- const chatSuggestions = Array.from(chatSuggestionsElement.children);
|
|
|
|
|
-
|
|
|
|
|
- chatSuggestions.forEach(suggestion => {
|
|
|
|
|
- suggestion.addEventListener("click", () => {
|
|
|
|
|
- sendSuggestion(suggestion.querySelector(".chat-suggestion").textContent);
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Initialize login modal and authentication
|
|
|
|
|
+ */
|
|
|
function initializeLoginModal() {
|
|
function initializeLoginModal() {
|
|
|
const sessionModal = document.getElementById('sessionModal');
|
|
const sessionModal = document.getElementById('sessionModal');
|
|
|
const loginForm = document.getElementById('loginForm');
|
|
const loginForm = document.getElementById('loginForm');
|
|
|
const logoutBtn = document.getElementById('logoutBtn');
|
|
const logoutBtn = document.getElementById('logoutBtn');
|
|
|
sessionModal.classList.remove('hidden');
|
|
sessionModal.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
+ // Login form submission
|
|
|
loginForm.addEventListener('submit', async (event) => {
|
|
loginForm.addEventListener('submit', async (event) => {
|
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
|
event.stopPropagation();
|
|
event.stopPropagation();
|
|
@@ -92,7 +138,7 @@ function initializeLoginModal() {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
- const { data } = await login(email, pin)
|
|
|
|
|
|
|
+ const { data } = await login(email, pin);
|
|
|
userToken = data.token;
|
|
userToken = data.token;
|
|
|
userName = data.name;
|
|
userName = data.name;
|
|
|
userId = data.id;
|
|
userId = data.id;
|
|
@@ -104,15 +150,15 @@ function initializeLoginModal() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
sessionModal.classList.add('hidden');
|
|
sessionModal.classList.add('hidden');
|
|
|
-
|
|
|
|
|
initializeApp();
|
|
initializeApp();
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- console.error(error)
|
|
|
|
|
|
|
+ console.error(error);
|
|
|
}
|
|
}
|
|
|
- })
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ // Logout functionality
|
|
|
if (logoutBtn) {
|
|
if (logoutBtn) {
|
|
|
logoutBtn.addEventListener('click', () => {
|
|
logoutBtn.addEventListener('click', () => {
|
|
|
- setCookie("userToken", "", -1); // Eliminar cookie
|
|
|
|
|
|
|
+ setCookie("userToken", "", -1);
|
|
|
userId = -1;
|
|
userId = -1;
|
|
|
userName = "Cliente";
|
|
userName = "Cliente";
|
|
|
userTable = null;
|
|
userTable = null;
|
|
@@ -120,40 +166,95 @@ function initializeLoginModal() {
|
|
|
cacheMode = false;
|
|
cacheMode = false;
|
|
|
hideGUI();
|
|
hideGUI();
|
|
|
sessionModal.classList.remove('hidden');
|
|
sessionModal.classList.remove('hidden');
|
|
|
|
|
+
|
|
|
|
|
+ // Reset UI elements
|
|
|
document.querySelector("#emailInputContainer").classList.remove("hidden");
|
|
document.querySelector("#emailInputContainer").classList.remove("hidden");
|
|
|
document.querySelector("#pinInputContainer").classList.remove("hidden");
|
|
document.querySelector("#pinInputContainer").classList.remove("hidden");
|
|
|
- const loginTitle = document.getElementById("loginTitle");
|
|
|
|
|
- loginTitle.textContent = "¡Bienvenido!";
|
|
|
|
|
- // Restaurar atributos required a los inputs
|
|
|
|
|
|
|
+ document.getElementById("loginTitle").textContent = "¡Bienvenido!";
|
|
|
document.querySelector("#emailInput").setAttribute("required", "");
|
|
document.querySelector("#emailInput").setAttribute("required", "");
|
|
|
document.querySelector("#pinInput").setAttribute("required", "");
|
|
document.querySelector("#pinInput").setAttribute("required", "");
|
|
|
logoutBtn.classList.add("hidden");
|
|
logoutBtn.classList.add("hidden");
|
|
|
document.querySelector("#recoveryPIN").classList.remove("hidden");
|
|
document.querySelector("#recoveryPIN").classList.remove("hidden");
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
+function initializeWebSocket() {
|
|
|
|
|
+ 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
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ 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 === "ping") {
|
|
|
|
|
+ chatWebsocket.send(JSON.stringify({
|
|
|
|
|
+ type: "pong"
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Initialize chat functionality
|
|
|
|
|
+ */
|
|
|
function initializeChat() {
|
|
function initializeChat() {
|
|
|
if (!chatForm) return;
|
|
if (!chatForm) return;
|
|
|
chatForm.addEventListener("submit", (event) => {
|
|
chatForm.addEventListener("submit", (event) => {
|
|
|
event.preventDefault();
|
|
event.preventDefault();
|
|
|
if (chatInputElement.value.trim() === "") return;
|
|
if (chatInputElement.value.trim() === "") return;
|
|
|
|
|
|
|
|
- sendMessageToAI();
|
|
|
|
|
- chatInputElement.addEventListener("input", () => {
|
|
|
|
|
- if (chatInputElement.value.trim() === "") {
|
|
|
|
|
- chatSuggestionsElement.classList.remove("hidden");
|
|
|
|
|
- } else {
|
|
|
|
|
- chatSuggestionsElement.classList.add("hidden");
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // sendMessageToAI();
|
|
|
|
|
+ if (chatWebsocket) {
|
|
|
|
|
+ chatWebsocket.send(JSON.stringify({
|
|
|
|
|
+ type: "message",
|
|
|
|
|
+ username: chatUserName,
|
|
|
|
|
+ message: chatInputElement.value
|
|
|
|
|
+ }));
|
|
|
|
|
+ chatInputElement.value = "";
|
|
|
|
|
+ }
|
|
|
|
|
+ // displayChatMessage(chatUserName, chatInputElement.value)
|
|
|
|
|
+ });
|
|
|
|
|
+ chatInputElement.addEventListener("input", () => {
|
|
|
|
|
+ const lastWord = chatInputElement.value.split(" ").at(-1)
|
|
|
|
|
+ if (lastWord.trim().startsWith("@")) {
|
|
|
|
|
+ userList.classList.remove("hidden")
|
|
|
|
|
+
|
|
|
|
|
+ }else{
|
|
|
|
|
+ userList.classList.add("hidden")
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Setup basic event listeners
|
|
|
|
|
+ */
|
|
|
function setupBasicListeners() {
|
|
function setupBasicListeners() {
|
|
|
if (!checkoutButton) return;
|
|
if (!checkoutButton) return;
|
|
|
|
|
+
|
|
|
checkoutButton.addEventListener("click", processOrder);
|
|
checkoutButton.addEventListener("click", processOrder);
|
|
|
initializeRewards();
|
|
initializeRewards();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Setup search functionality with debouncing
|
|
|
|
|
+ */
|
|
|
function setupSearchListener() {
|
|
function setupSearchListener() {
|
|
|
const searchInput = document.getElementById("searchInput");
|
|
const searchInput = document.getElementById("searchInput");
|
|
|
if (!searchInput) return;
|
|
if (!searchInput) return;
|
|
@@ -161,38 +262,39 @@ function setupSearchListener() {
|
|
|
let debounceTimer;
|
|
let debounceTimer;
|
|
|
|
|
|
|
|
searchInput.addEventListener("input", () => {
|
|
searchInput.addEventListener("input", () => {
|
|
|
- // Limpiar el timer anterior si existe
|
|
|
|
|
clearTimeout(debounceTimer);
|
|
clearTimeout(debounceTimer);
|
|
|
- const specialCases = {
|
|
|
|
|
- "shop": ["cerveza"]
|
|
|
|
|
- }
|
|
|
|
|
- // Agregar una clase de "buscando" para el feedback visual
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Visual feedback during search
|
|
|
productListElement.style.opacity = "0.7";
|
|
productListElement.style.opacity = "0.7";
|
|
|
productListElement.style.transform = "scale(0.98)";
|
|
productListElement.style.transform = "scale(0.98)";
|
|
|
productListElement.style.transition = "opacity 0.2s ease, transform 0.2s ease";
|
|
productListElement.style.transition = "opacity 0.2s ease, transform 0.2s ease";
|
|
|
|
|
|
|
|
- // Debounce de 200ms para evitar muchas llamadas
|
|
|
|
|
|
|
+ // Debounce search for 200ms
|
|
|
debounceTimer = setTimeout(() => {
|
|
debounceTimer = setTimeout(() => {
|
|
|
const searchTerm = searchInput.value.toLowerCase();
|
|
const searchTerm = searchInput.value.toLowerCase();
|
|
|
if (searchTerm.trim() === "") {
|
|
if (searchTerm.trim() === "") {
|
|
|
renderProductsWithAnimation(Allproducts);
|
|
renderProductsWithAnimation(Allproducts);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- const finded = smartSearch(Allproducts, searchTerm);
|
|
|
|
|
- // Renderizar con animación
|
|
|
|
|
- renderProductsWithAnimation(finded, false, searchTerm);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const foundProducts = smartSearch(Allproducts, searchTerm);
|
|
|
|
|
+ renderProductsWithAnimation(foundProducts, false, searchTerm);
|
|
|
}, 200);
|
|
}, 200);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
-//#endregion
|
|
|
|
|
-//#region ===== Utilidad =====
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+//--- Funciones de Utilidad ---
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Set a cookie with expiration
|
|
|
|
|
+ */
|
|
|
function setCookie(name, value, days) {
|
|
function setCookie(name, value, days) {
|
|
|
const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
|
|
const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
|
|
|
document.cookie = `${name}=${value}; expires=${expires}; path=/`;
|
|
document.cookie = `${name}=${value}; expires=${expires}; path=/`;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get a cookie value by name
|
|
|
|
|
+ */
|
|
|
function getCookie(name) {
|
|
function getCookie(name) {
|
|
|
const cookies = document.cookie.split('; ');
|
|
const cookies = document.cookie.split('; ');
|
|
|
for (const cookie of cookies) {
|
|
for (const cookie of cookies) {
|
|
@@ -203,28 +305,42 @@ function getCookie(name) {
|
|
|
}
|
|
}
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
+/**
|
|
|
|
|
|
|
|
|
|
+ * Check and validate cached user data
|
|
|
|
|
+ */
|
|
|
async function checkCache() {
|
|
async function checkCache() {
|
|
|
const tokenCache = getCookie("userToken");
|
|
const tokenCache = getCookie("userToken");
|
|
|
- let user = null
|
|
|
|
|
|
|
+ let user = null;
|
|
|
|
|
+
|
|
|
if (tokenCache) {
|
|
if (tokenCache) {
|
|
|
userToken = tokenCache;
|
|
userToken = tokenCache;
|
|
|
- try{
|
|
|
|
|
|
|
+ try {
|
|
|
const data = await getUserData(userToken);
|
|
const data = await getUserData(userToken);
|
|
|
- user = data.data
|
|
|
|
|
- }catch (error) {
|
|
|
|
|
|
|
+ user = data.data;
|
|
|
|
|
+ } catch (error) {
|
|
|
console.error("Error fetching user data:", error);
|
|
console.error("Error fetching user data:", error);
|
|
|
console.error("Invalid user token, clearing cache.");
|
|
console.error("Invalid user token, clearing cache.");
|
|
|
- setCookie("userToken", "", -1); // Eliminar cookie si es inválida
|
|
|
|
|
|
|
+ setCookie("userToken", "", -1);
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if (user) {
|
|
if (user) {
|
|
|
userId = user.id;
|
|
userId = user.id;
|
|
|
userName = user.name;
|
|
userName = user.name;
|
|
|
updateProgress(user.reward_progress || 0);
|
|
updateProgress(user.reward_progress || 0);
|
|
|
- const [emailInputContainer, pinInputContainer] = [document.getElementById("emailInputContainer"), document.getElementById("pinInputContainer")];
|
|
|
|
|
- const [emailInput, pinInput] = [document.getElementById("emailInput"), document.getElementById("pinInput")];
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Update UI for cached user
|
|
|
|
|
+ const [emailInputContainer, pinInputContainer] = [
|
|
|
|
|
+ document.getElementById("emailInputContainer"),
|
|
|
|
|
+ document.getElementById("pinInputContainer")
|
|
|
|
|
+ ];
|
|
|
|
|
+ const [emailInput, pinInput] = [
|
|
|
|
|
+ document.getElementById("emailInput"),
|
|
|
|
|
+ document.getElementById("pinInput")
|
|
|
|
|
+ ];
|
|
|
const loginTitle = document.getElementById("loginTitle");
|
|
const loginTitle = document.getElementById("loginTitle");
|
|
|
|
|
+
|
|
|
loginTitle.textContent = `¡Bienvenido ${user.name.split(" ")[0]}!`;
|
|
loginTitle.textContent = `¡Bienvenido ${user.name.split(" ")[0]}!`;
|
|
|
emailInputContainer.classList.add("hidden");
|
|
emailInputContainer.classList.add("hidden");
|
|
|
emailInput.removeAttribute("required");
|
|
emailInput.removeAttribute("required");
|
|
@@ -232,45 +348,61 @@ async function checkCache() {
|
|
|
pinInput.removeAttribute("required");
|
|
pinInput.removeAttribute("required");
|
|
|
document.querySelector("#logoutBtn").classList.remove("hidden");
|
|
document.querySelector("#logoutBtn").classList.remove("hidden");
|
|
|
document.querySelector("#recoveryPIN").classList.add("hidden");
|
|
document.querySelector("#recoveryPIN").classList.add("hidden");
|
|
|
- cacheMode = true;
|
|
|
|
|
- return true
|
|
|
|
|
|
|
+ cacheMode = true;
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return cacheMode
|
|
|
|
|
|
|
+ return cacheMode;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Format price to Chilean peso currency
|
|
|
|
|
+ */
|
|
|
function formatPrice(price) {
|
|
function formatPrice(price) {
|
|
|
return price.toLocaleString("es-CL", { style: "currency", currency: "CLP" });
|
|
return price.toLocaleString("es-CL", { style: "currency", currency: "CLP" });
|
|
|
}
|
|
}
|
|
|
-//#endregion
|
|
|
|
|
-//#region ===== Productos =====
|
|
|
|
|
|
|
+//--- Gestión de Productos ---
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Initialize products from API
|
|
|
|
|
+ */
|
|
|
async function initializeProducts() {
|
|
async function initializeProducts() {
|
|
|
Allproducts = await getProducts(userToken);
|
|
Allproducts = await getProducts(userToken);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Create category containers for products
|
|
|
|
|
+ */
|
|
|
async function createCategories(products) {
|
|
async function createCategories(products) {
|
|
|
let categories = new Set(products.map(product => product.type || "Sin categoría"));
|
|
let categories = new Set(products.map(product => product.type || "Sin categoría"));
|
|
|
categories = Array.from(categories).sort((a, b) => a.localeCompare(b));
|
|
categories = Array.from(categories).sort((a, b) => a.localeCompare(b));
|
|
|
|
|
|
|
|
- for (const category of favoriteCategories.reverse() ) {
|
|
|
|
|
|
|
+ // Prioritize favorite categories
|
|
|
|
|
+ for (const category of favoriteCategories.reverse()) {
|
|
|
if (categories.includes(category)) {
|
|
if (categories.includes(category)) {
|
|
|
categories = categories.filter(cat => cat !== category);
|
|
categories = categories.filter(cat => cat !== category);
|
|
|
- categories.unshift(category); // Mover la categoría favorita al inicio
|
|
|
|
|
|
|
+ categories.unshift(category);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if (!productListElement) return;
|
|
if (!productListElement) return;
|
|
|
|
|
|
|
|
const categoryContainers = categories.map(category => {
|
|
const categoryContainers = categories.map(category => {
|
|
|
const container = document.createElement("div");
|
|
const container = document.createElement("div");
|
|
|
- container.classList.add("category-container");
|
|
|
|
|
- container.classList.add("mb-8", "p-4");
|
|
|
|
|
|
|
+ container.classList.add("category-container", "mb-8", "p-4");
|
|
|
|
|
+
|
|
|
const title = document.createElement("h2");
|
|
const title = document.createElement("h2");
|
|
|
title.classList.add("category-title", "text-3xl", "font-bold", "border-b-2", "pb-3", "mb-4");
|
|
title.classList.add("category-title", "text-3xl", "font-bold", "border-b-2", "pb-3", "mb-4");
|
|
|
- let titleText = ["a", "e", "i", "o", "u", "á", "é", "í", "ó", "ú", "p"].includes(category.charAt(category.length - 1).toLowerCase()) ? "s" :
|
|
|
|
|
- category.charAt(category.length - 1).toLowerCase() === "s" ? "" : "es";
|
|
|
|
|
- title.textContent = category + titleText;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Pluralize category name
|
|
|
|
|
+ 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);
|
|
container.appendChild(title);
|
|
|
|
|
+
|
|
|
const productList = document.createElement("div");
|
|
const productList = document.createElement("div");
|
|
|
productList.classList.add("product-list", "space-y-6");
|
|
productList.classList.add("product-list", "space-y-6");
|
|
|
productList.id = `productList-${category}`;
|
|
productList.id = `productList-${category}`;
|
|
@@ -283,6 +415,9 @@ async function createCategories(products) {
|
|
|
return categoryContainers;
|
|
return categoryContainers;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Render products in their respective categories
|
|
|
|
|
+ */
|
|
|
async function renderProducts(products, groupInCategories = true, searchTerm = "") {
|
|
async function renderProducts(products, groupInCategories = true, searchTerm = "") {
|
|
|
if (!productListElement) return;
|
|
if (!productListElement) return;
|
|
|
|
|
|
|
@@ -291,11 +426,13 @@ async function renderProducts(products, groupInCategories = true, searchTerm = "
|
|
|
|
|
|
|
|
productListElement.innerHTML = "";
|
|
productListElement.innerHTML = "";
|
|
|
let categoryContainers = [];
|
|
let categoryContainers = [];
|
|
|
|
|
+
|
|
|
if (groupInCategories) {
|
|
if (groupInCategories) {
|
|
|
categoryContainers = await createCategories(products);
|
|
categoryContainers = await createCategories(products);
|
|
|
- }else{
|
|
|
|
|
- categoryContainers = [ { category: searchTerm, container: productListElement } ];
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ categoryContainers = [{ category: searchTerm, container: productListElement }];
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if (products.length === 0) {
|
|
if (products.length === 0) {
|
|
|
const noProductsMessage = document.createElement("p");
|
|
const noProductsMessage = document.createElement("p");
|
|
|
noProductsMessage.textContent = "No hay productos disponibles.";
|
|
noProductsMessage.textContent = "No hay productos disponibles.";
|
|
@@ -304,12 +441,9 @@ async function renderProducts(products, groupInCategories = true, searchTerm = "
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for (const { category, container } of categoryContainers) {
|
|
for (const { category, container } of categoryContainers) {
|
|
|
- let productsInCategory
|
|
|
|
|
- if (groupInCategories){
|
|
|
|
|
- productsInCategory = products.filter(product => product.type === category);
|
|
|
|
|
- }else{
|
|
|
|
|
- productsInCategory = products;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let productsInCategory = groupInCategories
|
|
|
|
|
+ ? products.filter(product => product.type === category)
|
|
|
|
|
+ : products;
|
|
|
|
|
|
|
|
if (productsInCategory.length === 0) continue;
|
|
if (productsInCategory.length === 0) continue;
|
|
|
|
|
|
|
@@ -323,8 +457,7 @@ async function renderProducts(products, groupInCategories = true, searchTerm = "
|
|
|
clone.querySelector(".product-image").style.backgroundImage = `url('${product.image}')`;
|
|
clone.querySelector(".product-image").style.backgroundImage = `url('${product.image}')`;
|
|
|
|
|
|
|
|
const addBtn = clone.querySelector(".add-to-cart-btn");
|
|
const addBtn = clone.querySelector(".add-to-cart-btn");
|
|
|
- addBtn.dataset.productId = product.id; // el listener usa esta info
|
|
|
|
|
- // Agregar event listener directamente al botón clonado
|
|
|
|
|
|
|
+ addBtn.dataset.productId = product.id;
|
|
|
addBtn.addEventListener('click', (event) => {
|
|
addBtn.addEventListener('click', (event) => {
|
|
|
const productId = parseInt(event.target.dataset.productId);
|
|
const productId = parseInt(event.target.dataset.productId);
|
|
|
addToCart(productId, event.target);
|
|
addToCart(productId, event.target);
|
|
@@ -335,24 +468,29 @@ async function renderProducts(products, groupInCategories = true, searchTerm = "
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Render products with smooth animations
|
|
|
|
|
+ */
|
|
|
async function renderProductsWithAnimation(products, groupInCategories = true, searchTerm = "") {
|
|
async function renderProductsWithAnimation(products, groupInCategories = true, searchTerm = "") {
|
|
|
if (!productListElement) return;
|
|
if (!productListElement) return;
|
|
|
|
|
|
|
|
const template = document.getElementById("product-card-template");
|
|
const template = document.getElementById("product-card-template");
|
|
|
if (!template) return;
|
|
if (!template) return;
|
|
|
|
|
|
|
|
- // Fade out actual content
|
|
|
|
|
|
|
+ // Fade out current content
|
|
|
productListElement.style.opacity = "0";
|
|
productListElement.style.opacity = "0";
|
|
|
productListElement.style.transform = "scale(0.95)";
|
|
productListElement.style.transform = "scale(0.95)";
|
|
|
|
|
|
|
|
setTimeout(async () => {
|
|
setTimeout(async () => {
|
|
|
productListElement.innerHTML = "";
|
|
productListElement.innerHTML = "";
|
|
|
let categoryContainers = [];
|
|
let categoryContainers = [];
|
|
|
- if (groupInCategories){
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (groupInCategories) {
|
|
|
categoryContainers = await createCategories(products);
|
|
categoryContainers = await createCategories(products);
|
|
|
- }else {
|
|
|
|
|
- categoryContainers = [ { category: searchTerm, container: productListElement } ];
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ categoryContainers = [{ category: searchTerm, container: productListElement }];
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if (products.length === 0) {
|
|
if (products.length === 0) {
|
|
|
const noProductsMessage = document.createElement("p");
|
|
const noProductsMessage = document.createElement("p");
|
|
|
noProductsMessage.textContent = "No hay productos disponibles.";
|
|
noProductsMessage.textContent = "No hay productos disponibles.";
|
|
@@ -375,12 +513,9 @@ async function renderProductsWithAnimation(products, groupInCategories = true, s
|
|
|
|
|
|
|
|
let animationDelay = 0;
|
|
let animationDelay = 0;
|
|
|
for (const { category, container } of categoryContainers) {
|
|
for (const { category, container } of categoryContainers) {
|
|
|
- let productsInCategory = [];
|
|
|
|
|
- if (groupInCategories){
|
|
|
|
|
- productsInCategory = products.filter(product => product.type === category);
|
|
|
|
|
- }else {
|
|
|
|
|
- productsInCategory = products
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let productsInCategory = groupInCategories
|
|
|
|
|
+ ? products.filter(product => product.type === category)
|
|
|
|
|
+ : products;
|
|
|
|
|
|
|
|
if (productsInCategory.length === 0) continue;
|
|
if (productsInCategory.length === 0) continue;
|
|
|
|
|
|
|
@@ -426,13 +561,15 @@ async function renderProductsWithAnimation(products, groupInCategories = true, s
|
|
|
|
|
|
|
|
}, 150); // Wait for fade out to complete
|
|
}, 150); // Wait for fade out to complete
|
|
|
}
|
|
}
|
|
|
-//#endregion
|
|
|
|
|
-//#region ===== Carrito =====
|
|
|
|
|
-
|
|
|
|
|
|
|
+//--- Gestión del Carrito ---
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Add product to cart with visual feedback
|
|
|
|
|
+ */
|
|
|
window.addToCart = function(productId, buttonElement = null) {
|
|
window.addToCart = function(productId, buttonElement = null) {
|
|
|
const product = Allproducts.find(p => p.id === productId);
|
|
const product = Allproducts.find(p => p.id === productId);
|
|
|
if (!product) return;
|
|
if (!product) return;
|
|
|
|
|
+
|
|
|
const cartItem = cart.find(item => item.id === productId);
|
|
const cartItem = cart.find(item => item.id === productId);
|
|
|
if (cartItem) {
|
|
if (cartItem) {
|
|
|
cartItem.quantity++;
|
|
cartItem.quantity++;
|
|
@@ -440,6 +577,7 @@ window.addToCart = function(productId, buttonElement = null) {
|
|
|
cart.push({ ...product, quantity: 1 });
|
|
cart.push({ ...product, quantity: 1 });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Visual feedback for button
|
|
|
if (buttonElement) {
|
|
if (buttonElement) {
|
|
|
const originalHTML = buttonElement.innerHTML;
|
|
const originalHTML = buttonElement.innerHTML;
|
|
|
buttonElement.textContent = "✔ Agregado!";
|
|
buttonElement.textContent = "✔ Agregado!";
|
|
@@ -449,12 +587,18 @@ window.addToCart = function(productId, buttonElement = null) {
|
|
|
buttonElement.disabled = false;
|
|
buttonElement.disabled = false;
|
|
|
}, 300);
|
|
}, 300);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
updateCartDisplay();
|
|
updateCartDisplay();
|
|
|
- // Dentro de addToCart (después de updateCartDisplay())
|
|
|
|
|
- if (typeof showToast === "function") showToast(`${product.name} agregado al carrito`);
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Show toast notification if available
|
|
|
|
|
+ if (typeof showToast === "function") {
|
|
|
|
|
+ showToast(`${product.name} agregado al carrito`);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Remove product from cart
|
|
|
|
|
+ */
|
|
|
window.removeFromCart = function(productId, removeAll = false) {
|
|
window.removeFromCart = function(productId, removeAll = false) {
|
|
|
const itemIndex = cart.findIndex(item => item.id === productId);
|
|
const itemIndex = cart.findIndex(item => item.id === productId);
|
|
|
if (itemIndex > -1) {
|
|
if (itemIndex > -1) {
|
|
@@ -467,16 +611,24 @@ window.removeFromCart = function(productId, removeAll = false) {
|
|
|
updateCartDisplay();
|
|
updateCartDisplay();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Calculate total cart amount
|
|
|
|
|
+ */
|
|
|
function calculateTotal() {
|
|
function calculateTotal() {
|
|
|
if (!cartTotalElement) return;
|
|
if (!cartTotalElement) return;
|
|
|
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
|
cartTotalElement.textContent = formatPrice(total);
|
|
cartTotalElement.textContent = formatPrice(total);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Update cart display with animations
|
|
|
|
|
+ */
|
|
|
function updateCartDisplay() {
|
|
function updateCartDisplay() {
|
|
|
if (!cartItemsElement || !emptyCartTextElement || !checkoutButton || !cartCountElement) return;
|
|
if (!cartItemsElement || !emptyCartTextElement || !checkoutButton || !cartCountElement) return;
|
|
|
|
|
+
|
|
|
cartItemsElement.innerHTML = "";
|
|
cartItemsElement.innerHTML = "";
|
|
|
cartCountElement.textContent = cart.reduce((sum, item) => sum + item.quantity, 0);
|
|
cartCountElement.textContent = cart.reduce((sum, item) => sum + item.quantity, 0);
|
|
|
|
|
+
|
|
|
if (cart.length === 0) {
|
|
if (cart.length === 0) {
|
|
|
cartCountElement.classList.add("hidden");
|
|
cartCountElement.classList.add("hidden");
|
|
|
emptyCartTextElement.classList.remove("hidden");
|
|
emptyCartTextElement.classList.remove("hidden");
|
|
@@ -484,6 +636,8 @@ function updateCartDisplay() {
|
|
|
itsEmpty = true;
|
|
itsEmpty = true;
|
|
|
} else {
|
|
} else {
|
|
|
cartCountElement.classList.remove("hidden");
|
|
cartCountElement.classList.remove("hidden");
|
|
|
|
|
+
|
|
|
|
|
+ // Animate cart count badge
|
|
|
if (cartCountElement && itsEmpty) {
|
|
if (cartCountElement && itsEmpty) {
|
|
|
itsEmpty = false;
|
|
itsEmpty = false;
|
|
|
cartCountElement.animate([
|
|
cartCountElement.animate([
|
|
@@ -493,21 +647,23 @@ function updateCartDisplay() {
|
|
|
duration: 300,
|
|
duration: 300,
|
|
|
iterations: 1,
|
|
iterations: 1,
|
|
|
easing: 'ease-in-out'
|
|
easing: 'ease-in-out'
|
|
|
- })
|
|
|
|
|
|
|
+ });
|
|
|
} else {
|
|
} else {
|
|
|
cartCountElement.animate([
|
|
cartCountElement.animate([
|
|
|
{ transform: 'scale(1) rotate(0deg)' },
|
|
{ transform: 'scale(1) rotate(0deg)' },
|
|
|
{ transform: 'scale(1.2) rotate(180deg)' },
|
|
{ transform: 'scale(1.2) rotate(180deg)' },
|
|
|
{ transform: 'scale(1) rotate(360deg)' }
|
|
{ transform: 'scale(1) rotate(360deg)' }
|
|
|
-
|
|
|
|
|
], {
|
|
], {
|
|
|
duration: 300,
|
|
duration: 300,
|
|
|
iterations: 1,
|
|
iterations: 1,
|
|
|
easing: 'ease-in-out'
|
|
easing: 'ease-in-out'
|
|
|
- })
|
|
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
emptyCartTextElement.classList.add("hidden");
|
|
emptyCartTextElement.classList.add("hidden");
|
|
|
checkoutButton.disabled = false;
|
|
checkoutButton.disabled = false;
|
|
|
|
|
+
|
|
|
|
|
+ // Render cart items
|
|
|
cart.forEach(item => {
|
|
cart.forEach(item => {
|
|
|
const cartItemHTML = `
|
|
const cartItemHTML = `
|
|
|
<div class="product-cart flex justify-between items-center border-b border-gray-700 pb-2 last:border-b-0 mb-2">
|
|
<div class="product-cart flex justify-between items-center border-b border-gray-700 pb-2 last:border-b-0 mb-2">
|
|
@@ -526,17 +682,20 @@ function updateCartDisplay() {
|
|
|
`;
|
|
`;
|
|
|
cartItemsElement.innerHTML += cartItemHTML;
|
|
cartItemsElement.innerHTML += cartItemHTML;
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
calculateTotal();
|
|
calculateTotal();
|
|
|
}
|
|
}
|
|
|
-//#endregion
|
|
|
|
|
-//#region ===== Pedidos =====
|
|
|
|
|
-
|
|
|
|
|
|
|
+//--- Procesamiento de Pedidos ---
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Process and send order to backend
|
|
|
|
|
+ */
|
|
|
async function processOrder() {
|
|
async function processOrder() {
|
|
|
if (cart.length === 0) return;
|
|
if (cart.length === 0) return;
|
|
|
|
|
+
|
|
|
showGlobalLoader();
|
|
showGlobalLoader();
|
|
|
|
|
+
|
|
|
if (checkoutButton) {
|
|
if (checkoutButton) {
|
|
|
checkoutButton.disabled = true;
|
|
checkoutButton.disabled = true;
|
|
|
checkoutButton.textContent = "Procesando...";
|
|
checkoutButton.textContent = "Procesando...";
|
|
@@ -546,15 +705,24 @@ async function processOrder() {
|
|
|
const orderData = {
|
|
const orderData = {
|
|
|
customerId: userId,
|
|
customerId: userId,
|
|
|
table: userTable,
|
|
table: userTable,
|
|
|
- items: cart.map(item => ({ id: item.id, price: item.price, quantity: item.quantity })),
|
|
|
|
|
|
|
+ items: cart.map(item => ({
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+ price: item.price,
|
|
|
|
|
+ quantity: item.quantity
|
|
|
|
|
+ })),
|
|
|
totalAmount: cart.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
totalAmount: cart.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
|
orderDate: new Date().toLocaleString('sv-SE').replace(' ', 'T')
|
|
orderDate: new Date().toLocaleString('sv-SE').replace(' ', 'T')
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
const data = await sendOrder(orderData, userToken);
|
|
const data = await sendOrder(orderData, userToken);
|
|
|
|
|
+
|
|
|
if (data && data.new_progress) {
|
|
if (data && data.new_progress) {
|
|
|
updateProgress(data.new_progress);
|
|
updateProgress(data.new_progress);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
alert("Pedido enviado con éxito.");
|
|
alert("Pedido enviado con éxito.");
|
|
|
|
|
+
|
|
|
|
|
+ // Add items to history
|
|
|
cart.forEach(item => {
|
|
cart.forEach(item => {
|
|
|
addHistoryRow({
|
|
addHistoryRow({
|
|
|
productName: item.name,
|
|
productName: item.name,
|
|
@@ -562,41 +730,72 @@ async function processOrder() {
|
|
|
price: item.price,
|
|
price: item.price,
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
- cart = []
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Clear cart
|
|
|
|
|
+ cart = [];
|
|
|
updateCartDisplay();
|
|
updateCartDisplay();
|
|
|
|
|
+
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error("Error al procesar la orden:", error);
|
|
console.error("Error al procesar la orden:", error);
|
|
|
alert(`Hubo un problema: ${error.message || "Por favor, inténtalo de nuevo."}`);
|
|
alert(`Hubo un problema: ${error.message || "Por favor, inténtalo de nuevo."}`);
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
} finally {
|
|
} finally {
|
|
|
hideGlobalLoader();
|
|
hideGlobalLoader();
|
|
|
checkoutButton.disabled = cart.length === 0;
|
|
checkoutButton.disabled = cart.length === 0;
|
|
|
- checkoutButton.textContent = originalCheckoutButtonText
|
|
|
|
|
-
|
|
|
|
|
|
|
+ checkoutButton.textContent = originalCheckoutButtonText;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-//#endregion
|
|
|
|
|
-//#region ===== Chat =====
|
|
|
|
|
-function displayChatMessage(sender, message) {
|
|
|
|
|
- if (!chatMessagesElement) return;
|
|
|
|
|
- const bubbleClass = sender === "user" ? "chat-bubble-user" : "chat-bubble-ai";
|
|
|
|
|
- const messageDiv = document.createElement("div");
|
|
|
|
|
- messageDiv.classList.add("chat-bubble", bubbleClass);
|
|
|
|
|
- messageDiv.innerHTML = sender === "ai" && window.marked ? marked.parse(message) : message;
|
|
|
|
|
- chatMessagesElement.appendChild(messageDiv);
|
|
|
|
|
|
|
+//--- Funcionalidad del Chat ---
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Display chat message with proper styling
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+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) {
|
|
|
|
|
+ console.log(`[${sender.toUpperCase()}] ${message}`);
|
|
|
|
|
+ let realtime = `[${time ? time : new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}] `;
|
|
|
|
|
+ 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;
|
|
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function sendSuggestion(suggestion) {
|
|
|
|
|
- if (!chatInputElement || !aiLoadingIndicator) return;
|
|
|
|
|
- chatInputElement.value = suggestion;
|
|
|
|
|
- chatInputElement.focus();
|
|
|
|
|
|
|
+function newUserInChat(userName) {
|
|
|
|
|
+ let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate");
|
|
|
|
|
+ if (!userTemplate) return;
|
|
|
|
|
+
|
|
|
|
|
+ let userClone = userTemplate.content.cloneNode(true).firstElementChild;
|
|
|
|
|
+ userClone.classList.add("text-green-600");
|
|
|
|
|
+ userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha unido al chat`;
|
|
|
|
|
+ chatMessagesElement.appendChild(userClone);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removeUserFromChat(userName) {
|
|
|
|
|
+ let userTemplate = chatMessagesElement.querySelector("#systemMessageTemplate");
|
|
|
|
|
+ if (!userTemplate) return;
|
|
|
|
|
|
|
|
|
|
+ let userClone = userTemplate.content.cloneNode(true).firstElementChild;
|
|
|
|
|
+ userClone.classList.add("text-red-600");
|
|
|
|
|
+ userClone.querySelector(".chat-message-text").innerHTML = `*** ${userName} se ha ido del chat`;
|
|
|
|
|
+ chatMessagesElement.appendChild(userClone);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Send message to AI service
|
|
|
|
|
+ */
|
|
|
async function sendMessageToAI() {
|
|
async function sendMessageToAI() {
|
|
|
if (!chatInputElement || !aiLoadingIndicator) return;
|
|
if (!chatInputElement || !aiLoadingIndicator) return;
|
|
|
|
|
+
|
|
|
const userInput = chatInputElement.value.trim();
|
|
const userInput = chatInputElement.value.trim();
|
|
|
if (!userInput) return;
|
|
if (!userInput) return;
|
|
|
|
|
|
|
@@ -607,14 +806,15 @@ async function sendMessageToAI() {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const response = await serviceSendMessage(userInput, chatHistory, userName, userToken);
|
|
const response = await serviceSendMessage(userInput, chatHistory, userName, userToken);
|
|
|
|
|
+
|
|
|
if (!response) {
|
|
if (!response) {
|
|
|
displayChatMessage("ai", "Hubo un problema al conectar con el Chef IA.");
|
|
displayChatMessage("ai", "Hubo un problema al conectar con el Chef IA.");
|
|
|
} else if (response === "not_init") {
|
|
} else if (response === "not_init") {
|
|
|
if (await initializeService()) {
|
|
if (await initializeService()) {
|
|
|
- const response = await serviceSendMessage(userInput, chatHistory, userName, userToken);
|
|
|
|
|
- if (response) {
|
|
|
|
|
- chatHistory = response.messageList;
|
|
|
|
|
- displayChatMessage("ai", response.assistantResponse);
|
|
|
|
|
|
|
+ const retryResponse = await serviceSendMessage(userInput, chatHistory, userName, userToken);
|
|
|
|
|
+ if (retryResponse) {
|
|
|
|
|
+ chatHistory = retryResponse.messageList;
|
|
|
|
|
+ displayChatMessage("ai", retryResponse.assistantResponse);
|
|
|
} else {
|
|
} else {
|
|
|
displayChatMessage("ai", "Hubo un problema al enviar el mensaje.");
|
|
displayChatMessage("ai", "Hubo un problema al enviar el mensaje.");
|
|
|
}
|
|
}
|
|
@@ -633,120 +833,108 @@ async function sendMessageToAI() {
|
|
|
if (chatInputElement) chatInputElement.focus();
|
|
if (chatInputElement) chatInputElement.focus();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+//--- Sistema de Recompensas ---
|
|
|
|
|
|
|
|
-//#endregion
|
|
|
|
|
-//#region ===== Rewards =====
|
|
|
|
|
-// Referencias a elementos del DOM
|
|
|
|
|
-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');
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Initialize rewards functionality
|
|
|
|
|
+ */
|
|
|
function initializeRewards() {
|
|
function initializeRewards() {
|
|
|
- // Abrir modal cuando se hace clic en el botón de recompensa
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // Event listeners for reward system
|
|
|
closeSuccessRewardModalButton.addEventListener("click", closeSuccessRewardModal);
|
|
closeSuccessRewardModalButton.addEventListener("click", closeSuccessRewardModal);
|
|
|
|
|
|
|
|
rewardBtn.addEventListener('click', function () {
|
|
rewardBtn.addEventListener('click', function () {
|
|
|
if (!rewardBtn.disabled) {
|
|
if (!rewardBtn.disabled) {
|
|
|
rewardModal.classList.remove('hidden');
|
|
rewardModal.classList.remove('hidden');
|
|
|
- document.body.style.overflow = 'hidden'; // Evitar scroll del fondo
|
|
|
|
|
|
|
+ document.body.style.overflow = 'hidden';
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // Cerrar modal - botón X
|
|
|
|
|
|
|
+ // Close modal - X button
|
|
|
closeRewardModal.addEventListener('click', function () {
|
|
closeRewardModal.addEventListener('click', function () {
|
|
|
closeModal();
|
|
closeModal();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // Cerrar modal - botón Cancelar
|
|
|
|
|
|
|
+ // Close modal - Cancel button
|
|
|
cancelRewardBtn.addEventListener('click', function () {
|
|
cancelRewardBtn.addEventListener('click', function () {
|
|
|
closeModal();
|
|
closeModal();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // Cerrar modal haciendo clic fuera de él
|
|
|
|
|
|
|
+ // Close modal - click outside
|
|
|
rewardModal.addEventListener('click', function (e) {
|
|
rewardModal.addEventListener('click', function (e) {
|
|
|
if (e.target === rewardModal) {
|
|
if (e.target === rewardModal) {
|
|
|
closeModal();
|
|
closeModal();
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // Cerrar modal con tecla Escape
|
|
|
|
|
|
|
+ // Close modal - Escape key
|
|
|
document.addEventListener('keydown', function (e) {
|
|
document.addEventListener('keydown', function (e) {
|
|
|
if (e.key === 'Escape' && !rewardModal.classList.contains('hidden')) {
|
|
if (e.key === 'Escape' && !rewardModal.classList.contains('hidden')) {
|
|
|
closeModal();
|
|
closeModal();
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // Manejar el reclamo del premio
|
|
|
|
|
|
|
+ // Handle reward claim
|
|
|
claimRewardBtn.addEventListener('click', function () {
|
|
claimRewardBtn.addEventListener('click', function () {
|
|
|
if (!this.disabled && acceptTermsCheckbox.checked) {
|
|
if (!this.disabled && acceptTermsCheckbox.checked) {
|
|
|
- // Generar código de premio (puedes cambiar esta lógica)
|
|
|
|
|
-
|
|
|
|
|
- // Mostrar mensaje de éxito
|
|
|
|
|
claimReward(userToken, userTable);
|
|
claimReward(userToken, userTable);
|
|
|
- // Cerrar modal
|
|
|
|
|
closeModal();
|
|
closeModal();
|
|
|
updateProgress(0);
|
|
updateProgress(0);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ // Handle terms checkbox
|
|
|
|
|
+ acceptTermsCheckbox.addEventListener('change', function () {
|
|
|
|
|
+ if (this.checked) {
|
|
|
|
|
+ claimRewardBtn.disabled = false;
|
|
|
|
|
+ claimRewardBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ claimRewardBtn.disabled = true;
|
|
|
|
|
+ claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Close success reward modal
|
|
|
|
|
+ */
|
|
|
function closeSuccessRewardModal() {
|
|
function closeSuccessRewardModal() {
|
|
|
const successRewardModal = document.getElementById("successRewardModal");
|
|
const successRewardModal = document.getElementById("successRewardModal");
|
|
|
successRewardModal.classList.add("hidden");
|
|
successRewardModal.classList.add("hidden");
|
|
|
- document.body.style.overflow = ''; // Restaurar scroll
|
|
|
|
|
|
|
+ document.body.style.overflow = '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Función para cerrar el modal
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Close reward modal and reset form
|
|
|
|
|
+ */
|
|
|
function closeModal() {
|
|
function closeModal() {
|
|
|
rewardModal.classList.add('hidden');
|
|
rewardModal.classList.add('hidden');
|
|
|
- document.body.style.overflow = ''; // Restaurar scroll
|
|
|
|
|
|
|
+ document.body.style.overflow = '';
|
|
|
|
|
|
|
|
- // Reset del formulario al cerrar
|
|
|
|
|
|
|
+ // Reset form
|
|
|
acceptTermsCheckbox.checked = false;
|
|
acceptTermsCheckbox.checked = false;
|
|
|
claimRewardBtn.disabled = true;
|
|
claimRewardBtn.disabled = true;
|
|
|
claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Manejar el checkbox de términos y condiciones
|
|
|
|
|
-acceptTermsCheckbox.addEventListener('change', function () {
|
|
|
|
|
- if (this.checked) {
|
|
|
|
|
- // Habilitar botón de reclamar
|
|
|
|
|
- claimRewardBtn.disabled = false;
|
|
|
|
|
- claimRewardBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
|
|
|
- } else {
|
|
|
|
|
- // Deshabilitar botón de reclamar
|
|
|
|
|
- claimRewardBtn.disabled = true;
|
|
|
|
|
- claimRewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-// Función para mostrar mensaje de éxito con el código
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Show reward success notification
|
|
|
|
|
+ */
|
|
|
function showRewardSuccess(code) {
|
|
function showRewardSuccess(code) {
|
|
|
- // Crear elemento de notificación de éxito
|
|
|
|
|
const successToast = document.createElement('div');
|
|
const successToast = document.createElement('div');
|
|
|
successToast.className = `
|
|
successToast.className = `
|
|
|
- fixed top-4 left-1/2 transform -translate-x-1/2
|
|
|
|
|
- bg-green-500 text-white text-sm rounded-lg px-6 py-4
|
|
|
|
|
- shadow-lg z-50 max-w-sm text-center
|
|
|
|
|
- `;
|
|
|
|
|
|
|
+ fixed top-4 left-1/2 transform -translate-x-1/2
|
|
|
|
|
+ bg-green-500 text-white text-sm rounded-lg px-6 py-4
|
|
|
|
|
+ shadow-lg z-50 max-w-sm text-center
|
|
|
|
|
+ `;
|
|
|
successToast.innerHTML = `
|
|
successToast.innerHTML = `
|
|
|
- <div class="font-bold mb-1">🎉 ¡Premio Reclamado!</div>
|
|
|
|
|
- <div class="text-xs opacity-90">Tu código: <strong>${code}</strong></div>
|
|
|
|
|
- <div class="text-xs mt-1 opacity-80">Muéstralo al mesero</div>
|
|
|
|
|
- `;
|
|
|
|
|
|
|
+ <div class="font-bold mb-1">🎉 ¡Premio Reclamado!</div>
|
|
|
|
|
+ <div class="text-xs opacity-90">Tu código: <strong>${code}</strong></div>
|
|
|
|
|
+ <div class="text-xs mt-1 opacity-80">Muéstralo al mesero</div>
|
|
|
|
|
+ `;
|
|
|
|
|
|
|
|
document.body.appendChild(successToast);
|
|
document.body.appendChild(successToast);
|
|
|
|
|
|
|
|
- // Remover después de 5 segundos
|
|
|
|
|
|
|
+ // Remove after 5 seconds
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
if (successToast.parentNode) {
|
|
if (successToast.parentNode) {
|
|
|
successToast.remove();
|
|
successToast.remove();
|
|
@@ -754,34 +942,37 @@ function showRewardSuccess(code) {
|
|
|
}, 5000);
|
|
}, 5000);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Función para resetear el progreso de recompensa
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Reset reward progress to 0%
|
|
|
|
|
+ */
|
|
|
function resetRewardProgress() {
|
|
function resetRewardProgress() {
|
|
|
const progressBar = document.getElementById('progressBar');
|
|
const progressBar = document.getElementById('progressBar');
|
|
|
const progressText = document.getElementById('progressText');
|
|
const progressText = document.getElementById('progressText');
|
|
|
const rewardBtn = document.getElementById('rewardBtn');
|
|
const rewardBtn = document.getElementById('rewardBtn');
|
|
|
|
|
|
|
|
- // Resetear a 0%
|
|
|
|
|
progressBar.style.width = '0%';
|
|
progressBar.style.width = '0%';
|
|
|
progressText.textContent = '0%';
|
|
progressText.textContent = '0%';
|
|
|
|
|
|
|
|
- // Deshabilitar botón de recompensa
|
|
|
|
|
rewardBtn.disabled = true;
|
|
rewardBtn.disabled = true;
|
|
|
rewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
rewardBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
|
rewardBtn.classList.remove('bg-yellow-500', 'hover:bg-yellow-600');
|
|
rewardBtn.classList.remove('bg-yellow-500', 'hover:bg-yellow-600');
|
|
|
rewardBtn.textContent = '🎉 ¡Reclamar!';
|
|
rewardBtn.textContent = '🎉 ¡Reclamar!';
|
|
|
}
|
|
}
|
|
|
|
|
+//--- Inicialización de la Aplicación ---
|
|
|
|
|
|
|
|
-//#endregion
|
|
|
|
|
-// --- APP initialization ---
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Initialize the application when DOM is loaded
|
|
|
|
|
+ */
|
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
|
const isCacheValid = await checkCache();
|
|
const isCacheValid = await checkCache();
|
|
|
if (!isCacheValid) {
|
|
if (!isCacheValid) {
|
|
|
- // Si la caché no es válida, redirigir al usuario o mostrar un mensaje
|
|
|
|
|
console.warn("Cache is invalid");
|
|
console.warn("Cache is invalid");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
createGlobalLoader();
|
|
createGlobalLoader();
|
|
|
|
|
+ // Uncomment these lines when ready to initialize the full app:
|
|
|
initializeLoginModal();
|
|
initializeLoginModal();
|
|
|
hideGUI();
|
|
hideGUI();
|
|
|
- // initializeApp()
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // initializeApp();
|
|
|
});
|
|
});
|
|
|
|
|
+
|