Merge branch 'main' into desarrollo_hector
This commit is contained in:
@@ -149,6 +149,16 @@
|
||||
Reportes
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/pos/historical-sales" class="nav-link">
|
||||
<svg class="nav-link__icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="12" height="11" rx="1"/>
|
||||
<path d="M2 6h12"/>
|
||||
<path d="M5 2v2M11 2v2"/>
|
||||
</svg>
|
||||
Ventas Históricas
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/pos/config" class="nav-link">
|
||||
<svg class="nav-link__icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
||||
@@ -148,6 +148,17 @@
|
||||
Reportes
|
||||
</a>
|
||||
|
||||
<a href="/pos/historical-sales" class="nav-link">
|
||||
<span class="nav-link__icon">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="2" y="3" width="12" height="11" rx="1"/>
|
||||
<path d="M2 6h12" stroke-width="1.2"/>
|
||||
<path d="M5 2v2M11 2v2"/>
|
||||
</svg>
|
||||
</span>
|
||||
Ventas Históricas
|
||||
</a>
|
||||
|
||||
<div class="sidebar__section-label">Gestión</div>
|
||||
|
||||
<a href="/pos/marketplace" class="nav-link">
|
||||
@@ -318,6 +329,68 @@
|
||||
</div><!-- end kpi-grid -->
|
||||
</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)
|
||||
================================================================= -->
|
||||
@@ -495,7 +568,7 @@
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/dashboard-stats.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/dashboard.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/dashboard.js?v=7" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
160
pos/templates/historical_sales.html
Normal file
160
pos/templates/historical_sales.html
Normal file
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Ventas Históricas - Atlas</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/pos.css">
|
||||
<style>
|
||||
:root { --header-h: 56px; }
|
||||
body { margin: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f6f8; color: #1f2937; }
|
||||
.header { position: fixed; top: 0; left: 0; right: 0; height: var(--header-h); background: #111827; color: #fff; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; z-index: 100; }
|
||||
.header h1 { margin: 0; font-size: 16px; font-weight: 600; }
|
||||
.header a { color: #9ca3af; text-decoration: none; font-size: 13px; }
|
||||
.header a:hover { color: #fff; }
|
||||
.container { padding: calc(var(--header-h) + 16px) 16px 24px; max-width: 1200px; margin: 0 auto; }
|
||||
.filters { background: #fff; padding: 12px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; margin-bottom: 16px; }
|
||||
.filters label { font-size: 12px; color: #6b7280; display: block; margin-bottom: 4px; }
|
||||
.filters input { padding: 6px 8px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 13px; }
|
||||
.filters button { padding: 7px 14px; background: #2563eb; color: #fff; border: none; border-radius: 4px; font-size: 13px; cursor: pointer; }
|
||||
.filters button:hover { background: #1d4ed8; }
|
||||
.summary { display: flex; gap: 12px; margin-bottom: 16px; }
|
||||
.card { background: #fff; padding: 12px 16px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); flex: 1; }
|
||||
.card .label { font-size: 12px; color: #6b7280; }
|
||||
.card .value { font-size: 18px; font-weight: 700; color: #111827; }
|
||||
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.08); font-size: 13px; }
|
||||
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
|
||||
th { background: #f9fafb; font-weight: 600; color: #374151; }
|
||||
tr:hover { background: #f9fafb; }
|
||||
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 999px; background: #e5e7eb; font-size: 11px; color: #374151; }
|
||||
.empty { text-align: center; padding: 40px; color: #6b7280; }
|
||||
.pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 16px; }
|
||||
.pagination button { padding: 6px 12px; border: 1px solid #d1d5db; background: #fff; border-radius: 4px; cursor: pointer; }
|
||||
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.pagination span { font-size: 13px; color: #4b5563; }
|
||||
.loading { text-align: center; padding: 40px; color: #6b7280; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>📊 Ventas Históricas - Atlas</h1>
|
||||
<a href="/pos/sale">← Regresar al POS</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="filters">
|
||||
<div>
|
||||
<label>Desde</label>
|
||||
<input type="date" id="dateFrom">
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasta</label>
|
||||
<input type="date" id="dateTo">
|
||||
</div>
|
||||
<div>
|
||||
<label>Cliente</label>
|
||||
<input type="text" id="customerFilter" placeholder="Nombre del cliente..." style="width:220px;">
|
||||
</div>
|
||||
<button onclick="loadData(1)">Buscar</button>
|
||||
</div>
|
||||
|
||||
<div class="summary">
|
||||
<div class="card">
|
||||
<div class="label">Total de ventas</div>
|
||||
<div class="value" id="totalCount">-</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Total vendido</div>
|
||||
<div class="value" id="totalSold">-</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Saldo pendiente</div>
|
||||
<div class="value" id="totalBalance">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div class="loading">Cargando...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/pos/static/js/api.js"></script>
|
||||
<script>
|
||||
const fmt = n => n == null ? '-' : '$' + Number(n).toLocaleString('es-MX', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
||||
const fmtDate = d => d ? new Date(d + 'T00:00:00').toLocaleDateString('es-MX') : '-';
|
||||
|
||||
async function loadData(page = 1) {
|
||||
const content = document.getElementById('content');
|
||||
content.innerHTML = '<div class="loading">Cargando...</div>';
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set('page', page);
|
||||
params.set('per_page', 50);
|
||||
const from = document.getElementById('dateFrom').value;
|
||||
const to = document.getElementById('dateTo').value;
|
||||
const customer = document.getElementById('customerFilter').value.trim();
|
||||
if (from) params.set('date_from', from);
|
||||
if (to) params.set('date_to', to);
|
||||
if (customer) params.set('customer', customer);
|
||||
|
||||
try {
|
||||
const res = await api('/pos/api/historical-sales?' + params.toString());
|
||||
render(res.data || [], res.pagination || {});
|
||||
} catch (e) {
|
||||
content.innerHTML = '<div class="empty">Error: ' + esc(e.message) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function render(rows, pagination) {
|
||||
const content = document.getElementById('content');
|
||||
if (!rows.length) {
|
||||
content.innerHTML = '<div class="empty">No se encontraron ventas históricas</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let totalSold = 0, totalBalance = 0;
|
||||
rows.forEach(r => { totalSold += r.total || 0; totalBalance += r.balance || 0; });
|
||||
|
||||
document.getElementById('totalCount').textContent = pagination.total || rows.length;
|
||||
document.getElementById('totalSold').textContent = fmt(totalSold);
|
||||
document.getElementById('totalBalance').textContent = fmt(totalBalance);
|
||||
|
||||
let html = '<table><thead><tr>' +
|
||||
'<th>Fecha</th><th>Documento</th><th>Cliente</th><th>Forma de pago</th>' +
|
||||
'<th class="num">Subtotal</th><th class="num">Total</th><th class="num">Pagado</th><th class="num">Saldo</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
rows.forEach(r => {
|
||||
html += '<tr>' +
|
||||
'<td>' + fmtDate(r.sale_date) + '</td>' +
|
||||
'<td>' + esc(r.document_no || r.external_document_id || '-') + '</td>' +
|
||||
'<td>' + esc(r.customer_name || '-') + '</td>' +
|
||||
'<td><span class="badge">' + esc(r.payment_method || '-') + '</span></td>' +
|
||||
'<td class="num">' + fmt(r.subtotal) + '</td>' +
|
||||
'<td class="num">' + fmt(r.total) + '</td>' +
|
||||
'<td class="num">' + fmt(r.amount_paid) + '</td>' +
|
||||
'<td class="num">' + fmt(r.balance) + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
|
||||
const totalPages = pagination.total_pages || 1;
|
||||
const page = pagination.page || 1;
|
||||
html += '<div class="pagination">' +
|
||||
'<button onclick="loadData(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>Anterior</button>' +
|
||||
'<span>Página ' + page + ' de ' + totalPages + '</span>' +
|
||||
'<button onclick="loadData(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>Siguiente</button>' +
|
||||
'</div>';
|
||||
|
||||
content.innerHTML = html;
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
loadData(1);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -149,6 +149,16 @@
|
||||
<span>Reportes</span>
|
||||
</a>
|
||||
|
||||
<a class="nav-item" href="/pos/historical-sales">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="4" y="5" width="16" height="16" rx="1"/>
|
||||
<line x1="4" y1="10" x2="20" y2="10"/>
|
||||
<line x1="8" y1="3" x2="8" y2="5"/>
|
||||
<line x1="16" y1="3" x2="16" y2="5"/>
|
||||
</svg>
|
||||
<span>Ventas Históricas</span>
|
||||
</a>
|
||||
|
||||
<div class="nav-section-label">Sistema</div>
|
||||
|
||||
<a class="nav-item" href="/pos/config">
|
||||
@@ -750,7 +760,13 @@
|
||||
</div>
|
||||
<div class="inv-modal__body">
|
||||
<div class="inv-form-grid">
|
||||
<div class="inv-field"><label>ID Producto *</label><input type="number" id="purchaseItemId" placeholder="ID inventario" /></div>
|
||||
<div class="inv-field inv-field--full" style="position:relative;">
|
||||
<label>Producto *</label>
|
||||
<input type="hidden" id="purchaseItemId" />
|
||||
<input type="text" id="purchaseItemSearch" placeholder="Escribe nombre, No. de parte o escanea código de barras..." autocomplete="off" />
|
||||
<div id="purchaseItemResults" style="position:absolute;left:0;right:0;top:100%;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-md);max-height:200px;overflow-y:auto;z-index:100;display:none;"></div>
|
||||
<div id="purchaseItemSelected" style="margin-top:var(--space-1);font-size:var(--text-caption);color:var(--color-text-secondary);"></div>
|
||||
</div>
|
||||
<div class="inv-field"><label>Cantidad *</label><input type="number" id="purchaseQty" placeholder="Cantidad" /></div>
|
||||
<div class="inv-field"><label>Costo Unitario *</label><input type="number" id="purchaseCost" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Factura Proveedor</label><input type="text" id="purchaseInvoice" placeholder="No. factura" /></div>
|
||||
@@ -1043,7 +1059,7 @@
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/virtual-scroll.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/inventory.js?v=17" defer></script>
|
||||
<script src="/pos/static/js/inventory.js?v=18" defer></script>
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
|
||||
@@ -865,6 +865,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FACTURAPI STATUS -->
|
||||
<div class="config-section" style="grid-column: span 2;">
|
||||
<div class="config-section__header">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
</svg>
|
||||
<span class="config-section__title">Facturapi (PAC)</span>
|
||||
</div>
|
||||
<div class="config-section__body" id="facturapi-status">
|
||||
<p style="color:var(--color-text-muted);">Cargando estado de Facturapi...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONFIGURACIÓN DE SERIES — full width -->
|
||||
<div class="config-section" style="grid-column: span 2;">
|
||||
<div class="config-section__header">
|
||||
@@ -1067,7 +1081,7 @@
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/invoicing.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/invoicing.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
@@ -130,6 +130,16 @@
|
||||
<span>Reportes</span>
|
||||
</a>
|
||||
|
||||
<a href="/pos/historical-sales" class="nav-item">
|
||||
<svg class="nav-item__icon" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="1" y="4" width="16" height="12" rx="1"/>
|
||||
<line x1="1" y1="8" x2="17" y2="8"/>
|
||||
<line x1="5" y1="2" x2="5" y2="4"/>
|
||||
<line x1="13" y1="2" x2="13" y2="4"/>
|
||||
</svg>
|
||||
<span>Ventas Históricas</span>
|
||||
</a>
|
||||
|
||||
<div class="nav-section-label">Sistema</div>
|
||||
|
||||
<a href="/pos/config" class="nav-item">
|
||||
@@ -206,6 +216,14 @@
|
||||
</svg>
|
||||
Financieros
|
||||
</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>
|
||||
|
||||
<!-- ==================================================================
|
||||
@@ -308,6 +326,30 @@
|
||||
<!-- Cortes de caja -->
|
||||
<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>
|
||||
<!-- End panels -->
|
||||
|
||||
@@ -323,7 +365,7 @@
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/reports.js" defer></script>
|
||||
<script src="/pos/static/js/reports.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
Reference in New Issue
Block a user