- Login: PIN pad con seleccion de usuario + auth real via API - Catalogo: grid de productos con sidebar nav y filtros - POS: layout split con numpad y area de venta - tokens.css: sistema completo de CSS variables (colores, tipografia, espaciado) - 2 temas: Industrial Robusto (dark/amber) y Tecnico Moderno (light/orange) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2098 lines
67 KiB
HTML
2098 lines
67 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 — Punto de Venta</title>
|
||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||
|
||
<style>
|
||
/* =====================================================================
|
||
BASE RESET & LAYOUT SHELL
|
||
===================================================================== */
|
||
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
html, body {
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
body {
|
||
font-family: var(--font-body);
|
||
font-size: var(--text-body);
|
||
font-weight: var(--font-weight-regular);
|
||
background-color: var(--color-bg-base);
|
||
color: var(--color-text-primary);
|
||
transition: background-color var(--duration-normal) var(--ease-in-out),
|
||
color var(--duration-normal) var(--ease-in-out);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Dot-grid for modern theme (applied to body shell) */
|
||
[data-theme="modern"] body,
|
||
[data-theme="modern"].pos-shell {
|
||
background-color: var(--color-bg-base);
|
||
background-image: radial-gradient(
|
||
circle,
|
||
var(--dot-grid-color) 1px,
|
||
transparent 1px
|
||
);
|
||
background-size: var(--dot-grid-size) var(--dot-grid-size);
|
||
}
|
||
|
||
/* =====================================================================
|
||
STATUS BAR (very top)
|
||
===================================================================== */
|
||
|
||
.status-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
height: 36px;
|
||
padding: 0 var(--space-5);
|
||
background-color: var(--color-surface-3);
|
||
border-bottom: 1px solid var(--color-border);
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wider);
|
||
text-transform: uppercase;
|
||
color: var(--color-text-muted);
|
||
flex-shrink: 0;
|
||
z-index: var(--z-sticky);
|
||
}
|
||
|
||
[data-theme="industrial"] .status-bar {
|
||
background-color: #111111;
|
||
border-bottom-color: var(--color-primary-muted);
|
||
}
|
||
|
||
.status-bar__store {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
color: var(--color-text-accent);
|
||
font-family: var(--font-heading);
|
||
font-weight: var(--heading-weight-primary);
|
||
font-size: 0.8rem;
|
||
letter-spacing: var(--tracking-widest);
|
||
}
|
||
|
||
.status-bar__store-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
background-color: var(--color-success);
|
||
border-radius: var(--radius-full);
|
||
box-shadow: 0 0 6px var(--color-success);
|
||
animation: pulse-dot 2.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse-dot {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.4; }
|
||
}
|
||
|
||
.status-bar__center {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-6);
|
||
}
|
||
|
||
.status-bar__right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-4);
|
||
}
|
||
|
||
.status-bar__user {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.status-bar__user-avatar {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: var(--radius-full);
|
||
background-color: var(--color-primary);
|
||
color: var(--color-text-inverse);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.6rem;
|
||
font-weight: var(--font-weight-bold);
|
||
}
|
||
|
||
/* Theme switcher inside status bar */
|
||
.theme-switcher {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
background-color: var(--color-surface-1);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-full);
|
||
padding: 2px;
|
||
}
|
||
|
||
.theme-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-1);
|
||
padding: 3px var(--space-3);
|
||
border: none;
|
||
border-radius: var(--radius-full);
|
||
font-family: var(--font-body);
|
||
font-size: 0.68rem;
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wide);
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
background: transparent;
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.theme-btn.active {
|
||
background-color: var(--color-primary);
|
||
color: var(--color-text-inverse);
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
|
||
[data-theme="industrial"] .theme-btn.active {
|
||
color: #000;
|
||
}
|
||
|
||
/* =====================================================================
|
||
MAIN CONTENT — split layout
|
||
===================================================================== */
|
||
|
||
.pos-shell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.pos-main {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
gap: 0;
|
||
}
|
||
|
||
/* =====================================================================
|
||
LEFT PANEL — Product Browser (60%)
|
||
===================================================================== */
|
||
|
||
.panel-products {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 60%;
|
||
min-width: 0;
|
||
border-right: 1px solid var(--color-border);
|
||
background-color: var(--color-bg-base);
|
||
overflow: hidden;
|
||
}
|
||
|
||
[data-theme="modern"] .panel-products {
|
||
background-color: transparent;
|
||
}
|
||
|
||
/* Search bar row */
|
||
.search-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
padding: var(--space-4) var(--space-5);
|
||
background-color: var(--color-surface-1);
|
||
border-bottom: 1px solid var(--color-border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-wrap {
|
||
position: relative;
|
||
flex: 1;
|
||
}
|
||
|
||
.search-icon {
|
||
position: absolute;
|
||
left: var(--space-4);
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: var(--color-text-muted);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
height: 48px;
|
||
padding: 0 var(--space-4) 0 44px;
|
||
background-color: var(--color-bg-overlay);
|
||
border: 1.5px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
font-family: var(--font-body);
|
||
font-size: var(--text-body-lg);
|
||
font-weight: var(--font-weight-regular);
|
||
color: var(--color-text-primary);
|
||
outline: none;
|
||
transition: var(--transition-fast);
|
||
}
|
||
|
||
.search-input::placeholder {
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.search-input:focus {
|
||
border-color: var(--color-border-focus);
|
||
box-shadow: var(--shadow-focus);
|
||
background-color: var(--color-bg-overlay);
|
||
}
|
||
|
||
[data-theme="modern"] .search-input {
|
||
border-radius: var(--radius-lg);
|
||
background-color: #fff;
|
||
}
|
||
|
||
.btn-scan {
|
||
width: 48px;
|
||
height: 48px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: var(--btn-secondary-bg);
|
||
border: 1.5px solid var(--btn-secondary-border);
|
||
border-radius: var(--radius-md);
|
||
color: var(--color-primary);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.btn-scan:hover {
|
||
background-color: var(--btn-secondary-bg-hover);
|
||
box-shadow: var(--shadow-accent);
|
||
}
|
||
|
||
[data-theme="industrial"] .btn-scan {
|
||
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 0 100%);
|
||
}
|
||
|
||
[data-theme="modern"] .btn-scan {
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
|
||
/* Quick category buttons */
|
||
.categories-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-3) var(--space-5);
|
||
border-bottom: 1px solid var(--color-border);
|
||
overflow-x: auto;
|
||
flex-shrink: 0;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
.categories-row::-webkit-scrollbar { display: none; }
|
||
|
||
.category-label {
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wider);
|
||
text-transform: uppercase;
|
||
color: var(--color-text-muted);
|
||
white-space: nowrap;
|
||
margin-right: var(--space-1);
|
||
}
|
||
|
||
.cat-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-2) var(--space-4);
|
||
border: 1.5px solid var(--color-border);
|
||
border-radius: var(--radius-sm);
|
||
background-color: transparent;
|
||
font-family: var(--font-body);
|
||
font-size: var(--text-body-sm);
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-secondary);
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition: var(--transition-fast);
|
||
letter-spacing: var(--tracking-snug);
|
||
}
|
||
|
||
.cat-btn:hover {
|
||
border-color: var(--color-primary);
|
||
color: var(--color-primary);
|
||
background-color: var(--color-primary-muted);
|
||
}
|
||
|
||
.cat-btn.active {
|
||
border-color: var(--color-primary);
|
||
background-color: var(--color-primary);
|
||
color: var(--color-text-inverse);
|
||
}
|
||
|
||
[data-theme="industrial"] .cat-btn.active { color: #000; }
|
||
|
||
[data-theme="modern"] .cat-btn {
|
||
border-radius: var(--radius-full);
|
||
}
|
||
|
||
[data-theme="industrial"] .cat-btn {
|
||
clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 0 100%);
|
||
}
|
||
|
||
/* Product grid */
|
||
.product-grid-wrap {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: var(--space-4) var(--space-5);
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||
}
|
||
|
||
.product-grid-wrap::-webkit-scrollbar { width: 6px; }
|
||
.product-grid-wrap::-webkit-scrollbar-track { background: var(--scrollbar-track); }
|
||
.product-grid-wrap::-webkit-scrollbar-thumb {
|
||
background-color: var(--scrollbar-thumb);
|
||
border-radius: var(--radius-full);
|
||
}
|
||
.product-grid-wrap::-webkit-scrollbar-thumb:hover {
|
||
background-color: var(--scrollbar-thumb-hover);
|
||
}
|
||
|
||
.product-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: var(--space-3);
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.product-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
/* Product card */
|
||
.product-card {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: var(--space-4);
|
||
background-color: var(--color-surface-1);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.product-card:hover {
|
||
border-color: var(--color-primary);
|
||
box-shadow: var(--shadow-md);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.product-card:hover .product-card__add {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
|
||
[data-theme="industrial"] .product-card {
|
||
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
|
||
background-color: var(--color-surface-1);
|
||
border-color: #2a2a2a;
|
||
}
|
||
|
||
[data-theme="industrial"] .product-card:hover {
|
||
border-color: var(--color-primary);
|
||
box-shadow: 0 0 0 1px var(--color-primary), var(--shadow-md);
|
||
}
|
||
|
||
[data-theme="modern"] .product-card {
|
||
border-radius: var(--radius-lg);
|
||
background-color: #fff;
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
|
||
[data-theme="modern"] .product-card:hover {
|
||
box-shadow: var(--shadow-lg);
|
||
}
|
||
|
||
.product-card__category {
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-widest);
|
||
text-transform: uppercase;
|
||
color: var(--color-primary);
|
||
margin-bottom: var(--space-1);
|
||
}
|
||
|
||
.product-card__name {
|
||
font-family: var(--font-heading);
|
||
font-size: var(--text-body);
|
||
font-weight: var(--heading-weight-primary);
|
||
line-height: var(--leading-h5);
|
||
color: var(--color-text-primary);
|
||
margin-bottom: var(--space-1);
|
||
letter-spacing: var(--heading-tracking-h5);
|
||
}
|
||
|
||
[data-theme="industrial"] .product-card__name {
|
||
font-size: 1.05rem;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
.product-card__oem {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-caption);
|
||
color: var(--color-text-muted);
|
||
margin-bottom: var(--space-3);
|
||
}
|
||
|
||
.product-card__footer {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
margin-top: auto;
|
||
gap: var(--space-2);
|
||
}
|
||
|
||
.product-card__price {
|
||
font-family: var(--font-mono);
|
||
font-size: 1.1rem;
|
||
font-weight: var(--font-weight-bold);
|
||
color: var(--color-text-primary);
|
||
line-height: 1;
|
||
}
|
||
|
||
.product-card__stock {
|
||
font-size: var(--text-caption);
|
||
color: var(--color-text-muted);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.product-card__stock.low { color: var(--color-warning); }
|
||
.product-card__stock.out { color: var(--color-error); }
|
||
|
||
.product-card__add {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: none;
|
||
border-radius: var(--radius-sm);
|
||
background-color: var(--color-primary);
|
||
color: var(--color-text-inverse);
|
||
font-size: 1.2rem;
|
||
font-weight: var(--font-weight-bold);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
opacity: 0.75;
|
||
transform: scale(0.95);
|
||
transition: var(--transition-fast);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.product-card__add:hover {
|
||
background-color: var(--color-primary-hover);
|
||
opacity: 1;
|
||
transform: scale(1.08) !important;
|
||
}
|
||
|
||
[data-theme="industrial"] .product-card__add { color: #000; }
|
||
[data-theme="modern"] .product-card__add { border-radius: var(--radius-md); }
|
||
|
||
/* Add flash animation */
|
||
@keyframes card-flash {
|
||
0% { background-color: var(--color-primary-muted); }
|
||
100% { background-color: transparent; }
|
||
}
|
||
|
||
.product-card.added {
|
||
animation: card-flash 0.4s ease forwards;
|
||
}
|
||
|
||
/* =====================================================================
|
||
RIGHT PANEL — Cart / Ticket (40%)
|
||
===================================================================== */
|
||
|
||
.panel-cart {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: 40%;
|
||
min-width: 320px;
|
||
background-color: var(--color-surface-1);
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
[data-theme="modern"] .panel-cart {
|
||
background-color: #fff;
|
||
box-shadow: -4px 0 20px rgba(26, 26, 46, 0.06);
|
||
}
|
||
|
||
[data-theme="industrial"] .panel-cart {
|
||
background-color: #141414;
|
||
border-left: 1px solid #2a2a2a;
|
||
}
|
||
|
||
/* Cart header */
|
||
.cart-header {
|
||
padding: var(--space-4) var(--space-5);
|
||
border-bottom: 1px solid var(--color-border);
|
||
flex-shrink: 0;
|
||
background-color: var(--color-surface-2);
|
||
}
|
||
|
||
[data-theme="industrial"] .cart-header {
|
||
background-color: #1a1a1a;
|
||
border-bottom-color: var(--color-primary-muted);
|
||
}
|
||
|
||
.cart-header__top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: var(--space-3);
|
||
}
|
||
|
||
.cart-header__sale-id {
|
||
font-family: var(--font-heading);
|
||
font-size: var(--text-h5);
|
||
font-weight: var(--heading-weight-primary);
|
||
color: var(--color-text-primary);
|
||
letter-spacing: var(--heading-tracking-h5);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
}
|
||
|
||
.cart-header__sale-id::before {
|
||
content: '';
|
||
display: block;
|
||
width: 4px;
|
||
height: 20px;
|
||
background-color: var(--color-primary);
|
||
border-radius: 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
[data-theme="industrial"] .cart-header__sale-id {
|
||
font-size: 1.35rem;
|
||
letter-spacing: 0.03em;
|
||
}
|
||
|
||
.cart-header__status {
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wider);
|
||
text-transform: uppercase;
|
||
color: var(--color-success);
|
||
background-color: rgba(34, 197, 94, 0.12);
|
||
padding: 3px var(--space-3);
|
||
border-radius: var(--radius-full);
|
||
border: 1px solid rgba(34, 197, 94, 0.25);
|
||
}
|
||
|
||
/* Customer row */
|
||
.customer-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
}
|
||
|
||
.customer-icon {
|
||
width: 34px;
|
||
height: 34px;
|
||
border-radius: var(--radius-full);
|
||
background-color: var(--color-primary-muted);
|
||
border: 1.5px solid var(--color-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--color-primary);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.customer-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.customer-info__name {
|
||
font-size: var(--text-body-sm);
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-primary);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.customer-info__label {
|
||
font-size: var(--text-caption);
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.btn-change-customer {
|
||
padding: var(--space-1) var(--space-3);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-full);
|
||
background: transparent;
|
||
font-family: var(--font-body);
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-muted);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.btn-change-customer:hover {
|
||
border-color: var(--color-primary);
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
/* Cart items list */
|
||
.cart-items {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: var(--space-3) var(--space-4);
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||
}
|
||
|
||
.cart-items::-webkit-scrollbar { width: 4px; }
|
||
.cart-items::-webkit-scrollbar-track { background: transparent; }
|
||
.cart-items::-webkit-scrollbar-thumb {
|
||
background-color: var(--scrollbar-thumb);
|
||
border-radius: var(--radius-full);
|
||
}
|
||
|
||
.cart-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 120px;
|
||
color: var(--color-text-muted);
|
||
font-size: var(--text-body-sm);
|
||
gap: var(--space-2);
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.cart-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
padding: var(--space-3) var(--space-2);
|
||
border-bottom: 1px solid var(--color-border);
|
||
transition: var(--transition-fast);
|
||
animation: item-slide-in 0.2s ease;
|
||
}
|
||
|
||
@keyframes item-slide-in {
|
||
from { opacity: 0; transform: translateX(10px); }
|
||
to { opacity: 1; transform: translateX(0); }
|
||
}
|
||
|
||
.cart-item:hover {
|
||
background-color: var(--color-primary-muted);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
|
||
.cart-item:last-child { border-bottom: none; }
|
||
|
||
.cart-item__qty-ctrl {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-1);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.qty-btn {
|
||
width: 26px;
|
||
height: 26px;
|
||
border: 1px solid var(--color-border-strong);
|
||
border-radius: var(--radius-sm);
|
||
background-color: var(--color-surface-3);
|
||
color: var(--color-text-primary);
|
||
font-size: 0.9rem;
|
||
font-weight: var(--font-weight-bold);
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: var(--transition-fast);
|
||
line-height: 1;
|
||
}
|
||
|
||
.qty-btn:hover {
|
||
border-color: var(--color-primary);
|
||
background-color: var(--color-primary);
|
||
color: var(--color-text-inverse);
|
||
}
|
||
|
||
[data-theme="industrial"] .qty-btn:hover { color: #000; }
|
||
|
||
.qty-display {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-body-sm);
|
||
font-weight: var(--font-weight-bold);
|
||
color: var(--color-text-primary);
|
||
min-width: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.cart-item__info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.cart-item__name {
|
||
font-size: var(--text-body-sm);
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-primary);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.cart-item__unit {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-caption);
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.cart-item__total {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-body-sm);
|
||
font-weight: var(--font-weight-bold);
|
||
color: var(--color-text-primary);
|
||
text-align: right;
|
||
min-width: 72px;
|
||
}
|
||
|
||
.cart-item__remove {
|
||
width: 24px;
|
||
height: 24px;
|
||
border: none;
|
||
border-radius: var(--radius-sm);
|
||
background: transparent;
|
||
color: var(--color-text-muted);
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: var(--transition-fast);
|
||
flex-shrink: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.cart-item__remove:hover {
|
||
background-color: rgba(239, 68, 68, 0.15);
|
||
color: var(--color-error);
|
||
}
|
||
|
||
/* Cart footer (totals + payment) */
|
||
.cart-footer {
|
||
flex-shrink: 0;
|
||
border-top: 1px solid var(--color-border);
|
||
background-color: var(--color-surface-2);
|
||
}
|
||
|
||
[data-theme="industrial"] .cart-footer {
|
||
background-color: #1a1a1a;
|
||
border-top-color: var(--color-primary-muted);
|
||
}
|
||
|
||
.totals-block {
|
||
padding: var(--space-3) var(--space-5) var(--space-2);
|
||
}
|
||
|
||
.totals-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: var(--space-1) 0;
|
||
}
|
||
|
||
.totals-row__label {
|
||
font-size: var(--text-body-sm);
|
||
color: var(--color-text-muted);
|
||
}
|
||
|
||
.totals-row__value {
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-body-sm);
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.totals-row--total {
|
||
margin-top: var(--space-2);
|
||
padding-top: var(--space-2);
|
||
border-top: 1px solid var(--color-border);
|
||
}
|
||
|
||
.totals-row--total .totals-row__label {
|
||
font-family: var(--font-heading);
|
||
font-size: var(--text-body-lg);
|
||
font-weight: var(--heading-weight-primary);
|
||
color: var(--color-text-primary);
|
||
letter-spacing: var(--tracking-wide);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.totals-row--total .totals-row__value {
|
||
font-family: var(--font-mono);
|
||
font-size: 1.5rem;
|
||
font-weight: var(--font-weight-bold);
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
/* Discount row */
|
||
.discount-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
padding: 0 var(--space-5) var(--space-3);
|
||
}
|
||
|
||
.discount-label {
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: var(--tracking-wider);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.discount-input {
|
||
flex: 1;
|
||
height: 30px;
|
||
padding: 0 var(--space-3);
|
||
background-color: var(--color-bg-base);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-sm);
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-body-sm);
|
||
color: var(--color-text-primary);
|
||
outline: none;
|
||
transition: var(--transition-fast);
|
||
}
|
||
|
||
.discount-input:focus {
|
||
border-color: var(--color-border-focus);
|
||
box-shadow: var(--shadow-focus);
|
||
}
|
||
|
||
[data-theme="modern"] .discount-input {
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
.discount-input::placeholder { color: var(--color-text-muted); }
|
||
|
||
/* Payment method buttons */
|
||
.payment-methods {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: var(--space-2);
|
||
padding: 0 var(--space-5) var(--space-3);
|
||
}
|
||
|
||
.pay-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 3px;
|
||
padding: var(--space-2) var(--space-2);
|
||
border: 1.5px solid var(--color-border);
|
||
border-radius: var(--radius-sm);
|
||
background: transparent;
|
||
font-family: var(--font-body);
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wide);
|
||
text-transform: uppercase;
|
||
color: var(--color-text-muted);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
}
|
||
|
||
.pay-btn:hover {
|
||
border-color: var(--color-primary);
|
||
color: var(--color-primary);
|
||
background-color: var(--color-primary-muted);
|
||
}
|
||
|
||
.pay-btn.selected {
|
||
border-color: var(--color-primary);
|
||
background-color: var(--color-primary-muted);
|
||
color: var(--color-primary);
|
||
box-shadow: var(--shadow-accent);
|
||
}
|
||
|
||
.pay-btn__icon {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
[data-theme="modern"] .pay-btn {
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
[data-theme="industrial"] .pay-btn {
|
||
clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 0 100%);
|
||
}
|
||
|
||
/* Main CTA button */
|
||
.btn-cobrar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: var(--space-3);
|
||
width: calc(100% - var(--space-10));
|
||
margin: 0 var(--space-5) var(--space-3);
|
||
height: 56px;
|
||
background-color: var(--btn-primary-bg);
|
||
border: none;
|
||
border-radius: var(--radius-md);
|
||
font-family: var(--font-heading);
|
||
font-size: 1.2rem;
|
||
font-weight: var(--heading-weight-primary);
|
||
letter-spacing: var(--tracking-widest);
|
||
text-transform: uppercase;
|
||
color: var(--btn-primary-text);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
|
||
.btn-cobrar:hover {
|
||
background-color: var(--btn-primary-bg-hover);
|
||
transform: translateY(-1px);
|
||
box-shadow: var(--shadow-lg);
|
||
}
|
||
|
||
.btn-cobrar:active {
|
||
background-color: var(--btn-primary-bg-active);
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.btn-cobrar:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
[data-theme="industrial"] .btn-cobrar {
|
||
clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 0 100%);
|
||
}
|
||
|
||
[data-theme="modern"] .btn-cobrar {
|
||
border-radius: var(--radius-lg);
|
||
}
|
||
|
||
/* Secondary actions row */
|
||
.secondary-actions {
|
||
display: flex;
|
||
gap: var(--space-2);
|
||
padding: 0 var(--space-5) var(--space-4);
|
||
}
|
||
|
||
.btn-secondary-action {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: var(--space-1);
|
||
height: 34px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-sm);
|
||
background: transparent;
|
||
font-family: var(--font-body);
|
||
font-size: var(--text-caption);
|
||
font-weight: var(--font-weight-semibold);
|
||
letter-spacing: var(--tracking-wide);
|
||
text-transform: uppercase;
|
||
color: var(--color-text-muted);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
}
|
||
|
||
.btn-secondary-action:hover {
|
||
border-color: var(--color-border-strong);
|
||
color: var(--color-text-secondary);
|
||
background-color: var(--color-surface-3);
|
||
}
|
||
|
||
.btn-secondary-action.danger:hover {
|
||
border-color: var(--color-error);
|
||
color: var(--color-error);
|
||
background-color: rgba(239, 68, 68, 0.08);
|
||
}
|
||
|
||
[data-theme="modern"] .btn-secondary-action {
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
/* =====================================================================
|
||
NUMPAD OVERLAY
|
||
===================================================================== */
|
||
|
||
.numpad-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background-color: var(--overlay-backdrop);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
z-index: var(--z-modal);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity var(--duration-normal) var(--ease-in-out);
|
||
}
|
||
|
||
.numpad-overlay.visible {
|
||
opacity: 1;
|
||
pointer-events: all;
|
||
}
|
||
|
||
.numpad-panel {
|
||
width: 360px;
|
||
background-color: var(--color-surface-2);
|
||
border: 1px solid var(--color-border-strong);
|
||
border-bottom: none;
|
||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||
padding: var(--space-5);
|
||
transform: translateY(100%);
|
||
transition: transform var(--duration-normal) var(--ease-out);
|
||
}
|
||
|
||
.numpad-overlay.visible .numpad-panel {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
[data-theme="industrial"] .numpad-panel {
|
||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||
background-color: #1e1e1e;
|
||
border-color: var(--color-primary-muted);
|
||
}
|
||
|
||
.numpad-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.numpad-title {
|
||
font-family: var(--font-heading);
|
||
font-size: var(--text-h6);
|
||
font-weight: var(--heading-weight-primary);
|
||
text-transform: uppercase;
|
||
letter-spacing: var(--tracking-widest);
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
.numpad-close {
|
||
width: 28px;
|
||
height: 28px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-full);
|
||
background: transparent;
|
||
color: var(--color-text-muted);
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: var(--transition-fast);
|
||
}
|
||
|
||
.numpad-close:hover {
|
||
border-color: var(--color-error);
|
||
color: var(--color-error);
|
||
}
|
||
|
||
.numpad-display {
|
||
background-color: var(--color-bg-base);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-sm);
|
||
padding: var(--space-3) var(--space-4);
|
||
font-family: var(--font-mono);
|
||
font-size: 1.8rem;
|
||
font-weight: var(--font-weight-bold);
|
||
color: var(--color-primary);
|
||
text-align: right;
|
||
margin-bottom: var(--space-4);
|
||
min-height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
letter-spacing: var(--tracking-wide);
|
||
}
|
||
|
||
.numpad-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: var(--space-2);
|
||
}
|
||
|
||
.numpad-key {
|
||
height: 52px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-sm);
|
||
background-color: var(--color-surface-3);
|
||
font-family: var(--font-mono);
|
||
font-size: 1.1rem;
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-primary);
|
||
cursor: pointer;
|
||
transition: var(--transition-fast);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.numpad-key:hover {
|
||
background-color: var(--color-primary-muted);
|
||
border-color: var(--color-primary);
|
||
color: var(--color-primary);
|
||
}
|
||
|
||
.numpad-key:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.numpad-key.key-0 { grid-column: span 2; }
|
||
|
||
.numpad-key.key-clear {
|
||
background-color: rgba(239, 68, 68, 0.1);
|
||
border-color: rgba(239, 68, 68, 0.3);
|
||
color: var(--color-error);
|
||
}
|
||
|
||
.numpad-key.key-enter {
|
||
background-color: var(--color-primary);
|
||
border-color: var(--color-primary);
|
||
color: var(--color-text-inverse);
|
||
font-family: var(--font-heading);
|
||
font-size: 0.85rem;
|
||
letter-spacing: var(--tracking-wider);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
[data-theme="industrial"] .numpad-key.key-enter { color: #000; }
|
||
|
||
.numpad-key.key-enter:hover {
|
||
background-color: var(--color-primary-hover);
|
||
}
|
||
|
||
[data-theme="modern"] .numpad-key {
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
/* =====================================================================
|
||
TOAST NOTIFICATION
|
||
===================================================================== */
|
||
|
||
.toast-container {
|
||
position: fixed;
|
||
bottom: var(--space-6);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: var(--z-toast);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-2);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.toast {
|
||
padding: var(--space-3) var(--space-5);
|
||
background-color: var(--color-surface-3);
|
||
border: 1px solid var(--color-border);
|
||
border-left: 3px solid var(--color-primary);
|
||
border-radius: var(--radius-md);
|
||
font-size: var(--text-body-sm);
|
||
font-weight: var(--font-weight-semibold);
|
||
color: var(--color-text-primary);
|
||
box-shadow: var(--shadow-lg);
|
||
white-space: nowrap;
|
||
animation: toast-in 0.25s ease, toast-out 0.3s ease 1.7s forwards;
|
||
}
|
||
|
||
@keyframes toast-in {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
@keyframes toast-out {
|
||
from { opacity: 1; transform: translateY(0); }
|
||
to { opacity: 0; transform: translateY(-6px); }
|
||
}
|
||
|
||
/* =====================================================================
|
||
RESPONSIVE — tablet/small desktop
|
||
===================================================================== */
|
||
|
||
@media (max-width: 900px) {
|
||
.pos-main {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.panel-products,
|
||
.panel-cart {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
.panel-products {
|
||
border-right: none;
|
||
border-bottom: 1px solid var(--color-border);
|
||
max-height: 55%;
|
||
}
|
||
|
||
.panel-cart {
|
||
max-height: 45%;
|
||
}
|
||
|
||
.product-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
|
||
.status-bar__center { display: none; }
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.product-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
|
||
.payment-methods {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
|
||
.status-bar { padding: 0 var(--space-3); }
|
||
.search-row { padding: var(--space-3); }
|
||
.categories-row { padding: var(--space-2) var(--space-3); }
|
||
}
|
||
|
||
/* =====================================================================
|
||
UTILITY
|
||
===================================================================== */
|
||
|
||
.sr-only {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
padding: 0;
|
||
margin: -1px;
|
||
overflow: hidden;
|
||
clip: rect(0, 0, 0, 0);
|
||
border: 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body class="pos-shell themed-scrollbar" id="appBody">
|
||
|
||
<!-- ================================================================
|
||
STATUS BAR
|
||
================================================================ -->
|
||
<header class="status-bar" role="banner">
|
||
<div class="status-bar__store">
|
||
<span class="status-bar__store-dot"></span>
|
||
Nexus Autoparts — Suc. Centro
|
||
</div>
|
||
|
||
<div class="status-bar__center">
|
||
<span id="statusClock">Mar 31, 2026 — 10:42 AM</span>
|
||
<span>Terminal #3</span>
|
||
</div>
|
||
|
||
<div class="status-bar__right">
|
||
<!-- Theme Switcher -->
|
||
<div class="theme-switcher" role="group" aria-label="Cambiar tema">
|
||
<button class="theme-btn active" id="btnIndustrial" data-theme-val="industrial" title="Tema Industrial">
|
||
⬡ Industrial
|
||
</button>
|
||
<button class="theme-btn" id="btnModern" data-theme-val="modern" title="Tema Moderno">
|
||
○ Moderno
|
||
</button>
|
||
</div>
|
||
|
||
<div class="status-bar__user" aria-label="Usuario activo">
|
||
<div class="status-bar__user-avatar" aria-hidden="true">HR</div>
|
||
<span>Hugo Reyes</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ================================================================
|
||
MAIN POS LAYOUT
|
||
================================================================ -->
|
||
<main class="pos-main" role="main">
|
||
|
||
<!-- ============================================================
|
||
LEFT — PRODUCT BROWSER
|
||
============================================================ -->
|
||
<section class="panel-products" aria-label="Catálogo de productos">
|
||
|
||
<!-- Search Bar -->
|
||
<div class="search-row">
|
||
<div class="search-wrap">
|
||
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true">
|
||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||
</svg>
|
||
<input
|
||
class="search-input"
|
||
type="search"
|
||
id="searchInput"
|
||
placeholder="Buscar por nombre, OEM#, SKU..."
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
aria-label="Buscar productos"
|
||
/>
|
||
</div>
|
||
|
||
<button class="btn-scan" id="btnScan" title="Escanear código de barras" aria-label="Escanear código">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true">
|
||
<path d="M3 5v2M3 19v-2M5 3h2M19 3h-2M21 5v2M21 19v-2M19 21h-2M5 21h2"/>
|
||
<line x1="7" y1="8" x2="7" y2="16"/><line x1="10" y1="8" x2="10" y2="16"/>
|
||
<line x1="13" y1="8" x2="13" y2="16"/><line x1="17" y1="8" x2="17" y2="16"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Category Buttons -->
|
||
<div class="categories-row" role="toolbar" aria-label="Categorías de productos">
|
||
<span class="category-label">Filtrar:</span>
|
||
<button class="cat-btn active" data-category="all">Todos</button>
|
||
<button class="cat-btn" data-category="frenos">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2.5"/>
|
||
<circle cx="12" cy="12" r="4"/>
|
||
</svg>
|
||
Frenos
|
||
</button>
|
||
<button class="cat-btn" data-category="motor">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||
<rect x="2" y="7" width="20" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="2"/>
|
||
<line x1="6" y1="7" x2="6" y2="17" stroke="currentColor" stroke-width="1.5"/>
|
||
<line x1="12" y1="7" x2="12" y2="17" stroke="currentColor" stroke-width="1.5"/>
|
||
<line x1="18" y1="7" x2="18" y2="17" stroke="currentColor" stroke-width="1.5"/>
|
||
</svg>
|
||
Motor
|
||
</button>
|
||
<button class="cat-btn" data-category="filtros">Filtros</button>
|
||
<button class="cat-btn" data-category="aceites">Aceites</button>
|
||
<button class="cat-btn" data-category="suspension">Suspensión</button>
|
||
<button class="cat-btn" data-category="electrico">Eléctrico</button>
|
||
</div>
|
||
|
||
<!-- Product Grid -->
|
||
<div class="product-grid-wrap" id="productGridWrap">
|
||
<div class="product-grid" id="productGrid" role="list" aria-label="Productos disponibles">
|
||
<!-- Populated by JS -->
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================
|
||
RIGHT — CART / TICKET
|
||
============================================================ -->
|
||
<aside class="panel-cart" aria-label="Carrito de venta">
|
||
|
||
<!-- Cart Header -->
|
||
<div class="cart-header">
|
||
<div class="cart-header__top">
|
||
<div class="cart-header__sale-id" id="saleId">Venta #1247</div>
|
||
<span class="cart-header__status">Activa</span>
|
||
</div>
|
||
<div class="customer-row">
|
||
<div class="customer-icon" aria-hidden="true">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||
<circle cx="12" cy="7" r="4"/>
|
||
</svg>
|
||
</div>
|
||
<div class="customer-info">
|
||
<div class="customer-info__name" id="customerName">Cliente General</div>
|
||
<div class="customer-info__label">Sin RFC registrado</div>
|
||
</div>
|
||
<button class="btn-change-customer" id="btnChangeCustomer" aria-label="Cambiar cliente">
|
||
Cambiar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cart Items -->
|
||
<div class="cart-items themed-scrollbar" id="cartItems" role="list" aria-label="Artículos en carrito">
|
||
<!-- Populated by JS -->
|
||
</div>
|
||
|
||
<!-- Cart Footer -->
|
||
<div class="cart-footer">
|
||
|
||
<!-- Totals -->
|
||
<div class="totals-block">
|
||
<div class="totals-row">
|
||
<span class="totals-row__label">Subtotal</span>
|
||
<span class="totals-row__value" id="subtotalDisplay">$0.00</span>
|
||
</div>
|
||
<div class="totals-row">
|
||
<span class="totals-row__label">IVA (16%)</span>
|
||
<span class="totals-row__value" id="ivaDisplay">$0.00</span>
|
||
</div>
|
||
<div class="totals-row totals-row--total">
|
||
<span class="totals-row__label">Total</span>
|
||
<span class="totals-row__value" id="totalDisplay">$0.00</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Discount -->
|
||
<div class="discount-row">
|
||
<span class="discount-label">Desc.</span>
|
||
<input
|
||
type="number"
|
||
class="discount-input"
|
||
id="discountInput"
|
||
placeholder="0.00"
|
||
min="0"
|
||
step="0.01"
|
||
aria-label="Descuento en pesos"
|
||
/>
|
||
<span class="discount-label">MXN</span>
|
||
</div>
|
||
|
||
<!-- Payment Methods -->
|
||
<div class="payment-methods" role="radiogroup" aria-label="Forma de pago">
|
||
<button class="pay-btn selected" data-method="efectivo" aria-pressed="true">
|
||
<span class="pay-btn__icon">💵</span>
|
||
<span>Efectivo</span>
|
||
</button>
|
||
<button class="pay-btn" data-method="tarjeta" aria-pressed="false">
|
||
<span class="pay-btn__icon">💳</span>
|
||
<span>Tarjeta</span>
|
||
</button>
|
||
<button class="pay-btn" data-method="transferencia" aria-pressed="false">
|
||
<span class="pay-btn__icon">📲</span>
|
||
<span>Transfer.</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- COBRAR Button -->
|
||
<button class="btn-cobrar" id="btnCobrar" aria-label="Procesar cobro">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true">
|
||
<line x1="12" y1="1" x2="12" y2="23"/>
|
||
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
|
||
</svg>
|
||
<span id="cobrarLabel">COBRAR $0.00</span>
|
||
</button>
|
||
|
||
<!-- Secondary Actions -->
|
||
<div class="secondary-actions" role="toolbar" aria-label="Acciones secundarias">
|
||
<button class="btn-secondary-action" id="btnGuardar" title="Guardar venta">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true">
|
||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||
<polyline points="17 21 17 13 7 13 7 21"/>
|
||
<polyline points="7 3 7 8 15 8"/>
|
||
</svg>
|
||
Guardar
|
||
</button>
|
||
<button class="btn-secondary-action" id="btnImprimir" title="Imprimir ticket">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true">
|
||
<polyline points="6 9 6 2 18 2 18 9"/>
|
||
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/>
|
||
<rect x="6" y="14" width="12" height="8"/>
|
||
</svg>
|
||
Imprimir
|
||
</button>
|
||
<button class="btn-secondary-action danger" id="btnCancelar" title="Cancelar venta">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
|
||
aria-hidden="true">
|
||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||
</svg>
|
||
Cancelar
|
||
</button>
|
||
<button class="btn-secondary-action" id="btnNumpad" title="Abrir teclado numérico">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||
<rect x="3" y="3" width="4" height="4" rx="1"/>
|
||
<rect x="10" y="3" width="4" height="4" rx="1"/>
|
||
<rect x="17" y="3" width="4" height="4" rx="1"/>
|
||
<rect x="3" y="10" width="4" height="4" rx="1"/>
|
||
<rect x="10" y="10" width="4" height="4" rx="1"/>
|
||
<rect x="17" y="10" width="4" height="4" rx="1"/>
|
||
<rect x="3" y="17" width="4" height="4" rx="1"/>
|
||
<rect x="10" y="17" width="4" height="4" rx="1"/>
|
||
<rect x="17" y="17" width="4" height="4" rx="1"/>
|
||
</svg>
|
||
Teclado
|
||
</button>
|
||
</div>
|
||
|
||
</div><!-- /.cart-footer -->
|
||
</aside>
|
||
|
||
</main>
|
||
|
||
<!-- ================================================================
|
||
NUMPAD OVERLAY
|
||
================================================================ -->
|
||
<div class="numpad-overlay" id="numpadOverlay" role="dialog" aria-modal="true"
|
||
aria-label="Teclado numérico" aria-hidden="true">
|
||
<div class="numpad-panel">
|
||
<div class="numpad-header">
|
||
<span class="numpad-title">Cantidad / Importe</span>
|
||
<button class="numpad-close" id="numpadClose" aria-label="Cerrar teclado">✕</button>
|
||
</div>
|
||
<div class="numpad-display" id="numpadDisplay" aria-live="polite">0</div>
|
||
<div class="numpad-grid" role="group" aria-label="Teclas numéricas">
|
||
<button class="numpad-key" data-key="7">7</button>
|
||
<button class="numpad-key" data-key="8">8</button>
|
||
<button class="numpad-key" data-key="9">9</button>
|
||
<button class="numpad-key key-clear" data-key="C">C</button>
|
||
<button class="numpad-key" data-key="4">4</button>
|
||
<button class="numpad-key" data-key="5">5</button>
|
||
<button class="numpad-key" data-key="6">6</button>
|
||
<button class="numpad-key" data-key="back">⌫</button>
|
||
<button class="numpad-key" data-key="1">1</button>
|
||
<button class="numpad-key" data-key="2">2</button>
|
||
<button class="numpad-key" data-key="3">3</button>
|
||
<button class="numpad-key key-enter" data-key="ENTER">OK</button>
|
||
<button class="numpad-key key-0" data-key="0">0</button>
|
||
<button class="numpad-key" data-key=".">.</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================================================================
|
||
TOAST CONTAINER
|
||
================================================================ -->
|
||
<div class="toast-container" id="toastContainer" aria-live="assertive" aria-atomic="true"></div>
|
||
|
||
|
||
<!-- ================================================================
|
||
JAVASCRIPT
|
||
================================================================ -->
|
||
<script>
|
||
'use strict';
|
||
|
||
/* ------------------------------------------------------------------
|
||
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' },
|
||
];
|
||
|
||
/* ------------------------------------------------------------------
|
||
STATE
|
||
------------------------------------------------------------------ */
|
||
const state = {
|
||
theme: 'industrial',
|
||
cart: [], // { productId, qty }
|
||
selectedMethod: 'efectivo',
|
||
discount: 0,
|
||
activeCategory: 'all',
|
||
searchQuery: '',
|
||
numpadValue: '0',
|
||
saleNumber: 1247,
|
||
};
|
||
|
||
// Seed cart with 3 demo items
|
||
state.cart = [
|
||
{ productId: 4, qty: 4 },
|
||
{ productId: 7, qty: 2 },
|
||
{ productId: 11, qty: 1 },
|
||
];
|
||
|
||
/* ------------------------------------------------------------------
|
||
DOM REFS
|
||
------------------------------------------------------------------ */
|
||
const $html = document.documentElement;
|
||
const $body = document.getElementById('appBody');
|
||
const $btnIndustrial = document.getElementById('btnIndustrial');
|
||
const $btnModern = document.getElementById('btnModern');
|
||
const $searchInput = document.getElementById('searchInput');
|
||
const $productGrid = document.getElementById('productGrid');
|
||
const $cartItems = document.getElementById('cartItems');
|
||
const $subtotal = document.getElementById('subtotalDisplay');
|
||
const $iva = document.getElementById('ivaDisplay');
|
||
const $total = document.getElementById('totalDisplay');
|
||
const $cobrarLabel = document.getElementById('cobrarLabel');
|
||
const $btnCobrar = document.getElementById('btnCobrar');
|
||
const $discountInput = document.getElementById('discountInput');
|
||
const $numpadOverlay = document.getElementById('numpadOverlay');
|
||
const $numpadDisplay = document.getElementById('numpadDisplay');
|
||
const $numpadClose = document.getElementById('numpadClose');
|
||
const $statusClock = document.getElementById('statusClock');
|
||
const $toastContainer = document.getElementById('toastContainer');
|
||
|
||
/* ------------------------------------------------------------------
|
||
FORMATTING HELPERS
|
||
------------------------------------------------------------------ */
|
||
const fmt = (n) =>
|
||
new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(n);
|
||
|
||
/* ------------------------------------------------------------------
|
||
THEME SWITCHING
|
||
------------------------------------------------------------------ */
|
||
function setTheme(theme) {
|
||
state.theme = theme;
|
||
$html.setAttribute('data-theme', theme);
|
||
|
||
// dot-grid class on body for modern theme
|
||
$body.classList.toggle('bg-dot-grid', theme === 'modern');
|
||
|
||
// update buttons
|
||
$btnIndustrial.classList.toggle('active', theme === 'industrial');
|
||
$btnModern.classList.toggle('active', theme === 'modern');
|
||
|
||
// update aria-pressed
|
||
$btnIndustrial.setAttribute('aria-pressed', theme === 'industrial');
|
||
$btnModern.setAttribute('aria-pressed', theme === 'modern');
|
||
|
||
showToast(theme === 'industrial' ? 'Tema Industrial activado' : 'Tema Moderno activado');
|
||
}
|
||
|
||
$btnIndustrial.addEventListener('click', () => setTheme('industrial'));
|
||
$btnModern.addEventListener('click', () => setTheme('modern'));
|
||
|
||
/* ------------------------------------------------------------------
|
||
CLOCK
|
||
------------------------------------------------------------------ */
|
||
function updateClock() {
|
||
const now = new Date();
|
||
const opts = { year: 'numeric', month: 'short', day: 'numeric',
|
||
hour: '2-digit', minute: '2-digit', hour12: true };
|
||
$statusClock.textContent = now.toLocaleString('es-MX', opts);
|
||
}
|
||
updateClock();
|
||
setInterval(updateClock, 30_000);
|
||
|
||
/* ------------------------------------------------------------------
|
||
PRODUCT GRID RENDERING
|
||
------------------------------------------------------------------ */
|
||
function stockClass(stock) {
|
||
if (stock === 0) return 'out';
|
||
if (stock <= 3) return 'low';
|
||
return '';
|
||
}
|
||
|
||
function stockText(stock) {
|
||
if (stock === 0) return 'Sin stock';
|
||
if (stock <= 3) return `¡Solo ${stock}!`;
|
||
return `${stock} en stock`;
|
||
}
|
||
|
||
function renderProducts() {
|
||
const q = state.searchQuery.toLowerCase().trim();
|
||
const cat = state.activeCategory;
|
||
|
||
const filtered = PRODUCTS.filter(p => {
|
||
const matchCat = cat === 'all' || p.category === cat;
|
||
const matchSearch = !q ||
|
||
p.name.toLowerCase().includes(q) ||
|
||
p.oem.toLowerCase().includes(q);
|
||
return matchCat && matchSearch;
|
||
});
|
||
|
||
$productGrid.innerHTML = '';
|
||
|
||
if (filtered.length === 0) {
|
||
$productGrid.innerHTML = `
|
||
<div style="grid-column: 1/-1; padding: 2rem; text-align: center;
|
||
color: var(--color-text-muted); font-size: var(--text-body-sm);">
|
||
No se encontraron productos
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
filtered.forEach(p => {
|
||
const card = document.createElement('div');
|
||
card.className = 'product-card';
|
||
card.setAttribute('role', 'listitem');
|
||
card.dataset.productId = p.id;
|
||
|
||
const sc = stockClass(p.stock);
|
||
card.innerHTML = `
|
||
<div class="product-card__category">${p.cat_label}</div>
|
||
<div class="product-card__name">${p.name}</div>
|
||
<div class="product-card__oem">OEM# ${p.oem}</div>
|
||
<div class="product-card__footer">
|
||
<div>
|
||
<div class="product-card__price">${fmt(p.price)}</div>
|
||
<div class="product-card__stock ${sc}">${stockText(p.stock)}</div>
|
||
</div>
|
||
<button
|
||
class="product-card__add"
|
||
data-product-id="${p.id}"
|
||
aria-label="Agregar ${p.name} al carrito"
|
||
${p.stock === 0 ? 'disabled' : ''}
|
||
>+</button>
|
||
</div>`;
|
||
|
||
card.querySelector('.product-card__add').addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
addToCart(p.id);
|
||
card.classList.remove('added');
|
||
// force reflow
|
||
void card.offsetWidth;
|
||
card.classList.add('added');
|
||
});
|
||
|
||
card.addEventListener('click', () => addToCart(p.id));
|
||
$productGrid.appendChild(card);
|
||
});
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
CART LOGIC
|
||
------------------------------------------------------------------ */
|
||
function getProduct(id) {
|
||
return PRODUCTS.find(p => p.id === id);
|
||
}
|
||
|
||
function addToCart(productId) {
|
||
const product = getProduct(productId);
|
||
if (!product || product.stock === 0) return;
|
||
|
||
const existing = state.cart.find(i => i.productId === productId);
|
||
if (existing) {
|
||
existing.qty = Math.min(existing.qty + 1, product.stock);
|
||
} else {
|
||
state.cart.push({ productId, qty: 1 });
|
||
}
|
||
|
||
renderCart();
|
||
showToast(`${product.name} agregado`);
|
||
}
|
||
|
||
function removeFromCart(productId) {
|
||
state.cart = state.cart.filter(i => i.productId !== productId);
|
||
renderCart();
|
||
}
|
||
|
||
function updateQty(productId, delta) {
|
||
const item = state.cart.find(i => i.productId === productId);
|
||
const product = getProduct(productId);
|
||
if (!item || !product) return;
|
||
|
||
item.qty += delta;
|
||
if (item.qty <= 0) {
|
||
removeFromCart(productId);
|
||
} else {
|
||
item.qty = Math.min(item.qty, product.stock);
|
||
renderCart();
|
||
}
|
||
}
|
||
|
||
function calcTotals() {
|
||
const subtotal = state.cart.reduce((acc, item) => {
|
||
const p = getProduct(item.productId);
|
||
return acc + (p ? p.price * item.qty : 0);
|
||
}, 0);
|
||
const discount = Math.min(state.discount, subtotal);
|
||
const subtotalNet = subtotal - discount;
|
||
const iva = subtotalNet * 0.16;
|
||
const total = subtotalNet + iva;
|
||
return { subtotal, discount, subtotalNet, iva, total };
|
||
}
|
||
|
||
function renderCart() {
|
||
const { subtotal, iva, total } = calcTotals();
|
||
|
||
// render items
|
||
if (state.cart.length === 0) {
|
||
$cartItems.innerHTML = `
|
||
<div class="cart-empty" role="status">
|
||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="1.5" opacity="0.4" aria-hidden="true">
|
||
<circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
|
||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
|
||
</svg>
|
||
<span>Carrito vacío — agrega productos</span>
|
||
</div>`;
|
||
} else {
|
||
$cartItems.innerHTML = state.cart.map(item => {
|
||
const p = getProduct(item.productId);
|
||
if (!p) return '';
|
||
const lineTotal = p.price * item.qty;
|
||
return `
|
||
<div class="cart-item" role="listitem" data-product-id="${p.id}">
|
||
<div class="cart-item__qty-ctrl">
|
||
<button class="qty-btn qty-minus" data-id="${p.id}" aria-label="Quitar uno de ${p.name}">−</button>
|
||
<span class="qty-display" aria-label="Cantidad: ${item.qty}">${item.qty}</span>
|
||
<button class="qty-btn qty-plus" data-id="${p.id}" aria-label="Agregar uno más de ${p.name}">+</button>
|
||
</div>
|
||
<div class="cart-item__info">
|
||
<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__total">${fmt(lineTotal)}</div>
|
||
<button class="cart-item__remove" data-id="${p.id}" aria-label="Eliminar ${p.name} del carrito">✕</button>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// wire cart item events
|
||
$cartItems.querySelectorAll('.qty-minus').forEach(btn =>
|
||
btn.addEventListener('click', () => updateQty(+btn.dataset.id, -1)));
|
||
$cartItems.querySelectorAll('.qty-plus').forEach(btn =>
|
||
btn.addEventListener('click', () => updateQty(+btn.dataset.id, +1)));
|
||
$cartItems.querySelectorAll('.cart-item__remove').forEach(btn =>
|
||
btn.addEventListener('click', () => removeFromCart(+btn.dataset.id)));
|
||
|
||
// update totals display
|
||
$subtotal.textContent = fmt(subtotal);
|
||
$iva.textContent = fmt(iva);
|
||
$total.textContent = fmt(total);
|
||
$cobrarLabel.textContent = `COBRAR ${fmt(total)}`;
|
||
|
||
// disable cobrar if cart is empty
|
||
$btnCobrar.disabled = state.cart.length === 0;
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
DISCOUNT INPUT
|
||
------------------------------------------------------------------ */
|
||
$discountInput.addEventListener('input', () => {
|
||
state.discount = parseFloat($discountInput.value) || 0;
|
||
renderCart();
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
CATEGORY FILTERS
|
||
------------------------------------------------------------------ */
|
||
document.querySelectorAll('.cat-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
state.activeCategory = btn.dataset.category;
|
||
document.querySelectorAll('.cat-btn').forEach(b => {
|
||
b.classList.remove('active');
|
||
b.removeAttribute('aria-current');
|
||
});
|
||
btn.classList.add('active');
|
||
btn.setAttribute('aria-current', 'true');
|
||
renderProducts();
|
||
});
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
SEARCH
|
||
------------------------------------------------------------------ */
|
||
$searchInput.addEventListener('input', () => {
|
||
state.searchQuery = $searchInput.value;
|
||
renderProducts();
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
PAYMENT METHODS
|
||
------------------------------------------------------------------ */
|
||
document.querySelectorAll('.pay-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
state.selectedMethod = btn.dataset.method;
|
||
document.querySelectorAll('.pay-btn').forEach(b => {
|
||
b.classList.remove('selected');
|
||
b.setAttribute('aria-pressed', 'false');
|
||
});
|
||
btn.classList.add('selected');
|
||
btn.setAttribute('aria-pressed', 'true');
|
||
});
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
COBRAR
|
||
------------------------------------------------------------------ */
|
||
$btnCobrar.addEventListener('click', () => {
|
||
const { total } = calcTotals();
|
||
if (state.cart.length === 0) return;
|
||
const method = {
|
||
efectivo: 'Efectivo',
|
||
tarjeta: 'Tarjeta',
|
||
transferencia: 'Transferencia',
|
||
}[state.selectedMethod];
|
||
showToast(`✓ Venta #${state.saleNumber} completada — ${method} ${fmt(total)}`);
|
||
// Simulate new sale
|
||
setTimeout(() => {
|
||
state.saleNumber++;
|
||
state.cart = [];
|
||
state.discount = 0;
|
||
$discountInput.value = '';
|
||
document.getElementById('saleId').textContent = `Venta #${state.saleNumber}`;
|
||
renderCart();
|
||
}, 800);
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
SECONDARY ACTIONS
|
||
------------------------------------------------------------------ */
|
||
document.getElementById('btnGuardar').addEventListener('click', () => {
|
||
showToast('Venta guardada como pendiente');
|
||
});
|
||
|
||
document.getElementById('btnImprimir').addEventListener('click', () => {
|
||
showToast('Enviando a impresora...');
|
||
});
|
||
|
||
document.getElementById('btnCancelar').addEventListener('click', () => {
|
||
if (state.cart.length === 0) return;
|
||
if (confirm('¿Cancelar la venta actual? Se perderán los artículos en el carrito.')) {
|
||
state.cart = [];
|
||
state.discount = 0;
|
||
$discountInput.value = '';
|
||
renderCart();
|
||
showToast('Venta cancelada');
|
||
}
|
||
});
|
||
|
||
document.getElementById('btnScan').addEventListener('click', () => {
|
||
showToast('Listo para escanear...');
|
||
$searchInput.focus();
|
||
});
|
||
|
||
document.getElementById('btnChangeCustomer').addEventListener('click', () => {
|
||
const name = prompt('Nombre del cliente:', 'Cliente General');
|
||
if (name && name.trim()) {
|
||
document.getElementById('customerName').textContent = name.trim();
|
||
showToast(`Cliente: ${name.trim()}`);
|
||
}
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
NUMPAD OVERLAY
|
||
------------------------------------------------------------------ */
|
||
function openNumpad() {
|
||
state.numpadValue = '0';
|
||
$numpadDisplay.textContent = '0';
|
||
$numpadOverlay.classList.add('visible');
|
||
$numpadOverlay.setAttribute('aria-hidden', 'false');
|
||
}
|
||
|
||
function closeNumpad() {
|
||
$numpadOverlay.classList.remove('visible');
|
||
$numpadOverlay.setAttribute('aria-hidden', 'true');
|
||
}
|
||
|
||
document.getElementById('btnNumpad').addEventListener('click', openNumpad);
|
||
$numpadClose.addEventListener('click', closeNumpad);
|
||
|
||
$numpadOverlay.addEventListener('click', e => {
|
||
if (e.target === $numpadOverlay) closeNumpad();
|
||
});
|
||
|
||
document.querySelectorAll('.numpad-key').forEach(key => {
|
||
key.addEventListener('click', () => {
|
||
const k = key.dataset.key;
|
||
if (k === 'C') {
|
||
state.numpadValue = '0';
|
||
} else if (k === 'back') {
|
||
state.numpadValue = state.numpadValue.length > 1
|
||
? state.numpadValue.slice(0, -1)
|
||
: '0';
|
||
} else if (k === 'ENTER') {
|
||
const v = parseFloat(state.numpadValue);
|
||
if (!isNaN(v)) {
|
||
$discountInput.value = v.toFixed(2);
|
||
state.discount = v;
|
||
renderCart();
|
||
showToast(`Descuento aplicado: ${fmt(v)}`);
|
||
}
|
||
closeNumpad();
|
||
return;
|
||
} else if (k === '.') {
|
||
if (!state.numpadValue.includes('.')) {
|
||
state.numpadValue += '.';
|
||
}
|
||
return;
|
||
} else {
|
||
state.numpadValue = state.numpadValue === '0' ? k : state.numpadValue + k;
|
||
}
|
||
// max 8 chars
|
||
if (state.numpadValue.replace('.', '').length > 8) return;
|
||
$numpadDisplay.textContent = state.numpadValue;
|
||
});
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
TOAST
|
||
------------------------------------------------------------------ */
|
||
function showToast(msg) {
|
||
const el = document.createElement('div');
|
||
el.className = 'toast';
|
||
el.textContent = msg;
|
||
$toastContainer.appendChild(el);
|
||
setTimeout(() => el.remove(), 2100);
|
||
}
|
||
|
||
/* ------------------------------------------------------------------
|
||
KEYBOARD SHORTCUTS
|
||
------------------------------------------------------------------ */
|
||
document.addEventListener('keydown', e => {
|
||
// Escape — close numpad
|
||
if (e.key === 'Escape' && $numpadOverlay.classList.contains('visible')) {
|
||
closeNumpad();
|
||
return;
|
||
}
|
||
// F2 — focus search
|
||
if (e.key === 'F2') {
|
||
e.preventDefault();
|
||
$searchInput.focus();
|
||
$searchInput.select();
|
||
}
|
||
// F5 — toggle theme
|
||
if (e.key === 'F5') {
|
||
e.preventDefault();
|
||
setTheme(state.theme === 'industrial' ? 'modern' : 'industrial');
|
||
}
|
||
});
|
||
|
||
/* ------------------------------------------------------------------
|
||
INITIAL RENDER
|
||
------------------------------------------------------------------ */
|
||
renderProducts();
|
||
renderCart();
|
||
setTheme('industrial');
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|