From 31bfd0064e6161373d93428e992a3d0e7e40fd3f Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Sat, 4 Apr 2026 02:20:41 +0000 Subject: [PATCH] =?UTF-8?q?feat(pos):=20reportes=20funcionales=20=E2=80=94?= =?UTF-8?q?=20ventas,=20inventario,=20ABC,=20cortes=20de=20caja?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace static demo data with live API calls across all four report tabs: - Ventas: sales by period/employee/payment method with date filters - Inventario: valuation, ABC classification, low stock, no movement - Clientes: accounts receivable aging report - Financieros: income statement, balance sheet, trial balance, register history All tabs use lazy loading (fetch on first visit) and JWT auth. Co-Authored-By: Claude Opus 4.6 (1M context) --- pos/static/js/reports.js | 636 +++++++++++++++++++- pos/templates/reports.html | 1167 ++---------------------------------- 2 files changed, 675 insertions(+), 1128 deletions(-) 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 = '
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 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 = ''; + } } // ------------------------------------------------------------------------- @@ -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 @@
Desde - + Hasta - - +
- +
- -
-
-
- -
-
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
-
-
-
-
- $14.2k -
-
-
Lun
-
-
-
-
- $17.1k -
-
-
Mar
-
-
-
-
- $11.5k -
-
-
Mié
-
-
-
-
- $19.0k -
-
-
Jue
-
-
-
-
- $20.9k -
-
-
Vie
-
-
-
-
- $15.9k -
-
-
Sáb
-
-
-
-
- $7.9k -
-
-
Dom
-
-
-
+ +
- -
-
- Top 10 Productos más Vendidos -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#ProductoSKUCantidadMonto% del Total
1Filtro de aceite BoschBSC-FLT-001342$18,240 -
-
- 11.8% -
-
2Pastillas freno BremboBRM-PAS-204289$24,565 -
-
- 10.0% -
-
3Aceite Mobil 5W-30 1LMOB-5W30-1L521$15,630 -
-
- 8.6% -
-
4Bujías NGK IridiumNGK-IRI-B6198$11,880 -
-
- 6.5% -
-
5Batería Optima 12VOPT-BAT-12V87$32,190 -
-
- 5.7% -
-
6Filtro aire K&NKN-AIR-33156$9,360 -
-
- 5.1% -
-
7Amortiguador MonroeMON-AMO-G764$19,200 -
-
- 4.5% -
-
8Anticongelante 50/50ATC-5050-4L203$8,120 -
-
- 3.9% -
-
9Correa de distribuciónGAT-COR-K08041$14,760 -
-
- 3.2% -
-
10Líquido frenos DOT4DOT4-FRE-1L178$5,340 -
-
- 2.7% -
-
-
-
- -
-
- Ventas por Vendedor -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vendedor# VentasTotalTicket Prom.Estado
-
-
MR
- María Rodríguez -
-
312$128,448$411.69Activo
-
-
JL
- Juan López -
-
287$109,060$380.00Activo
-
-
AG
- Ana Guerrero -
-
241$93,990$390.00Activo
-
-
CH
- Carlos Herrera -
-
198$74,448$376.00Licencia
-
-
PV
- Pedro Vargas -
-
246$81,374$330.79Activo
-
-
+
+ + +
- -
-

Ventas por Método de Pago

-
-
-
-
- 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 -
-
-
+ +
+ + +
+ + +
-
-
- Productos con Stock Bajo - 47 productos -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ProductoStock ActualMínimoProveedorÚltima Compra
Filtro combustible Toyota Corolla210Distribuidora ABC15/Feb/2026
Bujía Bosch Iridium BK6MCI420Bosch México20/Feb/2026
Amortiguador trasero Nissan Sentra68Monroe Distribución10/Mar/2026
Pastilla freno delantera VW Jetta712Brembo MX12/Mar/2026
Aceite transmisión ATF Dexron VI915Castrol México05/Mar/2026
Sensor O2 Bosch Honda Civic35Bosch México28/Feb/2026
-
-
+
- -
-
- Movimientos del Periodo -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TipoProductoCantidadFechaUsuario
EntradaFiltro de aceite Bosch x484801/Mar/2026M. Rodríguez
SalidaBujías NGK Iridium x121203/Mar/2026J. López
EntradaPastillas Brembo Racing x242407/Mar/2026H. García
SalidaAceite Mobil 5W-30 1L x363610/Mar/2026A. Guerrero
EntradaBatería Optima 12V x101015/Mar/2026M. Rodríguez
SalidaAmortiguador Monroe x8818/Mar/2026C. Herrera
-
-
- - -
-
- Productos Sin Movimiento (>30 días) - 218 SKUs -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ProductoStockÚltimo MovimientoValor
Radiador Nissan Pathfinder 2004326/Nov/2025$5,610
Caja dirección Honda CR-V 2001124/Dec/2025$4,800
Alternador Chevrolet S10 2000216/Jan/2026$3,200
Compresor A/C Ford Explorer 2002130/Jan/2026$6,900
Eje flecha Jetta A3 izq.414/Feb/2026$5,200
Bomba agua Seat Ibiza 1.6228/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 -
-
-
+ +
- -
-
- Top 10 Clientes por Compras -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ClienteRFCComprasMonto TotalÚltima Compra
Taller Mecánico El RápidoTME840312HX448$42,89031 Mar 2026
Autoservicio García Hnos.AGH920518KL939$38,12030 Mar 2026
Flota Transporte Noreste SAFTN010628AB327$89,43025 Mar 2026
Roberto Mendoza TorresMETR780904QJ231$24,56028 Mar 2026
Refaccionaria El CentroREC851107MN522$31,08029 Mar 2026
Miguel Ángel RíosRIMA771215FP724$18,32027 Mar 2026
Servicio Express MonterreySEM960430DX118$22,14026 Mar 2026
Distribuidora Xóchitl S.A.DXS030811CR415$27,60018 Mar 2026
Laura Sánchez PérezSAPL820612RH819$14,25020 Mar 2026
Arturo Vega CastilloVECA790318GK617$11,90022 Mar 2026
-
-
- - -
-
- Clientes con Saldo Pendiente - 12 clientes -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ClienteCrédito AutorizadoSaldoVencimientoEstado
Taller Moreno e Hijos$25,000$18,40028 Dic 2025Vencido
Servicio López Automóviles$15,000$9,75023 Ene 2026Vencido
Ricardo Castro N.$8,000$4,20015 Feb 2026Por vencer
Autoelectrónica Ramos$12,000$6,89022 Feb 2026Por vencer
Grupo Autopartes del Sur$20,000$12,30030 Mar 2026Vigente
-
-
- - -
-
- Clientes Nuevos del Mes - 143 este mes -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NombreFecha AltaComprasTipo
Ernesto Jiménez Soto31 Mar 2026$1,240Mostrador
Taller Veloz S.A.30 Mar 2026$4,890Taller
Patricia Núñez Domínguez28 Mar 2026$680Mostrador
Flotilla Logística MX27 Mar 2026$12,430Taller
Francisco Serrano V.25 Mar 2026$920Mostrador
Electro-Auto Precisión24 Mar 2026$3,400Taller
-
-
+ +
@@ -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
-
-
Ingresos
-
Egresos
-
-
-
-
-
-
-
-
Oct
-
-
-
-
-
-
-
Nov
-
-
-
-
-
-
-
Dic
-
-
-
-
-
-
-
Ene
-
-
-
-
-
-
-
Feb
-
-
-
-
-
-
-
Mar
-
-
-
+ +
- -
-

Desglose de Gastos

-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CategoríaMonto% del Total
Compra de mercancía$157,840 -
-
- 53.6% -
-
Nómina$89,400 -
-
- 30.4% -
-
Renta y servicios$28,100 -
-
- 9.5% -
-
Impuestos y contribuciones$14,640 -
-
- 4.9% -
-
Logística y transporte$4,200 -
-
- 1.4% -
-
Otros$200 -
-
- 0.1% -
-
-
-
-
Total Egresos del Periodo
-
$294,180
-
Marzo 2026
-
-
- Mayor gasto: - Compra mercancía -
-
- % sobre ventas: - 60.4% -
-
- vs mes anterior: - ↑ 9.8% -
-
-
-
-
-
+ +
- -
-
- Flujo de Efectivo Semanal — Marzo 2026 -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SemanaIngresosEgresosNetoAcumulado
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
-
-
+ +
+ + +
+ + +