Mesa
Mesero
📋 Items
TOTAL CUENTA RD$0.00
💳 Método de Pago
Summer Beach
Restaurant & Bar
// ========== CASHIER DASHBOARD FUNCTIONS ========== let _cashierPollingInterval = null; let _cashierAutoRefreshInterval = null; function showCashierTab() { const tab = document.getElementById('nav-cashier'); if (!user || user.role !== 'cashier') { if (tab) tab.style.display = 'none'; } else { if (tab) tab.style.display = 'flex'; } } function pollCashierStatus() { if (!user || (user.role !== 'cashier' && user.role !== 'admin')) return; // Obtener totales del día api.req('GET', '/api/sales?date=' + new Date().toISOString().split('T')[0], {}, (err, data) => { if (err) return; let cash = 0, card = 0, transfer = 0, total = 0; (data || []).forEach(sale => { const amount = sale.total || 0; if (sale.payment_method === 'cash') cash += amount; else if (sale.payment_method === 'card') card += amount; else if (sale.payment_method === 'transfer') transfer += amount; total += amount; }); document.getElementById('total-cash').textContent = '$' + cash.toFixed(2); document.getElementById('total-card').textContent = '$' + card.toFixed(2); document.getElementById('total-transfer').textContent = '$' + transfer.toFixed(2); document.getElementById('total-general').textContent = '$' + total.toFixed(2); document.getElementById('expected-amount').textContent = '$' + total.toFixed(2); }); // Mesas pendientes (si es restaurante) loadPendingTables(); // Historial de sesiones loadCashierSessions(); } function loadPendingTables() { api.req('GET', '/api/restaurant/tables?status=open', {}, (err, tables) => { if (err || !tables || tables.length === 0) { document.getElementById('pending-tables-card').style.display = 'none'; return; } document.getElementById('pending-tables-card').style.display = 'block'; const tbody = document.getElementById('pending-tables-tbody'); tbody.innerHTML = ''; tables.forEach(table => { const row = document.createElement('tr'); row.style.borderBottom = '1px solid var(--border)'; row.innerHTML = ` ${table.table_number || 'N/A'} ${table.waiter_name || 'Sin asignar'} $${(table.total || 0).toFixed(2)} ${table.payment_method || 'Pendiente'} `; tbody.appendChild(row); }); }); } function loadCashierSessions() { api.req('GET', '/api/cash-sessions?limit=10', {}, (err, sessions) => { if (err || !sessions) return; const tbody = document.getElementById('sessions-history-tbody'); tbody.innerHTML = ''; sessions.forEach(session => { const date = new Date(session.opened_at); const difference = (session.counted_amount || 0) - (session.total || 0); const differenceClass = difference === 0 ? 'green' : (difference > 0 ? 'blue' : 'red'); const row = document.createElement('tr'); row.style.borderBottom = '1px solid var(--border)'; const colorHex = differenceClass === 'green' ? '10b981' : (differenceClass === 'blue' ? '3b82f6' : 'ef4444'); row.innerHTML = ` ${date.toLocaleString('es-DO')} $${(session.opening_amount || 0).toFixed(2)} $${(session.total || 0).toFixed(2)} $${difference.toFixed(2)} `; tbody.appendChild(row); }); }); } function collectPaymentFromTable(tableId) { const token = sessionStorage.getItem('pos_token'); if (!token) { alert('No hay sesión'); return; } const payMethod = prompt('Método de pago:\ncash\ncard\ntransfer', 'cash'); if (!payMethod) return; fetch(`/waiter-api.php?action=mesa-profile&id=${tableId}`, { headers: {'Authorization': 'Bearer ' + token} }) .then(r => r.json()) .then(data => { const order = (data.orders || [])[0]; if (!order) { alert('❌ Sin orden activa'); return; } fetch(`/api/restaurant/orders/${order.id}/close`, { method: 'POST', headers: {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}, body: JSON.stringify({payment_method: payMethod}) }) .then(r => r.json()) .then(res => { if (res.error) { alert('❌ ' + res.error); } else { alert('✅ Venta #' + res.sale_id + ' creada'); renderAdminMesas(); } }); }); } function showPaymentReceiptModal(data) { const { orderId, saleId, total, paymentMethod, tableId, onClose } = data; // Obtener venta y mostrar recibo del POS fetch(`/api/sales/${saleId}`, { headers: {'Authorization': 'Bearer ' + sessionStorage.getItem('pos_token')} }) .then(r => r.json()) .then(result => { if (result.data) { showReceipt(result.data); setTimeout(() => onClose(), 500); } else { alert('❌ Error obteniendo venta'); onClose(); } }) .catch(err => { console.error('Error:', err); alert('❌ Error: ' + err.message); onClose(); }); } function closeCashierSessionModal() { const countedAmount = parseFloat(document.getElementById('counted-amount').value || 0); const expectedAmount = parseFloat(document.getElementById('expected-amount').textContent.replace('$', '') || 0); const notes = document.getElementById('close-notes').value || ''; if (countedAmount === 0) { alert('Ingrese el monto contado'); return; } if (!confirm(`Cerrar caja con diferencia de $${(countedAmount - expectedAmount).toFixed(2)}?`)) { return; } api.req('POST', '/api/cash-sessions/close', { counted_amount: countedAmount, notes: notes }, (err, data) => { if (err) { alert('Error: ' + err); return; } alert('Caja cerrada correctamente'); document.getElementById('counted-amount').value = ''; document.getElementById('close-notes').value = ''; pollCashierStatus(); } ); } // Evento para calcular diferencia al ingresar monto document.addEventListener('DOMContentLoaded', () => { const countedInput = document.getElementById('counted-amount'); if (countedInput) { countedInput.addEventListener('input', (e) => { const counted = parseFloat(e.target.value || 0); const expected = parseFloat(document.getElementById('expected-amount').textContent.replace('$', '') || 0); const difference = counted - expected; const indicator = document.getElementById('difference-indicator'); document.getElementById('amount-difference').textContent = '$' + difference.toFixed(2); if (difference === 0) { indicator.style.background = 'rgba(16,185,129,0.1)'; indicator.style.color = '#10b981'; indicator.textContent = 'Cuadra perfecto'; } else if (difference > 0) { indicator.style.background = 'rgba(59,130,246,0.1)'; indicator.style.color = '#3b82f6'; indicator.textContent = `Sobrante: $${difference.toFixed(2)}`; } else { indicator.style.background = 'rgba(239,68,68,0.1)'; indicator.style.color = '#ef4444'; indicator.textContent = `Faltante: $${Math.abs(difference).toFixed(2)}`; } }); } }); function startCashierAutoRefresh() { stopCashierAutoRefresh(); _cashierAutoRefreshInterval = setInterval(() => { if (currentViewName === 'cashier') { pollCashierStatus(); } }, 5000); // Actualizar cada 5 segundos } function stopCashierAutoRefresh() { if (_cashierAutoRefreshInterval) { clearInterval(_cashierAutoRefreshInterval); _cashierAutoRefreshInterval = null; } } // AUTO-LOGIN: Si hay sesión guardada, mostrar app automáticamente window.addEventListener('DOMContentLoaded', async () => { console.log('🔍 Verificando sesión guardada...'); // Agregar event listener al botón de login const loginBtn = document.getElementById('login-btn'); if (loginBtn) { loginBtn.addEventListener('click', doLogin); loginBtn.addEventListener('touchend', (e) => { e.preventDefault(); doLogin(); }); } // También agregar Enter en campos de password const passInput = document.getElementById('login-pass'); if (passInput) { passInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') doLogin(); }); } if (token && currentUser) { console.log('✅ Sesión encontrada. Usuario:', currentUser.name); try { await showApp(); console.log('✅ App cargada automáticamente'); } catch(err) { console.error('❌ Error cargando app:', err); // Limpiar sesión y volver al login token = null; currentUser = null; sessionStorage.clear(); document.getElementById('login-screen').style.display = 'flex'; document.getElementById('app').style.display = 'none'; } } else { console.log('ℹ️ Sin sesión. Mostrando login.'); document.getElementById('login-screen').style.display = 'flex'; document.getElementById('app').style.display = 'none'; } });