Skip to content

Instantly share code, notes, and snippets.

@iDavidMorales
Created November 3, 2025 19:15
Show Gist options
  • Select an option

  • Save iDavidMorales/b702a1d1055c1f81c9e811fc126f4889 to your computer and use it in GitHub Desktop.

Select an option

Save iDavidMorales/b702a1d1055c1f81c9e811fc126f4889 to your computer and use it in GitHub Desktop.
<?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">
&times;
</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