// /home/Autopartes/pos/static/js/reports.js // Reports module: sales reports, inventory reports, financial reports const Reports = (() => { function token() { return localStorage.getItem('pos_token') || ''; } function checkAuth() { if (!token()) { window.location.href = '/pos/login'; return false; } return true; } function headers() { return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' }; } function fmt(n) { return parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function fmtInt(n) { return parseInt(n || 0).toLocaleString('es-MX'); } function fmtDate(s) { if (!s) return '--'; var d = new Date(s); if (isNaN(d)) return s; return d.toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' }); } function fmtDateTime(s) { if (!s) return '--'; var d = new Date(s); if (isNaN(d)) return s; return d.toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' }) + ' ' + d.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }); } function spinner() { return '
Cargando...
'; } function emptyMsg(text) { return '
' + text + '
'; } function errorMsg(text) { return '
' + text + '
'; } // Track which tabs have been loaded var loaded = { ventas: false, inventario: false, clientes: false, financieros: false, historico: false }; // ------------------------------------------------------------------------- // Theme switcher // ------------------------------------------------------------------------- function setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); try { localStorage.setItem('nexus-theme', theme); } catch(e) {} var btnInd = document.getElementById('btn-industrial'); var btnMod = document.getElementById('btn-modern'); if (btnInd) btnInd.classList.toggle('is-active', theme === 'industrial'); if (btnMod) btnMod.classList.toggle('is-active', theme === 'modern'); } window.setTheme = setTheme; // ------------------------------------------------------------------------- // Tab switcher with lazy loading // ------------------------------------------------------------------------- function switchTab(id, btn) { document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('is-active'); }); document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('is-active'); }); var panel = document.getElementById('panel-' + id); if (panel) panel.classList.add('is-active'); if (btn) btn.classList.add('is-active'); // Lazy load on first visit if (!loaded[id]) { if (id === 'ventas') loadVentas(); else if (id === 'inventario') loadInventario(); else if (id === 'clientes') loadClientes(); else if (id === 'financieros') loadFinancieros(); else if (id === 'historico') loadHistorico(); } } window.switchTab = switchTab; // ------------------------------------------------------------------------- // Live clock // ------------------------------------------------------------------------- function updateClock() { var el = document.getElementById('live-clock'); if (!el) return; var now = new Date(); var pad = function(n) { return String(n).padStart(2, '0'); }; el.textContent = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds()); } // ------------------------------------------------------------------------- // Generic fetch helper // ------------------------------------------------------------------------- async function apiFetch(url) { var resp = await fetch(url, { headers: headers() }); if (!resp.ok) throw new Error('HTTP ' + resp.status); return resp.json(); } // ------------------------------------------------------------------------- // KPI card builder // ------------------------------------------------------------------------- function kpiCard(label, value, sub) { return '
' + '
' + label + '
' + '
' + value + '
' + (sub ? '
' + sub + '
' : '') + '
'; } // ========================================================================= // TAB 1: VENTAS // ========================================================================= async function loadVentas() { loaded.ventas = true; var dateFrom = document.getElementById('ventas-date-from').value; var dateTo = document.getElementById('ventas-date-to').value; var params = new URLSearchParams(); if (dateFrom) params.set('date_from', dateFrom); if (dateTo) params.set('date_to', dateTo); params.set('per_page', '200'); // Show spinners var kpiEl = document.getElementById('ventas-kpis'); var barEl = document.getElementById('ventas-bar-chart'); var vendedorEl = document.getElementById('ventas-por-vendedor'); var metodoEl = document.getElementById('ventas-por-metodo'); var detalleEl = document.getElementById('ventas-detalle'); kpiEl.innerHTML = spinner(); barEl.innerHTML = ''; vendedorEl.innerHTML = spinner(); metodoEl.innerHTML = spinner(); detalleEl.innerHTML = spinner(); try { // Fetch all pages to get complete data for the period var allSales = []; var page = 1; var totalPages = 1; while (page <= totalPages) { params.set('page', page); var json = await apiFetch('/pos/api/sales?' + params.toString()); allSales = allSales.concat(json.data || []); totalPages = json.pagination ? json.pagination.total_pages : 1; page++; if (page > 50) break; // safety limit } var sales = allSales.filter(function(s) { return s.status === 'completed'; }); // KPIs var totalVentas = sales.reduce(function(a, s) { return a + s.total; }, 0); var numVentas = sales.length; var ticketProm = numVentas > 0 ? totalVentas / numVentas : 0; kpiEl.innerHTML = kpiCard('Total Ventas', '$' + fmt(totalVentas), numVentas + ' transacciones') + kpiCard('Ticket Promedio', '$' + fmt(ticketProm), '') + kpiCard('Transacciones', fmtInt(numVentas), '') + kpiCard('Descuentos', '$' + fmt(sales.reduce(function(a, s) { return a + s.discount_total; }, 0)), ''); // Bar chart: sales by day var byDay = {}; var dayNames = ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab']; sales.forEach(function(s) { var d = s.created_at.substring(0, 10); byDay[d] = (byDay[d] || 0) + s.total; }); var days = Object.keys(byDay).sort().slice(-7); var maxDay = Math.max.apply(null, days.map(function(d) { return byDay[d]; })) || 1; if (days.length > 0) { var barHtml = '
Ventas por Dia
'; days.forEach(function(d) { var val = byDay[d]; var pct = Math.round(val / maxDay * 100); var label = dayNames[new Date(d + 'T12:00:00').getDay()]; var valStr = val >= 1000 ? '$' + (val / 1000).toFixed(1) + 'k' : '$' + fmt(val); barHtml += '
' + '
' + '' + valStr + '
' + '
' + label + '
'; }); barHtml += '
'; barEl.innerHTML = barHtml; } // Ventas por vendedor var byEmployee = {}; sales.forEach(function(s) { var key = s.employee_id || 0; if (!byEmployee[key]) { byEmployee[key] = { name: s.employee_name || 'Sin asignar', count: 0, total: 0 }; } byEmployee[key].count++; byEmployee[key].total += s.total; }); var empList = Object.values(byEmployee).sort(function(a, b) { return b.total - a.total; }); var empHtml = '
Ventas por Vendedor
'; empHtml += '
' + '' + ''; empList.forEach(function(e) { var initials = e.name.split(' ').map(function(w) { return w[0]; }).join('').substring(0, 2).toUpperCase(); empHtml += '' + '' + '' + ''; }); empHtml += '
Vendedor# VentasTotalTicket Prom.
' + '
' + initials + '
' + '' + e.name + '
' + e.count + '$' + fmt(e.total) + '$' + fmt(e.count > 0 ? e.total / e.count : 0) + '
'; vendedorEl.innerHTML = empList.length ? empHtml : emptyMsg('Sin datos de vendedores'); // Ventas por metodo de pago var byMethod = {}; sales.forEach(function(s) { var m = s.payment_method || 'Otro'; byMethod[m] = (byMethod[m] || 0) + s.total; }); var methods = Object.entries(byMethod).sort(function(a, b) { return b[1] - a[1]; }); var maxMethod = methods.length > 0 ? methods[0][1] : 1; var methodLabels = { 'cash': 'Efectivo', 'card': 'Tarjeta', 'transfer': 'Transferencia', 'credit': 'Credito', 'mixed': 'Mixto' }; var barColors = ['', 'pay-method__bar--b', 'pay-method__bar--c', 'pay-method__bar--d']; var metHtml = '
Ventas por Metodo de Pago
'; metHtml += '
'; methods.forEach(function(m, idx) { var pct = totalVentas > 0 ? Math.round(m[1] / totalVentas * 100) : 0; var label = methodLabels[m[0]] || m[0]; metHtml += '
' + label + '' + '
' + '$' + fmt(m[1]) + ' ' + pct + '%
'; }); metHtml += '
'; metodoEl.innerHTML = methods.length ? metHtml : emptyMsg('Sin datos de metodos'); // Sales detail table var dtlHtml = '
Detalle de Ventas' + '' + allSales.length + ' registros
'; dtlHtml += '
' + '' + '' + '' + ''; allSales.slice(0, 100).forEach(function(s) { var statusPill = s.status === 'completed' ? 'pill--success' : s.status === 'cancelled' ? 'pill--error' : 'pill--warning'; var statusLabel = s.status === 'completed' ? 'Completada' : s.status === 'cancelled' ? 'Cancelada' : s.status; dtlHtml += '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; }); dtlHtml += '
#FechaVendedorClientePagoSubtotalDesc.IVATotalEstado
' + s.id + '' + fmtDateTime(s.created_at) + '' + (s.employee_name || '--') + '' + (s.customer_name || 'Mostrador') + '' + (methodLabels[s.payment_method] || s.payment_method || '--') + '$' + fmt(s.subtotal) + '$' + fmt(s.discount_total) + '$' + fmt(s.tax_total) + '$' + fmt(s.total) + '' + statusLabel + '
'; detalleEl.innerHTML = dtlHtml; } catch (err) { kpiEl.innerHTML = errorMsg('Error cargando ventas: ' + err.message); vendedorEl.innerHTML = ''; metodoEl.innerHTML = ''; detalleEl.innerHTML = ''; } } // ========================================================================= // TAB 5: HISTÓRICO // ========================================================================= async function loadHistorico() { loaded.historico = true; var dateFrom = document.getElementById('historico-date-from').value; var dateTo = document.getElementById('historico-date-to').value; var customer = document.getElementById('historico-customer').value.trim(); var params = new URLSearchParams(); if (dateFrom) params.set('date_from', dateFrom); if (dateTo) params.set('date_to', dateTo); if (customer) params.set('customer', customer); params.set('per_page', '200'); var kpiEl = document.getElementById('historico-kpis'); var detalleEl = document.getElementById('historico-detalle'); kpiEl.innerHTML = spinner(); detalleEl.innerHTML = spinner(); try { var allRows = []; var page = 1; var totalPages = 1; while (page <= totalPages) { params.set('page', page); var json = await apiFetch('/pos/api/historical-sales?' + params.toString()); allRows = allRows.concat(json.data || []); totalPages = json.pagination ? json.pagination.total_pages : 1; page++; if (page > 50) break; } var total = allRows.reduce(function(a, r) { return a + r.total; }, 0); var subtotal = allRows.reduce(function(a, r) { return a + r.subtotal; }, 0); var balance = allRows.reduce(function(a, r) { return a + r.balance; }, 0); kpiEl.innerHTML = kpiCard('Total Histórico', '$' + fmt(total), allRows.length + ' registros') + kpiCard('Subtotal', '$' + fmt(subtotal), '') + kpiCard('Saldo Pendiente', '$' + fmt(balance), '') + kpiCard('Tickets', fmtInt(allRows.length), ''); var html = '
Ventas Históricas Importadas' + '' + allRows.length + ' registros
'; html += '
' + '' + '' + '' + ''; allRows.slice(0, 200).forEach(function(r) { html += '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; }); html += '
FechaDocumentoClientePagoSubtotalTotalPagadoSaldo
' + fmtDate(r.sale_date) + '' + esc(r.document_no || r.external_document_id || '--') + '' + esc(r.customer_name || '--') + '' + esc(r.payment_method || '--') + '$' + fmt(r.subtotal) + '$' + fmt(r.total) + '$' + fmt(r.amount_paid) + '$' + fmt(r.balance) + '
'; detalleEl.innerHTML = html; } catch (err) { kpiEl.innerHTML = errorMsg('Error cargando histórico: ' + err.message); detalleEl.innerHTML = ''; } } function esc(s) { if (s == null) return ''; return String(s).replace(/[&<>"']/g, function(c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } // ========================================================================= // TAB 2: INVENTARIO // ========================================================================= async function loadInventario() { loaded.inventario = true; var kpiEl = document.getElementById('inventario-kpis'); var valEl = document.getElementById('inventario-valorizacion'); var abcEl = document.getElementById('inventario-abc'); var lowEl = document.getElementById('inventario-low-stock'); var noMoveEl = document.getElementById('inventario-no-movement'); kpiEl.innerHTML = spinner(); valEl.innerHTML = spinner(); abcEl.innerHTML = spinner(); lowEl.innerHTML = spinner(); noMoveEl.innerHTML = spinner(); try { var [valData, abcData, lowData, noMoveData] = await Promise.all([ apiFetch('/pos/api/inventory/reports/valuation'), apiFetch('/pos/api/inventory/reports/abc'), apiFetch('/pos/api/inventory/reports/low-stock'), apiFetch('/pos/api/inventory/reports/no-movement') ]); // KPIs kpiEl.innerHTML = kpiCard('Valor Total Inventario', '$' + fmt(valData.grand_total), fmtInt(valData.item_count) + ' SKUs activos') + kpiCard('Clasificacion A', fmtInt(abcData.summary.A) + ' SKUs', '80% del volumen de ventas') + kpiCard('Stock Bajo', fmtInt(lowData.count) + ' productos', 'debajo del minimo') + kpiCard('Sin Movimiento', fmtInt(noMoveData.count) + ' productos', '>' + noMoveData.days_threshold + ' dias'); // Valuation table (top 20) var vItems = (valData.data || []).slice(0, 20); var vHtml = '
Inventario Valorizado' + 'Top 20 de ' + fmtInt(valData.item_count) + '
'; vHtml += '
' + '' + '' + ''; vItems.forEach(function(i) { vHtml += '' + '' + '' + '' + '' + ''; }); vHtml += '
ProductoNo. ParteMarcaStockCosto Unit.Valor
' + i.name + '' + (i.part_number || '--') + '' + (i.brand || '--') + '' + fmtInt(i.stock) + '$' + fmt(i.cost) + '$' + fmt(i.value) + '
'; valEl.innerHTML = vHtml; // ABC table var abcItems = (abcData.data || []).slice(0, 30); var abcHtml = '
Clasificacion ABC de Inventario' + 'A: ' + abcData.summary.A + ' ' + 'B: ' + abcData.summary.B + ' ' + 'C: ' + abcData.summary.C + '
'; abcHtml += '
' + '' + '' + ''; abcItems.forEach(function(i) { var clsPill = i.classification === 'A' ? 'pill--success' : i.classification === 'B' ? 'pill--warning' : 'pill--muted'; abcHtml += '' + '' + '' + '' + '' + ''; }); abcHtml += '
ProductoNo. ParteMarcaVol. Ventas% Acum.Clase
' + i.name + '' + (i.part_number || '--') + '' + (i.brand || '--') + '' + fmtInt(i.sales_volume) + '' + i.cumulative_pct + '%' + i.classification + '
'; abcEl.innerHTML = abcHtml; // Low stock var lowItems = lowData.data || []; var lowHtml = '
Productos con Stock Bajo' + '' + lowData.count + ' productos
'; lowHtml += '
' + '' + '' + ''; lowItems.slice(0, 30).forEach(function(i) { var stockColor = i.stock <= 0 ? 'color:var(--color-error)' : i.stock < i.min_stock / 2 ? 'color:var(--color-error)' : 'color:var(--color-warning)'; lowHtml += '' + '' + '' + '' + '' + ''; }); lowHtml += '
ProductoNo. ParteMarcaStockMinimoDeficit
' + i.name + '' + (i.part_number || '--') + '' + (i.brand || '--') + '' + fmtInt(i.stock) + '' + fmtInt(i.min_stock) + '' + fmtInt(i.deficit) + '
'; lowEl.innerHTML = lowItems.length ? lowHtml : emptyMsg('No hay productos con stock bajo'); // No movement var noItems = noMoveData.data || []; var noHtml = '
Productos Sin Movimiento (>' + noMoveData.days_threshold + ' dias)' + '' + noMoveData.count + ' SKUs
'; noHtml += '
' + '' + '' + ''; noItems.slice(0, 30).forEach(function(i) { noHtml += '' + '' + '' + '' + '' + ''; }); noHtml += '
ProductoNo. ParteMarcaStockCosto Unit.Ultimo Movimiento
' + i.name + '' + (i.part_number || '--') + '' + (i.brand || '--') + '' + fmtInt(i.stock) + '$' + fmt(i.cost) + '' + fmtDate(i.last_movement) + '
'; noMoveEl.innerHTML = noItems.length ? noHtml : emptyMsg('No hay productos sin movimiento'); } catch (err) { kpiEl.innerHTML = errorMsg('Error cargando inventario: ' + err.message); valEl.innerHTML = ''; abcEl.innerHTML = ''; lowEl.innerHTML = ''; noMoveEl.innerHTML = ''; } } // ========================================================================= // TAB 3: CLIENTES (Aging report) // ========================================================================= async function loadClientes() { loaded.clientes = true; var kpiEl = document.getElementById('clientes-kpis'); var agingEl = document.getElementById('clientes-aging'); kpiEl.innerHTML = spinner(); agingEl.innerHTML = spinner(); try { var data = await apiFetch('/pos/api/accounting/aging'); var clients = data.data || []; var totals = data.totals || {}; // KPIs kpiEl.innerHTML = kpiCard('Clientes con Credito', fmtInt(clients.length), 'con saldo pendiente') + kpiCard('Saldo Total', '$' + fmt(totals.total), '') + kpiCard('Corriente', '$' + fmt(totals.corriente), 'no vencido') + kpiCard('Vencido >90 dias', '$' + fmt(totals.d90_plus), totals.d90_plus > 0 ? 'requiere atencion' : ''); // Aging table var html = '
Antiguedad de Saldos' + '' + clients.length + ' clientes
'; html += '
' + '' + '' + '' + '' + ''; clients.forEach(function(c) { html += '' + '' + '' + '' + '' + '' + '' + ''; }); // Totals row html += '' + '' + '' + '' + '' + '' + '' + ''; html += '
ClienteRFCCorriente1-30 dias31-60 dias61-90 dias90+ diasTotal
' + c.name + '' + (c.rfc || '--') + '$' + fmt(c.corriente) + '$' + fmt(c.d1_30) + ' 0 ? ' style="color:var(--color-warning)"' : '') + '>$' + fmt(c.d31_60) + ' 0 ? ' style="color:var(--color-error)"' : '') + '>$' + fmt(c.d61_90) + ' 0 ? ' style="color:var(--color-error);font-weight:700"' : '') + '>$' + fmt(c.d90_plus) + '$' + fmt(c.total) + '
TOTAL$' + fmt(totals.corriente) + '$' + fmt(totals.d1_30) + '$' + fmt(totals.d31_60) + '$' + fmt(totals.d61_90) + '$' + fmt(totals.d90_plus) + '$' + fmt(totals.total) + '
'; agingEl.innerHTML = clients.length ? html : emptyMsg('No hay saldos pendientes de credito'); } catch (err) { kpiEl.innerHTML = errorMsg('Error cargando datos de clientes: ' + err.message); agingEl.innerHTML = ''; } } // ========================================================================= // TAB 4: FINANCIEROS // ========================================================================= async function loadFinancieros() { loaded.financieros = true; var monthSel = document.getElementById('fin-month'); var yearSel = document.getElementById('fin-year'); var year = parseInt(yearSel.value); var month = parseInt(monthSel.value); var kpiEl = document.getElementById('financieros-kpis'); var incomeEl = document.getElementById('financieros-income'); var balanceEl = document.getElementById('financieros-balance'); var trialEl = document.getElementById('financieros-trial'); var cortesEl = document.getElementById('financieros-cortes'); kpiEl.innerHTML = spinner(); incomeEl.innerHTML = spinner(); balanceEl.innerHTML = spinner(); trialEl.innerHTML = spinner(); cortesEl.innerHTML = spinner(); try { var [incData, balData, trialData, cortesData] = await Promise.all([ apiFetch('/pos/api/accounting/income-statement?year=' + year + '&month=' + month), apiFetch('/pos/api/accounting/balance-sheet'), apiFetch('/pos/api/accounting/trial-balance?year=' + year + '&month=' + month), apiFetch('/pos/api/register/history?per_page=50') ]); // KPIs from income statement kpiEl.innerHTML = kpiCard('Ingresos', '$' + fmt(incData.ingresos.total), 'periodo ' + month + '/' + year) + kpiCard('Costos', '$' + fmt(incData.costos.total), '') + kpiCard('Utilidad Bruta', '$' + fmt(incData.utilidad_bruta), '') + kpiCard('Utilidad Neta', '$' + fmt(incData.utilidad_neta), incData.ingresos.total > 0 ? 'Margen: ' + (incData.utilidad_neta / incData.ingresos.total * 100).toFixed(1) + '%' : ''); // Income statement var iHtml = '
Estado de Resultados' + '' + month + '/' + year + '
'; iHtml += '
' + ''; // Ingresos section iHtml += ''; (incData.ingresos.items || []).forEach(function(i) { iHtml += '' + '' + ''; }); iHtml += '' + ''; // Costos section iHtml += ''; (incData.costos.items || []).forEach(function(i) { iHtml += '' + '' + ''; }); iHtml += '' + ''; // Utilidad bruta iHtml += '' + ''; // Gastos section iHtml += ''; (incData.gastos.items || []).forEach(function(i) { iHtml += '' + '' + ''; }); iHtml += '' + ''; // Utilidad neta var netColor = incData.utilidad_neta >= 0 ? 'var(--color-success)' : 'var(--color-error)'; iHtml += '' + ''; iHtml += '
CuentaCodigoMonto
INGRESOS
' + i.name + '' + i.code + '$' + fmt(i.amount) + '
Total Ingresos$' + fmt(incData.ingresos.total) + '
COSTOS
' + i.name + '' + i.code + '$' + fmt(i.amount) + '
Total Costos$' + fmt(incData.costos.total) + '
UTILIDAD BRUTA$' + fmt(incData.utilidad_bruta) + '
GASTOS
' + i.name + '' + i.code + '$' + fmt(i.amount) + '
Total Gastos$' + fmt(incData.gastos.total) + '
UTILIDAD NETA$' + fmt(incData.utilidad_neta) + '
'; incomeEl.innerHTML = iHtml; // Balance sheet var bHtml = '
Balance General' + '' + (balData.balanced ? 'Cuadrado' : 'Descuadrado') + '
'; bHtml += '
' + ''; // Activo bHtml += ''; (balData.activo.items || []).forEach(function(i) { bHtml += '' + '' + ''; }); bHtml += '' + ''; // Pasivo bHtml += ''; (balData.pasivo.items || []).forEach(function(i) { bHtml += '' + '' + ''; }); bHtml += '' + ''; // Capital bHtml += ''; (balData.capital.items || []).forEach(function(i) { bHtml += '' + '' + ''; }); bHtml += '' + ''; bHtml += '' + ''; bHtml += '
CuentaCodigoSaldo
ACTIVO
' + i.name + '' + i.code + '$' + fmt(i.balance) + '
Total Activo$' + fmt(balData.activo.total) + '
PASIVO
' + i.name + '' + i.code + '$' + fmt(i.balance) + '
Total Pasivo$' + fmt(balData.pasivo.total) + '
CAPITAL
' + i.name + '' + i.code + '$' + fmt(i.balance) + '
Total Capital$' + fmt(balData.capital.total) + '
Pasivo + Capital$' + fmt(balData.pasivo.total + balData.capital.total) + '
'; balanceEl.innerHTML = bHtml; // Trial balance var tRows = trialData.data || []; var tHtml = '
Balanza de Comprobacion' + '' + month + '/' + year + '
'; tHtml += '
' + '' + '' + '' + ''; tRows.forEach(function(r) { tHtml += '' + '' + '' + '' + '' + '' + ''; }); tHtml += '
CodigoCuentaTipoSaldo InicialCargosAbonosSaldo Final
' + r.code + '' + r.name + '' + r.type + '$' + fmt(r.saldo_inicial) + '$' + fmt(r.cargos) + '$' + fmt(r.abonos) + '$' + fmt(r.saldo_final) + '
'; trialEl.innerHTML = tRows.length ? tHtml : emptyMsg('Sin movimientos contables en este periodo'); // Cortes de caja var regs = cortesData.data || []; var cHtml = '
Cortes de Caja' + '' + (cortesData.pagination ? cortesData.pagination.total : regs.length) + ' cortes
'; cHtml += '
' + '' + '' + '' + ''; regs.forEach(function(r) { var diffColor = r.difference < 0 ? 'color:var(--color-error)' : r.difference > 0 ? 'color:var(--color-warning)' : 'color:var(--color-success)'; cHtml += '' + '' + '' + '' + '' + '' + '' + ''; }); cHtml += '
CajaEmpleadoAperturaCierreMonto AperturaEsperadoCierre RealDiferencia
#' + r.register_number + '' + (r.employee_name || '--') + '' + fmtDateTime(r.opened_at) + '' + fmtDateTime(r.closed_at) + '$' + fmt(r.opening_amount) + '$' + fmt(r.expected_amount) + '$' + fmt(r.closing_amount) + '$' + fmt(r.difference) + '
'; cortesEl.innerHTML = regs.length ? cHtml : emptyMsg('No hay cortes de caja registrados'); } catch (err) { kpiEl.innerHTML = errorMsg('Error cargando reportes financieros: ' + err.message); incomeEl.innerHTML = ''; balanceEl.innerHTML = ''; trialEl.innerHTML = ''; cortesEl.innerHTML = ''; } } // ------------------------------------------------------------------------- // Init // ------------------------------------------------------------------------- function init() { if (!checkAuth()) return; // Restore theme try { var saved = localStorage.getItem('nexus-theme') || 'industrial'; setTheme(saved); } catch(e) {} // Start clock updateClock(); setInterval(updateClock, 1000); // Set default date range: first day of current month to today var now = new Date(); var firstDay = new Date(now.getFullYear(), now.getMonth(), 1); var fromEl = document.getElementById('ventas-date-from'); var toEl = document.getElementById('ventas-date-to'); if (fromEl) fromEl.value = firstDay.toISOString().substring(0, 10); if (toEl) toEl.value = now.toISOString().substring(0, 10); // Populate financial period selectors var monthSel = document.getElementById('fin-month'); var yearSel = document.getElementById('fin-year'); if (monthSel) { var monthNames = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; for (var m = 0; m < 12; m++) { var opt = document.createElement('option'); opt.value = m + 1; opt.textContent = monthNames[m]; if (m === now.getMonth()) opt.selected = true; monthSel.appendChild(opt); } } if (yearSel) { for (var y = now.getFullYear(); y >= now.getFullYear() - 3; y--) { var opt = document.createElement('option'); opt.value = y; opt.textContent = y; yearSel.appendChild(opt); } } // Load the default active tab (ventas) loadVentas(); } document.addEventListener('DOMContentLoaded', init); return { init, setTheme, switchTab, loadVentas, loadInventario, loadClientes, loadFinancieros, loadHistorico, fmt }; // Register Cmd+K items if (typeof registerCmdKItem === "function") { registerCmdKItem({ group: "Principal", label: "Reportes", href: "/pos/reports", icon: "📈" }); registerCmdKItem({ group: "Principal", label: "Dashboard", href: "/pos/dashboard", icon: "📊" }); registerCmdKItem({ group: "Principal", label: "POS Ventas", href: "/pos/sale", icon: "🛒" }); } })(); // ── Global: Export visible table as CSV (Excel-compatible) ── function exportReportCSV() { var tables = document.querySelectorAll('table'); // Find the first visible table var table = null; for (var i = 0; i < tables.length; i++) { var t = tables[i]; if (t.offsetParent !== null && t.querySelector('tbody tr')) { table = t; break; } } if (!table) { alert('No hay tabla de datos para exportar en esta vista.'); return; } var rows = []; var ths = table.querySelectorAll('thead th'); if (ths.length) { rows.push(Array.from(ths).map(function(th) { return '"' + th.textContent.trim().replace(/"/g, '""') + '"'; }).join(',')); } table.querySelectorAll('tbody tr').forEach(function(tr) { var cells = tr.querySelectorAll('td'); rows.push(Array.from(cells).map(function(td) { return '"' + td.textContent.trim().replace(/"/g, '""') + '"'; }).join(',')); }); if (rows.length <= 1) { alert('La tabla esta vacia.'); return; } var csv = rows.join('\n'); var blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'reporte_nexus_' + new Date().toISOString().slice(0, 10) + '.csv'; a.click(); URL.revokeObjectURL(url); }