Instantly share code, notes, and snippets.
Created
November 3, 2025 19:15
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save iDavidMorales/b702a1d1055c1f81c9e811fc126f4889 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // CONFIGURACIÓN DE LA TIENDA | |
| $user_id = 31; | |
| $titulo_tienda = "La Regia Grill"; | |
| $lema_tienda = "¡Descubre nuestras deliciosas opciones!"; | |
| $id_tienda = "regia-grill-01"; | |
| $telefono_contacto = "+529844534957"; | |
| $correo_contacto = "[email protected]"; | |
| $status_servicio = 1; // 1 = Activo (Abierto), 0 = Desactivado (Cerrado) | |
| $color_principal = '#FF8C00'; | |
| $color_oscuro = '#E67E00'; | |
| $color_header = '#1E1E1E'; | |
| $logo_url = 'https://i.imgur.com/EJ8HgZa.jpeg'; | |
| // Configuración de horario por día | |
| // Formato de hora: 'HH:MM' (24 horas) | |
| $horario = [ | |
| "domingo" => ["abierto" => "12:00", "cierre" => "22:00"], | |
| "lunes" => ["abierto" => "00:00", "cierre" => "00:00"], // Cerrado todo el día | |
| "martes" => ["abierto" => "12:00", "cierre" => "22:00"], | |
| "miercoles" => ["abierto" => "12:00", "cierre" => "22:00"], | |
| "jueves" => ["abierto" => "12:00", "cierre" => "22:00"], | |
| "viernes" => ["abierto" => "12:00", "cierre" => "23:00"], | |
| "sabado" => ["abierto" => "12:00", "cierre" => "23:00"] | |
| ]; | |
| ?> | |
| <!DOCTYPE html> | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title><?php echo $titulo_tienda; ?> - Menú Digital</title> | |
| <!-- Incluye Tailwind CSS desde CDN --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <!-- Configura la fuente Inter para una mejor legibilidad --> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> | |
| <!-- Incluye Phosphor Icons para iconos modernos y atractivos --> | |
| <script src="https://unpkg.com/phosphor-icons"></script> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #f0f0f0; | |
| background-image: radial-gradient(#d4d4d4 1px, transparent 1px); | |
| background-size: 20px 20px; | |
| } | |
| /* Estilos personalizados para la barra de desplazamiento */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #e5e7eb; /* Gris claro */ | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background-color: var(--main-color); | |
| border-radius: 4px; | |
| } | |
| /* Animación para el botón del carrito */ | |
| .pulse-once { | |
| animation: pulse 0.5s cubic-bezier(0.4, 0, 0.6, 1) 1; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.1); | |
| } | |
| } | |
| /* Estilos para el slider */ | |
| .slider-container { | |
| scroll-snap-type: x mandatory; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| .slider-item { | |
| scroll-snap-align: center; | |
| } | |
| /* Efecto de "hover" para los botones de cantidad */ | |
| .quantity-btn:hover { | |
| transform: scale(1.1); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .quantity-btn:active { | |
| transform: scale(0.95); | |
| box-shadow: none; | |
| } | |
| </style> | |
| </head> | |
| <body class="text-gray-900 min-h-screen"> | |
| <!-- Variables globales de JavaScript obtenidas de PHP --> | |
| <script> | |
| // Variables de configuración globales | |
| const userId = <?php echo json_encode($user_id); ?>; | |
| const storeTitle = <?php echo json_encode($titulo_tienda); ?>; | |
| const storeTagline = <?php echo json_encode($lema_tienda); ?>; | |
| const storeId = <?php echo json_encode($id_tienda); ?>; | |
| const contactPhone = <?php echo json_encode($telefono_contacto); ?>; | |
| const contactEmail = <?php echo json_encode($correo_contacto); ?>; | |
| const mainColor = <?php echo json_encode($color_principal); ?>; | |
| const darkColor = <?php echo json_encode($color_oscuro); ?>; | |
| const headerColor = <?php echo json_encode($color_header); ?>; | |
| const logoUrl = <?php echo json_encode($logo_url); ?>; | |
| const serviceStatus = <?php echo json_encode($status_servicio); ?>; | |
| const schedule = <?php echo json_encode($horario); ?>; | |
| // Define las variables CSS para su uso en Tailwind | |
| document.documentElement.style.setProperty('--main-color', mainColor); | |
| document.documentElement.style.setProperty('--dark-color', darkColor); | |
| document.documentElement.style.setProperty('--header-bg-color', headerColor); | |
| </script> | |
| <!-- Preloader de carga inicial --> | |
| <div id="preloader" class="fixed inset-0 flex flex-col items-center justify-center bg-gray-100 z-[200] transition-opacity duration-1000"> | |
| <img id="preloader-logo" src="" alt="Logo de La Regia Grill" class="h-40 w-40 rounded-full animate-pulse"> | |
| <svg class="animate-spin h-10 w-10 mt-6" style="color: var(--main-color);" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
| </svg> | |
| </div> | |
| <!-- Encabezado de la página --><br><br> | |
| <header class="py-16 px-4 sm:px-8 text-center text-white shadow-xl" style="background-color: var(--header-bg-color);"> | |
| <!-- Contenedor del logo --> | |
| <div class="mb-4"> | |
| <!-- Aumentamos el tamaño del logo --> | |
| <img id="company-logo" src="" alt="Logo de La Regia Grill" class="h-48 sm:h-60 w-auto mx-auto rounded-xl"> | |
| </div> | |
| <h1 id="store-title" class="text-4xl sm:text-5xl font-extrabold tracking-wider"> | |
| </h1> | |
| <p id="store-tagline" class="mt-2 text-xl sm:text-2xl"> | |
| </p> | |
| <!-- Horario del día actual --> | |
| <div id="daily-schedule" class="mt-4 flex items-center justify-center space-x-2 text-lg font-medium rounded-full py-1 px-4 text-white mx-auto w-fit"> | |
| <i class="ph-fill ph-clock text-xl"></i> | |
| <span id="schedule-text"></span> | |
| </div> | |
| </header> | |
| <!-- Barra de búsqueda fija en la parte superior --> | |
| <div class="fixed top-0 left-0 w-full z-40 bg-gray-100 shadow-lg py-4 px-4 sm:px-8 bg-opacity-95 backdrop-blur-sm"> | |
| <div class="relative max-w-6xl mx-auto"> | |
| <input type="text" id="search-input" placeholder="Buscar productos..." class="w-full pl-12 pr-4 py-3 bg-white text-gray-900 placeholder-gray-500 rounded-xl shadow-md focus:outline-none focus:ring-2 focus:ring-[var(--main-color)] transition-colors"> | |
| <!-- Ícono de búsqueda (SVG) --> | |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | |
| <svg class="h-6 w-6 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Espacio para el encabezado fijo --> | |
| <!-- Se agrega este div para que el contenido principal no quede oculto detrás del buscador fijo --> | |
| <div class="h-28 sm:h-40"></div> | |
| <!-- Contenedor principal de contenido con padding superior para evitar que el contenido quede detrás del buscador fijo --> | |
| <!-- Se ha reducido el padding superior de pt-36 a pt-24 para compactar el diseño --> | |
| <div class="p-4 pb-24 max-w-6xl mx-auto"> | |
| <!-- Slider de productos --> | |
| <div class="relative mb-8"> | |
| <h2 class="text-3xl font-bold mb-4">Nuestros Destacados</h2> | |
| <div id="product-slider" class="slider-container flex overflow-x-auto space-x-4 p-4 -mx-4 rounded-2xl shadow-inner bg-gray-100 border border-gray-200"> | |
| <!-- Los items del slider se renderizarán aquí --> | |
| </div> | |
| <!-- Botones de navegación del slider --> | |
| <button id="slider-prev" class="absolute -left-2 top-1/2 transform -translate-y-1/2 bg-white rounded-full p-2 shadow-lg hover:bg-gray-200 focus:outline-none z-10 transition-transform hover:scale-110" style="color: var(--main-color);"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> | |
| </svg> | |
| </button> | |
| <button id="slider-next" class="absolute -right-2 top-1/2 transform -translate-y-1/2 bg-white rounded-full p-2 shadow-lg hover:bg-gray-200 focus:outline-none z-10 transition-transform hover:scale-110" style="color: var(--main-color);"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> | |
| </svg> | |
| </button> | |
| </div> | |
| <!-- Título para la sección del menú completo --> | |
| <h2 class="text-3xl font-bold mb-4">Menú Completo</h2> | |
| <!-- Contenedor principal del menú --> | |
| <main id="menu-container" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Los productos se renderizarán aquí --> | |
| </main> | |
| </div> | |
| <!-- Sección del Código QR --> | |
| <section class="max-w-6xl mx-auto my-12 p-6 bg-white rounded-2xl shadow-xl text-center"> | |
| <h2 class="text-3xl font-bold text-[#1E1E1E] mb-4"> | |
| Escanea para ver el menú | |
| </h2> | |
| <a href="https://routicket.com/qr.php?clave=https%3A%2F%2Frouticket.com%2Fmenu%2Fregia-grill%2F&choe=UTF-8" target="_blank" class="inline-block transition-transform transform hover:scale-105"> | |
| <img src="https://routicket.com/qr.php?clave=https%3A%2F%2Frouticket.com%2Fmenu%2Fregia-grill%2F&choe=UTF-8" alt="Código QR del menú" class="mx-auto rounded-lg shadow-lg"> | |
| </a> | |
| <p class="mt-4 text-gray-600 text-lg"> | |
| ¡Pide fácilmente desde tu teléfono! | |
| </p> | |
| </section> | |
| <!-- Botón flotante para el carrito de compras con contador de productos --> | |
| <button id="open-cart-btn" class="fixed bottom-6 right-6 p-4 text-white rounded-full shadow-lg transition-transform transform hover:scale-110 focus:outline-none focus:ring-4 focus:ring-[var(--main-color)] focus:ring-opacity-50 z-50" style="background-color: var(--main-color);"> | |
| <!-- Ícono de carrito (SVG) --> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm-9.35-1.55l.85-1.13.11-.15c.16-.23.4-.37.66-.37h10.74c.48 0 .85.38.93.85L21.49 14.8c.18.9-.45 1.7-1.35 1.7H8.57c-.52 0-.96-.32-1.14-.8L4.2 4.41C4.05 3.79 3.44 3.4 2.81 3.4H1c-.55 0-1 .45-1 1s.45 1 1 1h1.56L5.27 15.65c.18.52.62.85 1.14.85h11.23c.52 0 .96-.32 1.14-.8L18.89 10H8.91c-.55 0-1-.45-1-1s.45-1 1-1h10.9c.55 0 1 .45 1 1v1.16l-3.35 5.25c-.18.28-.48.45-.82.45H7.65c-.34 0-.65-.17-.82-.45L5.7 12.16zM7.5 14h10.36L16 9H9l-1.5 5z"/> | |
| </svg> | |
| <span id="cart-counter" class="absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2 bg-[#1E1E1E] text-white text-sm font-bold rounded-full h-6 w-6 flex items-center justify-center hidden">0</span> | |
| </button> | |
| <!-- Modal del carrito de compras --> | |
| <div id="cart-modal" class="fixed inset-0 bg-black bg-opacity-70 hidden items-center justify-center p-4 z-[100]"> | |
| <div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-xl relative transition-all duration-300 transform scale-95 opacity-0"> | |
| <!-- Botón para cerrar la modal --> | |
| <button id="close-cart-btn" class="absolute top-3 right-3 text-gray-500 hover:text-gray-900 text-3xl transition-colors"> | |
| × | |
| </button> | |
| <h2 class="text-3xl font-bold mb-4 flex items-center space-x-2" style="color: var(--main-color);"> | |
| <i class="ph-fill ph-bag text-3xl"></i> | |
| <span>Tu Pedido</span> | |
| </h2> | |
| <!-- Contenedor de items con scrollbar --> | |
| <div id="cart-items" class="max-h-[50vh] overflow-y-auto mb-4 border-b border-gray-200 pb-4 space-y-3 px-1"> | |
| <!-- Los elementos del carrito se renderizarán aquí --> | |
| <p class="text-center text-gray-500 text-sm">El carrito está vacío.</p> | |
| </div> | |
| <div class="flex justify-between items-center text-2xl font-semibold mb-6"> | |
| <span>Total:</span> | |
| <span id="cart-total" style="color: var(--main-color);">$${total.toFixed(2)}</span> | |
| </div> | |
| <!-- Contenedor para la confirmación de la orden --> | |
| <div id="order-confirmation" class="bg-gray-100 p-4 rounded-xl hidden mb-4 text-center"> | |
| <i class="ph-fill ph-check-circle text-4xl text-green-600"></i> | |
| <h3 class="text-xl font-bold text-green-600 mt-2"> | |
| ¡Orden Registrada! | |
| </h3> | |
| <p class="mt-2 text-gray-700">Número de Orden: <span id="order-number" class="font-bold"></span></p> | |
| <p class="text-gray-700">Fecha: <span id="order-date" class="font-bold"></span></p> | |
| <p class="text-gray-700">Hora: <span id="order-time" class="font-bold"></span></p> | |
| <button id="close-confirmation" class="mt-4 w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg transition-colors">Cerrar</button> | |
| </div> | |
| <!-- Formulario de información del usuario --> | |
| <div id="user-info-section" class="mb-4"> | |
| <label class="flex items-center text-lg font-medium text-gray-700 mb-2 cursor-pointer"> | |
| <input type="checkbox" id="show-user-form" class="mr-2 h-5 w-5 rounded text-[var(--main-color)] focus:ring-[var(--main-color)] focus:border-[var(--main-color)]"> | |
| Agregar mis datos | |
| </label> | |
| <div id="user-form-container" class="space-y-3 hidden"> | |
| <input type="text" id="user-name" placeholder="Tu Nombre" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring focus:ring-[var(--main-color)] focus:border-[var(--main-color)]"> | |
| <input type="tel" id="user-phone" placeholder="Tu Teléfono" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring focus:ring-[var(--main-color)] focus:border-[var(--main-color)]"> | |
| <input type="email" id="user-email" placeholder="Tu Correo Electrónico" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring focus:ring-[var(--main-color)] focus:border-[var(--main-color)]"> | |
| </div> | |
| </div> | |
| <!-- Botones de acción --> | |
| <div id="action-buttons" class="flex flex-col space-y-4"> | |
| <center><h4 style="color:green;">Todos los pedidos son + Envio</h4></center> | |
| <!-- Botón de "Registrar Orden" con icono de TICKET --> | |
| <button id="pay-local-btn" class="w-full text-white font-bold py-4 px-6 rounded-xl transition-colors shadow-lg text-xl flex items-center justify-center space-x-2" style="background-color: var(--main-color); hover:background-color: var(--dark-color)"> | |
| <i class="ph-fill ph-ticket text-xl"></i> | |
| <span>Registrar Orden</span> | |
| </button> | |
| <!-- Botón de WhatsApp con icono --> | |
| <button id="send-whatsapp-btn" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl flex items-center justify-center space-x-2"> | |
| <i class="ph-fill ph-whatsapp-logo text-xl"></i> | |
| <span>Enviar pedido por WhatsApp</span> | |
| </button> | |
| <!-- Botón de Email con icono --> | |
| <button id="send-email-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl flex items-center justify-center space-x-2"> | |
| <i class="ph-fill ph-envelope-simple text-xl"></i> | |
| <span>Enviar pedido por Email</span> | |
| </button> | |
| <!-- Botón de "Vaciar carrito" con icono --> | |
| <button id="clear-cart-btn" class="w-full bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-6 rounded-xl transition-colors shadow-lg text-xl flex items-center justify-center space-x-2"> | |
| <i class="ph-fill ph-trash-simple text-xl"></i> | |
| <span>Vaciar carrito</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Notificación (Toast) de producto agregado --> | |
| <div id="toast-notification" class="fixed bottom-6 right-24 px-4 py-2 bg-white rounded-xl shadow-lg transition-all duration-300 ease-out opacity-0 translate-x-4 z-[200]"> | |
| <div class="flex items-center space-x-3"> | |
| <img id="toast-image" src="" class="h-10 w-10 rounded-lg object-cover" onerror="this.src='https://placehold.co/40x40/ffffff/000000?text=IMG'"> | |
| <div class="flex flex-col"> | |
| <p id="toast-message" class="text-sm text-gray-800 font-semibold"></p> | |
| <div class="flex items-center space-x-1 text-green-600 mt-0.5"> | |
| <i class="ph-fill ph-check-circle text-base"></i> | |
| <span class="text-xs">Añadido al carrito</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Pie de página (Footer) --> | |
| <footer class="text-white text-center py-6 px-4 mt-12" style="background-color: var(--header-bg-color);"> | |
| <p class="text-sm"> | |
| Creado en <a href="https://routicket.com" target="_blank" class="text-[var(--main-color)] hover:underline">routicket.com</a> | |
| </p> | |
| <?php if (!empty($telefono_contacto)): ?> | |
| <p class="text-sm mt-2"> | |
| Contacto: <a href="tel:<?php echo $telefono_contacto; ?>" class="text-[var(--main-color)] hover:underline"><?php echo $telefono_contacto; ?></a> | |
| </p> | |
| <?php endif; ?> | |
| <?php if (!empty($correo_contacto)): ?> | |
| <p class="text-sm mt-2"> | |
| Correo: <a href="mailto:<?php echo $correo_contacto; ?>" class="text-[var(--main-color)] hover:underline"><?php echo $correo_contacto; ?></a> | |
| </p> | |
| <?php endif; ?> | |
| <?php if (!empty($correo_contacto)): ?> | |
| <p class="text-sm mt-2"> | |
| <a href="panel.php" class="text-[var(--main-color)] hover:underline">Panel</a> | |
| </p> | |
| <p class="text-sm mt-2"> | |
| <a href="https://routicket.com/menu/admin/lista-productos.php" class="text-[var(--main-color)] hover:underline">Admin</a> | |
| </p> | |
| <?php endif; ?> | |
| </footer> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Asigna el URL del logo al preloader y al encabezado | |
| document.getElementById('company-logo').src = logoUrl; | |
| document.getElementById('preloader-logo').src = logoUrl; | |
| document.getElementById('store-title').textContent = storeTitle; | |
| document.getElementById('store-tagline').textContent = storeTagline; | |
| // Elementos del DOM para el horario | |
| const dailyScheduleEl = document.getElementById('daily-schedule'); | |
| const scheduleTextEl = document.getElementById('schedule-text'); | |
| // Función para mostrar el horario del día actual | |
| function displayDailySchedule() { | |
| const now = new Date(); | |
| const dayOfWeek = now.toLocaleDateString('es-MX', { weekday: 'long' }).toLowerCase(); | |
| const currentDaySchedule = schedule[dayOfWeek]; | |
| if (!currentDaySchedule || (currentDaySchedule.abierto === "00:00" && currentDaySchedule.cierre === "00:00")) { | |
| scheduleTextEl.textContent = "Cerrado hoy"; | |
| } else { | |
| scheduleTextEl.textContent = `Abierto de ${currentDaySchedule.abierto} a ${currentDaySchedule.cierre}`; | |
| } | |
| } | |
| // Variable global para los datos del menú | |
| let menuData = []; | |
| // Variables de estado | |
| let cart = []; | |
| let total = 0; | |
| let totalItems = 0; | |
| // Elementos del DOM | |
| const preloader = document.getElementById('preloader'); | |
| const menuContainer = document.getElementById('menu-container'); | |
| const searchInput = document.getElementById('search-input'); | |
| const cartModal = document.getElementById('cart-modal'); | |
| const openCartBtn = document.getElementById('open-cart-btn'); | |
| const closeCartBtn = document.getElementById('close-cart-btn'); | |
| const cartItemsContainer = document.getElementById('cart-items'); | |
| const cartTotalEl = document.getElementById('cart-total'); | |
| const sendWhatsappBtn = document.getElementById('send-whatsapp-btn'); | |
| const sendEmailBtn = document.getElementById('send-email-btn'); | |
| const payLocalBtn = document.getElementById('pay-local-btn'); | |
| const clearCartBtn = document.getElementById('clear-cart-btn'); | |
| const cartCounterEl = document.getElementById('cart-counter'); | |
| const toastNotificationEl = document.getElementById('toast-notification'); | |
| const toastMessageEl = document.getElementById('toast-message'); | |
| const toastImageEl = document.getElementById('toast-image'); | |
| const showUserFormCheckbox = document.getElementById('show-user-form'); | |
| const userFormContainer = document.getElementById('user-form-container'); | |
| const userNameInput = document.getElementById('user-name'); | |
| const userPhoneInput = document.getElementById('user-phone'); | |
| const userEmailInput = document.getElementById('user-email'); | |
| const orderConfirmation = document.getElementById('order-confirmation'); | |
| const actionButtons = document.getElementById('action-buttons'); | |
| const orderNumberEl = document.getElementById('order-number'); | |
| const orderDateEl = document.getElementById('order-date'); | |
| const orderTimeEl = document.getElementById('order-time'); | |
| const closeConfirmationBtn = document.getElementById('close-confirmation'); | |
| const productSlider = document.getElementById('product-slider'); | |
| const sliderPrevBtn = document.getElementById('slider-prev'); | |
| const sliderNextBtn = document.getElementById('slider-next'); | |
| // Función para renderizar los elementos del menú | |
| function renderMenu(productsToRender) { | |
| // Limpiar el contenedor | |
| menuContainer.innerHTML = ''; | |
| const products = productsToRender || menuData; | |
| if (products.length === 0) { | |
| menuContainer.innerHTML = ` | |
| <div class="col-span-1 sm:col-span-2 lg:col-span-3 text-center p-8"> | |
| <p class="text-gray-500 text-lg">No se encontraron productos para mostrar.</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| products.forEach(item => { | |
| // Buscar la cantidad actual en el carrito o empezar en 0 | |
| const cartItem = cart.find(i => i.id === item.id); | |
| const currentQuantity = cartItem ? cartItem.quantity : 0; | |
| const card = document.createElement('div'); | |
| card.className = 'bg-white rounded-2xl shadow-xl overflow-hidden transform hover:scale-105 transition-transform duration-300 ease-in-out cursor-pointer'; | |
| card.innerHTML = ` | |
| <img src="${item.image}" alt="${item.name}" class="w-full h-64 sm:h-80 object-cover" onerror="this.src='https://placehold.co/600x400/FF8C00/ffffff?text=Imagen+No+Disponible'"> | |
| <div class="p-4"> | |
| <h3 class="text-2xl font-bold text-[#1E1E1E] mb-1">${item.name}</h3> | |
| <p class="text-gray-600 text-base mb-3">${item.description}</p> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-xl font-bold text-gray-900">$${item.price.toFixed(2)}</span> | |
| <div class="flex items-center space-x-1" data-id="${item.id}"> | |
| <button class="quantity-btn-minus quantity-btn bg-gray-200 text-gray-900 text-2xl font-bold w-10 h-10 rounded-full shadow-md transition-all"> | |
| - | |
| </button> | |
| <span class="quantity-display text-xl font-bold w-10 text-center text-gray-900">${currentQuantity}</span> | |
| <button class="quantity-btn-plus quantity-btn text-white text-2xl font-bold w-10 h-10 rounded-full shadow-md transition-all" style="background-color: var(--main-color);"> | |
| + | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| menuContainer.appendChild(card); | |
| }); | |
| } | |
| // Función para mezclar un array (Algoritmo Fisher-Yates) | |
| function shuffleArray(array) { | |
| const shuffled = [...array]; | |
| for (let i = shuffled.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; | |
| } | |
| return shuffled; | |
| } | |
| // Función para renderizar el slider de productos de forma aleatoria | |
| function renderSlider() { | |
| productSlider.innerHTML = ''; | |
| // Mezcla los datos del menú y toma los primeros 5 productos | |
| const shuffledProducts = shuffleArray(menuData); | |
| const sliderItems = shuffledProducts.slice(0, 5); | |
| sliderItems.forEach(item => { | |
| const sliderItem = document.createElement('div'); | |
| sliderItem.className = 'slider-item min-w-[280px] sm:min-w-[320px] bg-white rounded-2xl shadow-xl overflow-hidden transform hover:scale-105 transition-transform duration-300 ease-in-out cursor-pointer flex flex-col justify-between'; | |
| sliderItem.innerHTML = ` | |
| <div> | |
| <img src="${item.image}" alt="${item.name}" class="w-full h-40 object-cover" onerror="this.src='https://placehold.co/600x400/FF8C00/ffffff?text=Imagen+No+Disponible'"> | |
| <div class="p-4"> | |
| <h3 class="text-lg font-bold text-[#1E1E1E] mb-1">${item.name}</h3> | |
| <p class="text-gray-600 text-sm mb-2">${item.description}</p> | |
| <span class="text-xl font-bold text-gray-900">$${item.price.toFixed(2)}</span> | |
| </div> | |
| </div> | |
| <div class="p-4 pt-0"> | |
| <button class="w-full text-white font-bold py-2 px-4 rounded-xl transition-colors shadow-lg text-lg flex items-center justify-center space-x-2 add-to-cart-slider-btn" style="background-color: var(--main-color); hover:background-color: var(--dark-color)" data-id="${item.id}"> | |
| <i class="ph-fill ph-plus-circle text-2xl"></i> | |
| <span>Añadir</span> | |
| </button> | |
| </div> | |
| `; | |
| productSlider.appendChild(sliderItem); | |
| }); | |
| } | |
| // Función para obtener los datos del menú desde el servidor | |
| async function fetchMenuData() { | |
| try { | |
| const response = await fetch('https://routicket.com/menu/dataMenu.php?user_id=31'); | |
| if (!response.ok) { | |
| throw new Error('Error al cargar los productos'); | |
| } | |
| menuData = await response.json(); | |
| renderMenu(menuData); | |
| renderSlider(); | |
| // Oculta el preloader con una transición de opacidad | |
| preloader.classList.add('opacity-0'); | |
| // Espera 5 segundos antes de ocultar completamente | |
| setTimeout(() => { | |
| preloader.classList.add('hidden'); | |
| }, 5000); | |
| } catch (error) { | |
| console.error('Error:', error); | |
| menuContainer.innerHTML = ` | |
| <div class="col-span-1 sm:col-span-2 lg:col-span-3 text-center p-8"> | |
| <p class="text-red-600 text-lg">Ocurrió un error al cargar el menú. Por favor, intenta de nuevo más tarde.</p> | |
| </div> | |
| `; | |
| preloader.classList.add('opacity-0'); | |
| setTimeout(() => { | |
| preloader.classList.add('hidden'); | |
| }, 5000); | |
| } | |
| } | |
| // Función para actualizar el carrito y refrescar la UI | |
| function updateCart(itemId, quantityChange, showToastMessage = true) { | |
| const existingItemIndex = cart.findIndex(item => item.id === itemId); | |
| const menuItem = menuData.find(item => item.id === itemId); | |
| if (!menuItem) return; | |
| if (existingItemIndex > -1) { | |
| cart[existingItemIndex].quantity += quantityChange; | |
| } else if (quantityChange > 0) { | |
| cart.push({ ...menuItem, quantity: quantityChange }); | |
| } | |
| // Filtrar elementos con cantidad 0 o menos | |
| cart = cart.filter(item => item.quantity > 0); | |
| // Recalcular el total y el número de artículos | |
| total = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); | |
| totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); | |
| renderCart(); | |
| updateCartCounter(); | |
| // Actualizar la cantidad en la tarjeta del menú | |
| const card = menuContainer.querySelector(`[data-id="${itemId}"]`); | |
| if (card) { | |
| const quantityDisplay = card.querySelector('.quantity-display'); | |
| const cartItem = cart.find(i => i.id === itemId); | |
| quantityDisplay.textContent = cartItem ? cartItem.quantity : 0; | |
| } | |
| // Mostrar la notificación si es necesario | |
| if (showToastMessage && quantityChange > 0) { | |
| showToast(`${menuItem.name} añadido`, menuItem.image); | |
| } | |
| } | |
| // Función para actualizar el contador del carrito | |
| function updateCartCounter() { | |
| cartCounterEl.textContent = totalItems; | |
| if (totalItems > 0) { | |
| cartCounterEl.classList.remove('hidden'); | |
| cartCounterEl.classList.add('flex'); | |
| openCartBtn.classList.add('pulse-once'); | |
| } else { | |
| cartCounterEl.classList.add('hidden'); | |
| cartCounterEl.classList.remove('flex'); | |
| } | |
| } | |
| // Función para renderizar el carrito | |
| function renderCart() { | |
| cartItemsContainer.innerHTML = ''; | |
| if (cart.length === 0) { | |
| cartItemsContainer.innerHTML = '<p class="text-center text-gray-500 py-4 text-sm">El carrito está vacío.</p>'; | |
| } else { | |
| cart.forEach(item => { | |
| const cartItemEl = document.createElement('div'); | |
| cartItemEl.className = 'flex items-center justify-between py-2 border-b border-gray-200'; | |
| cartItemEl.innerHTML = ` | |
| <div class="flex-1"> | |
| <span class="text-base font-semibold text-gray-900">${item.name}</span> | |
| <span class="block text-gray-500 text-xs">Cantidad: ${item.quantity}</span> | |
| </div> | |
| <span class="text-base font-bold" style="color: var(--main-color);">$${(item.price * item.quantity).toFixed(2)}</span> | |
| `; | |
| cartItemsContainer.appendChild(cartItemEl); | |
| }); | |
| } | |
| cartTotalEl.textContent = `$${total.toFixed(2)}`; | |
| } | |
| // Función para mostrar un mensaje en una notificación (toast) | |
| function showToast(message, imageUrl) { | |
| toastMessageEl.textContent = message; | |
| toastImageEl.src = imageUrl; | |
| toastNotificationEl.classList.remove('opacity-0', 'translate-x-4'); | |
| toastNotificationEl.classList.add('opacity-100', 'translate-x-0'); | |
| setTimeout(() => { | |
| toastNotificationEl.classList.remove('opacity-100', 'translate-x-0'); | |
| toastNotificationEl.classList.add('opacity-0', 'translate-x-4'); | |
| }, 3000); // Ocultar después de 3 segundos | |
| } | |
| // Función para construir el mensaje del pedido | |
| function buildOrderMessage(includeUserInfo = false) { | |
| if (cart.length === 0) { | |
| return null; | |
| } | |
| let message = "¡Hola! Me gustaría hacer el siguiente pedido:\n\n"; | |
| if (includeUserInfo) { | |
| const userName = userNameInput.value.trim(); | |
| const userPhone = userPhoneInput.value.trim(); | |
| const userEmail = userEmailInput.value.trim(); | |
| if (userName) message += `Nombre: ${userName}\n`; | |
| if (userPhone) message += `Teléfono: ${userPhone}\n`; | |
| if (userEmail) message += `Email: ${userEmail}\n`; | |
| message += "\n"; | |
| } | |
| cart.forEach(item => { | |
| message += `- ${item.name} (${item.quantity} unidad${item.quantity > 1 ? 'es' : ''}) - $${(item.price * item.quantity).toFixed(2)}\n`; | |
| }); | |
| message += `\nTotal: $${total.toFixed(2)}\n\n`; | |
| return message; | |
| } | |
| // Función para enviar el pedido a WhatsApp | |
| function sendWhatsApp() { | |
| const message = buildOrderMessage(true); | |
| if (!message) { | |
| showToast("El carrito está vacío. Agrega productos antes de enviar el pedido.", ''); | |
| return; | |
| } | |
| const encodedMessage = encodeURIComponent(message); | |
| const phoneNumber = contactPhone; // Usar la variable de PHP | |
| if (!phoneNumber) { | |
| showToast("No se ha configurado un número de teléfono de contacto.", ''); | |
| return; | |
| } | |
| const whatsappUrl = `https://wa.me/${phoneNumber}?text=${encodedMessage}`; | |
| window.open(whatsappUrl, '_blank'); | |
| } | |
| // Función para enviar el pedido por correo electrónico | |
| function sendEmail() { | |
| const message = buildOrderMessage(true); | |
| if (!message) { | |
| showToast("El carrito está vacío. Agrega productos antes de enviar el pedido.", ''); | |
| return; | |
| } | |
| const subject = "Nuevo Pedido de Menú Digital"; | |
| const body = message + "Espero tu confirmación. ¡Gracias!"; | |
| const emailAddress = contactEmail; // Usar la variable de PHP | |
| if (!emailAddress) { | |
| showToast("No se ha configurado un correo electrónico de contacto.", ''); | |
| return; | |
| } | |
| const mailtoUrl = `mailto:${emailAddress}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; | |
| window.open(mailtoUrl, '_blank'); | |
| } | |
| // Función para manejar el pedido en el local y enviarlo por AJAX | |
| async function handlePayLocal() { | |
| if (cart.length === 0) { | |
| showToast("El carrito está vacío. Agrega productos antes de finalizar el pedido.", ''); | |
| return; | |
| } | |
| // Ocultar botones de acción y mostrar mensaje de carga | |
| actionButtons.classList.add('hidden'); | |
| orderConfirmation.classList.remove('hidden'); | |
| orderConfirmation.innerHTML = ` | |
| <div class="flex flex-col items-center"> | |
| <svg class="animate-spin h-10 w-10 text-[var(--main-color)]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
| </svg> | |
| <p class="mt-4 text-xl text-gray-700">Procesando su orden... por favor, espere.</p> | |
| </div> | |
| `; | |
| // --- MODIFICACIÓN CLAVE: Se crea el objeto 'tienda' aquí --- | |
| const tiendaData = { | |
| id: storeId, | |
| user_id: userId, | |
| nombre: storeTitle, | |
| telefono: contactPhone | |
| }; | |
| // TODO LO QUE SE ENVÍA A registrar_orden.php | |
| const orderData = { | |
| cart: cart, | |
| total: total, | |
| userName: showUserFormCheckbox.checked ? userNameInput.value.trim() : '', | |
| userPhone: showUserFormCheckbox.checked ? userPhoneInput.value.trim() : '', | |
| userEmail: showUserFormCheckbox.checked ? userEmailInput.value.trim() : '', | |
| // Se agrega el nuevo objeto 'tienda' al payload | |
| tienda: tiendaData | |
| }; | |
| try { | |
| const response = await fetch('registrar_orden.php', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(orderData), | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Hubo un problema con la solicitud.'); | |
| } | |
| const result = await response.json(); | |
| if (result.status === 'success') { | |
| // --- Lógica actualizada: usar los datos de la respuesta del servidor --- | |
| const { order_number, order_date, order_time } = result; | |
| // Mostrar la confirmación | |
| orderConfirmation.innerHTML = ` | |
| <i class="ph-fill ph-check-circle text-4xl text-green-600"></i> | |
| <h3 class="text-xl font-bold text-green-600 mt-2"> | |
| ¡Orden Registrada! | |
| </h3> | |
| <p class="mt-2 text-gray-700">Número de Orden: <span id="order-number" class="font-bold">${order_number}</span></p> | |
| <p class="text-gray-700">Fecha: <span id="order-date" class="font-bold">${order_date}</span></p> | |
| <p class="text-gray-700">Hora: <span id="order-time" class="font-bold">${order_time}</span></p> | |
| <button id="close-confirmation" class="mt-4 w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg transition-colors">Cerrar</button> | |
| `; | |
| // Re-asignar el listener al nuevo botón de cierre | |
| document.getElementById('close-confirmation').addEventListener('click', () => { | |
| // Cerrar la modal del carrito | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| // Vaciar el carrito | |
| cart = []; | |
| total = 0; | |
| totalItems = 0; | |
| renderCart(); | |
| renderMenu(); // Refrescar el menú para resetear los contadores | |
| updateCartCounter(); | |
| }); | |
| showToast("¡Orden registrada! Tu número de orden es " + order_number, ''); | |
| // Vaciar el carrito después de un registro exitoso | |
| //cart = []; | |
| //total = 0; | |
| //totalItems = 0; | |
| //renderCart(); | |
| //renderMenu(); | |
| //updateCartCounter(); | |
| } else { | |
| // Mostrar mensaje de error si el servidor devuelve un fallo | |
| orderConfirmation.innerHTML = ` | |
| <i class="ph-fill ph-x-circle text-4xl text-red-600"></i> | |
| <h3 class="text-xl font-bold text-red-600 mt-2">Error al registrar</h3> | |
| <p class="mt-2 text-gray-700">${result.message}</p> | |
| <button id="close-error" class="mt-4 w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg transition-colors">Cerrar</button> | |
| `; | |
| // Re-asignar listener al botón de cierre de error | |
| document.getElementById('close-error').addEventListener('click', () => { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| orderConfirmation.innerHTML = ` | |
| <i class="ph-fill ph-x-circle text-4xl text-red-600"></i> | |
| <h3 class="text-xl font-bold text-red-600 mt-2">Error de conexión</h3> | |
| <p class="mt-2 text-gray-700">No se pudo conectar con el servidor. Por favor, intente de nuevo.</p> | |
| <button id="close-error" class="mt-4 w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg transition-colors">Cerrar</button> | |
| `; | |
| // Re-asignar listener al botón de cierre de error | |
| document.getElementById('close-error').addEventListener('click', () => { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| }); | |
| } | |
| } | |
| // Event Listeners | |
| menuContainer.addEventListener('click', (e) => { | |
| const plusBtn = e.target.closest('.quantity-btn-plus'); | |
| const minusBtn = e.target.closest('.quantity-btn-minus'); | |
| if (plusBtn) { | |
| const itemId = parseInt(plusBtn.closest('[data-id]').dataset.id); | |
| updateCart(itemId, 1); | |
| } else if (minusBtn) { | |
| const itemId = parseInt(minusBtn.closest('[data-id]').dataset.id); | |
| // Solo si la cantidad es mayor a 0, restar | |
| const cartItem = cart.find(i => i.id === itemId); | |
| if (cartItem && cartItem.quantity > 0) { | |
| updateCart(itemId, -1, false); // No mostrar notificación al eliminar | |
| } else { | |
| showToast("No hay elementos para eliminar.", ''); | |
| } | |
| } | |
| }); | |
| // Listener para el buscador | |
| searchInput.addEventListener('input', (e) => { | |
| const searchTerm = e.target.value.toLowerCase(); | |
| const filteredProducts = menuData.filter(item => { | |
| return item.name.toLowerCase().includes(searchTerm) || | |
| item.description.toLowerCase().includes(searchTerm); | |
| }); | |
| renderMenu(filteredProducts); | |
| }); | |
| // Lógica para el slider | |
| sliderPrevBtn.addEventListener('click', () => { | |
| productSlider.scrollBy({ | |
| left: -productSlider.offsetWidth * 0.8, | |
| behavior: 'smooth' | |
| }); | |
| }); | |
| sliderNextBtn.addEventListener('click', () => { | |
| productSlider.scrollBy({ | |
| left: productSlider.offsetWidth * 0.8, | |
| behavior: 'smooth' | |
| }); | |
| }); | |
| // Nuevo listener para los botones del slider | |
| productSlider.addEventListener('click', (e) => { | |
| const addToCartBtn = e.target.closest('.add-to-cart-slider-btn'); | |
| if (addToCartBtn) { | |
| const itemId = parseInt(addToCartBtn.dataset.id); | |
| updateCart(itemId, 1); | |
| } | |
| }); | |
| openCartBtn.addEventListener('click', () => { | |
| orderConfirmation.classList.add('hidden'); | |
| actionButtons.classList.remove('hidden'); | |
| cartModal.classList.remove('hidden'); | |
| cartModal.classList.add('flex'); | |
| setTimeout(() => { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-95', 'opacity-0'); | |
| modalContent.classList.add('scale-100', 'opacity-100'); | |
| }, 10); | |
| renderCart(); | |
| }); | |
| closeCartBtn.addEventListener('click', () => { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| }); | |
| closeConfirmationBtn.addEventListener('click', () => { | |
| // Cerrar la modal del carrito | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| // Vaciar el carrito | |
| cart = []; | |
| total = 0; | |
| totalItems = 0; | |
| renderCart(); | |
| renderMenu(); // Refrescar el menú para resetear los contadores | |
| updateCartCounter(); | |
| }); | |
| clearCartBtn.addEventListener('click', () => { | |
| cart = []; | |
| total = 0; | |
| totalItems = 0; | |
| renderCart(); | |
| renderMenu(); // Refrescar el menú para resetear los contadores | |
| updateCartCounter(); | |
| showToast("El carrito ha sido vaciado.", ''); | |
| }); | |
| showUserFormCheckbox.addEventListener('change', (e) => { | |
| if (e.target.checked) { | |
| userFormContainer.classList.remove('hidden'); | |
| } else { | |
| userFormContainer.classList.add('hidden'); | |
| } | |
| }); | |
| sendWhatsappBtn.addEventListener('click', sendWhatsApp); | |
| sendEmailBtn.addEventListener('click', sendEmail); | |
| payLocalBtn.addEventListener('click', handlePayLocal); | |
| // Cierra la modal si se hace clic fuera del contenido | |
| cartModal.addEventListener('click', (e) => { | |
| if (e.target === cartModal) { | |
| const modalContent = cartModal.querySelector('div'); | |
| modalContent.classList.remove('scale-100', 'opacity-100'); | |
| modalContent.classList.add('scale-95', 'opacity-0'); | |
| setTimeout(() => { | |
| cartModal.classList.remove('flex'); | |
| cartModal.classList.add('hidden'); | |
| }, 300); | |
| } | |
| }); | |
| // Inicializa la página | |
| displayDailySchedule(); // Llama a la nueva función para mostrar el horario | |
| fetchMenuData(); // Llama a la nueva función para cargar los datos | |
| updateCartCounter(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment