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:
606
docs/design/design-system/components/arbol-colapsable.html
Normal file
606
docs/design/design-system/components/arbol-colapsable.html
Normal file
@@ -0,0 +1,606 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Árbol Colapsable</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Tree controls */
|
||||
.tree-controls {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.tree-controls button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
background: var(--btn-ghost-bg);
|
||||
color: var(--btn-ghost-text);
|
||||
border: 1px solid var(--btn-ghost-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.tree-controls button:hover {
|
||||
background: var(--color-surface-2);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Tree container */
|
||||
.tree-container {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tree header */
|
||||
.tree-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-3) var(--space-5);
|
||||
background: var(--color-surface-1);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
}
|
||||
|
||||
/* Tree node */
|
||||
.tree-node {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.tree-node:last-child { border-bottom: none; }
|
||||
|
||||
.tree-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
gap: var(--space-2);
|
||||
}
|
||||
.tree-row:hover {
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
.tree-row.selected {
|
||||
background: var(--color-primary-muted);
|
||||
border-left: 3px solid var(--color-primary);
|
||||
}
|
||||
|
||||
/* Arrow */
|
||||
.tree-arrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted);
|
||||
transition: transform var(--duration-fast) var(--ease-out);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.tree-arrow:hover {
|
||||
background: var(--color-surface-3);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.tree-arrow.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.tree-arrow.leaf {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Account code */
|
||||
.tree-code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-accent);
|
||||
min-width: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Account name */
|
||||
.tree-name {
|
||||
flex: 1;
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.tree-row.level-0 .tree-name {
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--text-body);
|
||||
}
|
||||
.tree-row.level-1 .tree-name {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* Balance */
|
||||
.tree-balance {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: right;
|
||||
min-width: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tree-row.level-0 .tree-balance {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--text-body);
|
||||
}
|
||||
|
||||
/* Children container */
|
||||
.tree-children {
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
transition: max-height var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
.tree-children.expanded {
|
||||
max-height: 2000px;
|
||||
}
|
||||
|
||||
/* Indentation */
|
||||
.tree-row.level-0 { padding-left: var(--space-4); }
|
||||
.tree-row.level-1 { padding-left: calc(var(--space-4) + var(--space-6)); }
|
||||
.tree-row.level-2 { padding-left: calc(var(--space-4) + var(--space-6) * 2); }
|
||||
|
||||
/* Level-specific styling */
|
||||
.tree-node.level-0 > .tree-row {
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
.tree-node.level-0 > .tree-row:hover {
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
|
||||
/* Summary row */
|
||||
.tree-summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4) var(--space-5);
|
||||
background: var(--color-surface-2);
|
||||
border-top: 2px solid var(--color-border-accent);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.tree-summary .label {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-body);
|
||||
color: var(--color-text-primary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
.tree-summary .total {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body);
|
||||
color: var(--color-text-accent);
|
||||
}
|
||||
|
||||
/* Count badge */
|
||||
.child-count {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-disabled);
|
||||
font-family: var(--font-mono);
|
||||
margin-left: var(--space-1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Árbol Colapsable</h1>
|
||||
<p class="subtitle">Catálogo de cuentas SAT con estructura jerárquica colapsable</p>
|
||||
|
||||
<section>
|
||||
<h2>Catálogo de Cuentas</h2>
|
||||
|
||||
<div class="tree-controls">
|
||||
<button onclick="expandAll()">Expandir Todo</button>
|
||||
<button onclick="collapseAll()">Colapsar Todo</button>
|
||||
</div>
|
||||
|
||||
<div class="tree-container" id="accountTree">
|
||||
<div class="tree-header">
|
||||
<span>Cuenta</span>
|
||||
<span>Saldo</span>
|
||||
</div>
|
||||
|
||||
<!-- 1000 Activo -->
|
||||
<div class="tree-node level-0" data-code="1000">
|
||||
<div class="tree-row level-0" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">1000</span>
|
||||
<span class="tree-name">Activo <span class="child-count">(3)</span></span>
|
||||
<span class="tree-balance">$1,245,800.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<!-- 1100 Bancos -->
|
||||
<div class="tree-node level-1" data-code="1100">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">1100</span>
|
||||
<span class="tree-name">Bancos <span class="child-count">(3)</span></span>
|
||||
<span class="tree-balance">$485,320.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1101</span>
|
||||
<span class="tree-name">Banamex Cta. 7845</span>
|
||||
<span class="tree-balance">$198,450.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1102</span>
|
||||
<span class="tree-name">BBVA Cta. 3021</span>
|
||||
<span class="tree-balance">$245,870.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1103</span>
|
||||
<span class="tree-name">Banorte Cta. 5590</span>
|
||||
<span class="tree-balance">$41,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 1200 Inventarios -->
|
||||
<div class="tree-node level-1" data-code="1200">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">1200</span>
|
||||
<span class="tree-name">Inventarios <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$628,480.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1201</span>
|
||||
<span class="tree-name">Almacén General</span>
|
||||
<span class="tree-balance">$520,300.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1202</span>
|
||||
<span class="tree-name">Mercancía en Tránsito</span>
|
||||
<span class="tree-balance">$108,180.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 1300 Cuentas por Cobrar -->
|
||||
<div class="tree-node level-1" data-code="1300">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">1300</span>
|
||||
<span class="tree-name">Cuentas por Cobrar <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$132,000.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1301</span>
|
||||
<span class="tree-name">Clientes Nacionales</span>
|
||||
<span class="tree-balance">$118,500.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">1302</span>
|
||||
<span class="tree-name">Deudores Diversos</span>
|
||||
<span class="tree-balance">$13,500.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2000 Pasivo -->
|
||||
<div class="tree-node level-0" data-code="2000">
|
||||
<div class="tree-row level-0" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">2000</span>
|
||||
<span class="tree-name">Pasivo <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$389,200.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-1" data-code="2100">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">2100</span>
|
||||
<span class="tree-name">Proveedores <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$285,000.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">2101</span>
|
||||
<span class="tree-name">Proveedores Nacionales</span>
|
||||
<span class="tree-balance">$245,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">2102</span>
|
||||
<span class="tree-name">Proveedores Extranjeros</span>
|
||||
<span class="tree-balance">$40,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-1" data-code="2200">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">2200</span>
|
||||
<span class="tree-name">Impuestos por Pagar <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$104,200.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">2201</span>
|
||||
<span class="tree-name">IVA Trasladado</span>
|
||||
<span class="tree-balance">$72,200.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">2202</span>
|
||||
<span class="tree-name">ISR por Pagar</span>
|
||||
<span class="tree-balance">$32,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3000 Capital -->
|
||||
<div class="tree-node level-0" data-code="3000">
|
||||
<div class="tree-row level-0" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">3000</span>
|
||||
<span class="tree-name">Capital <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$500,000.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-1">
|
||||
<div class="tree-row level-1" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">3100</span>
|
||||
<span class="tree-name">Capital Social</span>
|
||||
<span class="tree-balance">$350,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-1">
|
||||
<div class="tree-row level-1" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">3200</span>
|
||||
<span class="tree-name">Utilidades Acumuladas</span>
|
||||
<span class="tree-balance">$150,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4000 Ingresos -->
|
||||
<div class="tree-node level-0" data-code="4000">
|
||||
<div class="tree-row level-0" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">4000</span>
|
||||
<span class="tree-name">Ingresos <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$892,450.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-1" data-code="4100">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">4100</span>
|
||||
<span class="tree-name">Ventas <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$845,200.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">4101</span>
|
||||
<span class="tree-name">Ventas Mostrador</span>
|
||||
<span class="tree-balance">$562,800.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">4102</span>
|
||||
<span class="tree-name">Ventas a Crédito</span>
|
||||
<span class="tree-balance">$282,400.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-1">
|
||||
<div class="tree-row level-1" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">4200</span>
|
||||
<span class="tree-name">Otros Ingresos</span>
|
||||
<span class="tree-balance">$47,250.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5000 Gastos -->
|
||||
<div class="tree-node level-0" data-code="5000">
|
||||
<div class="tree-row level-0" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">5000</span>
|
||||
<span class="tree-name">Gastos <span class="child-count">(2)</span></span>
|
||||
<span class="tree-balance">$536,050.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-1" data-code="5100">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">5100</span>
|
||||
<span class="tree-name">Costo de Ventas <span class="child-count">(1)</span></span>
|
||||
<span class="tree-balance">$421,500.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">5101</span>
|
||||
<span class="tree-name">Costo Mercancía Vendida</span>
|
||||
<span class="tree-balance">$421,500.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-1" data-code="5200">
|
||||
<div class="tree-row level-1" onclick="toggleNode(this)">
|
||||
<span class="tree-arrow">▶</span>
|
||||
<span class="tree-code">5200</span>
|
||||
<span class="tree-name">Gastos de Operación <span class="child-count">(3)</span></span>
|
||||
<span class="tree-balance">$114,550.00</span>
|
||||
</div>
|
||||
<div class="tree-children">
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">5201</span>
|
||||
<span class="tree-name">Sueldos y Salarios</span>
|
||||
<span class="tree-balance">$68,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">5202</span>
|
||||
<span class="tree-name">Renta Local</span>
|
||||
<span class="tree-balance">$28,000.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-node level-2">
|
||||
<div class="tree-row level-2" onclick="selectNode(this)">
|
||||
<span class="tree-arrow leaf">▶</span>
|
||||
<span class="tree-code">5203</span>
|
||||
<span class="tree-name">Servicios (Luz, Tel, Internet)</span>
|
||||
<span class="tree-balance">$18,550.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div class="tree-summary">
|
||||
<span class="label">Total Activos - Pasivos</span>
|
||||
<span class="total">$856,600.00</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function toggleNode(rowEl) {
|
||||
const node = rowEl.parentElement;
|
||||
const children = node.querySelector('.tree-children');
|
||||
const arrow = rowEl.querySelector('.tree-arrow');
|
||||
|
||||
if (!children) {
|
||||
selectNode(rowEl);
|
||||
return;
|
||||
}
|
||||
|
||||
const isExpanded = children.classList.contains('expanded');
|
||||
if (isExpanded) {
|
||||
children.classList.remove('expanded');
|
||||
arrow.classList.remove('expanded');
|
||||
} else {
|
||||
children.classList.add('expanded');
|
||||
arrow.classList.add('expanded');
|
||||
}
|
||||
|
||||
selectNode(rowEl);
|
||||
}
|
||||
|
||||
function selectNode(rowEl) {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll('.tree-row.selected').forEach(el => el.classList.remove('selected'));
|
||||
rowEl.classList.add('selected');
|
||||
}
|
||||
|
||||
function expandAll() {
|
||||
document.querySelectorAll('.tree-children').forEach(el => el.classList.add('expanded'));
|
||||
document.querySelectorAll('.tree-arrow:not(.leaf)').forEach(el => el.classList.add('expanded'));
|
||||
}
|
||||
|
||||
function collapseAll() {
|
||||
document.querySelectorAll('.tree-children').forEach(el => el.classList.remove('expanded'));
|
||||
document.querySelectorAll('.tree-arrow').forEach(el => el.classList.remove('expanded'));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
353
docs/design/design-system/components/badge-cfdi.html
Normal file
353
docs/design/design-system/components/badge-cfdi.html
Normal file
@@ -0,0 +1,353 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Badge CFDI</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Badge base */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
letter-spacing: var(--tracking-wide);
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid transparent;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Dot indicator */
|
||||
.badge .dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: var(--radius-full);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Pendiente — Blue / Primary */
|
||||
.badge-pendiente {
|
||||
background: var(--color-primary-muted);
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.badge-pendiente .dot {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Enviando — Yellow / Warning */
|
||||
.badge-enviando {
|
||||
background: var(--color-warning-light);
|
||||
color: var(--color-warning-dark);
|
||||
border-color: var(--color-warning);
|
||||
}
|
||||
.badge-enviando .dot {
|
||||
background: var(--color-warning);
|
||||
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Timbrada — Green / Success */
|
||||
.badge-timbrada {
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
.badge-timbrada .dot {
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
/* Fallida — Red / Error */
|
||||
.badge-fallida {
|
||||
background: var(--color-error-light);
|
||||
color: var(--color-error-dark);
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
.badge-fallida .dot {
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
/* Cancelada — Gray / Muted */
|
||||
.badge-cancelada {
|
||||
background: var(--color-surface-2);
|
||||
color: var(--color-text-muted);
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
.badge-cancelada .dot {
|
||||
background: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Pulse animation for Enviando */
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.4; transform: scale(0.75); }
|
||||
}
|
||||
|
||||
/* Badge gallery */
|
||||
.badge-gallery {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
padding: var(--space-5);
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
/* Badge sizes */
|
||||
.badge-lg {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
.badge-lg .dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* CFDI Table */
|
||||
.cfdi-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
.cfdi-table thead {
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
.cfdi-table th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.cfdi-table td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-secondary);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cfdi-table tbody tr {
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.cfdi-table tbody tr:hover {
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
.cfdi-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.cfdi-table .folio {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.cfdi-table .uuid {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.cfdi-table .amount {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.cfdi-table th.col-amount { text-align: right; }
|
||||
|
||||
/* Date column */
|
||||
.cfdi-table .date {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Legend */
|
||||
.legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Badge CFDI</h1>
|
||||
<p class="subtitle">Indicadores de estatus para comprobantes fiscales digitales (CFDI)</p>
|
||||
|
||||
<!-- All badges inline -->
|
||||
<section>
|
||||
<h2>Variantes de Badge</h2>
|
||||
<div class="badge-gallery">
|
||||
<span class="badge badge-pendiente"><span class="dot"></span>Pendiente</span>
|
||||
<span class="badge badge-enviando"><span class="dot"></span>Enviando</span>
|
||||
<span class="badge badge-timbrada"><span class="dot"></span>Timbrada</span>
|
||||
<span class="badge badge-fallida"><span class="dot"></span>Fallida</span>
|
||||
<span class="badge badge-cancelada"><span class="dot"></span>Cancelada</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Large size -->
|
||||
<section>
|
||||
<h2>Tamaño Grande</h2>
|
||||
<div class="badge-gallery">
|
||||
<span class="badge badge-lg badge-pendiente"><span class="dot"></span>Pendiente</span>
|
||||
<span class="badge badge-lg badge-enviando"><span class="dot"></span>Enviando</span>
|
||||
<span class="badge badge-lg badge-timbrada"><span class="dot"></span>Timbrada</span>
|
||||
<span class="badge badge-lg badge-fallida"><span class="dot"></span>Fallida</span>
|
||||
<span class="badge badge-lg badge-cancelada"><span class="dot"></span>Cancelada</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Legend -->
|
||||
<section>
|
||||
<h2>Leyenda</h2>
|
||||
<div class="legend">
|
||||
<div class="legend-item"><span class="badge badge-pendiente"><span class="dot"></span>Pendiente</span> Generada, sin enviar al PAC</div>
|
||||
<div class="legend-item"><span class="badge badge-enviando"><span class="dot"></span>Enviando</span> En proceso de timbrado</div>
|
||||
<div class="legend-item"><span class="badge badge-timbrada"><span class="dot"></span>Timbrada</span> Timbrada correctamente por el SAT</div>
|
||||
<div class="legend-item"><span class="badge badge-fallida"><span class="dot"></span>Fallida</span> Error al timbrar</div>
|
||||
<div class="legend-item"><span class="badge badge-cancelada"><span class="dot"></span>Cancelada</span> Cancelada ante el SAT</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Table context -->
|
||||
<section>
|
||||
<h2>En Contexto: Lista de CFDI</h2>
|
||||
<table class="cfdi-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Folio</th>
|
||||
<th>Cliente</th>
|
||||
<th>UUID</th>
|
||||
<th>Fecha</th>
|
||||
<th class="col-amount">Total</th>
|
||||
<th>Estatus</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="folio">FA-00142</td>
|
||||
<td>Taller Hermanos López</td>
|
||||
<td class="uuid">a8d2e1f4-3b7c-...</td>
|
||||
<td class="date">01/Abr/2026</td>
|
||||
<td class="amount">$18,450.00</td>
|
||||
<td><span class="badge badge-timbrada"><span class="dot"></span>Timbrada</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="folio">FA-00141</td>
|
||||
<td>Roberto Méndez G.</td>
|
||||
<td class="uuid">c3f1a902-7d4e-...</td>
|
||||
<td class="date">01/Abr/2026</td>
|
||||
<td class="amount">$4,828.50</td>
|
||||
<td><span class="badge badge-enviando"><span class="dot"></span>Enviando</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="folio">FA-00140</td>
|
||||
<td>Auto Partes del Norte SA</td>
|
||||
<td class="uuid">--</td>
|
||||
<td class="date">31/Mar/2026</td>
|
||||
<td class="amount">$32,100.00</td>
|
||||
<td><span class="badge badge-pendiente"><span class="dot"></span>Pendiente</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="folio">FA-00139</td>
|
||||
<td>María Elena Ríos</td>
|
||||
<td class="uuid">f7e2d301-1a9b-...</td>
|
||||
<td class="date">30/Mar/2026</td>
|
||||
<td class="amount">$1,250.00</td>
|
||||
<td><span class="badge badge-timbrada"><span class="dot"></span>Timbrada</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="folio">FA-00138</td>
|
||||
<td>Servicio Automotriz Reyes</td>
|
||||
<td class="uuid">b1c4d5e6-8f2a-...</td>
|
||||
<td class="date">29/Mar/2026</td>
|
||||
<td class="amount">$7,680.00</td>
|
||||
<td><span class="badge badge-fallida"><span class="dot"></span>Fallida</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="folio">FA-00137</td>
|
||||
<td>Transportes Garza</td>
|
||||
<td class="uuid">e9a8b7c6-5d4f-...</td>
|
||||
<td class="date">28/Mar/2026</td>
|
||||
<td class="amount">$55,200.00</td>
|
||||
<td><span class="badge badge-cancelada"><span class="dot"></span>Cancelada</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="folio">FA-00136</td>
|
||||
<td>Refaccionaria Central</td>
|
||||
<td class="uuid">d2f3e4a5-6b7c-...</td>
|
||||
<td class="date">27/Mar/2026</td>
|
||||
<td class="amount">$9,320.00</td>
|
||||
<td><span class="badge badge-timbrada"><span class="dot"></span>Timbrada</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
379
docs/design/design-system/components/banner-cliente.html
Normal file
379
docs/design/design-system/components/banner-cliente.html
Normal file
@@ -0,0 +1,379 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Banner Cliente</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.label-text {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* ====== Simulated POS Panel ====== */
|
||||
.pos-panel {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
max-width: 420px;
|
||||
}
|
||||
.pos-panel-header {
|
||||
background: var(--color-surface-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* ====== Banner Cliente ====== */
|
||||
.banner-cliente {
|
||||
padding: var(--space-4);
|
||||
border-bottom: 2px solid var(--color-primary);
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
|
||||
.banner-top {
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.banner-avatar {
|
||||
width: 44px; height: 44px; border-radius: var(--radius-full);
|
||||
background: var(--color-primary);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: var(--font-heading); font-size: var(--text-body-lg);
|
||||
font-weight: var(--font-weight-bold); color: var(--color-text-inverse);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.banner-name-block {
|
||||
flex: 1; min-width: 0;
|
||||
}
|
||||
.banner-name {
|
||||
font-family: var(--font-heading); font-size: var(--text-body-lg);
|
||||
font-weight: var(--heading-weight-primary); color: var(--color-text-primary);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.banner-rfc {
|
||||
font-family: var(--font-mono); font-size: var(--text-caption);
|
||||
color: var(--color-text-muted); letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
.banner-close {
|
||||
width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
|
||||
background: transparent; border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
color: var(--color-text-muted); font-size: 14px;
|
||||
transition: var(--transition-fast); flex-shrink: 0;
|
||||
}
|
||||
.banner-close:hover { background: var(--color-surface-2); color: var(--color-text-primary); }
|
||||
|
||||
/* Info Grid */
|
||||
.banner-info {
|
||||
display: grid; grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
.banner-info-item {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
.banner-info-label {
|
||||
font-size: 10px; color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.banner-info-value {
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary); margin-top: 1px;
|
||||
}
|
||||
.banner-info-value.credit-ok { color: var(--color-success); }
|
||||
.banner-info-value.credit-low { color: var(--color-warning-dark); }
|
||||
.banner-info-value.credit-none { color: var(--color-error); }
|
||||
|
||||
.banner-info-item.full-width { grid-column: 1 / -1; }
|
||||
|
||||
/* Vehicle tag */
|
||||
.vehicle-tag {
|
||||
display: inline-flex; align-items: center; gap: var(--space-1);
|
||||
padding: 2px var(--space-2);
|
||||
background: var(--color-surface-2);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--text-caption); color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* ====== Variants Container ====== */
|
||||
.variants-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
/* ====== Compact Banner (for narrow panel) ====== */
|
||||
.banner-cliente.compact {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
.banner-cliente.compact .banner-top { margin-bottom: var(--space-2); }
|
||||
.banner-cliente.compact .banner-avatar { width: 36px; height: 36px; font-size: var(--text-body-sm); }
|
||||
.banner-cliente.compact .banner-name { font-size: var(--text-body); }
|
||||
.banner-cliente.compact .banner-info { grid-template-columns: 1fr 1fr 1fr; }
|
||||
|
||||
/* ====== No Client State ====== */
|
||||
.banner-empty {
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.banner-empty-icon {
|
||||
width: 44px; height: 44px; border-radius: var(--radius-full);
|
||||
background: var(--color-surface-2);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px; color: var(--color-text-muted);
|
||||
}
|
||||
.banner-empty-text {
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
.banner-empty-text strong {
|
||||
display: block; color: var(--color-text-secondary); margin-bottom: 2px;
|
||||
}
|
||||
.banner-empty .add-btn {
|
||||
margin-left: auto;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--btn-secondary-bg); color: var(--btn-secondary-text);
|
||||
border: 1px solid var(--btn-secondary-border); border-radius: var(--radius-md);
|
||||
font-size: var(--text-caption); font-weight: var(--font-weight-semibold);
|
||||
cursor: pointer; transition: var(--transition-fast); white-space: nowrap;
|
||||
}
|
||||
.banner-empty .add-btn:hover { background: var(--btn-secondary-bg-hover); }
|
||||
|
||||
/* ====== Warning banner ====== */
|
||||
.banner-warning {
|
||||
display: flex; align-items: center; gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: var(--color-error-light);
|
||||
font-size: var(--text-caption); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-error-dark);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Theme Switcher -->
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="setTheme('industrial')">A — Industrial Robusto</button>
|
||||
<button onclick="setTheme('modern')">B — Técnico Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Banner Cliente</h1>
|
||||
<p class="subtitle">Información inline del cliente seleccionado en el panel derecho del POS.</p>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Variants -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Variantes del Banner</h2>
|
||||
|
||||
<div class="variants-grid">
|
||||
|
||||
<!-- Standard: Client with credit -->
|
||||
<div>
|
||||
<p class="label-text">Cliente con crédito disponible</p>
|
||||
<div class="pos-panel">
|
||||
<div class="pos-panel-header">Panel de Venta</div>
|
||||
<div class="banner-cliente">
|
||||
<div class="banner-top">
|
||||
<div class="banner-avatar">TG</div>
|
||||
<div class="banner-name-block">
|
||||
<div class="banner-name">Taller Automotriz García</div>
|
||||
<div class="banner-rfc">RFC: TAU150301XX1</div>
|
||||
</div>
|
||||
<button class="banner-close">✕</button>
|
||||
</div>
|
||||
<div class="banner-info">
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Crédito disponible</div>
|
||||
<div class="banner-info-value credit-ok">$15,420.00</div>
|
||||
</div>
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Última compra</div>
|
||||
<div class="banner-info-value">28/Mar/2026</div>
|
||||
</div>
|
||||
<div class="banner-info-item full-width">
|
||||
<div class="banner-info-label">Vehículo registrado</div>
|
||||
<div class="banner-info-value">
|
||||
<span class="vehicle-tag">🚗 Nissan Sentra 2019 — NIS-B17-19</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Low credit warning -->
|
||||
<div>
|
||||
<p class="label-text">Cliente con crédito bajo</p>
|
||||
<div class="pos-panel">
|
||||
<div class="pos-panel-header">Panel de Venta</div>
|
||||
<div class="banner-cliente">
|
||||
<div class="banner-top">
|
||||
<div class="banner-avatar">RM</div>
|
||||
<div class="banner-name-block">
|
||||
<div class="banner-name">Refaccionaria Mendoza</div>
|
||||
<div class="banner-rfc">RFC: RME180512XX3</div>
|
||||
</div>
|
||||
<button class="banner-close">✕</button>
|
||||
</div>
|
||||
<div class="banner-info">
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Crédito disponible</div>
|
||||
<div class="banner-info-value credit-low">$2,100.00</div>
|
||||
</div>
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Última compra</div>
|
||||
<div class="banner-info-value">15/Mar/2026</div>
|
||||
</div>
|
||||
<div class="banner-info-item full-width">
|
||||
<div class="banner-info-label">Vehículo registrado</div>
|
||||
<div class="banner-info-value">
|
||||
<span class="vehicle-tag">🚙 Ford F-150 2021 — FOR-F15-21</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="banner-warning">⚠ Crédito bajo — Límite: $25,000 | Utilizado: $22,900</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No credit (public) -->
|
||||
<div>
|
||||
<p class="label-text">Cliente sin crédito (público general)</p>
|
||||
<div class="pos-panel">
|
||||
<div class="pos-panel-header">Panel de Venta</div>
|
||||
<div class="banner-cliente">
|
||||
<div class="banner-top">
|
||||
<div class="banner-avatar">JL</div>
|
||||
<div class="banner-name-block">
|
||||
<div class="banner-name">Juan López Hernández</div>
|
||||
<div class="banner-rfc">RFC: LOHJ850214XX5</div>
|
||||
</div>
|
||||
<button class="banner-close">✕</button>
|
||||
</div>
|
||||
<div class="banner-info">
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Crédito</div>
|
||||
<div class="banner-info-value credit-none">No aplica</div>
|
||||
</div>
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Última compra</div>
|
||||
<div class="banner-info-value">Primera visita</div>
|
||||
</div>
|
||||
<div class="banner-info-item full-width">
|
||||
<div class="banner-info-label">Vehículo</div>
|
||||
<div class="banner-info-value" style="color: var(--color-text-muted);">No registrado</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State: No client selected -->
|
||||
<div>
|
||||
<p class="label-text">Sin cliente seleccionado</p>
|
||||
<div class="pos-panel">
|
||||
<div class="pos-panel-header">Panel de Venta</div>
|
||||
<div class="banner-empty">
|
||||
<div class="banner-empty-icon">👤</div>
|
||||
<div class="banner-empty-text">
|
||||
<strong>Sin cliente</strong>
|
||||
Venta a público general (F2 para buscar)
|
||||
</div>
|
||||
<button class="add-btn">+ Cliente</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Compact Version -->
|
||||
<div>
|
||||
<p class="label-text">Versión compacta (panel angosto)</p>
|
||||
<div class="pos-panel" style="max-width: 360px;">
|
||||
<div class="pos-panel-header">Panel de Venta — Compacto</div>
|
||||
<div class="banner-cliente compact">
|
||||
<div class="banner-top">
|
||||
<div class="banner-avatar">TG</div>
|
||||
<div class="banner-name-block">
|
||||
<div class="banner-name">Taller Automotriz García</div>
|
||||
<div class="banner-rfc">TAU150301XX1</div>
|
||||
</div>
|
||||
<button class="banner-close">✕</button>
|
||||
</div>
|
||||
<div class="banner-info">
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Crédito</div>
|
||||
<div class="banner-info-value credit-ok">$15,420</div>
|
||||
</div>
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Compra</div>
|
||||
<div class="banner-info-value">28/Mar</div>
|
||||
</div>
|
||||
<div class="banner-info-item">
|
||||
<div class="banner-info-label">Vehículo</div>
|
||||
<div class="banner-info-value">Sentra '19</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.querySelectorAll('.theme-switcher button').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
394
docs/design/design-system/components/banner-offline.html
Normal file
394
docs/design/design-system/components/banner-offline.html
Normal file
@@ -0,0 +1,394 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Banner Offline</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* === Banner Offline Component === */
|
||||
|
||||
@keyframes slideDown {
|
||||
from { transform: translateY(-100%); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(0); opacity: 1; }
|
||||
to { transform: translateY(-100%); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
0% { content: ''; }
|
||||
25% { content: '.'; }
|
||||
50% { content: '..'; }
|
||||
75% { content: '...'; }
|
||||
100% { content: ''; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
animation: slideDown 0.35s ease-out forwards;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner--dismissing {
|
||||
animation: slideUp 0.3s ease-in forwards;
|
||||
}
|
||||
|
||||
.banner__icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.banner__text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.banner__text strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.banner__dismiss {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
padding: var(--space-1);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.banner__dismiss:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Warning variant */
|
||||
.banner--warning {
|
||||
background: var(--color-warning-light);
|
||||
color: var(--color-warning-dark);
|
||||
border: 1px solid var(--color-warning);
|
||||
}
|
||||
|
||||
.banner--warning .banner__dismiss {
|
||||
color: var(--color-warning-dark);
|
||||
}
|
||||
|
||||
/* Error variant */
|
||||
.banner--error {
|
||||
background: var(--color-error-light);
|
||||
color: var(--color-error-dark);
|
||||
border: 1px solid var(--color-error);
|
||||
}
|
||||
|
||||
.banner--error .banner__dismiss {
|
||||
color: var(--color-error-dark);
|
||||
}
|
||||
|
||||
.banner--error .dots {
|
||||
display: inline-block;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Success variant */
|
||||
.banner--success {
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
border: 1px solid var(--color-success);
|
||||
}
|
||||
|
||||
.banner--success .banner__dismiss {
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
|
||||
/* Demo controls */
|
||||
.demo-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
.demo-btn {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.demo-btn:hover {
|
||||
background: var(--color-surface-2);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.demo-btn--warning { border-color: var(--color-warning); color: var(--color-warning-dark); }
|
||||
.demo-btn--error { border-color: var(--color-error); color: var(--color-error-dark); }
|
||||
.demo-btn--success { border-color: var(--color-success); color: var(--color-success-dark); }
|
||||
.demo-btn--all { background: var(--btn-primary-bg); color: var(--btn-primary-text); border-color: var(--btn-primary-border); }
|
||||
|
||||
/* Banners container */
|
||||
.banners-live {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-8);
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* Static examples */
|
||||
.variant-card {
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-5);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.variant-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-bg-overlay);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: var(--radius-full);
|
||||
display: inline-block;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* Full-width fixed preview */
|
||||
.preview-frame {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.preview-frame__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-surface-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.preview-frame__dot {
|
||||
width: 10px; height: 10px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-border-strong);
|
||||
}
|
||||
|
||||
.preview-frame__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.preview-frame__body .banner {
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.preview-frame__content {
|
||||
padding: var(--space-6) var(--space-4);
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
<h1>Banner Offline</h1>
|
||||
<p class="subtitle">Banners de estado de conexión: advertencia, error y reconexión exitosa.</p>
|
||||
|
||||
<!-- ========== Interactive demo ========== -->
|
||||
<section>
|
||||
<h2>Demo interactivo</h2>
|
||||
<p style="color: var(--color-text-muted); font-size: var(--text-body-sm); margin-bottom: var(--space-4);">
|
||||
Haz clic en los botones para mostrar cada variante. Los banners se pueden cerrar con el botón × o simulan auto-dismiss.
|
||||
</p>
|
||||
<div class="demo-controls">
|
||||
<button class="demo-btn demo-btn--warning" onclick="showBanner('warning')">Mostrar Warning</button>
|
||||
<button class="demo-btn demo-btn--error" onclick="showBanner('error')">Mostrar Error</button>
|
||||
<button class="demo-btn demo-btn--success" onclick="showBanner('success')">Mostrar Success</button>
|
||||
<button class="demo-btn demo-btn--all" onclick="showBanner('warning'); setTimeout(()=>showBanner('error'),200); setTimeout(()=>showBanner('success'),400)">Mostrar todos</button>
|
||||
</div>
|
||||
<div class="banners-live" id="bannersLive"></div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Static variants ========== -->
|
||||
<section>
|
||||
<h2>Variantes estáticas</h2>
|
||||
|
||||
<!-- a) Warning -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">a) Warning — Modo offline</span>
|
||||
<div class="banner banner--warning" style="animation: none;">
|
||||
<span class="banner__icon">⚠️</span>
|
||||
<span class="banner__text"><strong>Modo offline</strong> — Funciones limitadas. Solo consultas en caché disponibles.</span>
|
||||
<button class="banner__dismiss" onclick="this.closest('.banner').style.display='none'" aria-label="Cerrar">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- b) Error -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">b) Error — Conexión perdida</span>
|
||||
<div class="banner banner--error" style="animation: none;">
|
||||
<span class="banner__icon">🔴</span>
|
||||
<span class="banner__text"><strong>Conexión perdida</strong> — Intentando reconectar<span class="dots">...</span></span>
|
||||
<button class="banner__dismiss" onclick="this.closest('.banner').style.display='none'" aria-label="Cerrar">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- c) Success -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">c) Success — Conexión restaurada (auto-dismiss)</span>
|
||||
<div class="banner banner--success" style="animation: none;">
|
||||
<span class="banner__icon">✅</span>
|
||||
<span class="banner__text"><strong>Conexión restaurada</strong> — Sincronizando datos...</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== In-context preview ========== -->
|
||||
<section>
|
||||
<h2>Uso en contexto: Header de aplicación</h2>
|
||||
<div class="preview-frame">
|
||||
<div class="preview-frame__header">
|
||||
<div class="preview-frame__dot"></div>
|
||||
<div class="preview-frame__dot"></div>
|
||||
<div class="preview-frame__dot"></div>
|
||||
<span style="font-size: var(--text-body-sm); color: var(--color-text-muted); margin-left: var(--space-2);">Nexus Autoparts POS</span>
|
||||
</div>
|
||||
<div class="preview-frame__body">
|
||||
<div class="banner banner--warning" style="animation: none;">
|
||||
<span class="banner__icon">⚠️</span>
|
||||
<span class="banner__text"><strong>Modo offline</strong> — Funciones limitadas. Solo consultas en caché disponibles.</span>
|
||||
<button class="banner__dismiss" onclick="this.closest('.banner').style.display='none'" aria-label="Cerrar">×</button>
|
||||
</div>
|
||||
<div class="preview-frame__content">
|
||||
<p>Contenido de la aplicación debajo del banner...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
let bannerCounter = 0;
|
||||
|
||||
const bannerTemplates = {
|
||||
warning: {
|
||||
icon: '⚠️',
|
||||
html: '<strong>Modo offline</strong> — Funciones limitadas. Solo consultas en caché disponibles.',
|
||||
cls: 'banner--warning',
|
||||
autoDismiss: false
|
||||
},
|
||||
error: {
|
||||
icon: '🔴',
|
||||
html: '<strong>Conexión perdida</strong> — Intentando reconectar<span class="dots">...</span>',
|
||||
cls: 'banner--error',
|
||||
autoDismiss: false
|
||||
},
|
||||
success: {
|
||||
icon: '✅',
|
||||
html: '<strong>Conexión restaurada</strong> — Sincronizando datos...',
|
||||
cls: 'banner--success',
|
||||
autoDismiss: true
|
||||
}
|
||||
};
|
||||
|
||||
function showBanner(type) {
|
||||
const tpl = bannerTemplates[type];
|
||||
const id = 'banner-' + (++bannerCounter);
|
||||
const container = document.getElementById('bannersLive');
|
||||
|
||||
const banner = document.createElement('div');
|
||||
banner.className = 'banner ' + tpl.cls;
|
||||
banner.id = id;
|
||||
banner.innerHTML = `
|
||||
<span class="banner__icon">${tpl.icon}</span>
|
||||
<span class="banner__text">${tpl.html}</span>
|
||||
<button class="banner__dismiss" onclick="dismissBanner('${id}')" aria-label="Cerrar">×</button>
|
||||
`;
|
||||
|
||||
container.appendChild(banner);
|
||||
|
||||
if (tpl.autoDismiss) {
|
||||
setTimeout(() => dismissBanner(id), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function dismissBanner(id) {
|
||||
const banner = document.getElementById(id);
|
||||
if (!banner || banner.classList.contains('banner--dismissing')) return;
|
||||
banner.classList.add('banner--dismissing');
|
||||
banner.addEventListener('animationend', () => banner.remove(), { once: true });
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
404
docs/design/design-system/components/calculadora-cambio.html
Normal file
404
docs/design/design-system/components/calculadora-cambio.html
Normal file
@@ -0,0 +1,404 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Calculadora de Cambio</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Calculator Container */
|
||||
.calc-container {
|
||||
max-width: 520px;
|
||||
margin: 0 auto;
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-8);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
/* Total display */
|
||||
.total-display {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: var(--space-4) var(--space-5);
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.total-display .label {
|
||||
font-size: var(--text-body);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
}
|
||||
.total-display .amount {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-h3);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Input area */
|
||||
.input-group {
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: var(--text-label);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.input-group .input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.input-group .currency-sign {
|
||||
position: absolute;
|
||||
left: var(--space-4);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-h2);
|
||||
color: var(--color-text-muted);
|
||||
pointer-events: none;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: var(--space-4) var(--space-4) var(--space-4) var(--space-10);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-h2);
|
||||
font-weight: var(--font-weight-bold);
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-primary);
|
||||
border: 2px solid var(--color-border-strong);
|
||||
border-radius: var(--radius-md);
|
||||
outline: none;
|
||||
transition: var(--transition-fast);
|
||||
text-align: right;
|
||||
}
|
||||
.input-group input:focus {
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: var(--shadow-focus);
|
||||
}
|
||||
.input-group input::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
/* Quick buttons */
|
||||
.quick-buttons {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-6);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.quick-btn {
|
||||
flex: 1;
|
||||
min-width: 80px;
|
||||
padding: var(--space-3) var(--space-3);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
background: var(--color-surface-2);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
text-align: center;
|
||||
}
|
||||
.quick-btn:hover {
|
||||
background: var(--color-primary-muted);
|
||||
border-color: var(--color-border-accent);
|
||||
color: var(--color-text-accent);
|
||||
}
|
||||
.quick-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
.quick-btn.exact {
|
||||
font-family: var(--font-body);
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
border-color: var(--btn-secondary-border);
|
||||
}
|
||||
.quick-btn.exact:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
}
|
||||
|
||||
/* Change display */
|
||||
.change-display {
|
||||
padding: var(--space-5) var(--space-6);
|
||||
border-radius: var(--radius-lg);
|
||||
text-align: center;
|
||||
transition: var(--transition-normal);
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
.change-display .change-label {
|
||||
font-size: var(--text-body-sm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
.change-display .change-amount {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-h1);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--leading-h1);
|
||||
}
|
||||
.change-display.positive {
|
||||
background: var(--color-success-light);
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
.change-display.positive .change-label {
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
.change-display.positive .change-amount {
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
.change-display.negative {
|
||||
background: var(--color-error-light);
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
.change-display.negative .change-label {
|
||||
color: var(--color-error-dark);
|
||||
}
|
||||
.change-display.negative .change-amount {
|
||||
color: var(--color-error-dark);
|
||||
}
|
||||
.change-display.zero {
|
||||
background: var(--color-surface-2);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
.change-display.zero .change-label {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.change-display.zero .change-amount {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.change-display.empty {
|
||||
background: var(--color-surface-1);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
.change-display.empty .change-label {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
.change-display.empty .change-amount {
|
||||
color: var(--color-text-disabled);
|
||||
font-size: var(--text-h3);
|
||||
}
|
||||
|
||||
/* Denomination breakdown */
|
||||
.breakdown {
|
||||
margin-top: var(--space-5);
|
||||
padding: var(--space-4);
|
||||
background: var(--color-surface-1);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
display: none;
|
||||
}
|
||||
.breakdown.visible { display: block; }
|
||||
.breakdown-title {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-widest);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.breakdown-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-1) 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.breakdown-row .denom { color: var(--color-text-muted); }
|
||||
.breakdown-row .count { color: var(--color-text-accent); font-weight: var(--font-weight-semibold); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Calculadora de Cambio</h1>
|
||||
<p class="subtitle">Calcula el cambio a devolver al cliente en tiempo real</p>
|
||||
|
||||
<section>
|
||||
<div class="calc-container">
|
||||
<!-- Total -->
|
||||
<div class="total-display">
|
||||
<span class="label">Total a cobrar</span>
|
||||
<span class="amount" id="totalAmount">$4,828.50</span>
|
||||
</div>
|
||||
|
||||
<!-- Input -->
|
||||
<div class="input-group">
|
||||
<label for="received">Monto Recibido</label>
|
||||
<div class="input-wrapper">
|
||||
<span class="currency-sign">$</span>
|
||||
<input type="text" id="received" placeholder="0.00" autocomplete="off" inputmode="decimal">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick buttons -->
|
||||
<div class="quick-buttons">
|
||||
<button class="quick-btn" onclick="setAmount(500)">$500</button>
|
||||
<button class="quick-btn" onclick="setAmount(1000)">$1,000</button>
|
||||
<button class="quick-btn" onclick="setAmount(2000)">$2,000</button>
|
||||
<button class="quick-btn" onclick="setAmount(5000)">$5,000</button>
|
||||
<button class="quick-btn exact" onclick="setExact()">Exacto</button>
|
||||
</div>
|
||||
|
||||
<!-- Change display -->
|
||||
<div class="change-display empty" id="changeDisplay">
|
||||
<div class="change-label">Cambio</div>
|
||||
<div class="change-amount" id="changeAmount">Ingrese monto</div>
|
||||
</div>
|
||||
|
||||
<!-- Breakdown -->
|
||||
<div class="breakdown" id="breakdown">
|
||||
<div class="breakdown-title">Desglose sugerido</div>
|
||||
<div id="breakdownRows"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const TOTAL = 4828.50;
|
||||
const input = document.getElementById('received');
|
||||
const changeDisplay = document.getElementById('changeDisplay');
|
||||
const changeAmount = document.getElementById('changeAmount');
|
||||
const breakdown = document.getElementById('breakdown');
|
||||
const breakdownRows = document.getElementById('breakdownRows');
|
||||
|
||||
function formatCurrency(n) {
|
||||
return '$' + n.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function setAmount(val) {
|
||||
input.value = val.toLocaleString('es-MX');
|
||||
calculate();
|
||||
input.focus();
|
||||
}
|
||||
|
||||
function setExact() {
|
||||
input.value = TOTAL.toLocaleString('es-MX');
|
||||
calculate();
|
||||
input.focus();
|
||||
}
|
||||
|
||||
function parseInput(str) {
|
||||
if (!str) return NaN;
|
||||
// Remove commas and spaces
|
||||
let cleaned = str.replace(/[,\s]/g, '');
|
||||
return parseFloat(cleaned);
|
||||
}
|
||||
|
||||
function getBreakdown(amount) {
|
||||
const denoms = [1000, 500, 200, 100, 50, 20, 10, 5, 2, 1, 0.50];
|
||||
const denomLabels = ['$1,000', '$500', '$200', '$100', '$50', '$20', '$10', '$5', '$2', '$1', '$0.50'];
|
||||
let remaining = Math.round(amount * 100) / 100;
|
||||
const result = [];
|
||||
for (let i = 0; i < denoms.length; i++) {
|
||||
const count = Math.floor(remaining / denoms[i]);
|
||||
if (count > 0) {
|
||||
result.push({ denom: denomLabels[i], count: count });
|
||||
remaining = Math.round((remaining - count * denoms[i]) * 100) / 100;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function calculate() {
|
||||
const received = parseInput(input.value);
|
||||
|
||||
if (isNaN(received) || input.value.trim() === '') {
|
||||
changeDisplay.className = 'change-display empty';
|
||||
changeAmount.textContent = 'Ingrese monto';
|
||||
breakdown.classList.remove('visible');
|
||||
return;
|
||||
}
|
||||
|
||||
const change = Math.round((received - TOTAL) * 100) / 100;
|
||||
|
||||
if (change > 0) {
|
||||
changeDisplay.className = 'change-display positive';
|
||||
changeAmount.textContent = formatCurrency(change);
|
||||
// Show breakdown
|
||||
const rows = getBreakdown(change);
|
||||
breakdownRows.innerHTML = rows.map(r =>
|
||||
`<div class="breakdown-row"><span class="denom">${r.denom}</span><span class="count">× ${r.count}</span></div>`
|
||||
).join('');
|
||||
breakdown.classList.add('visible');
|
||||
} else if (change < 0) {
|
||||
changeDisplay.className = 'change-display negative';
|
||||
changeAmount.textContent = '-' + formatCurrency(Math.abs(change));
|
||||
breakdown.classList.remove('visible');
|
||||
} else {
|
||||
changeDisplay.className = 'change-display zero';
|
||||
changeAmount.textContent = formatCurrency(0);
|
||||
breakdown.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
input.addEventListener('input', calculate);
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
input.value = '';
|
||||
calculate();
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-focus on load
|
||||
input.focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
507
docs/design/design-system/components/columnas-costo-margen.html
Normal file
507
docs/design/design-system/components/columnas-costo-margen.html
Normal file
@@ -0,0 +1,507 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Columnas Costo y Margen</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.label-text {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* ====== Toggle Permission ====== */
|
||||
.perm-toggle {
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.perm-toggle label {
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary); cursor: pointer;
|
||||
}
|
||||
.toggle-switch {
|
||||
position: relative; width: 44px; height: 24px;
|
||||
}
|
||||
.toggle-switch input {
|
||||
opacity: 0; width: 0; height: 0;
|
||||
}
|
||||
.toggle-slider {
|
||||
position: absolute; inset: 0;
|
||||
background: var(--color-border-strong);
|
||||
border-radius: var(--radius-full);
|
||||
cursor: pointer; transition: var(--transition-fast);
|
||||
}
|
||||
.toggle-slider::after {
|
||||
content: ''; position: absolute;
|
||||
left: 3px; top: 3px;
|
||||
width: 18px; height: 18px;
|
||||
background: var(--color-text-primary);
|
||||
border-radius: var(--radius-full);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.toggle-switch input:checked + .toggle-slider {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
.toggle-switch input:checked + .toggle-slider::after {
|
||||
transform: translateX(20px);
|
||||
background: var(--color-text-inverse);
|
||||
}
|
||||
.perm-badge {
|
||||
font-size: var(--text-caption); font-weight: var(--font-weight-semibold);
|
||||
padding: 2px var(--space-2); border-radius: var(--radius-sm);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
.perm-badge.admin { background: var(--color-primary-muted); color: var(--color-text-accent); }
|
||||
.perm-badge.owner { background: var(--color-success-light); color: var(--color-success-dark); }
|
||||
|
||||
/* ====== Table ====== */
|
||||
.sale-table-container {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.sale-table {
|
||||
width: 100%; border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
.sale-table thead {
|
||||
background: var(--color-surface-2);
|
||||
}
|
||||
.sale-table th {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left; font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-muted); font-size: var(--text-caption);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
border-bottom: 2px solid var(--color-border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sale-table td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.sale-table tbody tr:hover {
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
.sale-table th.right, .sale-table td.right { text-align: right; }
|
||||
.sale-table th.center, .sale-table td.center { text-align: center; }
|
||||
|
||||
/* Product cell */
|
||||
.product-cell {
|
||||
display: flex; flex-direction: column; gap: 2px;
|
||||
}
|
||||
.product-name {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.product-sku {
|
||||
font-family: var(--font-mono); font-size: 11px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Price cells */
|
||||
.price {
|
||||
font-family: var(--font-mono); font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* ====== Margin Column Styles ====== */
|
||||
.col-cost, .col-margin {
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.col-cost.hidden, .col-margin.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cost-value {
|
||||
font-family: var(--font-mono); font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.margin-badge {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 2px var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: var(--font-mono); font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-bold);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.margin-high {
|
||||
background: var(--color-success-light); color: var(--color-success-dark);
|
||||
}
|
||||
.margin-mid {
|
||||
background: var(--color-warning-light); color: var(--color-warning-dark);
|
||||
}
|
||||
.margin-low {
|
||||
background: var(--color-error-light); color: var(--color-error-dark);
|
||||
}
|
||||
|
||||
/* Margin bar (visual) */
|
||||
.margin-bar-container {
|
||||
display: flex; align-items: center; gap: var(--space-2);
|
||||
}
|
||||
.margin-bar {
|
||||
width: 60px; height: 6px;
|
||||
background: var(--color-surface-3);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
.margin-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: var(--radius-full);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.margin-bar-fill.high { background: var(--color-success); }
|
||||
.margin-bar-fill.mid { background: var(--color-warning); }
|
||||
.margin-bar-fill.low { background: var(--color-error); }
|
||||
|
||||
/* ====== Totals Row ====== */
|
||||
.sale-table tfoot td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-weight: var(--font-weight-bold);
|
||||
border-top: 2px solid var(--color-border);
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
.total-margin {
|
||||
font-family: var(--font-mono); font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* ====== Legend ====== */
|
||||
.legend {
|
||||
display: flex; gap: var(--space-6); margin-top: var(--space-4);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.legend-item {
|
||||
display: flex; align-items: center; gap: var(--space-2);
|
||||
font-size: var(--text-caption); color: var(--color-text-secondary);
|
||||
}
|
||||
.legend-dot {
|
||||
width: 10px; height: 10px; border-radius: var(--radius-full);
|
||||
}
|
||||
.legend-dot.high { background: var(--color-success); }
|
||||
.legend-dot.mid { background: var(--color-warning); }
|
||||
.legend-dot.low { background: var(--color-error); }
|
||||
|
||||
/* ====== Qty controls ====== */
|
||||
.qty-cell {
|
||||
display: flex; align-items: center; gap: var(--space-1);
|
||||
}
|
||||
.qty-btn {
|
||||
width: 24px; height: 24px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: var(--color-surface-2); border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm); cursor: pointer;
|
||||
font-size: 14px; font-weight: bold;
|
||||
color: var(--color-text-secondary);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.qty-btn:hover { background: var(--color-primary-muted); border-color: var(--color-primary); }
|
||||
.qty-value {
|
||||
min-width: 28px; text-align: center;
|
||||
font-family: var(--font-mono); font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* ====== Delete btn ====== */
|
||||
.del-btn {
|
||||
width: 28px; height: 28px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: transparent; border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm); cursor: pointer;
|
||||
color: var(--color-text-muted); font-size: 14px;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.del-btn:hover { background: var(--color-error-light); color: var(--color-error); border-color: var(--color-error); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Theme Switcher -->
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="setTheme('industrial')">A — Industrial Robusto</button>
|
||||
<button onclick="setTheme('modern')">B — Técnico Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Columnas Costo y Margen</h1>
|
||||
<p class="subtitle">2 columnas extra en la tabla de venta, visibles solo con permiso Admin/Owner. Costo unitario + Margen % con color semántico.</p>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: With Columns Visible -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Tabla de Venta — Columnas Visibles (Admin/Owner)</h2>
|
||||
|
||||
<div class="perm-toggle">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="toggle-cols" checked onchange="toggleColumns()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<label for="toggle-cols">Mostrar Costo y Margen</label>
|
||||
<span class="perm-badge admin">Admin</span>
|
||||
<span class="perm-badge owner">Owner</span>
|
||||
</div>
|
||||
|
||||
<div class="sale-table-container">
|
||||
<table class="sale-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 32px;">#</th>
|
||||
<th>Producto</th>
|
||||
<th class="center" style="width: 90px;">Cantidad</th>
|
||||
<th class="right">P. Unitario</th>
|
||||
<th class="right col-cost">Costo Unit.</th>
|
||||
<th class="center col-margin" style="width: 140px;">Margen</th>
|
||||
<th class="right">Importe</th>
|
||||
<th style="width: 40px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Row 1: High margin -->
|
||||
<tr>
|
||||
<td style="color: var(--color-text-muted);">1</td>
|
||||
<td>
|
||||
<div class="product-cell">
|
||||
<span class="product-name">Balatas cerám. del. Brembo P68034</span>
|
||||
<span class="product-sku">BRM-P68034</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="qty-cell" style="justify-content: center;">
|
||||
<button class="qty-btn">-</button>
|
||||
<span class="qty-value">2</span>
|
||||
<button class="qty-btn">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$1,250.00</span></td>
|
||||
<td class="right col-cost"><span class="cost-value">$720.00</span></td>
|
||||
<td class="center col-margin">
|
||||
<div class="margin-bar-container" style="justify-content: center;">
|
||||
<span class="margin-badge margin-high">42.4%</span>
|
||||
<div class="margin-bar"><div class="margin-bar-fill high" style="width: 42.4%;"></div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$2,500.00</span></td>
|
||||
<td class="center"><button class="del-btn">✕</button></td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 2: Mid margin -->
|
||||
<tr>
|
||||
<td style="color: var(--color-text-muted);">2</td>
|
||||
<td>
|
||||
<div class="product-cell">
|
||||
<span class="product-name">Filtro aceite Wix WL7200</span>
|
||||
<span class="product-sku">WIX-7200</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="qty-cell" style="justify-content: center;">
|
||||
<button class="qty-btn">-</button>
|
||||
<span class="qty-value">1</span>
|
||||
<button class="qty-btn">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$185.00</span></td>
|
||||
<td class="right col-cost"><span class="cost-value">$138.00</span></td>
|
||||
<td class="center col-margin">
|
||||
<div class="margin-bar-container" style="justify-content: center;">
|
||||
<span class="margin-badge margin-mid">25.4%</span>
|
||||
<div class="margin-bar"><div class="margin-bar-fill mid" style="width: 25.4%;"></div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$185.00</span></td>
|
||||
<td class="center"><button class="del-btn">✕</button></td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 3: Low margin -->
|
||||
<tr>
|
||||
<td style="color: var(--color-text-muted);">3</td>
|
||||
<td>
|
||||
<div class="product-cell">
|
||||
<span class="product-name">Amortiguador tras. Monroe OESpec 72364</span>
|
||||
<span class="product-sku">MON-72364</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="qty-cell" style="justify-content: center;">
|
||||
<button class="qty-btn">-</button>
|
||||
<span class="qty-value">2</span>
|
||||
<button class="qty-btn">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$1,071.25</span></td>
|
||||
<td class="right col-cost"><span class="cost-value">$950.00</span></td>
|
||||
<td class="center col-margin">
|
||||
<div class="margin-bar-container" style="justify-content: center;">
|
||||
<span class="margin-badge margin-low">11.3%</span>
|
||||
<div class="margin-bar"><div class="margin-bar-fill low" style="width: 11.3%;"></div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$2,142.50</span></td>
|
||||
<td class="center"><button class="del-btn">✕</button></td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 4: High margin -->
|
||||
<tr>
|
||||
<td style="color: var(--color-text-muted);">4</td>
|
||||
<td>
|
||||
<div class="product-cell">
|
||||
<span class="product-name">Aceite Motor Mobil 1 5W-30 Sintético 5L</span>
|
||||
<span class="product-sku">MOB-5W30-5L</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center">
|
||||
<div class="qty-cell" style="justify-content: center;">
|
||||
<button class="qty-btn">-</button>
|
||||
<span class="qty-value">1</span>
|
||||
<button class="qty-btn">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$890.00</span></td>
|
||||
<td class="right col-cost"><span class="cost-value">$520.00</span></td>
|
||||
<td class="center col-margin">
|
||||
<div class="margin-bar-container" style="justify-content: center;">
|
||||
<span class="margin-badge margin-high">41.6%</span>
|
||||
<div class="margin-bar"><div class="margin-bar-fill high" style="width: 41.6%;"></div></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="right"><span class="price">$890.00</span></td>
|
||||
<td class="center"><button class="del-btn">✕</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3" style="text-align: right; color: var(--color-text-muted);">5 artículos, 6 piezas</td>
|
||||
<td class="right">Subtotal:</td>
|
||||
<td class="right col-cost">
|
||||
<span class="cost-value">$4,098.00</span>
|
||||
</td>
|
||||
<td class="center col-margin">
|
||||
<span class="total-margin margin-badge margin-high" style="font-size: var(--text-body-sm);">
|
||||
Promedio: 32.8%
|
||||
</span>
|
||||
</td>
|
||||
<td class="right"><span class="price" style="font-size: var(--text-body);">$5,717.50</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot high"></span>
|
||||
<span>Verde: margen > 30%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot mid"></span>
|
||||
<span>Amarillo: margen 15% – 30%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot low"></span>
|
||||
<span>Rojo: margen < 15%</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Margin Badge Variants -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Variantes del Badge de Margen</h2>
|
||||
<p class="label-text">Los 3 niveles de color semántico</p>
|
||||
|
||||
<div style="display: flex; gap: var(--space-6); flex-wrap: wrap; align-items: center;">
|
||||
<div style="text-align: center;">
|
||||
<p class="label-text">Alto (>30%)</p>
|
||||
<div class="margin-bar-container">
|
||||
<span class="margin-badge margin-high">42.4%</span>
|
||||
<div class="margin-bar" style="width: 80px;"><div class="margin-bar-fill high" style="width: 42.4%;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<p class="label-text">Medio (15-30%)</p>
|
||||
<div class="margin-bar-container">
|
||||
<span class="margin-badge margin-mid">25.4%</span>
|
||||
<div class="margin-bar" style="width: 80px;"><div class="margin-bar-fill mid" style="width: 25.4%;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<p class="label-text">Bajo (<15%)</p>
|
||||
<div class="margin-bar-container">
|
||||
<span class="margin-badge margin-low">11.3%</span>
|
||||
<div class="margin-bar" style="width: 80px;"><div class="margin-bar-fill low" style="width: 11.3%;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<p class="label-text">Negativo</p>
|
||||
<div class="margin-bar-container">
|
||||
<span class="margin-badge margin-low">-5.2%</span>
|
||||
<div class="margin-bar" style="width: 80px;"><div class="margin-bar-fill low" style="width: 0%;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.querySelectorAll('.theme-switcher button').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
function toggleColumns() {
|
||||
const checked = document.getElementById('toggle-cols').checked;
|
||||
document.querySelectorAll('.col-cost, .col-margin').forEach(el => {
|
||||
el.classList.toggle('hidden', !checked);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
297
docs/design/design-system/components/estado-vacio.html
Normal file
297
docs/design/design-system/components/estado-vacio.html
Normal file
@@ -0,0 +1,297 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Estado Vacío</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* === Empty State Component === */
|
||||
.variants-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.variant-card {
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.variant-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-bg-overlay);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: var(--radius-full);
|
||||
margin-bottom: var(--space-4);
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
max-width: 320px;
|
||||
padding: var(--space-8) var(--space-4);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-state__icon {
|
||||
font-size: 48px;
|
||||
line-height: 1;
|
||||
margin-bottom: var(--space-4);
|
||||
opacity: 0.6;
|
||||
filter: grayscale(30%);
|
||||
}
|
||||
|
||||
.empty-state__title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h5);
|
||||
font-weight: var(--heading-weight-secondary);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.empty-state__subtitle {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.5;
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
.empty-state__action {
|
||||
padding: var(--space-2) var(--space-5);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 500;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid var(--btn-secondary-border);
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
}
|
||||
|
||||
.empty-state__action:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.empty-state__action--primary {
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text);
|
||||
border-color: var(--btn-primary-border);
|
||||
}
|
||||
|
||||
.empty-state__action--primary:hover {
|
||||
background: var(--btn-primary-bg-hover);
|
||||
}
|
||||
|
||||
/* Context: inside table */
|
||||
.context-table {
|
||||
width: 100%;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.context-table__header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
background: var(--color-surface-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-muted);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.context-table__body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--space-6) var(--space-4);
|
||||
background: var(--color-bg-base);
|
||||
min-height: 200px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Context: inside card */
|
||||
.context-card {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.context-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
|
||||
.context-card__header span {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: var(--heading-weight-secondary);
|
||||
font-size: var(--text-body);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.context-card__body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--space-4);
|
||||
min-height: 180px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
<h1>Estado Vacío</h1>
|
||||
<p class="subtitle">Componente reutilizable para estados vacíos en tablas, cards y páginas completas.</p>
|
||||
|
||||
<!-- ========== Variants ========== -->
|
||||
<section>
|
||||
<h2>Variantes</h2>
|
||||
<div class="variants-grid">
|
||||
|
||||
<!-- a) No hay productos -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">a) Sin productos</span>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">📦</div>
|
||||
<div class="empty-state__title">No se encontraron productos</div>
|
||||
<div class="empty-state__subtitle">Intenta con otro término de búsqueda</div>
|
||||
<button class="empty-state__action">Limpiar filtros</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- b) No hay ventas -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">b) Sin ventas</span>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">🧾</div>
|
||||
<div class="empty-state__title">No hay ventas registradas hoy</div>
|
||||
<div class="empty-state__subtitle">Las ventas aparecerán aquí conforme se realicen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- c) Sin resultados -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">c) Sin resultados</span>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">🔍</div>
|
||||
<div class="empty-state__title">Sin resultados para 'BAL-2847'</div>
|
||||
<div class="empty-state__subtitle">Verifica el número de parte o nombre</div>
|
||||
<button class="empty-state__action empty-state__action--primary">Nueva búsqueda</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- d) Sin conexión -->
|
||||
<div class="variant-card">
|
||||
<span class="variant-label">d) Sin conexión</span>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">📡</div>
|
||||
<div class="empty-state__title">Sin conexión al servidor</div>
|
||||
<div class="empty-state__subtitle">Verifica tu conexión a internet</div>
|
||||
<button class="empty-state__action empty-state__action--primary">Reintentar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Contextual usage: inside a table ========== -->
|
||||
<section>
|
||||
<h2>Uso en contexto: Dentro de tabla</h2>
|
||||
<div class="context-table">
|
||||
<div class="context-table__header">
|
||||
<span>Producto</span>
|
||||
<span>No. Parte</span>
|
||||
<span>Stock</span>
|
||||
<span>Precio</span>
|
||||
</div>
|
||||
<div class="context-table__body">
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">🔍</div>
|
||||
<div class="empty-state__title">Sin resultados para 'xyz'</div>
|
||||
<div class="empty-state__subtitle">Verifica el número de parte o nombre</div>
|
||||
<button class="empty-state__action">Limpiar filtros</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Contextual usage: inside a card ========== -->
|
||||
<section>
|
||||
<h2>Uso en contexto: Dentro de card</h2>
|
||||
<div class="context-card">
|
||||
<div class="context-card__header">
|
||||
<span>Ventas del día</span>
|
||||
<span style="font-size: var(--text-body-sm); color: var(--color-text-muted);">01 Abr 2026</span>
|
||||
</div>
|
||||
<div class="context-card__body">
|
||||
<div class="empty-state">
|
||||
<div class="empty-state__icon">🧾</div>
|
||||
<div class="empty-state__title">No hay ventas registradas hoy</div>
|
||||
<div class="empty-state__subtitle">Las ventas aparecerán aquí conforme se realicen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
341
docs/design/design-system/components/etiqueta-codigo-barras.html
Normal file
341
docs/design/design-system/components/etiqueta-codigo-barras.html
Normal file
@@ -0,0 +1,341 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Etiqueta Código de Barras</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ── Labels Grid ── */
|
||||
.labels-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
/* ── Base Label ── */
|
||||
.label-card {
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: var(--shadow-md);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Size variants */
|
||||
.label-50x25 {
|
||||
width: 189px; /* ~50mm at 96dpi */
|
||||
height: 95px; /* ~25mm */
|
||||
padding: 6px 8px 4px;
|
||||
}
|
||||
.label-50x30 {
|
||||
width: 189px;
|
||||
height: 113px; /* ~30mm */
|
||||
padding: 6px 8px 4px;
|
||||
}
|
||||
|
||||
/* ── Label inner layout ── */
|
||||
.label-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.label-partno {
|
||||
font-family: 'Courier New', 'Consolas', monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #000000;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.label-location {
|
||||
font-family: 'Courier New', 'Consolas', monospace;
|
||||
font-size: 8px;
|
||||
font-weight: 400;
|
||||
color: #555555;
|
||||
background: #f0f0f0;
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.label-name {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 8px;
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
/* ── Barcode simulation ── */
|
||||
.barcode-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 24px;
|
||||
}
|
||||
.barcode {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
max-height: 28px;
|
||||
gap: 0;
|
||||
}
|
||||
.barcode .b {
|
||||
background: #000000;
|
||||
}
|
||||
.barcode .w {
|
||||
background: #ffffff;
|
||||
}
|
||||
.barcode-number {
|
||||
text-align: center;
|
||||
font-family: 'Courier New', 'Consolas', monospace;
|
||||
font-size: 7px;
|
||||
color: #333333;
|
||||
letter-spacing: 0.08em;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* ── Price ── */
|
||||
.label-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
margin-top: auto;
|
||||
}
|
||||
.label-price {
|
||||
font-family: 'Courier New', 'Consolas', monospace;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #000000;
|
||||
line-height: 1;
|
||||
}
|
||||
.label-50x25 .label-price {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Size badge on preview */
|
||||
.size-badge {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-2);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ── Preview wrapper (for the design system page) ── */
|
||||
.label-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* ── Print button ── */
|
||||
.btn-print {
|
||||
display: inline-flex; align-items: center; gap: var(--space-2);
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--btn-secondary-border);
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.btn-print:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
}
|
||||
|
||||
/* Notes */
|
||||
.note {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
margin-top: var(--space-4);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-surface-1);
|
||||
border-radius: var(--radius-md);
|
||||
border-left: 3px solid var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Print Styles ── */
|
||||
@media print {
|
||||
body {
|
||||
background: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.theme-switcher, h1, .subtitle, section > h2,
|
||||
.btn-print, .note, .size-badge {
|
||||
display: none !important;
|
||||
}
|
||||
section {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
section > h2 { display: none !important; }
|
||||
.labels-grid {
|
||||
gap: 2mm;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.label-card {
|
||||
box-shadow: none !important;
|
||||
border: 0.5px solid #999 !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.label-preview {
|
||||
break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Etiqueta Código de Barras</h1>
|
||||
<p class="subtitle">Etiquetas para impresora de etiquetas. Fondo blanco fijo (para impresión). Tamaños estándar 50x25mm y 50x30mm.</p>
|
||||
|
||||
<section>
|
||||
<h2>Etiquetas 50x25mm</h2>
|
||||
<div class="labels-grid" id="labels25"></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Etiquetas 50x30mm (con ubicación)</h2>
|
||||
<div class="labels-grid" id="labels30"></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Acciones</h2>
|
||||
<button class="btn-print" onclick="window.print()">🖨 Imprimir etiquetas</button>
|
||||
<div class="note">
|
||||
Las etiquetas siempre se renderizan con fondo blanco y texto negro independientemente del tema activo.
|
||||
El CSS @media print oculta la interfaz y deja solo las etiquetas para impresión directa.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const products = [
|
||||
{ partNo: 'BRK-4521', name: 'Balata delantera cerámica Brembo', barcode: '7501234567890', price: 485.00, location: 'A-03-12' },
|
||||
{ partNo: 'FLT-0089', name: 'Filtro de aceite Fram PH3614', barcode: '7509876543210', price: 125.50, location: 'B-01-05' },
|
||||
{ partNo: 'AMR-1200', name: 'Amortiguador trasero Monroe Sensatrac', barcode: '7504561237890', price: 1249.99, location: 'C-07-02' },
|
||||
];
|
||||
|
||||
// Generate pseudo-barcode stripes from a number string
|
||||
function generateBarcode(code) {
|
||||
// Simple EAN-like pattern: start/end guards + data bars
|
||||
const patterns = [];
|
||||
// Start guard
|
||||
patterns.push(1,0,1);
|
||||
for (let i = 0; i < code.length; i++) {
|
||||
const d = parseInt(code[i]);
|
||||
// Simple visual pattern per digit (not real EAN encoding, just visually plausible)
|
||||
const widths = [
|
||||
[3,2,1,1], [2,2,2,1], [2,1,2,2], [1,4,1,1], [1,1,3,2],
|
||||
[1,2,3,1], [1,1,1,4], [1,3,1,2], [1,2,1,3], [3,1,1,2]
|
||||
];
|
||||
const w = widths[d];
|
||||
w.forEach((size, j) => {
|
||||
for (let k = 0; k < size; k++) {
|
||||
patterns.push(j % 2 === 0 ? 1 : 0);
|
||||
}
|
||||
});
|
||||
if (i === 5) patterns.push(0,1,0,1,0); // center guard
|
||||
}
|
||||
// End guard
|
||||
patterns.push(1,0,1);
|
||||
return patterns;
|
||||
}
|
||||
|
||||
function renderBarcode(code) {
|
||||
const bars = generateBarcode(code);
|
||||
let html = '<div class="barcode">';
|
||||
bars.forEach(b => {
|
||||
html += '<div class="' + (b ? 'b' : 'w') + '" style="width:1px;"></div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '<div class="barcode-number">' + code + '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function createLabel(product, sizeClass) {
|
||||
const showLocation = sizeClass === 'label-50x30';
|
||||
return `
|
||||
<div class="label-preview">
|
||||
<span class="size-badge">${sizeClass === 'label-50x25' ? '50 x 25 mm' : '50 x 30 mm'}</span>
|
||||
<div class="label-card ${sizeClass}">
|
||||
<div class="label-top">
|
||||
<span class="label-partno">${product.partNo}</span>
|
||||
${showLocation ? '<span class="label-location">' + product.location + '</span>' : ''}
|
||||
</div>
|
||||
<div class="label-name" title="${product.name}">${product.name}</div>
|
||||
<div class="barcode-area">
|
||||
${renderBarcode(product.barcode)}
|
||||
</div>
|
||||
<div class="label-bottom">
|
||||
<span class="label-price">$${product.price.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render labels
|
||||
const container25 = document.getElementById('labels25');
|
||||
const container30 = document.getElementById('labels30');
|
||||
|
||||
products.forEach(p => {
|
||||
container25.innerHTML += createLabel(p, 'label-50x25');
|
||||
container30.innerHTML += createLabel(p, 'label-50x30');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
338
docs/design/design-system/components/fkeys-footer.html
Normal file
338
docs/design/design-system/components/fkeys-footer.html
Normal file
@@ -0,0 +1,338 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — F-Keys Footer</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
padding-bottom: 120px;
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.label-text {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* ====== F-Keys Footer Bar ====== */
|
||||
.fkeys-footer {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
background: var(--color-surface-2);
|
||||
border-top: 2px solid var(--color-primary);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
gap: var(--space-1);
|
||||
z-index: var(--z-sticky);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.fkey {
|
||||
display: flex; align-items: center; gap: var(--space-1);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: transparent; border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer; transition: var(--transition-fast);
|
||||
white-space: nowrap; flex-shrink: 0;
|
||||
}
|
||||
.fkey:hover {
|
||||
background: var(--color-primary-muted);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.fkey:active {
|
||||
background: var(--color-primary);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
.fkey:active .fkey-label { color: var(--color-text-inverse); }
|
||||
.fkey:active .fkey-key { background: rgba(0,0,0,0.2); color: var(--color-text-inverse); border-color: transparent; }
|
||||
|
||||
.fkey-key {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
min-width: 28px; height: 22px;
|
||||
padding: 0 var(--space-2);
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border-strong);
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: var(--font-mono); font-size: 11px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-accent);
|
||||
line-height: 1;
|
||||
}
|
||||
.fkey-label {
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Separator */
|
||||
.fkey-sep {
|
||||
width: 1px; height: 24px;
|
||||
background: var(--color-border);
|
||||
margin: 0 var(--space-1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Highlight key (F3 = Cobrar) */
|
||||
.fkey.highlight {
|
||||
background: var(--color-primary-muted);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.fkey.highlight .fkey-key {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.fkey.highlight .fkey-label {
|
||||
color: var(--color-text-accent);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* ====== Inline Preview (non-fixed) ====== */
|
||||
.fkeys-inline {
|
||||
position: relative;
|
||||
background: var(--color-surface-2);
|
||||
border: 2px solid var(--color-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ====== Responsive: Stack on mobile ====== */
|
||||
@media (max-width: 768px) {
|
||||
.fkeys-footer, .fkeys-inline {
|
||||
flex-wrap: wrap; gap: var(--space-1);
|
||||
padding: var(--space-2);
|
||||
}
|
||||
.fkey {
|
||||
padding: var(--space-1) var(--space-2);
|
||||
}
|
||||
.fkey-label { font-size: 10px; }
|
||||
.fkey-key { min-width: 24px; height: 20px; font-size: 10px; }
|
||||
.fkey-sep { display: none; }
|
||||
}
|
||||
|
||||
/* ====== Compact variant ====== */
|
||||
.fkeys-compact .fkey {
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border: none; background: transparent;
|
||||
}
|
||||
.fkeys-compact .fkey:hover {
|
||||
background: var(--color-primary-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.fkeys-compact .fkey-key {
|
||||
min-width: 22px; height: 18px; font-size: 10px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.fkeys-compact .fkey-label { font-size: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Theme Switcher -->
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="setTheme('industrial')">A — Industrial Robusto</button>
|
||||
<button onclick="setTheme('modern')">B — Técnico Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>F-Keys Footer</h1>
|
||||
<p class="subtitle">Barra fija inferior con atajos de teclado del POS: F1-F6, +/-, *, Esc.</p>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Standard Footer -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Barra Estándar (Fixed Bottom)</h2>
|
||||
<p class="label-text">Preview inline — la barra real se muestra fija abajo de la página</p>
|
||||
|
||||
<div class="fkeys-inline">
|
||||
<div class="fkey" title="Buscar producto">
|
||||
<span class="fkey-key">F1</span>
|
||||
<span class="fkey-label">Buscar</span>
|
||||
</div>
|
||||
<div class="fkey" title="Seleccionar cliente">
|
||||
<span class="fkey-key">F2</span>
|
||||
<span class="fkey-label">Cliente</span>
|
||||
</div>
|
||||
<div class="fkey highlight" title="Cobrar venta">
|
||||
<span class="fkey-key">F3</span>
|
||||
<span class="fkey-label">Cobrar</span>
|
||||
</div>
|
||||
<div class="fkey" title="Generar cotización">
|
||||
<span class="fkey-key">F4</span>
|
||||
<span class="fkey-label">Cotización</span>
|
||||
</div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Última venta">
|
||||
<span class="fkey-key">F5</span>
|
||||
<span class="fkey-label">Últ.Venta</span>
|
||||
</div>
|
||||
<div class="fkey" title="Abrir cajón de dinero">
|
||||
<span class="fkey-key">F6</span>
|
||||
<span class="fkey-label">Cajón</span>
|
||||
</div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Aumentar / Disminuir cantidad">
|
||||
<span class="fkey-key">+/-</span>
|
||||
<span class="fkey-label">Cantidad</span>
|
||||
</div>
|
||||
<div class="fkey" title="Aplicar descuento">
|
||||
<span class="fkey-key">*</span>
|
||||
<span class="fkey-label">Descuento</span>
|
||||
</div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Cancelar / Limpiar venta">
|
||||
<span class="fkey-key">Esc</span>
|
||||
<span class="fkey-label">Cancelar</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Compact Variant -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Variante Compacta</h2>
|
||||
<p class="label-text">Para pantallas chicas o cuando se necesita más espacio</p>
|
||||
|
||||
<div class="fkeys-inline fkeys-compact">
|
||||
<div class="fkey"><span class="fkey-key">F1</span><span class="fkey-label">Buscar</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F2</span><span class="fkey-label">Cliente</span></div>
|
||||
<div class="fkey highlight"><span class="fkey-key">F3</span><span class="fkey-label">Cobrar</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F4</span><span class="fkey-label">Cotización</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F5</span><span class="fkey-label">Últ.Venta</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F6</span><span class="fkey-label">Cajón</span></div>
|
||||
<div class="fkey"><span class="fkey-key">+/-</span><span class="fkey-label">Cant</span></div>
|
||||
<div class="fkey"><span class="fkey-key">*</span><span class="fkey-label">Desc</span></div>
|
||||
<div class="fkey"><span class="fkey-key">Esc</span><span class="fkey-label">Salir</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Extended keys -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Variante Extendida (Admin)</h2>
|
||||
<p class="label-text">Teclas adicionales para rol Admin/Owner</p>
|
||||
|
||||
<div class="fkeys-inline">
|
||||
<div class="fkey"><span class="fkey-key">F1</span><span class="fkey-label">Buscar</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F2</span><span class="fkey-label">Cliente</span></div>
|
||||
<div class="fkey highlight"><span class="fkey-key">F3</span><span class="fkey-label">Cobrar</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F4</span><span class="fkey-label">Cotización</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F5</span><span class="fkey-label">Últ.Venta</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F6</span><span class="fkey-label">Cajón</span></div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey"><span class="fkey-key">F7</span><span class="fkey-label">Corte</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F8</span><span class="fkey-label">Devolución</span></div>
|
||||
<div class="fkey"><span class="fkey-key">F9</span><span class="fkey-label">Inventario</span></div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey"><span class="fkey-key">+/-</span><span class="fkey-label">Cant</span></div>
|
||||
<div class="fkey"><span class="fkey-key">*</span><span class="fkey-label">Desc</span></div>
|
||||
<div class="fkey"><span class="fkey-key">Esc</span><span class="fkey-label">Cancelar</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Individual Key States -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Estados de Tecla</h2>
|
||||
<p class="label-text">Normal, hover, active, highlight, disabled</p>
|
||||
|
||||
<div style="display: flex; gap: var(--space-4); flex-wrap: wrap; align-items: center;">
|
||||
<div>
|
||||
<p class="label-text">Normal</p>
|
||||
<div class="fkey"><span class="fkey-key">F1</span><span class="fkey-label">Buscar</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="label-text">Highlight (primary action)</p>
|
||||
<div class="fkey highlight"><span class="fkey-key">F3</span><span class="fkey-label">Cobrar</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="label-text">Disabled</p>
|
||||
<div class="fkey" style="opacity: 0.4; pointer-events: none;">
|
||||
<span class="fkey-key">F8</span><span class="fkey-label">Devolución</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ====== FIXED FOOTER (actual position) ====== -->
|
||||
<div class="fkeys-footer">
|
||||
<div class="fkey" title="Buscar producto"><span class="fkey-key">F1</span><span class="fkey-label">Buscar</span></div>
|
||||
<div class="fkey" title="Seleccionar cliente"><span class="fkey-key">F2</span><span class="fkey-label">Cliente</span></div>
|
||||
<div class="fkey highlight" title="Cobrar venta"><span class="fkey-key">F3</span><span class="fkey-label">Cobrar</span></div>
|
||||
<div class="fkey" title="Cotización"><span class="fkey-key">F4</span><span class="fkey-label">Cotización</span></div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Última venta"><span class="fkey-key">F5</span><span class="fkey-label">Últ.Venta</span></div>
|
||||
<div class="fkey" title="Abrir cajón"><span class="fkey-key">F6</span><span class="fkey-label">Cajón</span></div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Cantidad"><span class="fkey-key">+/-</span><span class="fkey-label">Cantidad</span></div>
|
||||
<div class="fkey" title="Descuento"><span class="fkey-key">*</span><span class="fkey-label">Descuento</span></div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Cancelar"><span class="fkey-key">Esc</span><span class="fkey-label">Cancelar</span></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.querySelectorAll('.theme-switcher button').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
// Keyboard shortcuts demo
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const keyMap = {
|
||||
'F1': 'Buscar producto',
|
||||
'F2': 'Seleccionar cliente',
|
||||
'F3': 'Cobrar venta',
|
||||
'F4': 'Generar cotización',
|
||||
'F5': 'Última venta',
|
||||
'F6': 'Abrir cajón',
|
||||
'Escape': 'Cancelar'
|
||||
};
|
||||
if (keyMap[e.key]) {
|
||||
e.preventDefault();
|
||||
console.log('Atajo:', keyMap[e.key]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
358
docs/design/design-system/components/grafica-barras.html
Normal file
358
docs/design/design-system/components/grafica-barras.html
Normal file
@@ -0,0 +1,358 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Gráfica de Barras</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ── Chart Container ── */
|
||||
.chart-container {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-6);
|
||||
max-width: 800px;
|
||||
}
|
||||
.chart-title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h5);
|
||||
font-weight: var(--heading-weight-secondary);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
.chart-subtitle {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
|
||||
/* ── Chart Layout ── */
|
||||
.chart-area {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
/* Y-Axis */
|
||||
.y-axis {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 28px; /* match x-axis label space */
|
||||
min-width: 52px;
|
||||
}
|
||||
.y-axis span {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
font-family: var(--font-mono);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Bars Area */
|
||||
.bars-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Grid lines */
|
||||
.grid-lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
bottom: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
pointer-events: none;
|
||||
}
|
||||
.grid-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
/* Bars flex container */
|
||||
.bars-row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-around;
|
||||
gap: var(--space-2);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Individual bar wrapper */
|
||||
.bar-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: flex-end;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Bar element */
|
||||
.bar {
|
||||
width: 70%;
|
||||
max-width: 56px;
|
||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||
background: var(--color-primary);
|
||||
transition: height 0.8s var(--ease-out), background 0.15s ease;
|
||||
height: 0;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.bar:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.bar-tooltip {
|
||||
position: absolute;
|
||||
top: -36px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--color-bg-overlay);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--color-border-strong);
|
||||
box-shadow: var(--shadow-md);
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
.bar-tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
width: 8px; height: 8px;
|
||||
background: var(--color-bg-overlay);
|
||||
border-right: 1px solid var(--color-border-strong);
|
||||
border-bottom: 1px solid var(--color-border-strong);
|
||||
}
|
||||
.bar:hover .bar-tooltip {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* X-Axis labels */
|
||||
.x-labels {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: var(--space-2);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.x-labels span {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
/* ── Chart footer / total ── */
|
||||
.chart-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: var(--space-4);
|
||||
padding-top: var(--space-3);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.chart-total {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h5);
|
||||
font-weight: var(--heading-weight-primary);
|
||||
color: var(--color-text-accent);
|
||||
}
|
||||
.chart-total-label {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
}
|
||||
.chart-avg {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.chart-avg strong {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Highlight best day */
|
||||
.bar.best {
|
||||
background: var(--color-success);
|
||||
}
|
||||
.bar.best:hover {
|
||||
background: var(--color-success-dark);
|
||||
}
|
||||
.bar.worst {
|
||||
opacity: 0.65;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Gráfica de Barras</h1>
|
||||
<p class="subtitle">Gráfica de barras verticales pura CSS/JS. Animación al cargar, tooltips al hover.</p>
|
||||
|
||||
<section>
|
||||
<h2>Ventas Semanales</h2>
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Ventas por Día</div>
|
||||
<div class="chart-subtitle">Semana del 24 al 30 de marzo 2026</div>
|
||||
|
||||
<div class="chart-area">
|
||||
<div class="y-axis" id="yAxis"></div>
|
||||
<div class="bars-area">
|
||||
<div class="grid-lines" id="gridLines"></div>
|
||||
<div class="bars-row" id="barsRow"></div>
|
||||
<div class="x-labels" id="xLabels"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-footer">
|
||||
<div>
|
||||
<div class="chart-total-label">Total semanal</div>
|
||||
<div class="chart-total" id="totalValue"></div>
|
||||
</div>
|
||||
<div class="chart-avg">Promedio diario: <strong id="avgValue"></strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const data = [
|
||||
{ day: 'Lun', value: 12500 },
|
||||
{ day: 'Mar', value: 18300 },
|
||||
{ day: 'Mié', value: 15700 },
|
||||
{ day: 'Jue', value: 22100 },
|
||||
{ day: 'Vie', value: 28500 },
|
||||
{ day: 'Sáb', value: 31200 },
|
||||
{ day: 'Dom', value: 8900 },
|
||||
];
|
||||
|
||||
const maxVal = Math.max(...data.map(d => d.value));
|
||||
const bestIdx = data.indexOf(data.find(d => d.value === maxVal));
|
||||
const worstIdx = data.indexOf(data.find(d => d.value === Math.min(...data.map(d => d.value))));
|
||||
|
||||
// Round up max to nearest nice number for Y-axis
|
||||
const yMax = Math.ceil(maxVal / 5000) * 5000;
|
||||
const ySteps = 5;
|
||||
|
||||
// Build Y-axis labels
|
||||
const yAxis = document.getElementById('yAxis');
|
||||
for (let i = ySteps; i >= 0; i--) {
|
||||
const val = (yMax / ySteps) * i;
|
||||
const span = document.createElement('span');
|
||||
span.textContent = '$' + (val / 1000).toFixed(0) + 'k';
|
||||
yAxis.appendChild(span);
|
||||
}
|
||||
|
||||
// Build grid lines
|
||||
const gridLines = document.getElementById('gridLines');
|
||||
for (let i = 0; i <= ySteps; i++) {
|
||||
const line = document.createElement('div');
|
||||
line.className = 'grid-line';
|
||||
gridLines.appendChild(line);
|
||||
}
|
||||
|
||||
// Build bars
|
||||
const barsRow = document.getElementById('barsRow');
|
||||
const xLabels = document.getElementById('xLabels');
|
||||
|
||||
data.forEach((d, i) => {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'bar-wrapper';
|
||||
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'bar';
|
||||
if (i === bestIdx) bar.classList.add('best');
|
||||
if (i === worstIdx) bar.classList.add('worst');
|
||||
bar.dataset.height = ((d.value / yMax) * 100).toFixed(1);
|
||||
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'bar-tooltip';
|
||||
tooltip.textContent = '$' + d.value.toLocaleString('es-MX');
|
||||
bar.appendChild(tooltip);
|
||||
|
||||
wrapper.appendChild(bar);
|
||||
barsRow.appendChild(wrapper);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.textContent = d.day;
|
||||
xLabels.appendChild(label);
|
||||
});
|
||||
|
||||
// Totals
|
||||
const total = data.reduce((s, d) => s + d.value, 0);
|
||||
document.getElementById('totalValue').textContent = '$' + total.toLocaleString('es-MX');
|
||||
document.getElementById('avgValue').textContent = '$' + Math.round(total / data.length).toLocaleString('es-MX');
|
||||
|
||||
// Animate bars on load
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.bar').forEach(bar => {
|
||||
bar.style.height = bar.dataset.height + '%';
|
||||
});
|
||||
}, 150);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
477
docs/design/design-system/components/modal-confirmacion.html
Normal file
477
docs/design/design-system/components/modal-confirmacion.html
Normal file
@@ -0,0 +1,477 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Modal de Confirmación</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* === Modal Component === */
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from { opacity: 0; transform: translate(-50%, -50%) scale(0.92); }
|
||||
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes scaleOut {
|
||||
from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
to { opacity: 0; transform: translate(-50%, -50%) scale(0.92); }
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--overlay-backdrop, rgba(0, 0, 0, 0.6));
|
||||
z-index: var(--z-modal, 1000);
|
||||
display: none;
|
||||
animation: fadeIn 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
.modal-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal-overlay.closing {
|
||||
animation: fadeOut 0.2s ease-in forwards;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: calc(var(--z-modal, 1000) + 1);
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-xl);
|
||||
width: 90%;
|
||||
max-width: 420px;
|
||||
padding: var(--space-6);
|
||||
text-align: center;
|
||||
display: none;
|
||||
animation: scaleIn 0.25s ease-out forwards;
|
||||
}
|
||||
|
||||
.modal-dialog.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal-dialog.closing {
|
||||
animation: scaleOut 0.2s ease-in forwards;
|
||||
}
|
||||
|
||||
.modal__icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: var(--radius-full);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26px;
|
||||
margin: 0 auto var(--space-4);
|
||||
}
|
||||
|
||||
.modal__icon--warning {
|
||||
background: var(--color-warning-light);
|
||||
}
|
||||
|
||||
.modal__icon--info {
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
|
||||
.modal__icon--danger {
|
||||
background: var(--color-error-light);
|
||||
}
|
||||
|
||||
.modal__title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-primary);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.modal__desc {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--space-6);
|
||||
padding: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.modal__actions {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal__btn {
|
||||
flex: 1;
|
||||
max-width: 180px;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.modal__btn--secondary {
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
border-color: var(--btn-secondary-border);
|
||||
}
|
||||
|
||||
.modal__btn--secondary:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
}
|
||||
|
||||
.modal__btn--primary {
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text);
|
||||
border-color: var(--btn-primary-border);
|
||||
}
|
||||
|
||||
.modal__btn--primary:hover {
|
||||
background: var(--btn-primary-bg-hover);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.modal__btn--danger {
|
||||
background: var(--btn-danger-bg);
|
||||
color: var(--btn-danger-text);
|
||||
border-color: var(--btn-danger-bg);
|
||||
}
|
||||
|
||||
.modal__btn--danger:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* === Demo trigger buttons === */
|
||||
.trigger-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.trigger-card {
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.trigger-card__label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-bg-overlay);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.trigger-card__desc {
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.trigger-btn {
|
||||
padding: var(--space-3) var(--space-5);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.trigger-btn:hover {
|
||||
background: var(--color-surface-2);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.trigger-btn--warning { border-color: var(--color-warning); }
|
||||
.trigger-btn--primary { border-color: var(--color-primary); }
|
||||
.trigger-btn--danger { border-color: var(--color-error); }
|
||||
|
||||
/* === Static previews === */
|
||||
.static-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||||
gap: var(--space-6);
|
||||
}
|
||||
|
||||
.static-preview {
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.static-preview .variant-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-bg-overlay);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: var(--radius-full);
|
||||
display: inline-block;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.static-modal {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-lg);
|
||||
padding: var(--space-6);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
<h1>Modal de Confirmación</h1>
|
||||
<p class="subtitle">Diálogos modales para confirmar acciones destructivas o importantes del sistema POS.</p>
|
||||
|
||||
<!-- ========== Interactive triggers ========== -->
|
||||
<section>
|
||||
<h2>Demo interactivo — Abrir modales</h2>
|
||||
<div class="trigger-grid">
|
||||
|
||||
<div class="trigger-card">
|
||||
<span class="trigger-card__label">a) Cancelar venta</span>
|
||||
<p class="trigger-card__desc">Confirma antes de descartar una venta en curso</p>
|
||||
<button class="trigger-btn trigger-btn--warning" onclick="openModal('cancelar-venta')">Abrir modal</button>
|
||||
</div>
|
||||
|
||||
<div class="trigger-card">
|
||||
<span class="trigger-card__label">b) Cerrar caja</span>
|
||||
<p class="trigger-card__desc">Genera corte de caja al final del turno</p>
|
||||
<button class="trigger-btn trigger-btn--primary" onclick="openModal('cerrar-caja')">Abrir modal</button>
|
||||
</div>
|
||||
|
||||
<div class="trigger-card">
|
||||
<span class="trigger-card__label">c) Ajustar stock</span>
|
||||
<p class="trigger-card__desc">Ajusta inventario de múltiples artículos</p>
|
||||
<button class="trigger-btn trigger-btn--warning" onclick="openModal('ajustar-stock')">Abrir modal</button>
|
||||
</div>
|
||||
|
||||
<div class="trigger-card">
|
||||
<span class="trigger-card__label">d) Cerrar periodo</span>
|
||||
<p class="trigger-card__desc">Cierra periodo fiscal contable</p>
|
||||
<button class="trigger-btn trigger-btn--danger" onclick="openModal('cerrar-periodo')">Abrir modal</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Static previews ========== -->
|
||||
<section>
|
||||
<h2>Variantes estáticas</h2>
|
||||
<div class="static-grid">
|
||||
|
||||
<div class="static-preview">
|
||||
<span class="variant-label">a) Cancelar venta</span>
|
||||
<div class="static-modal">
|
||||
<div class="modal__icon modal__icon--warning">⚠️</div>
|
||||
<div class="modal__title">¿Cancelar venta?</div>
|
||||
<div class="modal__desc">Se perderán los artículos agregados al ticket actual.</div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary">No, continuar</button>
|
||||
<button class="modal__btn modal__btn--danger">Sí, cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="static-preview">
|
||||
<span class="variant-label">b) Cerrar caja</span>
|
||||
<div class="static-modal">
|
||||
<div class="modal__icon modal__icon--info">ℹ️</div>
|
||||
<div class="modal__title">¿Cerrar caja?</div>
|
||||
<div class="modal__desc">Se generará el corte de caja con total <strong style="color: var(--color-text-primary);">$15,800.00</strong></div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary">Cancelar</button>
|
||||
<button class="modal__btn modal__btn--primary">Cerrar caja</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="static-preview">
|
||||
<span class="variant-label">c) Ajustar stock</span>
|
||||
<div class="static-modal">
|
||||
<div class="modal__icon modal__icon--warning">⚠️</div>
|
||||
<div class="modal__title">¿Ajustar stock?</div>
|
||||
<div class="modal__desc">Se ajustarán <strong style="color: var(--color-text-primary);">5 artículos</strong> del inventario. Esta acción no se puede deshacer.</div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary">Cancelar</button>
|
||||
<button class="modal__btn modal__btn--primary">Ajustar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="static-preview">
|
||||
<span class="variant-label">d) Cerrar periodo fiscal</span>
|
||||
<div class="static-modal">
|
||||
<div class="modal__icon modal__icon--danger">🛑</div>
|
||||
<div class="modal__title">¿Cerrar periodo fiscal?</div>
|
||||
<div class="modal__desc">Se cerrará el periodo <strong style="color: var(--color-text-primary);">Marzo 2026</strong>. No podrás registrar pólizas en este periodo.</div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary">Cancelar</button>
|
||||
<button class="modal__btn modal__btn--danger">Cerrar periodo</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Actual Modal Overlays (hidden by default) ========== -->
|
||||
|
||||
<!-- a) Cancelar venta -->
|
||||
<div class="modal-overlay" id="overlay-cancelar-venta" onclick="if(event.target===this) closeModal('cancelar-venta')"></div>
|
||||
<div class="modal-dialog" id="modal-cancelar-venta">
|
||||
<div class="modal__icon modal__icon--warning">⚠️</div>
|
||||
<div class="modal__title">¿Cancelar venta?</div>
|
||||
<div class="modal__desc">Se perderán los artículos agregados al ticket actual.</div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary" onclick="closeModal('cancelar-venta')">No, continuar</button>
|
||||
<button class="modal__btn modal__btn--danger" onclick="closeModal('cancelar-venta')">Sí, cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- b) Cerrar caja -->
|
||||
<div class="modal-overlay" id="overlay-cerrar-caja" onclick="if(event.target===this) closeModal('cerrar-caja')"></div>
|
||||
<div class="modal-dialog" id="modal-cerrar-caja">
|
||||
<div class="modal__icon modal__icon--info">ℹ️</div>
|
||||
<div class="modal__title">¿Cerrar caja?</div>
|
||||
<div class="modal__desc">Se generará el corte de caja con total <strong style="color: var(--color-text-primary);">$15,800.00</strong></div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary" onclick="closeModal('cerrar-caja')">Cancelar</button>
|
||||
<button class="modal__btn modal__btn--primary" onclick="closeModal('cerrar-caja')">Cerrar caja</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- c) Ajustar stock -->
|
||||
<div class="modal-overlay" id="overlay-ajustar-stock" onclick="if(event.target===this) closeModal('ajustar-stock')"></div>
|
||||
<div class="modal-dialog" id="modal-ajustar-stock">
|
||||
<div class="modal__icon modal__icon--warning">⚠️</div>
|
||||
<div class="modal__title">¿Ajustar stock?</div>
|
||||
<div class="modal__desc">Se ajustarán <strong style="color: var(--color-text-primary);">5 artículos</strong> del inventario. Esta acción no se puede deshacer.</div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary" onclick="closeModal('ajustar-stock')">Cancelar</button>
|
||||
<button class="modal__btn modal__btn--primary" onclick="closeModal('ajustar-stock')">Ajustar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- d) Cerrar periodo fiscal -->
|
||||
<div class="modal-overlay" id="overlay-cerrar-periodo" onclick="if(event.target===this) closeModal('cerrar-periodo')"></div>
|
||||
<div class="modal-dialog" id="modal-cerrar-periodo">
|
||||
<div class="modal__icon modal__icon--danger">🛑</div>
|
||||
<div class="modal__title">¿Cerrar periodo fiscal?</div>
|
||||
<div class="modal__desc">Se cerrará el periodo <strong style="color: var(--color-text-primary);">Marzo 2026</strong>. No podrás registrar pólizas en este periodo.</div>
|
||||
<div class="modal__actions">
|
||||
<button class="modal__btn modal__btn--secondary" onclick="closeModal('cerrar-periodo')">Cancelar</button>
|
||||
<button class="modal__btn modal__btn--danger" onclick="closeModal('cerrar-periodo')">Cerrar periodo</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openModal(id) {
|
||||
const overlay = document.getElementById('overlay-' + id);
|
||||
const dialog = document.getElementById('modal-' + id);
|
||||
overlay.classList.remove('closing');
|
||||
dialog.classList.remove('closing');
|
||||
overlay.classList.add('active');
|
||||
dialog.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
const overlay = document.getElementById('overlay-' + id);
|
||||
const dialog = document.getElementById('modal-' + id);
|
||||
overlay.classList.add('closing');
|
||||
dialog.classList.add('closing');
|
||||
setTimeout(() => {
|
||||
overlay.classList.remove('active', 'closing');
|
||||
dialog.classList.remove('active', 'closing');
|
||||
document.body.style.overflow = '';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Close on Escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.modal-overlay.active').forEach(overlay => {
|
||||
const id = overlay.id.replace('overlay-', '');
|
||||
closeModal(id);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
555
docs/design/design-system/components/modal-pago.html
Normal file
555
docs/design/design-system/components/modal-pago.html
Normal file
@@ -0,0 +1,555 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Modal de Pago</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.label-text {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* ====== Overlay ====== */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0;
|
||||
background: var(--overlay-backdrop);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: var(--z-modal);
|
||||
opacity: 0; pointer-events: none;
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.modal-overlay.open { opacity: 1; pointer-events: auto; }
|
||||
|
||||
/* ====== Modal Container ====== */
|
||||
.modal-pago {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
width: 560px; max-width: 95vw;
|
||||
max-height: 90vh; overflow-y: auto;
|
||||
transform: translateY(20px);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.modal-overlay.open .modal-pago { transform: translateY(0); }
|
||||
|
||||
/* ====== Header ====== */
|
||||
.modal-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: var(--space-5) var(--space-6);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.modal-header h3 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-primary); color: var(--color-text-primary);
|
||||
}
|
||||
.modal-close {
|
||||
width: 36px; height: 36px; display: flex; align-items: center; justify-content: center;
|
||||
background: transparent; border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
color: var(--color-text-muted); font-size: 18px;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.modal-close:hover { background: var(--color-surface-2); color: var(--color-text-primary); }
|
||||
|
||||
/* ====== Total Banner ====== */
|
||||
.total-banner {
|
||||
background: var(--color-primary-muted);
|
||||
border-bottom: 2px solid var(--color-primary);
|
||||
padding: var(--space-4) var(--space-6);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.total-label {
|
||||
font-size: var(--text-body); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary); text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
.total-amount {
|
||||
font-family: var(--font-mono); font-size: var(--text-h3);
|
||||
font-weight: var(--font-weight-bold); color: var(--color-text-accent);
|
||||
}
|
||||
|
||||
/* ====== Items Summary ====== */
|
||||
.items-summary {
|
||||
padding: var(--space-3) var(--space-6);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: var(--text-body-sm); color: var(--color-text-muted);
|
||||
display: flex; gap: var(--space-4);
|
||||
}
|
||||
|
||||
/* ====== Tabs ====== */
|
||||
.pago-tabs {
|
||||
display: flex; border-bottom: 2px solid var(--color-border);
|
||||
padding: 0 var(--space-6);
|
||||
}
|
||||
.pago-tab {
|
||||
padding: var(--space-3) var(--space-5);
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
background: transparent; border: none;
|
||||
color: var(--color-text-muted); cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px; transition: var(--transition-fast);
|
||||
display: flex; align-items: center; gap: var(--space-2);
|
||||
}
|
||||
.pago-tab:hover { color: var(--color-text-primary); }
|
||||
.pago-tab.active {
|
||||
color: var(--color-text-accent);
|
||||
border-bottom-color: var(--color-primary);
|
||||
}
|
||||
.pago-tab .tab-icon { font-size: 16px; }
|
||||
|
||||
/* ====== Tab Content ====== */
|
||||
.tab-content { padding: var(--space-6); display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
|
||||
/* ====== Form Elements ====== */
|
||||
.form-group { margin-bottom: var(--space-4); }
|
||||
.form-label {
|
||||
display: block; font-size: var(--text-caption); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary); margin-bottom: var(--space-2);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
.form-input {
|
||||
width: 100%; padding: var(--space-3) var(--space-4);
|
||||
font-family: var(--font-body); font-size: var(--text-body);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border); border-radius: var(--radius-md);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.form-input:focus { outline: none; border-color: var(--color-border-focus); box-shadow: var(--shadow-focus); }
|
||||
.form-input-lg {
|
||||
font-family: var(--font-mono); font-size: var(--text-h3);
|
||||
font-weight: var(--font-weight-bold); text-align: right;
|
||||
padding: var(--space-4) var(--space-5);
|
||||
}
|
||||
.form-hint {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted); margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
/* ====== Calculadora de Cambio ====== */
|
||||
.cambio-display {
|
||||
background: var(--color-surface-2);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
.cambio-label {
|
||||
font-size: var(--text-body-sm); color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.cambio-amount {
|
||||
font-family: var(--font-mono); font-size: var(--text-h4);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.cambio-amount.positive { color: var(--color-success); }
|
||||
.cambio-amount.negative { color: var(--color-error); }
|
||||
|
||||
.quick-amounts {
|
||||
display: grid; grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-2); margin-top: var(--space-3);
|
||||
}
|
||||
.quick-btn {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-family: var(--font-mono); font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
background: var(--color-surface-2); color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border); border-radius: var(--radius-md);
|
||||
cursor: pointer; transition: var(--transition-fast); text-align: center;
|
||||
}
|
||||
.quick-btn:hover { background: var(--color-primary-muted); border-color: var(--color-primary); }
|
||||
|
||||
/* ====== Mixto Split ====== */
|
||||
.split-row {
|
||||
display: grid; grid-template-columns: 1fr auto 1fr;
|
||||
gap: var(--space-3); align-items: end; margin-bottom: var(--space-3);
|
||||
}
|
||||
.split-method {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
background: var(--color-surface-2); color: var(--color-text-secondary);
|
||||
border-radius: var(--radius-md); text-align: center;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.split-remaining {
|
||||
background: var(--color-surface-2); border-radius: var(--radius-md);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-size: var(--text-body-sm); margin-top: var(--space-3);
|
||||
}
|
||||
.split-remaining .amount {
|
||||
font-family: var(--font-mono); font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-accent);
|
||||
}
|
||||
|
||||
/* ====== Checkbox CFDI ====== */
|
||||
.cfdi-check {
|
||||
display: flex; align-items: center; gap: var(--space-3);
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-top: 1px solid var(--color-border);
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
.cfdi-check input[type="checkbox"] {
|
||||
width: 20px; height: 20px; accent-color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cfdi-check label {
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary); cursor: pointer;
|
||||
}
|
||||
.cfdi-check .cfdi-hint {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ====== Footer ====== */
|
||||
.modal-footer {
|
||||
padding: var(--space-4) var(--space-6);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex; gap: var(--space-3); justify-content: flex-end;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-6);
|
||||
font-family: var(--font-body); font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-semibold); line-height: 1;
|
||||
border: 2px solid transparent; border-radius: var(--radius-md);
|
||||
cursor: pointer; transition: var(--transition-fast); white-space: nowrap;
|
||||
}
|
||||
.btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
||||
.btn:active { transform: scale(0.97); }
|
||||
.btn-primary { background: var(--btn-primary-bg); color: var(--btn-primary-text); }
|
||||
.btn-primary:hover { background: var(--btn-primary-bg-hover); }
|
||||
.btn-secondary { background: var(--btn-secondary-bg); color: var(--btn-secondary-text); border-color: var(--btn-secondary-border); }
|
||||
.btn-secondary:hover { background: var(--btn-secondary-bg-hover); }
|
||||
.btn-ghost { background: var(--btn-ghost-bg); color: var(--btn-ghost-text); border-color: var(--btn-ghost-border); }
|
||||
.btn-ghost:hover { background: var(--color-surface-2); }
|
||||
.btn-lg { padding: var(--space-4) var(--space-8); font-size: var(--text-body-lg); }
|
||||
|
||||
/* ====== Inline Preview (for showcase) ====== */
|
||||
.preview-container {
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
margin-bottom: var(--space-8);
|
||||
}
|
||||
|
||||
/* ====== Transfer / Card fields ====== */
|
||||
.ref-field {
|
||||
display: flex; gap: var(--space-3); align-items: end;
|
||||
}
|
||||
.ref-field .form-group { flex: 1; }
|
||||
.status-indicator {
|
||||
display: flex; align-items: center; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.status-indicator.pending {
|
||||
background: var(--color-warning-light); color: var(--color-warning-dark);
|
||||
}
|
||||
.status-indicator.confirmed {
|
||||
background: var(--color-success-light); color: var(--color-success-dark);
|
||||
}
|
||||
|
||||
/* ====== Terminal Select ====== */
|
||||
.terminal-cards {
|
||||
display: grid; grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.terminal-card {
|
||||
padding: var(--space-4);
|
||||
background: var(--color-surface-2);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer; transition: var(--transition-fast);
|
||||
text-align: center;
|
||||
}
|
||||
.terminal-card:hover { border-color: var(--color-primary); }
|
||||
.terminal-card.selected {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
.terminal-card .terminal-name {
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.terminal-card .terminal-type {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted); margin-top: var(--space-1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Theme Switcher -->
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="setTheme('industrial')">A — Industrial Robusto</button>
|
||||
<button onclick="setTheme('modern')">B — Técnico Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Modal de Pago</h1>
|
||||
<p class="subtitle">Componente de cobro con 4 métodos: Efectivo, Transferencia, Tarjeta, Mixto.</p>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Full Modal Preview -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Vista Completa del Modal</h2>
|
||||
<p class="label-text">Preview interactivo — haz click en las tabs para cambiar de método</p>
|
||||
|
||||
<div class="preview-container">
|
||||
<!-- Modal Header -->
|
||||
<div class="modal-header">
|
||||
<h3>Cobrar Venta</h3>
|
||||
<button class="modal-close">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Total Banner -->
|
||||
<div class="total-banner">
|
||||
<span class="total-label">Total a Cobrar</span>
|
||||
<span class="total-amount">$4,827.50</span>
|
||||
</div>
|
||||
|
||||
<!-- Items Summary -->
|
||||
<div class="items-summary">
|
||||
<span>3 productos</span>
|
||||
<span>•</span>
|
||||
<span>Cliente: Taller Automotriz García</span>
|
||||
<span>•</span>
|
||||
<span>Folio: V-2026-0847</span>
|
||||
</div>
|
||||
|
||||
<!-- Payment Tabs -->
|
||||
<div class="pago-tabs">
|
||||
<button class="pago-tab active" onclick="switchPagoTab('efectivo')">
|
||||
<span class="tab-icon">💵</span> Efectivo
|
||||
</button>
|
||||
<button class="pago-tab" onclick="switchPagoTab('transferencia')">
|
||||
<span class="tab-icon">🏦</span> Transferencia
|
||||
</button>
|
||||
<button class="pago-tab" onclick="switchPagoTab('tarjeta')">
|
||||
<span class="tab-icon">💳</span> Tarjeta
|
||||
</button>
|
||||
<button class="pago-tab" onclick="switchPagoTab('mixto')">
|
||||
<span class="tab-icon">🔄</span> Mixto
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- TAB: Efectivo -->
|
||||
<div class="tab-content active" id="tab-efectivo">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Monto recibido</label>
|
||||
<input type="text" class="form-input form-input-lg" value="$5,000.00" id="monto-recibido" oninput="calcCambio()">
|
||||
</div>
|
||||
<div class="quick-amounts">
|
||||
<button class="quick-btn" onclick="setMonto(4828)">$4,828</button>
|
||||
<button class="quick-btn" onclick="setMonto(4850)">$4,850</button>
|
||||
<button class="quick-btn" onclick="setMonto(5000)">$5,000</button>
|
||||
<button class="quick-btn" onclick="setMonto(5500)">$5,500</button>
|
||||
</div>
|
||||
<div class="cambio-display">
|
||||
<span class="cambio-label">Cambio</span>
|
||||
<span class="cambio-amount positive" id="cambio-valor">$172.50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB: Transferencia -->
|
||||
<div class="tab-content" id="tab-transferencia">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Monto</label>
|
||||
<input type="text" class="form-input form-input-lg" value="$4,827.50" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Banco destino</label>
|
||||
<select class="form-input">
|
||||
<option>BBVA — **** 4521</option>
|
||||
<option>Banorte — **** 7832</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ref-field">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Referencia / No. operación</label>
|
||||
<input type="text" class="form-input" placeholder="Ej: 2026040100847">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-hint">Verificar que la transferencia se haya recibido antes de confirmar</div>
|
||||
<div style="margin-top: var(--space-4);">
|
||||
<div class="status-indicator pending">⏳ Pendiente de confirmación</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB: Tarjeta -->
|
||||
<div class="tab-content" id="tab-tarjeta">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Monto</label>
|
||||
<input type="text" class="form-input form-input-lg" value="$4,827.50" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Terminal</label>
|
||||
<div class="terminal-cards">
|
||||
<div class="terminal-card selected">
|
||||
<div class="terminal-name">Terminal 1</div>
|
||||
<div class="terminal-type">Clip Pro — Visa/MC</div>
|
||||
</div>
|
||||
<div class="terminal-card">
|
||||
<div class="terminal-name">Terminal 2</div>
|
||||
<div class="terminal-type">Banorte TPV — Todas</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">No. de autorización</label>
|
||||
<input type="text" class="form-input" placeholder="6 dígitos de autorización">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Últimos 4 dígitos de tarjeta</label>
|
||||
<input type="text" class="form-input" placeholder="Ej: 4521" maxlength="4" style="width: 120px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB: Mixto -->
|
||||
<div class="tab-content" id="tab-mixto">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Método 1 — Efectivo</label>
|
||||
<div class="split-row">
|
||||
<input type="text" class="form-input" value="$3,000.00" style="font-family: var(--font-mono); text-align: right;">
|
||||
<div class="split-method">+</div>
|
||||
<div>
|
||||
<label class="form-label">Método 2 — Tarjeta</label>
|
||||
<input type="text" class="form-input" value="$1,827.50" style="font-family: var(--font-mono); text-align: right;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split-remaining">
|
||||
<span>Restante por asignar:</span>
|
||||
<span class="amount">$0.00</span>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: var(--space-4);">
|
||||
<label class="form-label">No. de autorización (tarjeta)</label>
|
||||
<input type="text" class="form-input" placeholder="6 dígitos">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CFDI Checkbox -->
|
||||
<div class="cfdi-check">
|
||||
<input type="checkbox" id="cfdi-check" checked>
|
||||
<label for="cfdi-check">Facturar CFDI</label>
|
||||
<span class="cfdi-hint">RFC: TAU150301XX1 — Régimen: 601</span>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-ghost">Cancelar</button>
|
||||
<button class="btn btn-primary btn-lg">✔ Confirmar Pago — $4,827.50</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: States -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Estados del Cambio</h2>
|
||||
<p class="label-text">Indicador visual de cambio positivo, exacto y faltante</p>
|
||||
<div style="display: flex; gap: var(--space-4); flex-wrap: wrap;">
|
||||
<div class="cambio-display" style="flex: 1; min-width: 200px;">
|
||||
<span class="cambio-label">Cambio</span>
|
||||
<span class="cambio-amount positive">$172.50</span>
|
||||
</div>
|
||||
<div class="cambio-display" style="flex: 1; min-width: 200px;">
|
||||
<span class="cambio-label">Cambio</span>
|
||||
<span class="cambio-amount" style="color: var(--color-text-accent);">$0.00</span>
|
||||
</div>
|
||||
<div class="cambio-display" style="flex: 1; min-width: 200px;">
|
||||
<span class="cambio-label">Faltante</span>
|
||||
<span class="cambio-amount negative">-$327.50</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Transfer Status -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Estados de Transferencia</h2>
|
||||
<div style="display: flex; gap: var(--space-4); flex-wrap: wrap;">
|
||||
<div class="status-indicator pending">⏳ Pendiente de confirmación</div>
|
||||
<div class="status-indicator confirmed">✅ Transferencia confirmada</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.querySelectorAll('.theme-switcher button').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
function switchPagoTab(tabId) {
|
||||
document.querySelectorAll('.pago-tab').forEach(t => t.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
event.target.closest('.pago-tab').classList.add('active');
|
||||
document.getElementById('tab-' + tabId).classList.add('active');
|
||||
}
|
||||
|
||||
function setMonto(val) {
|
||||
document.getElementById('monto-recibido').value = '$' + val.toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
const cambio = val - 4827.50;
|
||||
const el = document.getElementById('cambio-valor');
|
||||
el.textContent = (cambio >= 0 ? '$' : '-$') + Math.abs(cambio).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
el.className = 'cambio-amount ' + (cambio > 0 ? 'positive' : cambio < 0 ? 'negative' : '');
|
||||
}
|
||||
|
||||
function calcCambio() {
|
||||
const raw = document.getElementById('monto-recibido').value.replace(/[^0-9.]/g, '');
|
||||
const val = parseFloat(raw) || 0;
|
||||
const cambio = val - 4827.50;
|
||||
const el = document.getElementById('cambio-valor');
|
||||
el.textContent = (cambio >= 0 ? '$' : '-$') + Math.abs(cambio).toLocaleString('en-US', {minimumFractionDigits: 2});
|
||||
el.className = 'cambio-amount ' + (cambio > 0 ? 'positive' : cambio < 0 ? 'negative' : '');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
560
docs/design/design-system/components/panel-deslizante.html
Normal file
560
docs/design/design-system/components/panel-deslizante.html
Normal file
@@ -0,0 +1,560 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Panel Deslizante</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* Trigger button */
|
||||
.btn-open-panel {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-5);
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text);
|
||||
border: 1px solid var(--btn-primary-border);
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.btn-open-panel:hover {
|
||||
background: var(--btn-primary-bg-hover);
|
||||
}
|
||||
.btn-open-panel:active {
|
||||
background: var(--btn-primary-bg-active);
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
/* Demo context: fake table behind */
|
||||
.demo-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
.demo-table th, .demo-table td {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
.demo-table th {
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
background: var(--color-surface-1);
|
||||
}
|
||||
.demo-table td { color: var(--color-text-secondary); }
|
||||
.demo-table .name-link {
|
||||
color: var(--color-text-accent);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.demo-table .name-link:hover { text-decoration: underline; }
|
||||
|
||||
/* Overlay */
|
||||
.panel-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--overlay-backdrop);
|
||||
z-index: var(--z-modal);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity var(--duration-normal) var(--ease-out),
|
||||
visibility var(--duration-normal) var(--ease-out);
|
||||
}
|
||||
.panel-overlay.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Slide-in panel */
|
||||
.slide-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 380px;
|
||||
max-width: 90vw;
|
||||
height: 100vh;
|
||||
background: var(--color-bg-elevated);
|
||||
border-left: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: calc(var(--z-modal) + 1);
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--duration-slow) var(--ease-out);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.slide-panel.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Panel header */
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.panel-header h3 {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h5);
|
||||
font-weight: var(--heading-weight-secondary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.btn-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.btn-close:hover {
|
||||
background: var(--color-surface-2);
|
||||
color: var(--color-text-primary);
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
|
||||
/* Panel body */
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-5);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||
}
|
||||
|
||||
/* Client info sections */
|
||||
.client-avatar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
.client-avatar {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-primary-muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-primary);
|
||||
color: var(--color-text-accent);
|
||||
border: 2px solid var(--color-border-accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.client-info-main .client-name {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h5);
|
||||
font-weight: var(--heading-weight-primary);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.client-info-main .client-rfc {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.panel-section {
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
.panel-section-title {
|
||||
font-size: var(--text-caption);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-widest);
|
||||
color: var(--color-text-muted);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* Credit bar */
|
||||
.credit-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
.credit-item {
|
||||
text-align: center;
|
||||
padding: var(--space-3);
|
||||
background: var(--color-surface-1);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
.credit-item .credit-label {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
.credit-item .credit-value {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.credit-item .credit-value.success { color: var(--color-success); }
|
||||
.credit-item .credit-value.warning { color: var(--color-warning); }
|
||||
|
||||
.credit-bar-track {
|
||||
height: 8px;
|
||||
background: var(--color-surface-2);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
.credit-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-primary);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
|
||||
/* Vehicle list */
|
||||
.vehicle-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3);
|
||||
background: var(--color-surface-1);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
.vehicle-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.vehicle-details {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.vehicle-name {
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.vehicle-plate {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Recent purchases mini-table */
|
||||
.purchases-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.purchases-table th, .purchases-table td {
|
||||
padding: var(--space-2) var(--space-2);
|
||||
text-align: left;
|
||||
font-size: var(--text-caption);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.purchases-table th {
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wide);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.purchases-table td {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.purchases-table .amount-cell {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.purchases-table th:last-child { text-align: right; }
|
||||
|
||||
/* Panel footer */
|
||||
.panel-footer {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.panel-footer button {
|
||||
flex: 1;
|
||||
padding: var(--space-3);
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
border: 1px solid;
|
||||
}
|
||||
.panel-footer .btn-edit {
|
||||
background: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
border-color: var(--btn-secondary-border);
|
||||
}
|
||||
.panel-footer .btn-edit:hover {
|
||||
background: var(--btn-secondary-bg-hover);
|
||||
}
|
||||
.panel-footer .btn-sale {
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text);
|
||||
border-color: var(--btn-primary-border);
|
||||
}
|
||||
.panel-footer .btn-sale:hover {
|
||||
background: var(--btn-primary-bg-hover);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Panel Deslizante</h1>
|
||||
<p class="subtitle">Panel lateral que se desliza desde la derecha con detalle de cliente</p>
|
||||
|
||||
<section>
|
||||
<h2>Contexto: Lista de Clientes</h2>
|
||||
<p style="color: var(--color-text-secondary); margin-bottom: var(--space-4); font-size: var(--text-body-sm);">
|
||||
Haz clic en un nombre para abrir el panel deslizante.
|
||||
</p>
|
||||
|
||||
<table class="demo-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cliente</th>
|
||||
<th>RFC</th>
|
||||
<th>Saldo</th>
|
||||
<th>Vehículos</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a class="name-link" onclick="openPanel()">Roberto Méndez Gutiérrez</a></td>
|
||||
<td style="font-family: var(--font-mono); font-size: var(--text-caption);">MEGR820415QR3</td>
|
||||
<td style="font-family: var(--font-mono);">$12,450.00</td>
|
||||
<td>3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="name-link" onclick="openPanel()">Taller Mecánico Hermanos López</a></td>
|
||||
<td style="font-family: var(--font-mono); font-size: var(--text-caption);">THL950220MN8</td>
|
||||
<td style="font-family: var(--font-mono);">$28,900.00</td>
|
||||
<td>--</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="name-link" onclick="openPanel()">María Elena Ríos</a></td>
|
||||
<td style="font-family: var(--font-mono); font-size: var(--text-caption);">RIEM770903AB1</td>
|
||||
<td style="font-family: var(--font-mono);">$0.00</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button class="btn-open-panel" onclick="openPanel()" style="margin-top: var(--space-6);">
|
||||
<span>👤</span> Ver Detalle de Cliente
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Overlay -->
|
||||
<div class="panel-overlay" id="panelOverlay" onclick="closePanel()"></div>
|
||||
|
||||
<!-- Slide Panel -->
|
||||
<div class="slide-panel" id="slidePanel">
|
||||
<div class="panel-header">
|
||||
<h3>Detalle de Cliente</h3>
|
||||
<button class="btn-close" onclick="closePanel()" title="Cerrar">×</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<!-- Client avatar + name -->
|
||||
<div class="client-avatar-row">
|
||||
<div class="client-avatar">RM</div>
|
||||
<div class="client-info-main">
|
||||
<div class="client-name">Roberto Méndez Gutiérrez</div>
|
||||
<div class="client-rfc">RFC: MEGR820415QR3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Credit info -->
|
||||
<div class="panel-section">
|
||||
<div class="panel-section-title">Crédito</div>
|
||||
<div class="credit-grid">
|
||||
<div class="credit-item">
|
||||
<div class="credit-label">Límite</div>
|
||||
<div class="credit-value">$50,000</div>
|
||||
</div>
|
||||
<div class="credit-item">
|
||||
<div class="credit-label">Utilizado</div>
|
||||
<div class="credit-value warning">$12,450</div>
|
||||
</div>
|
||||
<div class="credit-item">
|
||||
<div class="credit-label">Disponible</div>
|
||||
<div class="credit-value success">$37,550</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="credit-bar-track">
|
||||
<div class="credit-bar-fill" style="width: 24.9%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vehicles -->
|
||||
<div class="panel-section">
|
||||
<div class="panel-section-title">Vehículos Registrados</div>
|
||||
<div class="vehicle-item">
|
||||
<div class="vehicle-icon">🚗</div>
|
||||
<div class="vehicle-details">
|
||||
<div class="vehicle-name">Nissan Sentra 2019</div>
|
||||
<div class="vehicle-plate">JKL-4521 / Motor: MR20DE</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vehicle-item">
|
||||
<div class="vehicle-icon">🛻</div>
|
||||
<div class="vehicle-details">
|
||||
<div class="vehicle-name">Toyota Hilux 2021</div>
|
||||
<div class="vehicle-plate">ABC-7890 / Motor: 2GD-FTV</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vehicle-item">
|
||||
<div class="vehicle-icon">🚗</div>
|
||||
<div class="vehicle-details">
|
||||
<div class="vehicle-name">VW Jetta 2017</div>
|
||||
<div class="vehicle-plate">MNO-1234 / Motor: EA211</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent purchases -->
|
||||
<div class="panel-section">
|
||||
<div class="panel-section-title">Compras Recientes</div>
|
||||
<table class="purchases-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>Folio</th>
|
||||
<th style="text-align:right;">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>28/Mar/2026</td>
|
||||
<td>V-04521</td>
|
||||
<td class="amount-cell">$3,240.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15/Mar/2026</td>
|
||||
<td>V-04389</td>
|
||||
<td class="amount-cell">$1,890.50</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>02/Mar/2026</td>
|
||||
<td>V-04210</td>
|
||||
<td class="amount-cell">$5,620.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18/Feb/2026</td>
|
||||
<td>V-04098</td>
|
||||
<td class="amount-cell">$780.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>05/Feb/2026</td>
|
||||
<td>V-03955</td>
|
||||
<td class="amount-cell">$2,410.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer">
|
||||
<button class="btn-edit">Editar Cliente</button>
|
||||
<button class="btn-sale">Nueva Venta</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const overlay = document.getElementById('panelOverlay');
|
||||
const panel = document.getElementById('slidePanel');
|
||||
|
||||
function openPanel() {
|
||||
overlay.classList.add('open');
|
||||
panel.classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
overlay.classList.remove('open');
|
||||
panel.classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
// Close on Escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') closePanel();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
414
docs/design/design-system/components/selector-periodo.html
Normal file
414
docs/design/design-system/components/selector-periodo.html
Normal file
@@ -0,0 +1,414 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Selector de Período</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ── Shared form styles ── */
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: var(--text-label);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-1);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-wider);
|
||||
}
|
||||
|
||||
select, input[type="date"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-base);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
padding-right: var(--space-8);
|
||||
transition: var(--transition-fast);
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
}
|
||||
select:hover, input[type="date"]:hover {
|
||||
border-color: var(--color-border-strong);
|
||||
}
|
||||
select:focus, input[type="date"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-border-focus);
|
||||
box-shadow: var(--shadow-focus);
|
||||
}
|
||||
input[type="date"] {
|
||||
padding-right: var(--space-3);
|
||||
}
|
||||
|
||||
/* Select arrow */
|
||||
.select-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.select-wrap::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12px; top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0; height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid var(--color-text-muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── Buttons ── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--btn-primary-bg);
|
||||
color: var(--btn-primary-text);
|
||||
border-color: var(--btn-primary-border);
|
||||
}
|
||||
.btn-primary:hover { background: var(--btn-primary-bg-hover); }
|
||||
.btn-primary:active { background: var(--btn-primary-bg-active); }
|
||||
|
||||
.btn-ghost {
|
||||
background: var(--btn-ghost-bg);
|
||||
color: var(--btn-ghost-text);
|
||||
border-color: var(--btn-ghost-border);
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
background: var(--color-surface-2);
|
||||
}
|
||||
|
||||
/* ── Period Selector: Dropdown variant ── */
|
||||
.period-selector {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
}
|
||||
.period-selector .field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.period-selector select {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
/* ── Presets (quick actions) ── */
|
||||
.presets {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.preset-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: var(--space-1) var(--space-3);
|
||||
border-radius: var(--radius-full);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.preset-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-muted);
|
||||
}
|
||||
.preset-btn.active {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ── Date Range variant ── */
|
||||
.date-range-selector {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-4) var(--space-5);
|
||||
}
|
||||
.date-range-selector .field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.separator {
|
||||
font-size: var(--text-body);
|
||||
color: var(--color-text-muted);
|
||||
padding-bottom: var(--space-2);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* ── Selected period display ── */
|
||||
.selected-display {
|
||||
margin-top: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-primary-muted);
|
||||
border-left: 3px solid var(--color-primary);
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.selected-display strong {
|
||||
color: var(--color-text-accent);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ── Compact inline variant ── */
|
||||
.period-inline {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
}
|
||||
.period-inline select {
|
||||
padding: var(--space-1) var(--space-2);
|
||||
padding-right: var(--space-6);
|
||||
font-size: var(--text-caption);
|
||||
min-width: 100px;
|
||||
}
|
||||
.period-inline .btn {
|
||||
padding: var(--space-1) var(--space-3);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
.period-inline .select-wrap::after {
|
||||
right: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Selector de Período</h1>
|
||||
<p class="subtitle">Controles para seleccionar períodos de reporte: mes/año, presets rápidos y rango de fechas.</p>
|
||||
|
||||
<!-- ── Variant A: Dropdowns + button ── -->
|
||||
<section>
|
||||
<h2>Selector por Mes y Año</h2>
|
||||
<div class="period-selector">
|
||||
<div class="field">
|
||||
<label class="form-label" for="selYear">Año</label>
|
||||
<div class="select-wrap">
|
||||
<select id="selYear">
|
||||
<option value="2024">2024</option>
|
||||
<option value="2025">2025</option>
|
||||
<option value="2026" selected>2026</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="form-label" for="selMonth">Mes</label>
|
||||
<div class="select-wrap">
|
||||
<select id="selMonth">
|
||||
<option value="1">Enero</option>
|
||||
<option value="2">Febrero</option>
|
||||
<option value="3">Marzo</option>
|
||||
<option value="4" selected>Abril</option>
|
||||
<option value="5">Mayo</option>
|
||||
<option value="6">Junio</option>
|
||||
<option value="7">Julio</option>
|
||||
<option value="8">Agosto</option>
|
||||
<option value="9">Septiembre</option>
|
||||
<option value="10">Octubre</option>
|
||||
<option value="11">Noviembre</option>
|
||||
<option value="12">Diciembre</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="consultarPeriodo()">Consultar</button>
|
||||
</div>
|
||||
<div class="selected-display" id="displayPeriod">
|
||||
Período seleccionado: <strong>Abril 2026</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Variant B: Quick presets ── -->
|
||||
<section>
|
||||
<h2>Presets Rápidos</h2>
|
||||
<div class="period-selector" style="flex-direction: column; align-items: flex-start; gap: var(--space-3);">
|
||||
<div class="presets" id="presetGroup">
|
||||
<button class="preset-btn active" onclick="selectPreset(this, 'Este mes')">Este mes</button>
|
||||
<button class="preset-btn" onclick="selectPreset(this, 'Mes anterior')">Mes anterior</button>
|
||||
<button class="preset-btn" onclick="selectPreset(this, 'Este trimestre')">Este trimestre</button>
|
||||
<button class="preset-btn" onclick="selectPreset(this, 'Este año')">Este año</button>
|
||||
</div>
|
||||
<div class="selected-display" id="displayPreset">
|
||||
Período: <strong>Abril 2026</strong> (Este mes)
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Variant C: Date range ── -->
|
||||
<section>
|
||||
<h2>Rango de Fechas</h2>
|
||||
<div class="date-range-selector">
|
||||
<div class="field">
|
||||
<label class="form-label" for="dateFrom">Desde</label>
|
||||
<input type="date" id="dateFrom" value="2026-04-01">
|
||||
</div>
|
||||
<span class="separator">—</span>
|
||||
<div class="field">
|
||||
<label class="form-label" for="dateTo">Hasta</label>
|
||||
<input type="date" id="dateTo" value="2026-04-30">
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="consultarRango()">Consultar</button>
|
||||
<button class="btn btn-ghost" onclick="limpiarRango()">Limpiar</button>
|
||||
</div>
|
||||
<div class="selected-display" id="displayRange">
|
||||
Rango: <strong>01/04/2026</strong> — <strong>30/04/2026</strong> (30 días)
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Variant D: Compact inline ── -->
|
||||
<section>
|
||||
<h2>Variante Compacta (Inline)</h2>
|
||||
<p style="color: var(--color-text-muted); font-size: var(--text-body-sm); margin-bottom: var(--space-3);">
|
||||
Para uso en headers de tablas o secciones con espacio reducido.
|
||||
</p>
|
||||
<div class="period-inline">
|
||||
<div class="select-wrap">
|
||||
<select>
|
||||
<option>Abril</option>
|
||||
<option>Marzo</option>
|
||||
<option>Febrero</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-wrap">
|
||||
<select>
|
||||
<option>2026</option>
|
||||
<option>2025</option>
|
||||
<option>2024</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary">Ir</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const meses = ['Enero','Febrero','Marzo','Abril','Mayo','Junio',
|
||||
'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'];
|
||||
|
||||
function consultarPeriodo() {
|
||||
const year = document.getElementById('selYear').value;
|
||||
const monthIdx = document.getElementById('selMonth').value;
|
||||
const monthName = meses[monthIdx - 1];
|
||||
document.getElementById('displayPeriod').innerHTML =
|
||||
'Período seleccionado: <strong>' + monthName + ' ' + year + '</strong>';
|
||||
}
|
||||
|
||||
function selectPreset(btn, label) {
|
||||
document.querySelectorAll('#presetGroup .preset-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
const now = new Date(2026, 3, 1); // April 2026
|
||||
let text = '';
|
||||
switch (label) {
|
||||
case 'Este mes':
|
||||
text = 'Período: <strong>Abril 2026</strong> (Este mes)';
|
||||
break;
|
||||
case 'Mes anterior':
|
||||
text = 'Período: <strong>Marzo 2026</strong> (Mes anterior)';
|
||||
break;
|
||||
case 'Este trimestre':
|
||||
text = 'Período: <strong>Enero — Marzo 2026</strong> (Q1 2026)';
|
||||
break;
|
||||
case 'Este año':
|
||||
text = 'Período: <strong>Enero — Diciembre 2026</strong> (Año completo)';
|
||||
break;
|
||||
}
|
||||
document.getElementById('displayPreset').innerHTML = text;
|
||||
}
|
||||
|
||||
function consultarRango() {
|
||||
const from = document.getElementById('dateFrom').value;
|
||||
const to = document.getElementById('dateTo').value;
|
||||
if (!from || !to) return;
|
||||
const d1 = new Date(from), d2 = new Date(to);
|
||||
const days = Math.round((d2 - d1) / (1000 * 60 * 60 * 24)) + 1;
|
||||
const fmt = d => d.toLocaleDateString('es-MX', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
document.getElementById('displayRange').innerHTML =
|
||||
'Rango: <strong>' + fmt(d1) + '</strong> — <strong>' + fmt(d2) + '</strong> (' + days + ' días)';
|
||||
}
|
||||
|
||||
function limpiarRango() {
|
||||
document.getElementById('dateFrom').value = '';
|
||||
document.getElementById('dateTo').value = '';
|
||||
document.getElementById('displayRange').innerHTML =
|
||||
'Rango: <strong>—</strong>';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
283
docs/design/design-system/components/tarjeta-metrica.html
Normal file
283
docs/design/design-system/components/tarjeta-metrica.html
Normal file
@@ -0,0 +1,283 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Tarjeta Métrica</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
/* ── Metric Card Grid ── */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* ── Metric Card ── */
|
||||
.metric-card {
|
||||
background: var(--color-bg-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-5) var(--space-5) var(--space-4);
|
||||
transition: var(--transition-normal);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.metric-card:hover {
|
||||
border-color: var(--color-border-accent);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.metric-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 3px;
|
||||
background: var(--color-primary);
|
||||
opacity: 0;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.metric-card:hover::before { opacity: 1; }
|
||||
|
||||
.metric-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-family: var(--font-heading);
|
||||
font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary);
|
||||
line-height: 1.1;
|
||||
color: var(--color-text-primary);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
|
||||
.metric-trend {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
.metric-trend.up {
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
}
|
||||
.metric-trend.down {
|
||||
background: var(--color-error-light);
|
||||
color: var(--color-error-dark);
|
||||
}
|
||||
.metric-trend .arrow {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
/* ── Progress Bar ── */
|
||||
.metric-progress {
|
||||
margin-top: auto;
|
||||
}
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
.progress-text {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.progress-value {
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.progress-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: var(--color-surface-3);
|
||||
border-radius: var(--radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-primary);
|
||||
transition: width 0.8s var(--ease-out);
|
||||
width: 0;
|
||||
}
|
||||
.progress-fill.success { background: var(--color-success); }
|
||||
.progress-fill.warning { background: var(--color-warning); }
|
||||
.progress-fill.danger { background: var(--color-error); }
|
||||
|
||||
/* ── Icon container ── */
|
||||
.metric-icon {
|
||||
width: 40px; height: 40px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-primary-muted);
|
||||
color: var(--color-primary);
|
||||
font-size: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="document.documentElement.dataset.theme='industrial'; this.classList.add('active'); this.nextElementSibling.classList.remove('active')">🔧 Industrial</button>
|
||||
<button onclick="document.documentElement.dataset.theme='modern'; this.classList.add('active'); this.previousElementSibling.classList.remove('active')">⚡ Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Tarjeta Métrica</h1>
|
||||
<p class="subtitle">Tarjetas de KPI con valor principal, tendencia y barra de progreso vs meta.</p>
|
||||
|
||||
<section>
|
||||
<h2>Dashboard de Ventas</h2>
|
||||
<div class="metrics-grid">
|
||||
|
||||
<!-- Card 1: Ventas Hoy -->
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">💰</div>
|
||||
<div class="metric-header">
|
||||
<span class="metric-value">$48,250</span>
|
||||
<span class="metric-trend up"><span class="arrow">▲</span> 12.5%</span>
|
||||
</div>
|
||||
<div class="metric-label">Ventas Hoy</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-header">
|
||||
<span class="progress-text">Meta diaria</span>
|
||||
<span class="progress-value">85%</span>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" data-width="85"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: Ticket Promedio -->
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">🧾</div>
|
||||
<div class="metric-header">
|
||||
<span class="metric-value">$1,285</span>
|
||||
<span class="metric-trend up"><span class="arrow">▲</span> 3.2%</span>
|
||||
</div>
|
||||
<div class="metric-label">Ticket Promedio</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-header">
|
||||
<span class="progress-text">Meta $1,500</span>
|
||||
<span class="progress-value">86%</span>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" data-width="86"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Margen Promedio -->
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">📊</div>
|
||||
<div class="metric-header">
|
||||
<span class="metric-value">32.4%</span>
|
||||
<span class="metric-trend down"><span class="arrow">▼</span> 1.8%</span>
|
||||
</div>
|
||||
<div class="metric-label">Margen Promedio</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-header">
|
||||
<span class="progress-text">Meta 35%</span>
|
||||
<span class="progress-value">93%</span>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill warning" data-width="93"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 4: Efectivo en Caja -->
|
||||
<div class="metric-card">
|
||||
<div class="metric-icon">🏦</div>
|
||||
<div class="metric-header">
|
||||
<span class="metric-value">$15,800</span>
|
||||
<span class="metric-trend up"><span class="arrow">▲</span> 8.7%</span>
|
||||
</div>
|
||||
<div class="metric-label">Efectivo en Caja</div>
|
||||
<div class="metric-progress">
|
||||
<div class="progress-header">
|
||||
<span class="progress-text">Corte anterior</span>
|
||||
<span class="progress-value">105%</span>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill success" data-width="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// Animate progress bars on load
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('.progress-fill').forEach(bar => {
|
||||
bar.style.width = bar.dataset.width + '%';
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
544
docs/design/design-system/components/ticket-termico.html
Normal file
544
docs/design/design-system/components/ticket-termico.html
Normal file
@@ -0,0 +1,544 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nexus Autoparts — Ticket Térmico</title>
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: var(--font-body); background: var(--color-bg-base);
|
||||
color: var(--color-text-primary); padding: var(--space-8);
|
||||
transition: var(--transition-normal);
|
||||
}
|
||||
.theme-switcher {
|
||||
position: sticky; top: 0; z-index: calc(var(--z-modal) + 10);
|
||||
display: flex; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
background: var(--color-bg-elevated);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin: calc(-1 * var(--space-8)); margin-bottom: var(--space-8);
|
||||
}
|
||||
.theme-switcher button {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-bg-base); color: var(--color-text-primary);
|
||||
border-radius: var(--radius-md); cursor: pointer;
|
||||
font-family: var(--font-body); font-size: var(--text-body-sm);
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.theme-switcher button.active {
|
||||
background: var(--color-primary); color: var(--color-text-inverse);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
h1 { font-family: var(--font-heading); font-size: var(--text-h2);
|
||||
font-weight: var(--heading-weight-primary); margin-bottom: var(--space-2);
|
||||
color: var(--color-text-accent); }
|
||||
.subtitle { color: var(--color-text-muted); font-size: var(--text-body); margin-bottom: var(--space-8); }
|
||||
section { margin-bottom: var(--space-10); }
|
||||
section > h2 {
|
||||
font-family: var(--font-heading); font-size: var(--text-h4);
|
||||
font-weight: var(--heading-weight-secondary); color: var(--color-text-secondary);
|
||||
margin-bottom: var(--space-4); padding-bottom: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.label-text {
|
||||
font-size: var(--text-caption); color: var(--color-text-muted);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-widest);
|
||||
font-weight: var(--font-weight-semibold); margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
/* ====== Ticket Sizes ====== */
|
||||
.tickets-row {
|
||||
display: flex; gap: var(--space-8); flex-wrap: wrap;
|
||||
justify-content: center; align-items: flex-start;
|
||||
}
|
||||
.ticket-wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
.ticket-label {
|
||||
font-size: var(--text-body-sm); font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-accent); margin-bottom: var(--space-3);
|
||||
text-transform: uppercase; letter-spacing: var(--tracking-wide);
|
||||
}
|
||||
|
||||
/* ====== Ticket Base ====== */
|
||||
.ticket {
|
||||
background: #ffffff; color: #000000;
|
||||
font-family: 'Courier New', 'Consolas', monospace;
|
||||
font-size: 11px; line-height: 1.4;
|
||||
padding: 12px; text-align: left;
|
||||
border: 1px dashed #ccc;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
.ticket-58 { width: 219px; /* 58mm ≈ 219px at 96dpi */ }
|
||||
.ticket-80 { width: 302px; /* 80mm ≈ 302px at 96dpi */ }
|
||||
|
||||
.ticket .store-name {
|
||||
font-size: 14px; font-weight: bold; text-align: center;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.ticket .store-tagline {
|
||||
font-size: 9px; text-align: center; color: #555;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.ticket .store-info {
|
||||
font-size: 9px; text-align: center; color: #333;
|
||||
margin-bottom: 6px; line-height: 1.3;
|
||||
}
|
||||
|
||||
.ticket .divider {
|
||||
border: none; border-top: 1px dashed #999;
|
||||
margin: 6px 0;
|
||||
}
|
||||
.ticket .divider-double {
|
||||
border: none; border-top: 2px solid #333;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.ticket .ticket-row {
|
||||
display: flex; justify-content: space-between;
|
||||
}
|
||||
.ticket .ticket-row .left { text-align: left; }
|
||||
.ticket .ticket-row .right { text-align: right; }
|
||||
|
||||
.ticket .folio-line {
|
||||
font-size: 10px; font-weight: bold;
|
||||
display: flex; justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.ticket .item-line {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.ticket .item-name {
|
||||
font-size: 10px; font-weight: bold;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.ticket .item-detail {
|
||||
display: flex; justify-content: space-between;
|
||||
font-size: 10px; color: #333;
|
||||
}
|
||||
.ticket .item-sku {
|
||||
font-size: 8px; color: #777;
|
||||
}
|
||||
|
||||
.ticket .total-section { margin-top: 4px; }
|
||||
.ticket .total-line {
|
||||
display: flex; justify-content: space-between;
|
||||
font-size: 11px;
|
||||
}
|
||||
.ticket .total-line.grand {
|
||||
font-size: 14px; font-weight: bold;
|
||||
margin-top: 2px; padding-top: 2px;
|
||||
border-top: 1px solid #333;
|
||||
}
|
||||
|
||||
.ticket .payment-section {
|
||||
margin-top: 4px; font-size: 10px;
|
||||
}
|
||||
|
||||
.ticket .footer-section {
|
||||
text-align: center; margin-top: 8px;
|
||||
font-size: 9px; color: #555;
|
||||
}
|
||||
.ticket .footer-section .thanks {
|
||||
font-size: 11px; font-weight: bold; color: #000;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.ticket .barcode {
|
||||
text-align: center; margin-top: 6px;
|
||||
font-size: 24px; letter-spacing: 2px;
|
||||
font-family: 'Libre Barcode 39', monospace;
|
||||
}
|
||||
.ticket .barcode-text {
|
||||
font-size: 8px; text-align: center; color: #666;
|
||||
}
|
||||
|
||||
/* ====== 80mm extras ====== */
|
||||
.ticket-80 .item-line-wide {
|
||||
display: grid; grid-template-columns: auto 1fr auto auto;
|
||||
gap: 8px; align-items: baseline;
|
||||
font-size: 10px; margin-bottom: 3px;
|
||||
}
|
||||
.ticket-80 .item-line-wide .qty {
|
||||
font-weight: bold; min-width: 24px; text-align: right;
|
||||
}
|
||||
.ticket-80 .item-line-wide .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.ticket-80 .item-line-wide .price { text-align: right; min-width: 55px; }
|
||||
.ticket-80 .item-line-wide .subtotal { text-align: right; font-weight: bold; min-width: 60px; }
|
||||
|
||||
/* ====== Print Styles ====== */
|
||||
@media print {
|
||||
body { background: #fff; padding: 0; }
|
||||
.theme-switcher, h1, .subtitle, section > h2,
|
||||
.label-text, .ticket-wrapper .ticket-label,
|
||||
.print-btn { display: none !important; }
|
||||
.tickets-row { justify-content: flex-start; }
|
||||
.ticket {
|
||||
border: none; box-shadow: none; padding: 4px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
/* ====== Print Button ====== */
|
||||
.print-btn {
|
||||
display: inline-flex; align-items: center; gap: var(--space-2);
|
||||
padding: var(--space-3) var(--space-6);
|
||||
background: var(--btn-primary-bg); color: var(--btn-primary-text);
|
||||
font-family: var(--font-body); font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border: none; border-radius: var(--radius-md);
|
||||
cursor: pointer; transition: var(--transition-fast);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.print-btn:hover { background: var(--btn-primary-bg-hover); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Theme Switcher -->
|
||||
<div class="theme-switcher">
|
||||
<button class="active" onclick="setTheme('industrial')">A — Industrial Robusto</button>
|
||||
<button onclick="setTheme('modern')">B — Técnico Moderno</button>
|
||||
</div>
|
||||
|
||||
<h1>Ticket Térmico</h1>
|
||||
<p class="subtitle">Layout de impresión para rollos de 58mm y 80mm. Fuente monoespaciada, CSS @media print.</p>
|
||||
|
||||
<button class="print-btn" onclick="window.print()">🖨 Imprimir Preview</button>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: 58mm vs 80mm Side by Side -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Comparativa: 58mm vs 80mm</h2>
|
||||
<p class="label-text">Ambos formatos con los mismos datos de venta</p>
|
||||
|
||||
<div class="tickets-row">
|
||||
<!-- ===== 58mm Ticket ===== -->
|
||||
<div class="ticket-wrapper">
|
||||
<div class="ticket-label">58mm (Mini Printer)</div>
|
||||
<div class="ticket ticket-58">
|
||||
<div class="store-name">NEXUS AUTOPARTS</div>
|
||||
<div class="store-tagline">Tu conexión con las refacciones</div>
|
||||
<div class="store-info">
|
||||
Av. Insurgentes Sur 1234<br>
|
||||
Col. Del Valle, CDMX 03100<br>
|
||||
Tel: (55) 1234-5678<br>
|
||||
RFC: NAU210315XX1
|
||||
</div>
|
||||
|
||||
<hr class="divider-double">
|
||||
|
||||
<div class="folio-line">
|
||||
<span>VENTA: V-0847</span>
|
||||
<span>01/04/2026</span>
|
||||
</div>
|
||||
<div class="ticket-row" style="font-size: 9px; color: #555; margin-bottom: 4px;">
|
||||
<span>Cajero: Carlos M.</span>
|
||||
<span>14:32</span>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- Items -->
|
||||
<div class="item-line">
|
||||
<div class="item-name">Balatas cer. del. Brembo</div>
|
||||
<div class="item-sku">SKU: BRM-P68034</div>
|
||||
<div class="item-detail">
|
||||
<span>2 x $1,250.00</span>
|
||||
<span>$2,500.00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-line">
|
||||
<div class="item-name">Filtro aceite Wix WL7200</div>
|
||||
<div class="item-sku">SKU: WIX-7200</div>
|
||||
<div class="item-detail">
|
||||
<span>1 x $185.00</span>
|
||||
<span>$185.00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-line">
|
||||
<div class="item-name">Amort. trasero Monroe OESpec</div>
|
||||
<div class="item-sku">SKU: MON-72364</div>
|
||||
<div class="item-detail">
|
||||
<span>2 x $1,071.25</span>
|
||||
<span>$2,142.50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="divider-double">
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="total-section">
|
||||
<div class="total-line">
|
||||
<span>Subtotal:</span><span>$4,161.64</span>
|
||||
</div>
|
||||
<div class="total-line">
|
||||
<span>IVA 16%:</span><span>$665.86</span>
|
||||
</div>
|
||||
<div class="total-line grand">
|
||||
<span>TOTAL:</span><span>$4,827.50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- Payment -->
|
||||
<div class="payment-section">
|
||||
<div class="ticket-row">
|
||||
<span>Forma pago:</span><span>Efectivo</span>
|
||||
</div>
|
||||
<div class="ticket-row">
|
||||
<span>Recibido:</span><span>$5,000.00</span>
|
||||
</div>
|
||||
<div class="ticket-row" style="font-weight: bold;">
|
||||
<span>Cambio:</span><span>$172.50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<div class="footer-section">
|
||||
<div class="thanks">¡Gracias por su compra!</div>
|
||||
<div>Garantía sujeta a condiciones</div>
|
||||
<div>del fabricante. Conserve su ticket.</div>
|
||||
<div style="margin-top: 4px;">www.nexusautoparts.com</div>
|
||||
</div>
|
||||
|
||||
<div class="barcode">||||| |||| |||||</div>
|
||||
<div class="barcode-text">V-2026-0847</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 80mm Ticket ===== -->
|
||||
<div class="ticket-wrapper">
|
||||
<div class="ticket-label">80mm (Standard POS)</div>
|
||||
<div class="ticket ticket-80">
|
||||
<div class="store-name">NEXUS AUTOPARTS</div>
|
||||
<div class="store-tagline">Tu conexión con las refacciones</div>
|
||||
<div class="store-info">
|
||||
Av. Insurgentes Sur 1234, Col. Del Valle<br>
|
||||
Ciudad de México, CP 03100 | Tel: (55) 1234-5678<br>
|
||||
RFC: NAU210315XX1
|
||||
</div>
|
||||
|
||||
<hr class="divider-double">
|
||||
|
||||
<div class="folio-line">
|
||||
<span>VENTA: V-2026-0847</span>
|
||||
<span>01/Abr/2026 14:32</span>
|
||||
</div>
|
||||
<div class="ticket-row" style="font-size: 9px; color: #555; margin-bottom: 4px;">
|
||||
<span>Cajero: Carlos Martínez</span>
|
||||
<span>Sucursal: Matriz</span>
|
||||
</div>
|
||||
<div class="ticket-row" style="font-size: 9px; color: #555; margin-bottom: 2px;">
|
||||
<span>Cliente: Taller Automotriz García</span>
|
||||
<span>RFC: TAU150301XX1</span>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- Column Headers -->
|
||||
<div class="item-line-wide" style="font-weight: bold; font-size: 9px; color: #555; text-transform: uppercase;">
|
||||
<span class="qty">Cant</span>
|
||||
<span class="name">Descripción</span>
|
||||
<span class="price">P. Unit</span>
|
||||
<span class="subtotal">Importe</span>
|
||||
</div>
|
||||
<hr class="divider" style="margin: 2px 0;">
|
||||
|
||||
<!-- Items -->
|
||||
<div class="item-line-wide">
|
||||
<span class="qty">2</span>
|
||||
<span class="name">Balatas cerám. del. Brembo P68034</span>
|
||||
<span class="price">$1,250.00</span>
|
||||
<span class="subtotal">$2,500.00</span>
|
||||
</div>
|
||||
<div class="item-line-wide">
|
||||
<span class="qty">1</span>
|
||||
<span class="name">Filtro aceite Wix WL7200</span>
|
||||
<span class="price">$185.00</span>
|
||||
<span class="subtotal">$185.00</span>
|
||||
</div>
|
||||
<div class="item-line-wide">
|
||||
<span class="qty">2</span>
|
||||
<span class="name">Amortiguador tras. Monroe OESpec 72364</span>
|
||||
<span class="price">$1,071.25</span>
|
||||
<span class="subtotal">$2,142.50</span>
|
||||
</div>
|
||||
|
||||
<hr class="divider-double">
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="total-section">
|
||||
<div class="total-line">
|
||||
<span>Subtotal (3 artículos, 5 piezas):</span><span>$4,161.64</span>
|
||||
</div>
|
||||
<div class="total-line">
|
||||
<span>IVA 16%:</span><span>$665.86</span>
|
||||
</div>
|
||||
<div class="total-line" style="font-size: 10px; color: #777;">
|
||||
<span>Descuento:</span><span>$0.00</span>
|
||||
</div>
|
||||
<div class="total-line grand">
|
||||
<span>TOTAL:</span><span>$4,827.50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- Payment -->
|
||||
<div class="payment-section">
|
||||
<div class="ticket-row">
|
||||
<span>Forma de pago:</span><span>01 — Efectivo</span>
|
||||
</div>
|
||||
<div class="ticket-row">
|
||||
<span>Monto recibido:</span><span>$5,000.00</span>
|
||||
</div>
|
||||
<div class="ticket-row" style="font-weight: bold;">
|
||||
<span>Cambio:</span><span>$172.50</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- CFDI -->
|
||||
<div style="font-size: 9px; color: #555; text-align: center; margin: 4px 0;">
|
||||
CFDI Timbrado: UUID a1b2c3d4-e5f6-7890-abcd-ef1234567890<br>
|
||||
Uso CFDI: G03 — Gastos en general<br>
|
||||
Régimen: 601 — General de Ley
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<div class="footer-section">
|
||||
<div class="thanks">¡Gracias por su compra!</div>
|
||||
<div>Garantía sujeta a condiciones del fabricante.</div>
|
||||
<div>Conserve su ticket como comprobante.</div>
|
||||
<div>Cambios y devoluciones: 15 días con ticket.</div>
|
||||
<div style="margin-top: 4px;">www.nexusautoparts.com</div>
|
||||
</div>
|
||||
|
||||
<div class="barcode">||||| |||| ||||| ||| ||||</div>
|
||||
<div class="barcode-text">V-2026-0847</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================== -->
|
||||
<!-- SECTION: Ticket Variants -->
|
||||
<!-- ============================================== -->
|
||||
<section>
|
||||
<h2>Variantes de Ticket</h2>
|
||||
<p class="label-text">Cotización y Corte de Caja</p>
|
||||
|
||||
<div class="tickets-row">
|
||||
<!-- Cotización -->
|
||||
<div class="ticket-wrapper">
|
||||
<div class="ticket-label">Cotización (80mm)</div>
|
||||
<div class="ticket ticket-80">
|
||||
<div class="store-name">NEXUS AUTOPARTS</div>
|
||||
<div class="store-info">Av. Insurgentes Sur 1234 | Tel: (55) 1234-5678</div>
|
||||
<hr class="divider-double">
|
||||
<div style="text-align: center; font-weight: bold; font-size: 13px; margin: 4px 0;">
|
||||
*** COTIZACIÓN ***
|
||||
</div>
|
||||
<div class="folio-line">
|
||||
<span>COT-2026-0312</span>
|
||||
<span>01/Abr/2026</span>
|
||||
</div>
|
||||
<div style="font-size: 9px; color: #555; margin-bottom: 4px;">
|
||||
Cliente: Taller Automotriz García | Vigencia: 7 días
|
||||
</div>
|
||||
<hr class="divider">
|
||||
<div class="item-line-wide" style="font-weight: bold; font-size: 9px;">
|
||||
<span class="qty">Cant</span><span class="name">Descripción</span>
|
||||
<span class="price">P. Unit</span><span class="subtotal">Importe</span>
|
||||
</div>
|
||||
<hr class="divider" style="margin: 2px 0;">
|
||||
<div class="item-line-wide">
|
||||
<span class="qty">2</span><span class="name">Balatas cerám. del. Brembo</span>
|
||||
<span class="price">$1,250.00</span><span class="subtotal">$2,500.00</span>
|
||||
</div>
|
||||
<div class="item-line-wide">
|
||||
<span class="qty">1</span><span class="name">Filtro aceite Wix WL7200</span>
|
||||
<span class="price">$185.00</span><span class="subtotal">$185.00</span>
|
||||
</div>
|
||||
<hr class="divider-double">
|
||||
<div class="total-line grand">
|
||||
<span>TOTAL:</span><span>$2,685.00</span>
|
||||
</div>
|
||||
<div style="font-size: 9px; color: #555; text-align: center; margin-top: 6px;">
|
||||
Precios sujetos a cambio sin previo aviso.<br>
|
||||
Esta cotización NO es un comprobante fiscal.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Corte de Caja -->
|
||||
<div class="ticket-wrapper">
|
||||
<div class="ticket-label">Corte de Caja (80mm)</div>
|
||||
<div class="ticket ticket-80">
|
||||
<div class="store-name">NEXUS AUTOPARTS</div>
|
||||
<div class="store-info">Sucursal: Matriz | Caja: 01</div>
|
||||
<hr class="divider-double">
|
||||
<div style="text-align: center; font-weight: bold; font-size: 13px; margin: 4px 0;">
|
||||
*** CORTE DE CAJA ***
|
||||
</div>
|
||||
<div class="folio-line">
|
||||
<span>Cajero: Carlos M.</span>
|
||||
<span>01/Abr/2026</span>
|
||||
</div>
|
||||
<div style="font-size: 9px; color: #555; margin-bottom: 4px;">
|
||||
Apertura: 08:00 | Cierre: 20:00
|
||||
</div>
|
||||
<hr class="divider">
|
||||
<div style="font-weight: bold; font-size: 10px; margin-bottom: 4px;">RESUMEN DE VENTAS</div>
|
||||
<div class="ticket-row"><span>Ventas realizadas:</span><span>47</span></div>
|
||||
<div class="ticket-row"><span>Artículos vendidos:</span><span>128</span></div>
|
||||
<div class="ticket-row"><span>Cancelaciones:</span><span>2</span></div>
|
||||
<div class="ticket-row"><span>Devoluciones:</span><span>1</span></div>
|
||||
<hr class="divider">
|
||||
<div style="font-weight: bold; font-size: 10px; margin-bottom: 4px;">FORMAS DE PAGO</div>
|
||||
<div class="ticket-row"><span>Efectivo:</span><span>$35,420.00</span></div>
|
||||
<div class="ticket-row"><span>Tarjeta crédito:</span><span>$12,850.00</span></div>
|
||||
<div class="ticket-row"><span>Tarjeta débito:</span><span>$8,200.00</span></div>
|
||||
<div class="ticket-row"><span>Transferencia:</span><span>$5,670.00</span></div>
|
||||
<hr class="divider-double">
|
||||
<div class="total-line grand"><span>TOTAL:</span><span>$62,140.00</span></div>
|
||||
<hr class="divider">
|
||||
<div style="font-weight: bold; font-size: 10px; margin-bottom: 4px;">EFECTIVO EN CAJA</div>
|
||||
<div class="ticket-row"><span>Fondo apertura:</span><span>$3,000.00</span></div>
|
||||
<div class="ticket-row"><span>+ Cobros efectivo:</span><span>$35,420.00</span></div>
|
||||
<div class="ticket-row"><span>- Retiros:</span><span>$15,000.00</span></div>
|
||||
<div class="ticket-row" style="font-weight: bold;"><span>Debe haber:</span><span>$23,420.00</span></div>
|
||||
<div class="ticket-row"><span>Conteo físico:</span><span>$23,380.00</span></div>
|
||||
<div class="ticket-row" style="color: #c00; font-weight: bold;"><span>Diferencia:</span><span>-$40.00</span></div>
|
||||
<hr class="divider">
|
||||
<div class="footer-section">
|
||||
<div style="margin-top: 20px; font-size: 9px;">
|
||||
Firma cajero: _______________<br><br>
|
||||
Firma supervisor: _______________
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.querySelectorAll('.theme-switcher button').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -188,7 +188,7 @@
|
||||
===================================================================== */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
========================================================================= */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
========================================================================= */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
========================================================================== */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--color-bg-elevated);
|
||||
border-right: 1px solid var(--color-border);
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
========================================================================= */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -2573,6 +2573,131 @@
|
||||
|
||||
</div><!-- /app-shell -->
|
||||
|
||||
<!-- =========================================================================
|
||||
MODAL: DETALLE CFDI + XML PREVIEW
|
||||
========================================================================= -->
|
||||
<div class="modal-overlay" id="modalDetalleOverlay" style="display:none; position:fixed; inset:0; z-index:var(--z-modal); background:var(--overlay-backdrop); display:none; align-items:center; justify-content:center;">
|
||||
<div class="modal-card" style="background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); width:720px; max-width:95vw; max-height:90vh; overflow:auto; box-shadow:var(--shadow-xl);">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; padding:var(--space-5) var(--space-6); border-bottom:1px solid var(--color-border);">
|
||||
<div>
|
||||
<div style="font-family:var(--font-heading); font-size:var(--text-h5); font-weight:var(--heading-weight-primary); color:var(--color-text-primary);">Detalle de Factura</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); margin-top:2px;">NAP-001289 — Timbrada</div>
|
||||
</div>
|
||||
<button onclick="document.getElementById('modalDetalleOverlay').style.display='none'" style="background:none; border:none; color:var(--color-text-muted); font-size:1.4rem; cursor:pointer; padding:var(--space-2);">✕</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-5) var(--space-6);">
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:var(--space-4); margin-bottom:var(--space-6);">
|
||||
<div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">Emisor</div>
|
||||
<div style="font-weight:var(--font-weight-semibold);">Nexus Autoparts SA de CV</div>
|
||||
<div style="font-family:var(--font-mono); font-size:var(--text-body-sm); color:var(--color-text-secondary);">NAP960714JK3</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">Receptor</div>
|
||||
<div style="font-weight:var(--font-weight-semibold);">Taller Mecánico Rodríguez</div>
|
||||
<div style="font-family:var(--font-mono); font-size:var(--text-body-sm); color:var(--color-text-secondary);">TMR8402156HJ</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">UUID</div>
|
||||
<div style="font-family:var(--font-mono); font-size:var(--text-caption); color:var(--color-text-accent); word-break:break-all;">6ba7b810-9dad-11d1-80b4-00c04fd430c8</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">Total</div>
|
||||
<div style="font-family:var(--font-mono); font-size:var(--text-h4); font-weight:var(--font-weight-bold); color:var(--color-text-primary);">$4,002.00</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- XML Preview -->
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-2);">Vista previa XML</div>
|
||||
<pre style="background:var(--color-surface-3); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:var(--space-4); font-family:var(--font-mono); font-size:11px; color:var(--color-text-secondary); overflow-x:auto; max-height:200px; line-height:1.6;"><?xml version="1.0" encoding="UTF-8"?>
|
||||
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/4"
|
||||
Version="4.0" Serie="A" Folio="001289"
|
||||
Fecha="2026-03-31T14:22:00" FormaPago="01"
|
||||
SubTotal="3450.00" Moneda="MXN"
|
||||
Total="4002.00" TipoDeComprobante="I"
|
||||
MetodoPago="PUE" LugarExpedicion="64000">
|
||||
<cfdi:Emisor Rfc="NAP960714JK3"
|
||||
Nombre="Nexus Autoparts SA de CV"
|
||||
RegimenFiscal="601"/>
|
||||
<cfdi:Receptor Rfc="TMR8402156HJ"
|
||||
Nombre="Taller Mecánico Rodríguez"
|
||||
UsoCFDI="G03" DomicilioFiscalReceptor="64000"
|
||||
RegimenFiscalReceptor="612"/>
|
||||
<cfdi:Conceptos>
|
||||
<cfdi:Concepto ClaveProdServ="25174800"
|
||||
Cantidad="4" ClaveUnidad="H87"
|
||||
Descripcion="Balatas delanteras TRW"
|
||||
ValorUnitario="485.00" Importe="1940.00"/>
|
||||
</cfdi:Conceptos>
|
||||
</cfdi:Comprobante></pre>
|
||||
</div>
|
||||
<div style="display:flex; gap:var(--space-3); justify-content:flex-end; padding:var(--space-4) var(--space-6); border-top:1px solid var(--color-border);">
|
||||
<button style="padding:var(--space-2) var(--space-5); border:1px solid var(--color-border); border-radius:var(--radius-md); background:transparent; color:var(--color-text-primary); cursor:pointer; font-family:var(--font-body); font-size:var(--text-body-sm);">Descargar XML</button>
|
||||
<button style="padding:var(--space-2) var(--space-5); border:1px solid var(--color-border); border-radius:var(--radius-md); background:transparent; color:var(--color-text-primary); cursor:pointer; font-family:var(--font-body); font-size:var(--text-body-sm);">Descargar PDF</button>
|
||||
<button style="padding:var(--space-2) var(--space-5); border:1px solid var(--btn-danger-bg); border-radius:var(--radius-md); background:var(--btn-danger-bg); color:var(--btn-danger-text); cursor:pointer; font-family:var(--font-body); font-size:var(--text-body-sm); font-weight:var(--font-weight-semibold);" onclick="document.getElementById('modalDetalleOverlay').style.display='none'; document.getElementById('modalCancelOverlay').style.display='flex';">Cancelar CFDI</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================================================================
|
||||
MODAL: CANCELACIÓN CFDI — Selector Motivo SAT
|
||||
========================================================================= -->
|
||||
<div class="modal-overlay" id="modalCancelOverlay" style="display:none; position:fixed; inset:0; z-index:var(--z-modal); background:var(--overlay-backdrop); display:none; align-items:center; justify-content:center;">
|
||||
<div class="modal-card" style="background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); width:520px; max-width:95vw; box-shadow:var(--shadow-xl);">
|
||||
<div style="display:flex; align-items:center; gap:var(--space-3); padding:var(--space-5) var(--space-6); border-bottom:1px solid var(--color-border);">
|
||||
<div style="width:40px; height:40px; border-radius:var(--radius-full); background:rgba(239,68,68,0.12); display:flex; align-items:center; justify-content:center; flex-shrink:0;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-error)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-family:var(--font-heading); font-size:var(--text-h5); font-weight:var(--heading-weight-primary); color:var(--color-text-primary);">Cancelar CFDI</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted);">NAP-001289 — $4,002.00 MXN</div>
|
||||
</div>
|
||||
<button onclick="document.getElementById('modalCancelOverlay').style.display='none'" style="background:none; border:none; color:var(--color-text-muted); font-size:1.4rem; cursor:pointer; padding:var(--space-2); margin-left:auto;">✕</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-5) var(--space-6);">
|
||||
<div style="font-size:var(--text-body-sm); font-weight:var(--font-weight-semibold); color:var(--color-text-primary); margin-bottom:var(--space-3);">Motivo de cancelación (SAT)</div>
|
||||
<div style="display:flex; flex-direction:column; gap:var(--space-2);">
|
||||
<label style="display:flex; align-items:flex-start; gap:var(--space-3); padding:var(--space-3) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); cursor:pointer; transition:var(--transition-fast);" onmouseover="this.style.borderColor='var(--color-primary)'" onmouseout="this.style.borderColor='var(--color-border)'">
|
||||
<input type="radio" name="motivo-sat" value="01" style="margin-top:3px; accent-color:var(--color-primary);" checked>
|
||||
<div>
|
||||
<div style="font-weight:var(--font-weight-semibold); font-size:var(--text-body-sm);">01 — Comprobante emitido con errores con relación</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted);">Se sustituirá por otro CFDI. Requiere UUID de reemplazo.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label style="display:flex; align-items:flex-start; gap:var(--space-3); padding:var(--space-3) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); cursor:pointer; transition:var(--transition-fast);" onmouseover="this.style.borderColor='var(--color-primary)'" onmouseout="this.style.borderColor='var(--color-border)'">
|
||||
<input type="radio" name="motivo-sat" value="02" style="margin-top:3px; accent-color:var(--color-primary);">
|
||||
<div>
|
||||
<div style="font-weight:var(--font-weight-semibold); font-size:var(--text-body-sm);">02 — Comprobante emitido con errores sin relación</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted);">No se sustituirá por otro CFDI.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label style="display:flex; align-items:flex-start; gap:var(--space-3); padding:var(--space-3) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); cursor:pointer; transition:var(--transition-fast);" onmouseover="this.style.borderColor='var(--color-primary)'" onmouseout="this.style.borderColor='var(--color-border)'">
|
||||
<input type="radio" name="motivo-sat" value="03" style="margin-top:3px; accent-color:var(--color-primary);">
|
||||
<div>
|
||||
<div style="font-weight:var(--font-weight-semibold); font-size:var(--text-body-sm);">03 — No se llevó a cabo la operación</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted);">La operación que amparó el CFDI no se realizó.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label style="display:flex; align-items:flex-start; gap:var(--space-3); padding:var(--space-3) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); cursor:pointer; transition:var(--transition-fast);" onmouseover="this.style.borderColor='var(--color-primary)'" onmouseout="this.style.borderColor='var(--color-border)'">
|
||||
<input type="radio" name="motivo-sat" value="04" style="margin-top:3px; accent-color:var(--color-primary);">
|
||||
<div>
|
||||
<div style="font-weight:var(--font-weight-semibold); font-size:var(--text-body-sm);">04 — Operación nominativa relacionada en una factura global</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted);">Cancelar cuando la operación se desglosó en una factura global.</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<!-- UUID de reemplazo (solo para motivo 01) -->
|
||||
<div style="margin-top:var(--space-4);">
|
||||
<label style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); display:block; margin-bottom:var(--space-1);">UUID de CFDI sustituto (motivo 01)</label>
|
||||
<input type="text" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" style="width:100%; padding:var(--space-2) var(--space-3); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-surface-2); color:var(--color-text-primary); font-family:var(--font-mono); font-size:var(--text-body-sm);">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:var(--space-3); justify-content:flex-end; padding:var(--space-4) var(--space-6); border-top:1px solid var(--color-border);">
|
||||
<button onclick="document.getElementById('modalCancelOverlay').style.display='none'" style="padding:var(--space-2) var(--space-5); border:1px solid var(--color-border); border-radius:var(--radius-md); background:transparent; color:var(--color-text-primary); cursor:pointer; font-family:var(--font-body); font-size:var(--text-body-sm);">Cancelar</button>
|
||||
<button onclick="document.getElementById('modalCancelOverlay').style.display='none'" style="padding:var(--space-2) var(--space-5); border:1px solid var(--btn-danger-bg); border-radius:var(--radius-md); background:var(--btn-danger-bg); color:var(--btn-danger-text); cursor:pointer; font-family:var(--font-body); font-size:var(--text-body-sm); font-weight:var(--font-weight-semibold);">Solicitar Cancelación SAT</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================================================================
|
||||
JAVASCRIPT
|
||||
========================================================================= -->
|
||||
@@ -2633,6 +2758,28 @@
|
||||
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
MODAL HELPERS
|
||||
------------------------------------------------------------------------- */
|
||||
// Wire all XML buttons to open the detail modal
|
||||
document.querySelectorAll('.btn--ghost').forEach(function(btn) {
|
||||
if (btn.textContent.trim() === 'XML') {
|
||||
btn.addEventListener('click', function() {
|
||||
document.getElementById('modalDetalleOverlay').style.display = 'flex';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Close modals on overlay click
|
||||
['modalDetalleOverlay', 'modalCancelOverlay'].forEach(function(id) {
|
||||
var overlay = document.getElementById(id);
|
||||
if (overlay) {
|
||||
overlay.addEventListener('click', function(e) {
|
||||
if (e.target === overlay) overlay.style.display = 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
========================================================================= */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -890,92 +890,8 @@
|
||||
<!-- USER SELECTION -->
|
||||
<section class="users-section" aria-labelledby="users-label">
|
||||
<div class="section-label" id="users-label">Seleccionar usuario</div>
|
||||
<div class="users-grid" role="radiogroup" aria-label="Usuarios disponibles">
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="JR"
|
||||
data-name="J. Ramírez"
|
||||
data-role="Vendedor"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Jorge Ramírez, Vendedor"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">JR</div>
|
||||
<div class="user-name">J. Ramírez</div>
|
||||
<div class="user-role">Vendedor</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="ML"
|
||||
data-name="M. López"
|
||||
data-role="Cajero"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="María López, Cajero"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">ML</div>
|
||||
<div class="user-name">M. López</div>
|
||||
<div class="user-role">Cajero</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="AP"
|
||||
data-name="A. Peña"
|
||||
data-role="Almacén"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Alejandro Peña, Almacén"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">AP</div>
|
||||
<div class="user-name">A. Peña</div>
|
||||
<div class="user-role">Almacén</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="SC"
|
||||
data-name="S. Cruz"
|
||||
data-role="Supervisor"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Sara Cruz, Supervisor"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">SC</div>
|
||||
<div class="user-name">S. Cruz</div>
|
||||
<div class="user-role">Supervisor</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="HG"
|
||||
data-name="H. García"
|
||||
data-role="Gerente"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Hugo García, Gerente"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">HG</div>
|
||||
<div class="user-name">H. García</div>
|
||||
<div class="user-role">Gerente</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="RT"
|
||||
data-name="R. Torres"
|
||||
data-role="Vendedor"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="Roberto Torres, Vendedor"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">RT</div>
|
||||
<div class="user-name">R. Torres</div>
|
||||
<div class="user-role">Vendedor</div>
|
||||
</button>
|
||||
|
||||
<div class="users-grid" id="users-grid" role="radiogroup" aria-label="Usuarios disponibles">
|
||||
<!-- Empleados cargados dinámicamente desde el tenant -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1071,12 +987,61 @@
|
||||
maxPinLength: 6,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
EMPLEADOS DEL TENANT (dinámico — en producción viene de la API)
|
||||
------------------------------------------------------------------ */
|
||||
const tenantEmployees = [
|
||||
{ id: 'JR', initials: 'JR', name: 'J. Ramírez', fullName: 'Jorge Ramírez', role: 'Vendedor' },
|
||||
{ id: 'ML', initials: 'ML', name: 'M. López', fullName: 'María López', role: 'Cajero' },
|
||||
{ id: 'AP', initials: 'AP', name: 'A. Peña', fullName: 'Alejandro Peña', role: 'Almacén' },
|
||||
{ id: 'SC', initials: 'SC', name: 'S. Cruz', fullName: 'Sara Cruz', role: 'Supervisor' },
|
||||
{ id: 'HG', initials: 'HG', name: 'H. García', fullName: 'Hugo García', role: 'Gerente' },
|
||||
{ id: 'RT', initials: 'RT', name: 'R. Torres', fullName: 'Roberto Torres', role: 'Vendedor' },
|
||||
];
|
||||
|
||||
function renderEmployees(employees) {
|
||||
const grid = document.getElementById('users-grid');
|
||||
grid.innerHTML = employees.map(emp => `
|
||||
<button
|
||||
class="user-avatar-btn"
|
||||
data-user="${emp.id}"
|
||||
data-name="${emp.name}"
|
||||
data-role="${emp.role}"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-label="${emp.fullName}, ${emp.role}"
|
||||
>
|
||||
<div class="user-initials" aria-hidden="true">${emp.initials}</div>
|
||||
<div class="user-name">${emp.name}</div>
|
||||
<div class="user-role">${emp.role}</div>
|
||||
</button>
|
||||
`).join('');
|
||||
bindUserButtons();
|
||||
}
|
||||
|
||||
function bindUserButtons() {
|
||||
document.querySelectorAll('.user-avatar-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.user-avatar-btn').forEach(b => {
|
||||
b.classList.remove('selected');
|
||||
b.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
btn.classList.add('selected');
|
||||
btn.setAttribute('aria-checked', 'true');
|
||||
state.selectedUser = btn.dataset.user;
|
||||
state.pin = [];
|
||||
enablePinPad();
|
||||
updatePinDisplay();
|
||||
updateLoginButton();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
DOM REFS
|
||||
------------------------------------------------------------------ */
|
||||
const html = document.documentElement;
|
||||
const themeBtns = document.querySelectorAll('.theme-btn');
|
||||
const userBtns = document.querySelectorAll('.user-avatar-btn');
|
||||
const pinDisplay = document.getElementById('pin-display');
|
||||
const pinPlaceholder = document.getElementById('pin-placeholder');
|
||||
const pinDots = document.querySelectorAll('.pin-dot');
|
||||
@@ -1105,28 +1070,9 @@
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
USER SELECTION
|
||||
RENDER EMPLOYEES ON INIT
|
||||
------------------------------------------------------------------ */
|
||||
userBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
// Deselect all
|
||||
userBtns.forEach(b => {
|
||||
b.classList.remove('selected');
|
||||
b.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
// Select clicked
|
||||
btn.classList.add('selected');
|
||||
btn.setAttribute('aria-checked', 'true');
|
||||
state.selectedUser = btn.dataset.user;
|
||||
state.pin = [];
|
||||
|
||||
// Enable PIN pad
|
||||
enablePinPad();
|
||||
updatePinDisplay();
|
||||
updateLoginButton();
|
||||
});
|
||||
});
|
||||
renderEmployees(tenantEmployees);
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
PIN PAD LOGIC
|
||||
|
||||
@@ -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
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
========================================================================= */
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user