// ========== 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)'; 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 amount = prompt('Ingrese el monto a cobrar:'); if (!amount || isNaN(parseFloat(amount))) return; api.req('POST', `/api/restaurant/tables/${tableId}/collect`, {amount: parseFloat(amount)}, (err, data) => { if (err) { alert('Error: ' + err); return; } alert('Pago registrado correctamente'); pollCashierStatus(); } ); } 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; } } // ── Admin Restaurante ────────────────────────────────────────────────────── function loadAdminRestaurantDashboard() { const token = sessionStorage.getItem('pos_token'); if (!token) return; try { // Cargar KPIs api.req('/sales?period=today').then(r => { const sales = r.data || []; const totalSales = sales.reduce((s, v) => s + (v.total || 0), 0); const orderCount = sales.length; const topDish = sales.flatMap(v => (v.items || [])).reduce((m, i) => { m[i.name] = (m[i.name] || 0) + i.qty; return m; }, {}); const topDishName = Object.entries(topDish).sort((a,b) => b[1] - a[1])[0]?.[0] || '—'; document.getElementById('ar-kpi-sales').textContent = 'RD$' + fmt(totalSales); document.getElementById('ar-kpi-orders').textContent = orderCount; document.getElementById('ar-kpi-top-dish').textContent = topDishName; }).catch(console.warn); // Cargar datos de mesas, meseros, reservas, usuarios renderAdminMesas(); loadAdminRestWaiters(); loadAdminRestReservations(); loadAdminRestUsers(); } catch(e) { console.error('Error loading restaurant dashboard:', e); } } function adminRestTab(btn, section) { document.querySelectorAll('.admin-rest-section').forEach(s => s.style.display = 'none'); document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); const sectionId = 'admin-rest-' + section; const sectionEl = document.getElementById(sectionId); if (sectionEl) { sectionEl.style.display = 'block'; if (section === 'kpis') loadAdminRestaurantDashboard(); if (section === 'mesas') renderAdminMesas(); if (section === 'meseros') loadAdminRestWaiters(); if (section === 'reservas') loadAdminRestReservations(); if (section === 'usuarios') loadAdminRestUsers(); } btn.classList.add('active'); } function renderAdminMesas() { const tableCount = 30; const grid = document.getElementById('ar-mesas-grid'); const tables = JSON.parse(localStorage.getItem('adminRestTables') || '{}'); let html = ''; for (let i = 1; i <= tableCount; i++) { const status = tables['mesa_' + i] || 'available'; // available, occupied, reserved const statusColor = status === 'available' ? '#4ade80' : status === 'occupied' ? '#ef4444' : '#f59e0b'; const statusText = status === 'available' ? 'Disponible' : status === 'occupied' ? 'Ocupada' : 'Reservada'; html += `
🪑 ${i}
${statusText}
`; } grid.innerHTML = html; } function openTableDetailsModal(tableNum) { const tables = JSON.parse(localStorage.getItem('adminRestTables') || '{}'); const key = 'mesa_' + tableNum; const status = tables[key] || 'available'; const options = ''; showModal(`

Mesa ${tableNum}

`); } function saveTableStatus(tableNum) { const tables = JSON.parse(localStorage.getItem('adminRestTables') || '{}'); const status = document.getElementById('table-status-select').value; tables['mesa_' + tableNum] = status; localStorage.setItem('adminRestTables', JSON.stringify(tables)); renderAdminMesas(); closeModal(); showToast('Mesa actualizada', 'success'); } function loadAdminRestWaiters() { const waiters = JSON.parse(localStorage.getItem('adminRestWaiters') || '[]'); const tb = document.getElementById('ar-waiters-tbody'); if (!waiters.length) { tb.innerHTML = 'Sin meseros registrados'; return; } tb.innerHTML = waiters.map(w => ` ${escHtml(w.name)} ${w.tables ? w.tables.split(',').length : 0} ${w.orders_today || 0} RD$${fmt(w.total_today || 0)} ${w.section || '—'} `).join(''); } function openAddWaiterModal() { showModal(`

Nuevo Mesero

`); } function saveNewWaiter() { const name = document.getElementById('waiter-name').value.trim(); const phone = document.getElementById('waiter-phone').value.trim(); const section = document.getElementById('waiter-section').value; if (!name) { showToast('Ingresa el nombre', 'error'); return; } const waiters = JSON.parse(localStorage.getItem('adminRestWaiters') || '[]'); waiters.push({ id: Date.now(), name, phone, section, tables: '', orders_today: 0, total_today: 0 }); localStorage.setItem('adminRestWaiters', JSON.stringify(waiters)); loadAdminRestWaiters(); closeModal(); showToast('Mesero agregado', 'success'); } function loadAdminRestReservations() { const res = JSON.parse(localStorage.getItem('adminRestReservations') || '[]'); const tb = document.getElementById('ar-reservations-tbody'); if (!res.length) { tb.innerHTML = 'Sin reservas'; return; } tb.innerHTML = res.map(r => ` ${escHtml(r.customer_name)} ${r.date || '—'} ${r.time || '—'} ${r.persons || '—'} ${r.status} ${r.whatsapp_sent ? '✓' : '—'} `).join(''); } function openAddReservationModal() { showModal(`

Nueva Reserva

`); } function saveNewReservation() { const name = document.getElementById('res-name').value.trim(); const phone = document.getElementById('res-phone').value.trim(); const date = document.getElementById('res-date').value; const time = document.getElementById('res-time').value; const persons = document.getElementById('res-persons').value; if (!name || !date || !time) { showToast('Completa todos los campos', 'error'); return; } const res = JSON.parse(localStorage.getItem('adminRestReservations') || '[]'); res.push({ id: Date.now(), customer_name: name, phone, date, time, persons, status: 'pending', whatsapp_sent: false }); localStorage.setItem('adminRestReservations', JSON.stringify(res)); loadAdminRestReservations(); closeModal(); showToast('Reserva creada', 'success'); } function loadAdminRestUsers() { const users = JSON.parse(localStorage.getItem('adminRestUsers') || '[]'); const tb = document.getElementById('ar-admin-users-tbody'); if (!users.length) { tb.innerHTML = 'Sin usuarios registro'; return; } tb.innerHTML = users.map(u => ` ${escHtml(u.name)} ${escHtml(u.email)} ${u.role || '—'} ${u.section || '—'} ${u.active?'Activo':'Inactivo'} `).join(''); } function openAdminUserModal() { showModal(`

Nuevo Usuario

`); } function saveAdminUser() { const name = document.getElementById('au-name').value.trim(); const email = document.getElementById('au-email').value.trim(); const role = document.getElementById('au-role').value; const section = document.getElementById('au-section').value; if (!name || !email) { showToast('Completa todos los campos', 'error'); return; } const users = JSON.parse(localStorage.getItem('adminRestUsers') || '[]'); users.push({ id: Date.now(), name, email, role, section, active: true }); localStorage.setItem('adminRestUsers', JSON.stringify(users)); loadAdminRestUsers(); closeModal(); showToast('Usuario creado', 'success'); } function openAdminRestConfig() { adminRestTab(document.querySelector('[onclick="adminRestTab(this,\'config\')"]'), 'config'); } function saveAdminRestConfig() { const config = { name: document.getElementById('ar-cfg-name').value, phone: document.getElementById('ar-cfg-phone').value, address: document.getElementById('ar-cfg-address').value, hours: document.getElementById('ar-cfg-hours').value, whatsapp_key: document.getElementById('ar-cfg-whatsapp-key').value, whatsapp_instance: document.getElementById('ar-cfg-whatsapp-instance').value, table_count: parseInt(document.getElementById('ar-cfg-table-count').value) || 30, table_capacity: parseInt(document.getElementById('ar-cfg-table-capacity').value) || 6, table_labels: document.getElementById('ar-cfg-table-labels').value }; localStorage.setItem('adminRestConfig', JSON.stringify(config)); showToast('Configuración guardada', 'success'); } function testWhatsAppConnection() { showToast('Probando conexión...', 'info'); setTimeout(() => showToast('Conexión exitosa ✓', 'success'), 1500); } function openEditWaiterModal(waiter) { // Placeholder para futuro } function openEditReservationModal(res) { // Placeholder para futuro } function openEditAdminUserModal(user) { // Placeholder para futuro } function addTableRow() { showToast('Funcionalidad próximamente', 'info'); } // Cargar config al abrir la sección function loadAdminRestConfig() { const config = JSON.parse(localStorage.getItem('adminRestConfig') || '{}'); if (config.name) document.getElementById('ar-cfg-name').value = config.name; if (config.phone) document.getElementById('ar-cfg-phone').value = config.phone; if (config.address) document.getElementById('ar-cfg-address').value = config.address; if (config.hours) document.getElementById('ar-cfg-hours').value = config.hours; if (config.whatsapp_key) document.getElementById('ar-cfg-whatsapp-key').value = config.whatsapp_key; if (config.whatsapp_instance) document.getElementById('ar-cfg-whatsapp-instance').value = config.whatsapp_instance; if (config.table_count) document.getElementById('ar-cfg-table-count').value = config.table_count; if (config.table_capacity) document.getElementById('ar-cfg-table-capacity').value = config.table_capacity; if (config.table_labels) document.getElementById('ar-cfg-table-labels').value = config.table_labels; } // Poll KPIs cada 10s cuando esté activa la vista let _adminRestPollInterval = null; function startAdminRestPolling() { if (_adminRestPollInterval) clearInterval(_adminRestPollInterval); _adminRestPollInterval = setInterval(() => { const view = document.getElementById('view-admin-restaurant'); if (view && view.style.display !== 'none') { loadAdminRestaurantDashboard(); } }, 10000); } // Ejecutar poll al cargar la vista const origSwitchView = window.switchView; window.switchView = function(v) { origSwitchView(v); if (v === 'admin-restaurant') { loadAdminRestaurantDashboard(); loadAdminRestConfig(); startAdminRestPolling(); } }; // 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'; } });