feat(design): add 16 new components + update 5 pages (ronda 2)
Bloqueantes POS: modal-pago, ticket-termico, banner-cliente, fkeys-footer, columnas-costo-margen Componentes nuevos: calculadora-cambio, panel-deslizante, badge-cfdi, arbol-colapsable, tarjeta-metrica, grafica-barras, selector-periodo, etiqueta-codigo-barras Estados: estado-vacio, banner-offline, modal-confirmacion Páginas actualizadas: facturación, contabilidad, dashboard, configuración, reportes
This commit is contained in:
@@ -804,6 +804,83 @@
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
/* Columnas Costo/Margen (solo visibles con permiso Admin/Owner) */
|
||||
.cart-item__cost,
|
||||
.cart-item__margin {
|
||||
display: none;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-caption);
|
||||
text-align: right;
|
||||
min-width: 56px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cart-item__cost {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.cart-item__margin {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.cart-item__margin.margin-high { color: var(--color-success); }
|
||||
.cart-item__margin.margin-mid { color: var(--color-warning); }
|
||||
.cart-item__margin.margin-low { color: var(--color-error); }
|
||||
|
||||
/* When cost columns are visible */
|
||||
body.show-cost-columns .cart-item__cost,
|
||||
body.show-cost-columns .cart-item__margin {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Margin footer summary */
|
||||
.margin-summary {
|
||||
display: none;
|
||||
padding: var(--space-2) var(--space-5);
|
||||
border-top: 1px dashed var(--color-border);
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
body.show-cost-columns .margin-summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.margin-summary__value {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* Toggle button for cost columns */
|
||||
.cost-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 10px;
|
||||
font-family: var(--font-body);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.cost-toggle:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-text-accent);
|
||||
}
|
||||
|
||||
.cost-toggle.active {
|
||||
background: var(--color-primary-muted);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-text-accent);
|
||||
}
|
||||
|
||||
/* Cart footer (totals + payment) */
|
||||
.cart-footer {
|
||||
flex-shrink: 0;
|
||||
@@ -1423,6 +1500,7 @@
|
||||
<div class="cart-header">
|
||||
<div class="cart-header__top">
|
||||
<div class="cart-header__sale-id" id="saleId">Venta #1247</div>
|
||||
<button class="cost-toggle" id="costToggle" title="Mostrar costo/margen (Admin)">💲 C/M</button>
|
||||
<span class="cart-header__status">Activa</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
@@ -1448,6 +1526,12 @@
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
|
||||
<!-- Margin Summary (visible when cost columns are on) -->
|
||||
<div class="margin-summary" id="marginSummary">
|
||||
<span>Margen promedio ponderado:</span>
|
||||
<span class="margin-summary__value" id="avgMargin">--</span>
|
||||
</div>
|
||||
|
||||
<!-- Cart Footer -->
|
||||
<div class="cart-footer">
|
||||
|
||||
@@ -1608,20 +1692,20 @@
|
||||
DATA — Product catalogue
|
||||
------------------------------------------------------------------ */
|
||||
const PRODUCTS = [
|
||||
{ id: 1, name: 'Balatas Delanteras', oem: 'TRW-GDB1246', price: 485.00, stock: 12, category: 'frenos', cat_label: 'Frenos' },
|
||||
{ id: 2, name: 'Disco de Freno Ventilado',oem: 'BREMBO-09A388X',price: 1290.00, stock: 4, category: 'frenos', cat_label: 'Frenos' },
|
||||
{ id: 3, name: 'Cilindro de Rueda', oem: 'LPR-4502', price: 320.00, stock: 7, category: 'frenos', cat_label: 'Frenos' },
|
||||
{ id: 4, name: 'Bujía Iridium NGK', oem: 'NGK-ILKAR7L11', price: 185.00, stock: 48, category: 'motor', cat_label: 'Motor' },
|
||||
{ id: 5, name: 'Empaque de Culata', oem: 'VICTOR-R10154', price: 890.00, stock: 2, category: 'motor', cat_label: 'Motor' },
|
||||
{ id: 6, name: 'Correa de Distribución', oem: 'GATES-T236', price: 650.00, stock: 9, category: 'motor', cat_label: 'Motor' },
|
||||
{ id: 7, name: 'Filtro de Aire Fram', oem: 'FRAM-CA10755', price: 195.00, stock: 23, category: 'filtros', cat_label: 'Filtros' },
|
||||
{ id: 8, name: 'Filtro de Aceite Bosch', oem: 'BOSCH-0986AF10',price: 145.00, stock: 31, category: 'filtros', cat_label: 'Filtros' },
|
||||
{ id: 9, name: 'Aceite Mobil 5W-30 1L', oem: 'MOBIL-5W30-1L', price: 210.00, stock: 64, category: 'aceites', cat_label: 'Aceites' },
|
||||
{ id: 10, name: 'Aceite Pennzoil 10W-40', oem: 'PZ-10W40-1L', price: 175.00, stock: 50, category: 'aceites', cat_label: 'Aceites' },
|
||||
{ id: 11, name: 'Amortiguador Delantero', oem: 'KYB-339123', price: 1450.00, stock: 3, category: 'suspension', cat_label: 'Suspensión'},
|
||||
{ id: 12, name: 'Rótula de Dirección', oem: 'MOOG-K9648', price: 560.00, stock: 6, category: 'suspension', cat_label: 'Suspensión'},
|
||||
{ id: 13, name: 'Bobina de Encendido', oem: 'DELPHI-GN10570',price: 780.00, stock: 5, category: 'electrico', cat_label: 'Eléctrico' },
|
||||
{ id: 14, name: 'Sensor de Oxígeno Denso', oem: 'DENSO-234-4127',price: 920.00, stock: 4, category: 'electrico', cat_label: 'Eléctrico' },
|
||||
{ id: 1, name: 'Balatas Delanteras', oem: 'TRW-GDB1246', price: 485.00, cost: 290.00, stock: 12, category: 'frenos', cat_label: 'Frenos' },
|
||||
{ id: 2, name: 'Disco de Freno Ventilado',oem: 'BREMBO-09A388X',price: 1290.00, cost: 820.00, stock: 4, category: 'frenos', cat_label: 'Frenos' },
|
||||
{ id: 3, name: 'Cilindro de Rueda', oem: 'LPR-4502', price: 320.00, cost: 195.00, stock: 7, category: 'frenos', cat_label: 'Frenos' },
|
||||
{ id: 4, name: 'Bujía Iridium NGK', oem: 'NGK-ILKAR7L11', price: 185.00, cost: 98.00, stock: 48, category: 'motor', cat_label: 'Motor' },
|
||||
{ id: 5, name: 'Empaque de Culata', oem: 'VICTOR-R10154', price: 890.00, cost: 580.00, stock: 2, category: 'motor', cat_label: 'Motor' },
|
||||
{ id: 6, name: 'Correa de Distribución', oem: 'GATES-T236', price: 650.00, cost: 390.00, stock: 9, category: 'motor', cat_label: 'Motor' },
|
||||
{ id: 7, name: 'Filtro de Aire Fram', oem: 'FRAM-CA10755', price: 195.00, cost: 85.00, stock: 23, category: 'filtros', cat_label: 'Filtros' },
|
||||
{ id: 8, name: 'Filtro de Aceite Bosch', oem: 'BOSCH-0986AF10',price: 145.00, cost: 68.00, stock: 31, category: 'filtros', cat_label: 'Filtros' },
|
||||
{ id: 9, name: 'Aceite Mobil 5W-30 1L', oem: 'MOBIL-5W30-1L', price: 210.00, cost: 155.00, stock: 64, category: 'aceites', cat_label: 'Aceites' },
|
||||
{ id: 10, name: 'Aceite Pennzoil 10W-40', oem: 'PZ-10W40-1L', price: 175.00, cost: 120.00, stock: 50, category: 'aceites', cat_label: 'Aceites' },
|
||||
{ id: 11, name: 'Amortiguador Delantero', oem: 'KYB-339123', price: 1450.00, cost: 950.00, stock: 3, category: 'suspension', cat_label: 'Suspensión'},
|
||||
{ id: 12, name: 'Rótula de Dirección', oem: 'MOOG-K9648', price: 560.00, cost: 320.00, stock: 6, category: 'suspension', cat_label: 'Suspensión'},
|
||||
{ id: 13, name: 'Bobina de Encendido', oem: 'DELPHI-GN10570',price: 780.00, cost: 480.00, stock: 5, category: 'electrico', cat_label: 'Eléctrico' },
|
||||
{ id: 14, name: 'Sensor de Oxígeno Denso', oem: 'DENSO-234-4127',price: 920.00, cost: 610.00, stock: 4, category: 'electrico', cat_label: 'Eléctrico' },
|
||||
];
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
@@ -1857,6 +1941,8 @@
|
||||
const p = getProduct(item.productId);
|
||||
if (!p) return '';
|
||||
const lineTotal = p.price * item.qty;
|
||||
const margin = p.cost ? ((p.price - p.cost) / p.price * 100) : 0;
|
||||
const marginClass = margin > 30 ? 'margin-high' : margin > 15 ? 'margin-mid' : 'margin-low';
|
||||
return `
|
||||
<div class="cart-item" role="listitem" data-product-id="${p.id}">
|
||||
<div class="cart-item__qty-ctrl">
|
||||
@@ -1868,6 +1954,8 @@
|
||||
<div class="cart-item__name" title="${p.name}">${p.name}</div>
|
||||
<div class="cart-item__unit">${fmt(p.price)} c/u</div>
|
||||
</div>
|
||||
<div class="cart-item__cost">${p.cost ? fmt(p.cost) : '—'}</div>
|
||||
<div class="cart-item__margin ${marginClass}">${margin.toFixed(1)}%</div>
|
||||
<div class="cart-item__total">${fmt(lineTotal)}</div>
|
||||
<button class="cart-item__remove" data-id="${p.id}" aria-label="Eliminar ${p.name} del carrito">✕</button>
|
||||
</div>`;
|
||||
@@ -1890,8 +1978,41 @@
|
||||
|
||||
// disable cobrar if cart is empty
|
||||
$btnCobrar.disabled = state.cart.length === 0;
|
||||
|
||||
// update weighted average margin
|
||||
updateAvgMargin();
|
||||
}
|
||||
|
||||
function updateAvgMargin() {
|
||||
const $avg = document.getElementById('avgMargin');
|
||||
if (!$avg) return;
|
||||
let totalRevenue = 0, totalCost = 0;
|
||||
state.cart.forEach(item => {
|
||||
const p = getProduct(item.productId);
|
||||
if (p && p.cost) {
|
||||
totalRevenue += p.price * item.qty;
|
||||
totalCost += p.cost * item.qty;
|
||||
}
|
||||
});
|
||||
if (totalRevenue > 0) {
|
||||
const avg = ((totalRevenue - totalCost) / totalRevenue * 100);
|
||||
$avg.textContent = avg.toFixed(1) + '%';
|
||||
$avg.className = 'margin-summary__value ' +
|
||||
(avg > 30 ? 'margin-high' : avg > 15 ? 'margin-mid' : 'margin-low');
|
||||
$avg.style.color = avg > 30 ? 'var(--color-success)' : avg > 15 ? 'var(--color-warning)' : 'var(--color-error)';
|
||||
} else {
|
||||
$avg.textContent = '--';
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
COST/MARGIN TOGGLE (Admin/Owner only)
|
||||
------------------------------------------------------------------ */
|
||||
document.getElementById('costToggle').addEventListener('click', function() {
|
||||
document.body.classList.toggle('show-cost-columns');
|
||||
this.classList.toggle('active');
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
DISCOUNT INPUT
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
Reference in New Issue
Block a user