kioscos / index.html
yosmani's picture
Add 2 files
e948c20 verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kiosco - Inventario y Cuadre</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.receipt-print {
font-family: 'Courier New', monospace;
background-color: white;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.product-row:hover {
background-color: #f0f9ff;
}
.sold-animation {
animation: soldPulse 0.5s ease-in-out;
}
@keyframes soldPulse {
0% { background-color: #f0fdf4; }
50% { background-color: #dcfce7; }
100% { background-color: #f0fdf4; }
}
.delete-confirmation {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-5px); }
40%, 80% { transform: translateX(5px); }
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-6 max-w-4xl">
<!-- Header -->
<header class="bg-blue-600 text-white rounded-lg shadow-md p-4 mb-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold">Kiosco Ventas PRO</h1>
<p class="text-blue-100 text-sm" id="current-date"></p>
</div>
<div class="flex items-center space-x-4">
<div class="bg-white text-blue-600 rounded-full w-12 h-12 flex items-center justify-center">
<i class="fas fa-cash-register text-2xl"></i>
</div>
<div class="text-right">
<p class="text-sm">Turno: <span id="current-shift">Mañana</span></p>
<p class="text-sm">Usuario: <span id="current-user">Admin</span></p>
</div>
</div>
</div>
</header>
<!-- Main App -->
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-6">
<!-- Tabs -->
<div class="flex border-b">
<button id="tab-inventory" class="flex-1 py-3 font-medium text-blue-600 border-b-2 border-blue-600">
<i class="fas fa-boxes mr-2"></i>Inventario
</button>
<button id="tab-register" class="flex-1 py-3 font-medium text-gray-500">
<i class="fas fa-calculator mr-2"></i>Cuadre
</button>
<button id="tab-history" class="flex-1 py-3 font-medium text-gray-500">
<i class="fas fa-history mr-2"></i>Historial
</button>
<button id="tab-settings" class="flex-1 py-3 font-medium text-gray-500">
<i class="fas fa-cog mr-2"></i>Ajustes
</button>
</div>
<!-- Inventory Tab Content -->
<div id="inventory-content" class="p-4 fade-in">
<div class="mb-4 flex">
<input type="text" id="product-search" class="flex-1 px-3 py-2 border rounded-l-lg focus:outline-none" placeholder="Buscar producto...">
<button id="search-product" class="bg-blue-600 text-white px-4 py-2 rounded-r-lg">
<i class="fas fa-search"></i>
</button>
</div>
<div class="mb-4 flex justify-between items-center">
<h3 class="font-bold text-lg">Productos en inventario</h3>
<div class="flex space-x-2">
<button id="add-product" class="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded-lg text-sm">
<i class="fas fa-plus mr-1"></i>Agregar
</button>
<button id="delete-selected" class="bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded-lg text-sm hidden">
<i class="fas fa-trash-alt mr-1"></i>Eliminar
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-100">
<tr>
<th class="px-4 py-2 text-left w-8">
<input type="checkbox" id="select-all" class="form-checkbox h-4 w-4 text-blue-600">
</th>
<th class="px-4 py-2 text-left">Producto</th>
<th class="px-4 py-2 text-left">Precio</th>
<th class="px-4 py-2 text-left">Stock</th>
<th class="px-4 py-2 text-left">Vendidos</th>
<th class="px-4 py-2 text-left">Total</th>
<th class="px-4 py-2 text-left">Acciones</th>
</tr>
</thead>
<tbody id="product-list">
<!-- Product rows will be added here dynamically -->
</tbody>
</table>
</div>
<div class="mt-4 p-3 bg-blue-50 rounded-lg">
<div class="flex justify-between">
<span class="font-medium">Total vendido hoy:</span>
<span id="daily-sales-total" class="font-bold">$0.00</span>
</div>
<div class="flex justify-between">
<span class="font-medium">Productos vendidos:</span>
<span id="products-sold-count" class="font-bold">0</span>
</div>
</div>
</div>
<!-- Register Tab Content -->
<div id="register-content" class="hidden p-4">
<div class="mb-4">
<label class="block text-gray-700 mb-2">Efectivo Inicial</label>
<input type="number" id="initial-cash" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="0.00">
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Ventas del día (automático)</label>
<input type="number" id="daily-sales" class="w-full px-3 py-2 border rounded-lg bg-gray-100" placeholder="0.00" readonly>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Efectivo en caja</label>
<input type="number" id="current-cash" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="0.00">
</div>
<div class="bg-blue-50 p-3 rounded-lg mb-4">
<div class="flex justify-between mb-1">
<span class="font-medium">Total teórico:</span>
<span id="theoretical-total" class="font-bold">$0.00</span>
</div>
<div class="flex justify-between mb-1">
<span class="font-medium">Diferencia:</span>
<span id="difference" class="font-bold">$0.00</span>
</div>
</div>
<button id="calculate-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 px-4 rounded-lg font-medium mb-4 transition duration-200">
<i class="fas fa-calculator mr-2"></i>Calcular Cuadre
</button>
<div id="results" class="hidden">
<div class="receipt-print mb-4">
<h3 class="text-center font-bold text-lg mb-2">CUADRE DE CAJA</h3>
<p class="text-center text-sm mb-4" id="receipt-date"></p>
<div class="flex justify-between py-1 border-b">
<span>Efectivo Inicial:</span>
<span id="receipt-initial"></span>
</div>
<div class="flex justify-between py-1 border-b">
<span>Ventas del día:</span>
<span id="receipt-sales"></span>
</div>
<div class="flex justify-between py-1 border-b">
<span>Total Teórico:</span>
<span id="receipt-theoretical"></span>
</div>
<div class="flex justify-between py-1 border-b">
<span>Efectivo en caja:</span>
<span id="receipt-current"></span>
</div>
<div class="flex justify-between py-1 font-bold mt-2">
<span>Diferencia:</span>
<span id="receipt-difference"></span>
</div>
</div>
<div class="flex space-x-2">
<button id="save-btn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg font-medium transition duration-200">
<i class="fas fa-save mr-2"></i>Guardar
</button>
<button id="print-btn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg font-medium transition duration-200">
<i class="fas fa-print mr-2"></i>Imprimir
</button>
</div>
</div>
</div>
<!-- History Tab Content -->
<div id="history-content" class="hidden p-4">
<div class="mb-4 flex items-center">
<input type="date" id="history-date" class="flex-1 px-3 py-2 border rounded-lg mr-2">
<button id="search-history" class="bg-blue-600 text-white px-4 py-2 rounded-lg">
<i class="fas fa-search"></i>
</button>
</div>
<div id="history-list" class="space-y-3">
<!-- Ejemplo de registro -->
<div class="border rounded-lg p-3">
<div class="flex justify-between items-center mb-1">
<span class="font-medium">15/06/2023</span>
<span class="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">Cuadre</span>
</div>
<div class="flex justify-between text-sm">
<span>Ventas: $1,250.00</span>
<span>Diferencia: <span class="font-bold text-green-600">$0.00</span></span>
</div>
</div>
<div class="border rounded-lg p-3">
<div class="flex justify-between items-center mb-1">
<span class="font-medium">14/06/2023</span>
<span class="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">Cuadre</span>
</div>
<div class="flex justify-between text-sm">
<span>Ventas: $980.50</span>
<span>Diferencia: <span class="font-bold text-red-600">-$5.50</span></span>
</div>
</div>
</div>
</div>
<!-- Settings Tab Content -->
<div id="settings-content" class="hidden p-4">
<div class="mb-4">
<label class="block text-gray-700 mb-2">Nombre del Kiosco</label>
<input type="text" class="w-full px-3 py-2 border rounded-lg" value="Mi Kiosco">
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Moneda</label>
<select class="w-full px-3 py-2 border rounded-lg">
<option>Pesos ($)</option>
<option>Dólares (USD)</option>
<option>Euros (€)</option>
</select>
</div>
<div class="mb-4">
<label class="flex items-center">
<input type="checkbox" class="form-checkbox h-5 w-5 text-blue-600" checked>
<span class="ml-2 text-gray-700">Mostrar decimales</span>
</label>
</div>
<div class="mb-4">
<label class="flex items-center">
<input type="checkbox" class="form-checkbox h-5 w-5 text-blue-600">
<span class="ml-2 text-gray-700">Imprimir automáticamente</span>
</label>
</div>
<button class="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 px-4 rounded-lg font-medium mb-2 transition duration-200">
<i class="fas fa-save mr-2"></i>Guardar Ajustes
</button>
<button class="w-full bg-gray-600 hover:bg-gray-700 text-white py-3 px-4 rounded-lg font-medium transition duration-200">
<i class="fas fa-sync-alt mr-2"></i>Respaldar Datos
</button>
</div>
</div>
<!-- Footer -->
<footer class="text-center text-gray-500 text-sm">
<p>Kiosco App PRO v2.0 - © 2023</p>
</footer>
</div>
<!-- Add Product Modal -->
<div id="add-product-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<h3 class="text-xl font-bold mb-4">Agregar Producto</h3>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Nombre del Producto</label>
<input type="text" id="product-name" class="w-full px-3 py-2 border rounded-lg">
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Precio</label>
<input type="number" id="product-price" class="w-full px-3 py-2 border rounded-lg">
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-2">Stock Inicial</label>
<input type="number" id="product-stock" class="w-full px-3 py-2 border rounded-lg" value="1">
</div>
<div class="flex justify-end space-x-2">
<button id="cancel-add" class="px-4 py-2 border rounded-lg">Cancelar</button>
<button id="confirm-add" class="px-4 py-2 bg-blue-600 text-white rounded-lg">Agregar</button>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div id="delete-confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<h3 class="text-xl font-bold mb-4 text-red-600">
<i class="fas fa-exclamation-triangle mr-2"></i>Confirmar Eliminación
</h3>
<p class="mb-4">¿Estás seguro que deseas eliminar los productos seleccionados?</p>
<p class="text-sm text-gray-500 mb-6">Esta acción no se puede deshacer.</p>
<div class="flex justify-end space-x-2">
<button id="cancel-delete" class="px-4 py-2 border rounded-lg">Cancelar</button>
<button id="confirm-delete" class="px-4 py-2 bg-red-600 text-white rounded-lg">
<i class="fas fa-trash-alt mr-2"></i>Eliminar
</button>
</div>
</div>
</div>
<script>
// Sample product data
let products = [
{ id: 1, name: "Coca-Cola 600ml", price: 25.00, stock: 50, sold: 0 },
{ id: 2, name: "Sabritas 45g", price: 18.00, stock: 30, sold: 0 },
{ id: 3, name: "Galletas Emperador", price: 15.00, stock: 40, sold: 0 },
{ id: 4, name: "Agua 1L", price: 12.00, stock: 25, sold: 0 },
{ id: 5, name: "Chocolate Hershey's", price: 20.00, stock: 20, sold: 0 }
];
// Variables for daily sales tracking
let dailySalesTotal = 0;
let productsSoldCount = 0;
let initialCash = 0;
let selectedProducts = [];
// Mostrar fecha actual
const now = new Date();
document.getElementById('current-date').textContent = now.toLocaleDateString('es-ES', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Formatear fecha para recibo
document.getElementById('receipt-date').textContent = now.toLocaleDateString('es-ES', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
// Función para renderizar la lista de productos
function renderProductList() {
const productList = document.getElementById('product-list');
productList.innerHTML = '';
products.forEach(product => {
const isSelected = selectedProducts.includes(product.id);
const row = document.createElement('tr');
row.className = `product-row border-b ${isSelected ? 'bg-blue-50' : ''}`;
row.innerHTML = `
<td class="px-4 py-3 w-8">
<input type="checkbox" class="product-checkbox form-checkbox h-4 w-4 text-blue-600"
data-id="${product.id}" ${isSelected ? 'checked' : ''}>
</td>
<td class="px-4 py-3">${product.name}</td>
<td class="px-4 py-3">${formatCurrency(product.price)}</td>
<td class="px-4 py-3">${product.stock}</td>
<td class="px-4 py-3">${product.sold}</td>
<td class="px-4 py-3">${formatCurrency(product.price * product.sold)}</td>
<td class="px-4 py-3">
<button class="sell-btn bg-green-100 text-green-800 px-2 py-1 rounded text-sm" data-id="${product.id}">
<i class="fas fa-cash-register mr-1"></i>Vender
</button>
<button class="edit-btn bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm ml-1" data-id="${product.id}">
<i class="fas fa-edit mr-1"></i>Editar
</button>
<button class="delete-btn bg-red-100 text-red-800 px-2 py-1 rounded text-sm ml-1" data-id="${product.id}">
<i class="fas fa-trash-alt mr-1"></i>
</button>
</td>
`;
productList.appendChild(row);
});
// Update daily sales display
document.getElementById('daily-sales-total').textContent = formatCurrency(dailySalesTotal);
document.getElementById('products-sold-count').textContent = productsSoldCount;
document.getElementById('daily-sales').value = dailySalesTotal.toFixed(2);
// Toggle delete selected button
const deleteBtn = document.getElementById('delete-selected');
if (selectedProducts.length > 0) {
deleteBtn.classList.remove('hidden');
deleteBtn.innerHTML = `<i class="fas fa-trash-alt mr-1"></i>Eliminar (${selectedProducts.length})`;
} else {
deleteBtn.classList.add('hidden');
}
// Update select all checkbox
const selectAll = document.getElementById('select-all');
selectAll.checked = selectedProducts.length > 0 && selectedProducts.length === products.length;
// Add event listeners
addProductEventListeners();
}
// Add event listeners to product actions
function addProductEventListeners() {
// Sell buttons
document.querySelectorAll('.sell-btn').forEach(button => {
button.addEventListener('click', function() {
const productId = parseInt(this.getAttribute('data-id'));
sellProduct(productId);
});
});
// Delete single product buttons
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', function() {
const productId = parseInt(this.getAttribute('data-id'));
showDeleteModal([productId]);
});
});
// Product checkboxes
document.querySelectorAll('.product-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const productId = parseInt(this.getAttribute('data-id'));
if (this.checked) {
if (!selectedProducts.includes(productId)) {
selectedProducts.push(productId);
}
} else {
selectedProducts = selectedProducts.filter(id => id !== productId);
}
renderProductList();
});
});
}
// Función para vender un producto
function sellProduct(productId) {
const product = products.find(p => p.id === productId);
if (product && product.stock > 0) {
product.stock--;
product.sold++;
dailySalesTotal += product.price;
productsSoldCount++;
// Update UI
renderProductList();
// Show visual feedback
const row = document.querySelector(`.sell-btn[data-id="${productId}"]`).closest('tr');
row.classList.add('sold-animation');
setTimeout(() => {
row.classList.remove('sold-animation');
}, 500);
} else {
alert('No hay suficiente stock de este producto');
}
}
// Función para formatear moneda
function formatCurrency(amount) {
return '$' + amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
}
// Manejo de pestañas
const tabs = {
inventory: document.getElementById('tab-inventory'),
register: document.getElementById('tab-register'),
history: document.getElementById('tab-history'),
settings: document.getElementById('tab-settings')
};
const contents = {
inventory: document.getElementById('inventory-content'),
register: document.getElementById('register-content'),
history: document.getElementById('history-content'),
settings: document.getElementById('settings-content')
};
function showTab(tabName) {
// Ocultar todos los contenidos
Object.values(contents).forEach(content => {
content.classList.add('hidden');
content.classList.remove('fade-in');
});
// Mostrar el contenido seleccionado
contents[tabName].classList.remove('hidden');
setTimeout(() => {
contents[tabName].classList.add('fade-in');
}, 10);
// Actualizar estilos de las pestañas
Object.values(tabs).forEach(tab => {
tab.classList.remove('text-blue-600', 'border-blue-600');
tab.classList.add('text-gray-500');
});
tabs[tabName].classList.remove('text-gray-500');
tabs[tabName].classList.add('text-blue-600', 'border-blue-600');
}
// Event listeners para pestañas
tabs.inventory.addEventListener('click', () => showTab('inventory'));
tabs.register.addEventListener('click', () => showTab('register'));
tabs.history.addEventListener('click', () => showTab('history'));
tabs.settings.addEventListener('click', () => showTab('settings'));
// Cálculo del cuadre de caja
document.getElementById('calculate-btn').addEventListener('click', function() {
initialCash = parseFloat(document.getElementById('initial-cash').value) || 0;
const currentCash = parseFloat(document.getElementById('current-cash').value) || 0;
const theoreticalTotal = initialCash + dailySalesTotal;
const difference = currentCash - theoreticalTotal;
// Mostrar resultados
document.getElementById('theoretical-total').textContent = formatCurrency(theoreticalTotal);
document.getElementById('difference').textContent = formatCurrency(difference);
// Colorear diferencia
const diffElement = document.getElementById('difference');
diffElement.classList.remove('text-green-600', 'text-red-600');
if (difference > 0) {
diffElement.classList.add('text-green-600');
} else if (difference < 0) {
diffElement.classList.add('text-red-600');
}
// Llenar recibo
document.getElementById('receipt-initial').textContent = formatCurrency(initialCash);
document.getElementById('receipt-sales').textContent = formatCurrency(dailySalesTotal);
document.getElementById('receipt-theoretical').textContent = formatCurrency(theoreticalTotal);
document.getElementById('receipt-current').textContent = formatCurrency(currentCash);
document.getElementById('receipt-difference').textContent = formatCurrency(difference);
// Mostrar sección de resultados
document.getElementById('results').classList.remove('hidden');
});
// Botón Guardar
document.getElementById('save-btn').addEventListener('click', function() {
alert('Cuadre guardado correctamente');
// Aquí iría la lógica para guardar en base de datos/localStorage
});
// Botón Imprimir
document.getElementById('print-btn').addEventListener('click', function() {
// En una app real, esto enviaría a impresora térmica
const printContent = document.querySelector('.receipt-print').innerHTML;
const originalContent = document.body.innerHTML;
document.body.innerHTML = printContent;
window.print();
document.body.innerHTML = originalContent;
showTab('register');
});
// Modal para agregar producto
document.getElementById('add-product').addEventListener('click', function() {
document.getElementById('add-product-modal').classList.remove('hidden');
});
document.getElementById('cancel-add').addEventListener('click', function() {
document.getElementById('add-product-modal').classList.add('hidden');
});
document.getElementById('confirm-add').addEventListener('click', function() {
const name = document.getElementById('product-name').value;
const price = parseFloat(document.getElementById('product-price').value);
const stock = parseInt(document.getElementById('product-stock').value);
if (name && !isNaN(price) && !isNaN(stock)) {
const newId = products.length > 0 ? Math.max(...products.map(p => p.id)) + 1 : 1;
products.push({
id: newId,
name: name,
price: price,
stock: stock,
sold: 0
});
renderProductList();
document.getElementById('add-product-modal').classList.add('hidden');
// Clear form
document.getElementById('product-name').value = '';
document.getElementById('product-price').value = '';
document.getElementById('product-stock').value = '1';
} else {
alert('Por favor complete todos los campos correctamente');
}
});
// Select all products
document.getElementById('select-all').addEventListener('change', function() {
if (this.checked) {
selectedProducts = products.map(p => p.id);
} else {
selectedProducts = [];
}
renderProductList();
});
// Delete selected products button
document.getElementById('delete-selected').addEventListener('click', function() {
if (selectedProducts.length > 0) {
showDeleteModal(selectedProducts);
}
});
// Show delete confirmation modal
function showDeleteModal(idsToDelete) {
const modal = document.getElementById('delete-confirm-modal');
modal.classList.remove('hidden');
document.getElementById('cancel-delete').addEventListener('click', function() {
modal.classList.add('hidden');
}, { once: true });
document.getElementById('confirm-delete').addEventListener('click', function() {
// Remove products
products = products.filter(p => !idsToDelete.includes(p.id));
// Remove from selected products
selectedProducts = selectedProducts.filter(id => !idsToDelete.includes(id));
// Update UI
renderProductList();
modal.classList.add('hidden');
// Show confirmation
const toast = document.createElement('div');
toast.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg';
toast.innerHTML = `<i class="fas fa-check-circle mr-2"></i>Productos eliminados correctamente`;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => toast.remove(), 300);
}, 2000);
}, { once: true });
}
// Mostrar pestaña de inventario por defecto
showTab('inventory');
renderProductList();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=yosmani/kioscos" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>