diff --git a/pos/static/js/reports.js b/pos/static/js/reports.js
index 46dec64..c68e6ca 100644
--- a/pos/static/js/reports.js
+++ b/pos/static/js/reports.js
@@ -22,6 +22,40 @@ const Reports = (() => {
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 };
+
// -------------------------------------------------------------------------
// Theme switcher
// -------------------------------------------------------------------------
@@ -36,7 +70,7 @@ const Reports = (() => {
window.setTheme = setTheme;
// -------------------------------------------------------------------------
- // Tab switcher
+ // Tab switcher with lazy loading
// -------------------------------------------------------------------------
function switchTab(id, btn) {
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('is-active'); });
@@ -44,6 +78,14 @@ const Reports = (() => {
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();
+ }
}
window.switchTab = switchTab;
@@ -59,18 +101,560 @@ const Reports = (() => {
}
// -------------------------------------------------------------------------
- // Placeholder API calls
+ // Generic fetch helper
// -------------------------------------------------------------------------
- async function loadSalesReport(params) {
- // TODO: call /pos/api/cashregister/... with date range
+ async function apiFetch(url) {
+ var resp = await fetch(url, { headers: headers() });
+ if (!resp.ok) throw new Error('HTTP ' + resp.status);
+ return resp.json();
}
- async function loadInventoryReport() {
- // TODO: call /pos/api/inventory/products for stock report
+ // -------------------------------------------------------------------------
+ // KPI card builder
+ // -------------------------------------------------------------------------
+ function kpiCard(label, value, sub) {
+ return '' +
+ '
' + label + '
' +
+ '
' + value + '
' +
+ (sub ? '
' + sub + '
' : '') +
+ '
';
}
- async function loadFinancialReport(params) {
- // TODO: call /pos/api/accounting/... for financial reports
+ // =========================================================================
+ // 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 = '';
+ empHtml += '' +
+ '| Vendedor | # Ventas | ' +
+ 'Total | Ticket Prom. |
';
+ empList.forEach(function(e) {
+ var initials = e.name.split(' ').map(function(w) { return w[0]; }).join('').substring(0, 2).toUpperCase();
+ empHtml += '' +
+ ' ' + initials + ' ' +
+ ' ' + e.name + ' | ' +
+ '' + e.count + ' | ' +
+ '$' + fmt(e.total) + ' | ' +
+ '$' + fmt(e.count > 0 ? e.total / e.count : 0) + ' |
';
+ });
+ empHtml += '
';
+ 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 = '';
+ 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 = '';
+ dtlHtml += '' +
+ '| # | Fecha | Vendedor | Cliente | Pago | ' +
+ 'Subtotal | Desc. | ' +
+ 'IVA | Total | Estado | ' +
+ '
';
+ 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 += '| ' + 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 + ' |
';
+ });
+ dtlHtml += '
';
+ detalleEl.innerHTML = dtlHtml;
+
+ } catch (err) {
+ kpiEl.innerHTML = errorMsg('Error cargando ventas: ' + err.message);
+ vendedorEl.innerHTML = '';
+ metodoEl.innerHTML = '';
+ detalleEl.innerHTML = '';
+ }
+ }
+
+ // =========================================================================
+ // 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 = '';
+ vHtml += '' +
+ '| Producto | No. Parte | Marca | ' +
+ 'Stock | Costo Unit. | ' +
+ 'Valor |
';
+ vItems.forEach(function(i) {
+ vHtml += '| ' + i.name + ' | ' +
+ '' + (i.part_number || '--') + ' | ' +
+ '' + (i.brand || '--') + ' | ' +
+ '' + fmtInt(i.stock) + ' | ' +
+ '$' + fmt(i.cost) + ' | ' +
+ '$' + fmt(i.value) + ' |
';
+ });
+ vHtml += '
';
+ valEl.innerHTML = vHtml;
+
+ // ABC table
+ var abcItems = (abcData.data || []).slice(0, 30);
+ var abcHtml = '';
+ abcHtml += '' +
+ '| Producto | No. Parte | Marca | ' +
+ 'Vol. Ventas | % Acum. | ' +
+ 'Clase |
';
+ abcItems.forEach(function(i) {
+ var clsPill = i.classification === 'A' ? 'pill--success' :
+ i.classification === 'B' ? 'pill--warning' : 'pill--muted';
+ abcHtml += '| ' + i.name + ' | ' +
+ '' + (i.part_number || '--') + ' | ' +
+ '' + (i.brand || '--') + ' | ' +
+ '' + fmtInt(i.sales_volume) + ' | ' +
+ '' + i.cumulative_pct + '% | ' +
+ '' + i.classification + ' |
';
+ });
+ abcHtml += '
';
+ abcEl.innerHTML = abcHtml;
+
+ // Low stock
+ var lowItems = lowData.data || [];
+ var lowHtml = '';
+ lowHtml += '' +
+ '| Producto | No. Parte | Marca | ' +
+ 'Stock | Minimo | ' +
+ 'Deficit |
';
+ 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 += '| ' + i.name + ' | ' +
+ '' + (i.part_number || '--') + ' | ' +
+ '' + (i.brand || '--') + ' | ' +
+ '' + fmtInt(i.stock) + ' | ' +
+ '' + fmtInt(i.min_stock) + ' | ' +
+ '' + fmtInt(i.deficit) + ' |
';
+ });
+ lowHtml += '
';
+ lowEl.innerHTML = lowItems.length ? lowHtml : emptyMsg('No hay productos con stock bajo');
+
+ // No movement
+ var noItems = noMoveData.data || [];
+ var noHtml = '';
+ noHtml += '' +
+ '| Producto | No. Parte | Marca | ' +
+ 'Stock | Costo Unit. | ' +
+ 'Ultimo Movimiento |
';
+ noItems.slice(0, 30).forEach(function(i) {
+ noHtml += '| ' + i.name + ' | ' +
+ '' + (i.part_number || '--') + ' | ' +
+ '' + (i.brand || '--') + ' | ' +
+ '' + fmtInt(i.stock) + ' | ' +
+ '$' + fmt(i.cost) + ' | ' +
+ '' + fmtDate(i.last_movement) + ' |
';
+ });
+ noHtml += '
';
+ 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 = '';
+ html += '' +
+ '| Cliente | RFC | ' +
+ 'Corriente | 1-30 dias | ' +
+ '31-60 dias | 61-90 dias | ' +
+ '90+ dias | Total | ' +
+ '
';
+
+ clients.forEach(function(c) {
+ html += '| ' + 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) + ' |
';
+ });
+
+ // Totals row
+ html += '' +
+ '| TOTAL | ' +
+ '$' + fmt(totals.corriente) + ' | ' +
+ '$' + fmt(totals.d1_30) + ' | ' +
+ '$' + fmt(totals.d31_60) + ' | ' +
+ '$' + fmt(totals.d61_90) + ' | ' +
+ '$' + fmt(totals.d90_plus) + ' | ' +
+ '$' + fmt(totals.total) + ' |
';
+
+ html += '
';
+ 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 = '';
+ iHtml += '' +
+ '| Cuenta | Codigo | Monto |
';
+
+ // Ingresos section
+ iHtml += '| INGRESOS | |
';
+ (incData.ingresos.items || []).forEach(function(i) {
+ iHtml += '| ' + i.name + ' | ' +
+ '' + i.code + ' | ' +
+ '$' + fmt(i.amount) + ' |
';
+ });
+ iHtml += '| Total Ingresos | | ' +
+ '$' + fmt(incData.ingresos.total) + ' |
';
+
+ // Costos section
+ iHtml += '| COSTOS | |
';
+ (incData.costos.items || []).forEach(function(i) {
+ iHtml += '| ' + i.name + ' | ' +
+ '' + i.code + ' | ' +
+ '$' + fmt(i.amount) + ' |
';
+ });
+ iHtml += '| Total Costos | | ' +
+ '$' + fmt(incData.costos.total) + ' |
';
+
+ // Utilidad bruta
+ iHtml += '| UTILIDAD BRUTA | | ' +
+ '$' + fmt(incData.utilidad_bruta) + ' |
';
+
+ // Gastos section
+ iHtml += '| GASTOS | |
';
+ (incData.gastos.items || []).forEach(function(i) {
+ iHtml += '| ' + i.name + ' | ' +
+ '' + i.code + ' | ' +
+ '$' + fmt(i.amount) + ' |
';
+ });
+ iHtml += '| Total Gastos | | ' +
+ '$' + fmt(incData.gastos.total) + ' |
';
+
+ // Utilidad neta
+ var netColor = incData.utilidad_neta >= 0 ? 'var(--color-success)' : 'var(--color-error)';
+ iHtml += '| UTILIDAD NETA | | ' +
+ '$' + fmt(incData.utilidad_neta) + ' |
';
+ iHtml += '
';
+ incomeEl.innerHTML = iHtml;
+
+ // Balance sheet
+ var bHtml = '';
+ bHtml += '' +
+ '| Cuenta | Codigo | Saldo |
';
+
+ // Activo
+ bHtml += '| ACTIVO | |
';
+ (balData.activo.items || []).forEach(function(i) {
+ bHtml += '| ' + i.name + ' | ' +
+ '' + i.code + ' | ' +
+ '$' + fmt(i.balance) + ' |
';
+ });
+ bHtml += '| Total Activo | | ' +
+ '$' + fmt(balData.activo.total) + ' |
';
+
+ // Pasivo
+ bHtml += '| PASIVO | |
';
+ (balData.pasivo.items || []).forEach(function(i) {
+ bHtml += '| ' + i.name + ' | ' +
+ '' + i.code + ' | ' +
+ '$' + fmt(i.balance) + ' |
';
+ });
+ bHtml += '| Total Pasivo | | ' +
+ '$' + fmt(balData.pasivo.total) + ' |
';
+
+ // Capital
+ bHtml += '| CAPITAL | |
';
+ (balData.capital.items || []).forEach(function(i) {
+ bHtml += '| ' + i.name + ' | ' +
+ '' + i.code + ' | ' +
+ '$' + fmt(i.balance) + ' |
';
+ });
+ bHtml += '| Total Capital | | ' +
+ '$' + fmt(balData.capital.total) + ' |
';
+
+ bHtml += '| Pasivo + Capital | | ' +
+ '$' + fmt(balData.pasivo.total + balData.capital.total) + ' |
';
+ bHtml += '
';
+ balanceEl.innerHTML = bHtml;
+
+ // Trial balance
+ var tRows = trialData.data || [];
+ var tHtml = '';
+ tHtml += '' +
+ '| Codigo | Cuenta | Tipo | ' +
+ 'Saldo Inicial | Cargos | ' +
+ 'Abonos | Saldo Final | ' +
+ '
';
+ tRows.forEach(function(r) {
+ tHtml += '| ' + r.code + ' | ' +
+ '' + r.name + ' | ' +
+ '' + r.type + ' | ' +
+ '$' + fmt(r.saldo_inicial) + ' | ' +
+ '$' + fmt(r.cargos) + ' | ' +
+ '$' + fmt(r.abonos) + ' | ' +
+ '$' + fmt(r.saldo_final) + ' |
';
+ });
+ tHtml += '
';
+ trialEl.innerHTML = tRows.length ? tHtml : emptyMsg('Sin movimientos contables en este periodo');
+
+ // Cortes de caja
+ var regs = cortesData.data || [];
+ var cHtml = '';
+ cHtml += '' +
+ '| Caja | Empleado | Apertura | Cierre | ' +
+ 'Monto Apertura | Esperado | ' +
+ 'Cierre Real | Diferencia | ' +
+ '
';
+ 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 += '| #' + 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) + ' |
';
+ });
+ cHtml += '
';
+ 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 = '';
+ }
}
// -------------------------------------------------------------------------
@@ -88,12 +672,46 @@ const Reports = (() => {
// 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,
- loadSalesReport, loadInventoryReport, loadFinancialReport, fmt
+ loadVentas, loadInventario, loadClientes, loadFinancieros, fmt
};
})();
diff --git a/pos/templates/reports.html b/pos/templates/reports.html
index fddaa9c..d71c13d 100644
--- a/pos/templates/reports.html
+++ b/pos/templates/reports.html
@@ -1744,404 +1744,30 @@
-
-
-
-
-
Total Ventas ($)
-
$487,320
-
- ↑ 12.4%
- vs mes anterior
-
-
-
-
-
-
-
Ticket Promedio ($)
-
$379.50
-
- ↑ 3.9%
- vs mes anterior
-
-
-
-
-
Productos Vendidos
-
8,941
-
- ↓ 2.3%
- vs mes anterior
-
-
-
-
-
Margen Promedio (%)
-
39.6%
-
- ↑ 1.3pp
- vs mes anterior
-
-
-
+
+
-
-
-
Ventas por Día — Últimos 7 días
-
-
+
+
-
-
-
-
-
-
-
- | # |
- Producto |
- SKU |
- Cantidad |
- Monto |
- % del Total |
-
-
-
-
- | 1 |
- Filtro de aceite Bosch |
- BSC-FLT-001 |
- 342 |
- $18,240 |
-
-
- |
-
-
- | 2 |
- Pastillas freno Brembo |
- BRM-PAS-204 |
- 289 |
- $24,565 |
-
-
- |
-
-
- | 3 |
- Aceite Mobil 5W-30 1L |
- MOB-5W30-1L |
- 521 |
- $15,630 |
-
-
- |
-
-
- | 4 |
- Bujías NGK Iridium |
- NGK-IRI-B6 |
- 198 |
- $11,880 |
-
-
- |
-
-
- | 5 |
- Batería Optima 12V |
- OPT-BAT-12V |
- 87 |
- $32,190 |
-
-
- |
-
-
- | 6 |
- Filtro aire K&N |
- KN-AIR-33 |
- 156 |
- $9,360 |
-
-
- |
-
-
- | 7 |
- Amortiguador Monroe |
- MON-AMO-G7 |
- 64 |
- $19,200 |
-
-
- |
-
-
- | 8 |
- Anticongelante 50/50 |
- ATC-5050-4L |
- 203 |
- $8,120 |
-
-
- |
-
-
- | 9 |
- Correa de distribución |
- GAT-COR-K080 |
- 41 |
- $14,760 |
-
-
- |
-
-
- | 10 |
- Líquido frenos DOT4 |
- DOT4-FRE-1L |
- 178 |
- $5,340 |
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
- | Vendedor |
- # Ventas |
- Total |
- Ticket Prom. |
- Estado |
-
-
-
-
- |
-
- |
- 312 |
- $128,448 |
- $411.69 |
- Activo |
-
-
- |
-
- |
- 287 |
- $109,060 |
- $380.00 |
- Activo |
-
-
- |
-
- |
- 241 |
- $93,990 |
- $390.00 |
- Activo |
-
-
- |
-
- |
- 198 |
- $74,448 |
- $376.00 |
- Licencia |
-
-
- |
-
- |
- 246 |
- $81,374 |
- $330.79 |
- Activo |
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
Efectivo
-
-
$234,114 48%
-
-
-
Tarjeta
-
-
$131,576 27%
-
-
-
Transferencia
-
-
$68,225 14%
-
-
-
Crédito
-
-
$53,605 11%
-
-
-
+
+
@@ -2150,237 +1776,20 @@
================================================================== -->
-
-
-
-
-
Valor Total Inventario ($)
-
$2,140,000
-
- ↑ 5.2%
- vs mes anterior
-
-
-
-
-
SKUs Activos
-
3,847
-
- +143
- nuevos este mes
-
-
-
-
-
Rotación Promedio
-
4.3x
-
veces / mes
-
-
-
-
-
-
Artículos Stock Bajo
-
218
-
- +34
- vs mes anterior
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- | Producto |
- Stock Actual |
- Mínimo |
- Proveedor |
- Última Compra |
-
-
-
-
- | Filtro combustible Toyota Corolla |
- 2 |
- 10 |
- Distribuidora ABC |
- 15/Feb/2026 |
-
-
- | Bujía Bosch Iridium BK6MCI |
- 4 |
- 20 |
- Bosch México |
- 20/Feb/2026 |
-
-
- | Amortiguador trasero Nissan Sentra |
- 6 |
- 8 |
- Monroe Distribución |
- 10/Mar/2026 |
-
-
- | Pastilla freno delantera VW Jetta |
- 7 |
- 12 |
- Brembo MX |
- 12/Mar/2026 |
-
-
- | Aceite transmisión ATF Dexron VI |
- 9 |
- 15 |
- Castrol México |
- 05/Mar/2026 |
-
-
- | Sensor O2 Bosch Honda Civic |
- 3 |
- 5 |
- Bosch México |
- 28/Feb/2026 |
-
-
-
-
-
+
-
-
-
-
-
-
-
- | Tipo |
- Producto |
- Cantidad |
- Fecha |
- Usuario |
-
-
-
-
- | Entrada |
- Filtro de aceite Bosch x48 |
- 48 |
- 01/Mar/2026 |
- M. Rodríguez |
-
-
- | Salida |
- Bujías NGK Iridium x12 |
- 12 |
- 03/Mar/2026 |
- J. López |
-
-
- | Entrada |
- Pastillas Brembo Racing x24 |
- 24 |
- 07/Mar/2026 |
- H. García |
-
-
- | Salida |
- Aceite Mobil 5W-30 1L x36 |
- 36 |
- 10/Mar/2026 |
- A. Guerrero |
-
-
- | Entrada |
- Batería Optima 12V x10 |
- 10 |
- 15/Mar/2026 |
- M. Rodríguez |
-
-
- | Salida |
- Amortiguador Monroe x8 |
- 8 |
- 18/Mar/2026 |
- C. Herrera |
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Producto |
- Stock |
- Último Movimiento |
- Valor |
-
-
-
-
- | Radiador Nissan Pathfinder 2004 |
- 3 |
- 26/Nov/2025 |
- $5,610 |
-
-
- | Caja dirección Honda CR-V 2001 |
- 1 |
- 24/Dec/2025 |
- $4,800 |
-
-
- | Alternador Chevrolet S10 2000 |
- 2 |
- 16/Jan/2026 |
- $3,200 |
-
-
- | Compresor A/C Ford Explorer 2002 |
- 1 |
- 30/Jan/2026 |
- $6,900 |
-
-
- | Eje flecha Jetta A3 izq. |
- 4 |
- 14/Feb/2026 |
- $5,200 |
-
-
- | Bomba agua Seat Ibiza 1.6 |
- 2 |
- 28/Feb/2026 |
- $1,640 |
-
-
-
-
-
+
+
@@ -2389,255 +1798,11 @@
================================================================== -->
-
-
-
-
-
Clientes Activos
-
4,210
-
registros activos
-
-
-
-
-
-
Nuevos del Mes
-
143
-
- ↑ 18.3%
- vs mes anterior
-
-
-
-
-
-
-
Clientes con Crédito
-
892
-
líneas activas
-
-
-
-
Saldo Total Créditos ($)
-
$249,510
-
- ↑ 4.2%
- vs mes anterior
-
-
-
+
+
-
-
-
-
-
-
-
- | Cliente |
- RFC |
- Compras |
- Monto Total |
- Última Compra |
-
-
-
-
- | Taller Mecánico El Rápido |
- TME840312HX4 |
- 48 |
- $42,890 |
- 31 Mar 2026 |
-
-
- | Autoservicio García Hnos. |
- AGH920518KL9 |
- 39 |
- $38,120 |
- 30 Mar 2026 |
-
-
- | Flota Transporte Noreste SA |
- FTN010628AB3 |
- 27 |
- $89,430 |
- 25 Mar 2026 |
-
-
- | Roberto Mendoza Torres |
- METR780904QJ2 |
- 31 |
- $24,560 |
- 28 Mar 2026 |
-
-
- | Refaccionaria El Centro |
- REC851107MN5 |
- 22 |
- $31,080 |
- 29 Mar 2026 |
-
-
- | Miguel Ángel Ríos |
- RIMA771215FP7 |
- 24 |
- $18,320 |
- 27 Mar 2026 |
-
-
- | Servicio Express Monterrey |
- SEM960430DX1 |
- 18 |
- $22,140 |
- 26 Mar 2026 |
-
-
- | Distribuidora Xóchitl S.A. |
- DXS030811CR4 |
- 15 |
- $27,600 |
- 18 Mar 2026 |
-
-
- | Laura Sánchez Pérez |
- SAPL820612RH8 |
- 19 |
- $14,250 |
- 20 Mar 2026 |
-
-
- | Arturo Vega Castillo |
- VECA790318GK6 |
- 17 |
- $11,900 |
- 22 Mar 2026 |
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Cliente |
- Crédito Autorizado |
- Saldo |
- Vencimiento |
- Estado |
-
-
-
-
- | Taller Moreno e Hijos |
- $25,000 |
- $18,400 |
- 28 Dic 2025 |
- Vencido |
-
-
- | Servicio López Automóviles |
- $15,000 |
- $9,750 |
- 23 Ene 2026 |
- Vencido |
-
-
- | Ricardo Castro N. |
- $8,000 |
- $4,200 |
- 15 Feb 2026 |
- Por vencer |
-
-
- | Autoelectrónica Ramos |
- $12,000 |
- $6,890 |
- 22 Feb 2026 |
- Por vencer |
-
-
- | Grupo Autopartes del Sur |
- $20,000 |
- $12,300 |
- 30 Mar 2026 |
- Vigente |
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Nombre |
- Fecha Alta |
- Compras |
- Tipo |
-
-
-
-
- | Ernesto Jiménez Soto |
- 31 Mar 2026 |
- $1,240 |
- Mostrador |
-
-
- | Taller Veloz S.A. |
- 30 Mar 2026 |
- $4,890 |
- Taller |
-
-
- | Patricia Núñez Domínguez |
- 28 Mar 2026 |
- $680 |
- Mostrador |
-
-
- | Flotilla Logística MX |
- 27 Mar 2026 |
- $12,430 |
- Taller |
-
-
- | Francisco Serrano V. |
- 25 Mar 2026 |
- $920 |
- Mostrador |
-
-
- | Electro-Auto Precisión |
- 24 Mar 2026 |
- $3,400 |
- Taller |
-
-
-
-
-
+
+
@@ -2646,266 +1811,30 @@
================================================================== -->
-
-
-
-
-
Ingresos ($)
-
$487,320
-
- ↑ 12.4%
- vs mes anterior
-
-
-
-
-
-
-
Egresos ($)
-
$294,180
-
- ↑ 9.8%
- vs mes anterior
-
-
-
-
-
Utilidad ($)
-
$193,140
-
- ↑ 16.2%
- vs mes anterior
-
-
-
-
-
Margen (%)
-
39.6%
-
- ↑ 1.3pp
- vs mes anterior
-
-
+
+
+
Mes
+
+
Anio
+
+
+
-
-
-
Ingresos vs Egresos — Últimos 6 Meses
-
-
-
+
+
-
-
-
-
-
-
-
-
-
- | Categoría |
- Monto |
- % del Total |
-
-
-
-
- | Compra de mercancía |
- $157,840 |
-
-
- |
-
-
- | Nómina |
- $89,400 |
-
-
- |
-
-
- | Renta y servicios |
- $28,100 |
-
-
- |
-
-
- | Impuestos y contribuciones |
- $14,640 |
-
-
- |
-
-
- | Logística y transporte |
- $4,200 |
-
-
- |
-
-
- | Otros |
- $200 |
-
-
- |
-
-
-
-
-
-
Total Egresos del Periodo
-
$294,180
-
Marzo 2026
-
-
- Mayor gasto:
- Compra mercancía
-
-
- % sobre ventas:
- 60.4%
-
-
- vs mes anterior:
- ↑ 9.8%
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
- | Semana |
- Ingresos |
- Egresos |
- Neto |
- Acumulado |
-
-
-
-
- | Semana 1 (01–07 Mar) |
- $98,240 |
- $61,800 |
- $36,440 |
- $36,440 |
-
-
- | Semana 2 (08–14 Mar) |
- $124,560 |
- $74,200 |
- $50,360 |
- $86,800 |
-
-
- | Semana 3 (15–21 Mar) |
- $132,890 |
- $82,640 |
- $50,250 |
- $137,050 |
-
-
- | Semana 4 (22–31 Mar) |
- $131,630 |
- $75,540 |
- $56,090 |
- $193,140 |
-
-
- | Total Marzo 2026 |
- $487,320 |
- $294,180 |
- $193,140 |
- $193,140 |
-
-
-
-
-
+
+
+
+
+
+
+
+