feat(reports/dashboard): integrate historical sales viewer
- Add 'Histórico' tab inside Reports page with date/customer filters - Show historical sales KPIs and detail table in reports - Add historical sales summary cards to Dashboard - Load current month totals and total imported records
This commit is contained in:
@@ -205,6 +205,50 @@ const Dashboard = (() => {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 1b. Historical sales KPIs (imported data)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
async function loadHistoricalSummary() {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);
|
||||||
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
// All historical sales
|
||||||
|
const all = await apiFetch('/pos/api/historical-sales?per_page=1');
|
||||||
|
const totalRecords = all.pagination ? all.pagination.total : 0;
|
||||||
|
|
||||||
|
// Current month historical sales
|
||||||
|
const month = await apiFetch(`/pos/api/historical-sales?date_from=${firstDay}&date_to=${lastDay}&per_page=200`);
|
||||||
|
const monthRows = month.data || [];
|
||||||
|
const monthTotal = monthRows.reduce((a, r) => a + (r.total || 0), 0);
|
||||||
|
|
||||||
|
const totalEl = document.getElementById('kpi-historico-total-value');
|
||||||
|
const totalMetaEl = document.getElementById('kpi-historico-total-meta');
|
||||||
|
if (totalEl) totalEl.textContent = fmt(monthTotal);
|
||||||
|
if (totalMetaEl) totalMetaEl.innerHTML = `<span class="kpi-meta-text">${fmtInt(totalRecords)} tickets importados</span>`;
|
||||||
|
|
||||||
|
const mesEl = document.getElementById('kpi-historico-mes-value');
|
||||||
|
const mesMetaEl = document.getElementById('kpi-historico-mes-meta');
|
||||||
|
if (mesEl) mesEl.textContent = fmt(monthTotal);
|
||||||
|
if (mesMetaEl) mesMetaEl.innerHTML = `<span class="kpi-meta-text">${monthRows.length} tickets este mes</span>`;
|
||||||
|
|
||||||
|
const countEl = document.getElementById('kpi-historico-count-value');
|
||||||
|
const countMetaEl = document.getElementById('kpi-historico-count-meta');
|
||||||
|
if (countEl) countEl.textContent = fmtInt(totalRecords);
|
||||||
|
if (countMetaEl) countMetaEl.innerHTML = `<span class="kpi-meta-text">Registros históricos</span>`;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading historical summary:', err);
|
||||||
|
const ids = [
|
||||||
|
['kpi-historico-total-value', 'kpi-historico-total-meta'],
|
||||||
|
['kpi-historico-mes-value', 'kpi-historico-mes-meta'],
|
||||||
|
['kpi-historico-count-value', 'kpi-historico-count-meta'],
|
||||||
|
];
|
||||||
|
ids.forEach(([v, m]) => setKpiError(v, m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setKpiError(valueId, metaId) {
|
function setKpiError(valueId, metaId) {
|
||||||
const v = document.getElementById(valueId);
|
const v = document.getElementById(valueId);
|
||||||
const m = document.getElementById(metaId);
|
const m = document.getElementById(metaId);
|
||||||
@@ -533,6 +577,7 @@ const Dashboard = (() => {
|
|||||||
|
|
||||||
// Load all data in parallel
|
// Load all data in parallel
|
||||||
loadDailySummary();
|
loadDailySummary();
|
||||||
|
loadHistoricalSummary();
|
||||||
loadAlerts();
|
loadAlerts();
|
||||||
loadTopProducts();
|
loadTopProducts();
|
||||||
loadWeeklyChart();
|
loadWeeklyChart();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const Reports = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Track which tabs have been loaded
|
// Track which tabs have been loaded
|
||||||
var loaded = { ventas: false, inventario: false, clientes: false, financieros: false };
|
var loaded = { ventas: false, inventario: false, clientes: false, financieros: false, historico: false };
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Theme switcher
|
// Theme switcher
|
||||||
@@ -85,6 +85,7 @@ const Reports = (() => {
|
|||||||
else if (id === 'inventario') loadInventario();
|
else if (id === 'inventario') loadInventario();
|
||||||
else if (id === 'clientes') loadClientes();
|
else if (id === 'clientes') loadClientes();
|
||||||
else if (id === 'financieros') loadFinancieros();
|
else if (id === 'financieros') loadFinancieros();
|
||||||
|
else if (id === 'historico') loadHistorico();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.switchTab = switchTab;
|
window.switchTab = switchTab;
|
||||||
@@ -289,6 +290,85 @@ const Reports = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 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 = '<div class="table-card__header"><span class="table-card__title">Ventas Históricas Importadas</span>' +
|
||||||
|
'<span class="pill pill--muted">' + allRows.length + ' registros</span></div>';
|
||||||
|
html += '<div class="table-wrap"><table class="data-table"><thead><tr>' +
|
||||||
|
'<th>Fecha</th><th>Documento</th><th>Cliente</th><th>Pago</th>' +
|
||||||
|
'<th class="align-right">Subtotal</th><th class="align-right">Total</th>' +
|
||||||
|
'<th class="align-right">Pagado</th><th class="align-right">Saldo</th>' +
|
||||||
|
'</tr></thead><tbody>';
|
||||||
|
allRows.slice(0, 200).forEach(function(r) {
|
||||||
|
html += '<tr>' +
|
||||||
|
'<td>' + fmtDate(r.sale_date) + '</td>' +
|
||||||
|
'<td class="td-mono">' + esc(r.document_no || r.external_document_id || '--') + '</td>' +
|
||||||
|
'<td>' + esc(r.customer_name || '--') + '</td>' +
|
||||||
|
'<td><span class="pill pill--muted">' + esc(r.payment_method || '--') + '</span></td>' +
|
||||||
|
'<td class="align-right td-mono">$' + fmt(r.subtotal) + '</td>' +
|
||||||
|
'<td class="align-right td-mono-accent">$' + fmt(r.total) + '</td>' +
|
||||||
|
'<td class="align-right td-mono">$' + fmt(r.amount_paid) + '</td>' +
|
||||||
|
'<td class="align-right td-mono">$' + fmt(r.balance) + '</td>' +
|
||||||
|
'</tr>';
|
||||||
|
});
|
||||||
|
html += '</tbody></table></div>';
|
||||||
|
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
|
// TAB 2: INVENTARIO
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -712,7 +792,7 @@ const Reports = (() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
init, setTheme, switchTab,
|
init, setTheme, switchTab,
|
||||||
loadVentas, loadInventario, loadClientes, loadFinancieros, fmt
|
loadVentas, loadInventario, loadClientes, loadFinancieros, loadHistorico, fmt
|
||||||
};
|
};
|
||||||
// Register Cmd+K items
|
// Register Cmd+K items
|
||||||
if (typeof registerCmdKItem === "function") {
|
if (typeof registerCmdKItem === "function") {
|
||||||
|
|||||||
@@ -329,6 +329,68 @@
|
|||||||
</div><!-- end kpi-grid -->
|
</div><!-- end kpi-grid -->
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- =================================================================
|
||||||
|
HISTÓRICO IMPORTADO
|
||||||
|
================================================================= -->
|
||||||
|
<section>
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-title">Histórico importado</span>
|
||||||
|
<a href="/pos/reports" class="section-action" style="text-decoration:none;color:inherit;">Ver en reportes →</a>
|
||||||
|
</div>
|
||||||
|
<div class="kpi-grid">
|
||||||
|
<div class="kpi-card" id="kpi-historico-total">
|
||||||
|
<div class="kpi-card__accent-bar"></div>
|
||||||
|
<div class="kpi-card__label">
|
||||||
|
Total Histórico
|
||||||
|
<span class="kpi-card__icon">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||||
|
<rect x="1" y="2" width="12" height="11" rx="1" stroke="currentColor" stroke-width="1.4"/>
|
||||||
|
<path d="M4 1v2M10 1v2M1 6h12" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="kpi-card__value" id="kpi-historico-total-value"><div class="skeleton skeleton--text" style="width:60%;"></div></div>
|
||||||
|
<div class="kpi-card__meta" id="kpi-historico-total-meta">
|
||||||
|
<div class="skeleton skeleton--text-sm" style="width:80%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kpi-card" id="kpi-historico-mes">
|
||||||
|
<div class="kpi-card__accent-bar"></div>
|
||||||
|
<div class="kpi-card__label">
|
||||||
|
Este Mes (Histórico)
|
||||||
|
<span class="kpi-card__icon">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||||
|
<path d="M2 3h10v9H2z" stroke="currentColor" stroke-width="1.4"/>
|
||||||
|
<path d="M2 6h10M5 1v2M9 1v2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="kpi-card__value" id="kpi-historico-mes-value"><div class="skeleton skeleton--text" style="width:60%;"></div></div>
|
||||||
|
<div class="kpi-card__meta" id="kpi-historico-mes-meta">
|
||||||
|
<div class="skeleton skeleton--text-sm" style="width:80%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="kpi-card" id="kpi-historico-count">
|
||||||
|
<div class="kpi-card__accent-bar"></div>
|
||||||
|
<div class="kpi-card__label">
|
||||||
|
Tickets Históricos
|
||||||
|
<span class="kpi-card__icon">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||||
|
<path d="M2 2h10a1 1 0 011 1v8a1 1 0 01-1 1H2a1 1 0 01-1-1V3a1 1 0 011-1z" stroke="currentColor" stroke-width="1.4"/>
|
||||||
|
<path d="M3.5 5.5h7M3.5 7.5h5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="kpi-card__value" id="kpi-historico-count-value"><div class="skeleton skeleton--text" style="width:40%;"></div></div>
|
||||||
|
<div class="kpi-card__meta" id="kpi-historico-count-meta">
|
||||||
|
<div class="skeleton skeleton--text-sm" style="width:70%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- =================================================================
|
<!-- =================================================================
|
||||||
SALES CHART (CSS-only bar chart)
|
SALES CHART (CSS-only bar chart)
|
||||||
================================================================= -->
|
================================================================= -->
|
||||||
|
|||||||
@@ -216,6 +216,14 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Financieros
|
Financieros
|
||||||
</button>
|
</button>
|
||||||
|
<button class="tab-btn" onclick="switchTab('historico', this)">
|
||||||
|
<svg viewBox="0 0 15 15" fill="none" stroke="currentColor" stroke-width="1.4">
|
||||||
|
<rect x="1" y="4" width="13" height="10" rx="1"/>
|
||||||
|
<path d="M1 7h13"/>
|
||||||
|
<path d="M4 2v2M10 2v2"/>
|
||||||
|
</svg>
|
||||||
|
Histórico
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ==================================================================
|
<!-- ==================================================================
|
||||||
@@ -318,6 +326,30 @@
|
|||||||
<!-- Cortes de caja -->
|
<!-- Cortes de caja -->
|
||||||
<div class="table-card mb-5" id="financieros-cortes"></div>
|
<div class="table-card mb-5" id="financieros-cortes"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- ==================================================================
|
||||||
|
TAB 5: HISTÓRICO
|
||||||
|
================================================================== -->
|
||||||
|
<div class="tab-panel" id="panel-historico">
|
||||||
|
|
||||||
|
<!-- Filter Bar -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<span class="filter-bar__label">Desde</span>
|
||||||
|
<input type="date" class="filter-input" id="historico-date-from" />
|
||||||
|
<span class="filter-bar__label">Hasta</span>
|
||||||
|
<input type="date" class="filter-input" id="historico-date-to" />
|
||||||
|
<span class="filter-bar__label">Cliente</span>
|
||||||
|
<input type="text" class="filter-input" id="historico-customer" placeholder="Nombre..." />
|
||||||
|
<div class="filter-bar__spacer"></div>
|
||||||
|
<button class="btn btn-primary btn-sm" onclick="Reports.loadHistorico()">Generar</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- KPI Cards (dynamic) -->
|
||||||
|
<div class="kpi-grid" id="historico-kpis"></div>
|
||||||
|
|
||||||
|
<!-- Sales detail table -->
|
||||||
|
<div class="table-card mb-5" id="historico-detalle"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- End panels -->
|
<!-- End panels -->
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user