Files
Autoparts-DB/docs/design/design-system/components/calculadora-cambio.html
Lucy ccd3962458 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
2026-04-01 07:06:34 +00:00

404 lines
13 KiB
HTML

<!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">&times; ${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>