FASE 7d: Lazy Loading + Minificación + Auto-serve minified

Cambios implementados:

1. Lazy loading de imágenes:
   - catalog.js: loading="lazy" decoding="async" en part cards y detail panel
   - inventory.js: lazy loading en imagen de detalle de item

2. Minificación de assets:
   - scripts/minify-assets.sh: minifica JS (terser) y CSS para POS y Dashboard
   - 25 archivos .min.js + 5 .min.css generados en pos/static/
   - 14 archivos .min.js + 8 .min.css generados en dashboard/

3. Nginx auto-serve minified:
   - try_files $1.min.js antes de servir .js original
   - try_files $1.min.css antes de servir .css original
   - Transparente para los templates HTML (cero cambios en HTML)

4. Cache warming script:
   - scripts/warm_vehicle_cache.py: pobla Redis con vehicle info por batches
   - Mitiga DISTINCT ON + 4 JOINs sobre 2B filas
   - Corre en background, procesa ~1.5M parts

Tests: 73/73 pasando
This commit is contained in:
2026-04-27 08:34:24 +00:00
parent e21722a3a9
commit 21959f1b37
56 changed files with 5629 additions and 4 deletions

1
dashboard/admin.min.js vendored Normal file

File diff suppressed because one or more lines are too long

485
dashboard/bodega.min.css vendored Normal file
View File

@@ -0,0 +1,485 @@
/* ============================================================
bodega.css -- Styles for Nexus Autoparts Warehouse (Bodega)
============================================================ */
/* --- Layout --- */
.bodega-container {
max-width: 1100px;
margin: 0 auto;
padding: 5.5rem 2rem 3rem;
}
/* --- Tabs --- */
.bodega-tabs {
display: flex;
gap: 0;
border-bottom: 2px solid var(--border);
margin-bottom: 1.5rem;
}
.bodega-tab {
padding: 0.8rem 1.8rem;
background: transparent;
border: none;
color: var(--text-secondary);
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.2s;
position: relative;
bottom: -2px;
}
.bodega-tab:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.bodega-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.bodega-section {
display: none;
}
.bodega-section.active {
display: block;
}
/* --- Section Intro --- */
.section-intro {
margin-bottom: 1.5rem;
}
.section-intro h2 {
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.section-intro p {
color: var(--text-secondary);
font-size: 0.9rem;
line-height: 1.5;
}
/* --- Mapping Form --- */
.mapping-form {
max-width: 550px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
}
.mapping-form .form-group {
margin-bottom: 1.25rem;
}
.mapping-form .form-label {
display: block;
margin-bottom: 0.4rem;
font-weight: 500;
font-size: 0.9rem;
color: var(--text-primary);
}
.required {
color: var(--danger);
font-weight: 700;
}
.optional {
color: var(--text-secondary);
font-size: 0.8rem;
font-weight: 400;
}
.form-hint {
display: block;
margin-top: 0.3rem;
font-size: 0.75rem;
color: var(--text-secondary);
}
.form-actions {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 1.25rem;
}
.status-msg {
font-size: 0.85rem;
font-weight: 500;
}
.status-msg.success {
color: var(--success);
}
.status-msg.error {
color: var(--danger);
}
/* --- Upload Zone --- */
.upload-zone {
border: 2px dashed var(--border);
border-radius: 12px;
padding: 3rem 2rem;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: var(--bg-card);
margin-bottom: 1rem;
}
.upload-zone:hover,
.upload-zone.dragover {
border-color: var(--accent);
background: rgba(255, 107, 53, 0.05);
}
.upload-icon {
font-size: 3rem;
margin-bottom: 0.75rem;
}
.upload-text {
font-size: 1rem;
font-weight: 500;
margin-bottom: 0.3rem;
}
.upload-hint {
font-size: 0.8rem;
color: var(--text-secondary);
}
/* --- Selected File --- */
.selected-file {
display: flex;
align-items: center;
gap: 0.75rem;
background: var(--bg-card);
border: 1px solid var(--accent);
border-radius: 8px;
padding: 0.6rem 1rem;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.btn-icon {
background: none;
border: none;
color: var(--text-secondary);
font-size: 1.2rem;
cursor: pointer;
padding: 0.1rem 0.3rem;
line-height: 1;
transition: color 0.2s;
}
.btn-icon:hover {
color: var(--danger);
}
/* --- Upload Result --- */
.upload-result {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.25rem;
margin-bottom: 1.5rem;
}
.upload-result h4 {
margin-bottom: 0.75rem;
font-size: 1rem;
}
.result-stats {
display: flex;
gap: 1.5rem;
margin-bottom: 0.75rem;
}
.result-stat {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.9rem;
}
.result-stat.ok {
color: var(--success);
}
.result-stat.err {
color: var(--danger);
}
.error-samples {
margin-top: 0.75rem;
padding: 0.75rem;
background: var(--bg-secondary);
border-radius: 8px;
font-size: 0.8rem;
color: var(--text-secondary);
max-height: 150px;
overflow-y: auto;
}
.error-samples p {
margin-bottom: 0.3rem;
}
/* --- History --- */
.history-section {
margin-top: 2rem;
}
.history-section h3 {
font-size: 1rem;
font-weight: 600;
margin-bottom: 1rem;
}
/* --- Tables --- */
.table-wrap {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.88rem;
}
.data-table thead th {
background: var(--bg-secondary);
color: var(--text-secondary);
text-transform: uppercase;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.05em;
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
.data-table tbody td {
padding: 0.7rem 1rem;
border-bottom: 1px solid var(--border);
color: var(--text-primary);
}
.data-table tbody tr:hover {
background: var(--bg-hover);
}
.empty-row {
text-align: center;
color: var(--text-secondary);
padding: 2rem 1rem !important;
}
/* --- Status Badges --- */
.badge {
display: inline-block;
padding: 0.2rem 0.6rem;
border-radius: 10px;
font-size: 0.72rem;
font-weight: 600;
text-transform: uppercase;
}
.badge-success {
background: rgba(34, 197, 94, 0.15);
color: var(--success);
}
.badge-error {
background: rgba(255, 68, 68, 0.15);
color: var(--danger);
}
.badge-pending {
background: rgba(245, 158, 11, 0.15);
color: var(--warning);
}
.badge-processing {
background: rgba(59, 130, 246, 0.15);
color: var(--info);
}
/* --- Inventory Toolbar --- */
.inventory-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.search-box {
display: flex;
gap: 0.5rem;
flex: 1;
max-width: 500px;
}
.search-box .form-input {
flex: 1;
}
.btn-danger {
background: rgba(255, 68, 68, 0.15);
border: 1px solid var(--danger);
color: var(--danger);
padding: 0.7rem 1.5rem;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s;
}
.btn-danger:hover {
background: var(--danger);
color: white;
}
/* --- Pagination --- */
.bodega-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 1.25rem;
flex-wrap: wrap;
}
.bodega-pagination button {
padding: 0.4rem 0.8rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.bodega-pagination button:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.bodega-pagination button.active {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.bodega-pagination button:disabled {
opacity: 0.4;
cursor: default;
}
/* --- Confirm Modal --- */
.confirm-box {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 2rem;
max-width: 420px;
width: 100%;
}
.confirm-box h3 {
margin-bottom: 0.75rem;
font-size: 1.1rem;
}
.confirm-box p {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1.5rem;
line-height: 1.5;
}
.confirm-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
/* --- Toast --- */
#toast-container {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 3000;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.toast {
padding: 0.8rem 1.2rem;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 500;
animation: fadeIn 0.3s ease;
max-width: 350px;
}
.toast.success {
background: rgba(34, 197, 94, 0.15);
border: 1px solid var(--success);
color: var(--success);
}
.toast.error {
background: rgba(255, 68, 68, 0.15);
border: 1px solid var(--danger);
color: var(--danger);
}
/* --- Responsive --- */
@media (max-width: 768px) {
.bodega-container {
padding: 5rem 1rem 2rem;
}
.bodega-tabs {
overflow-x: auto;
}
.bodega-tab {
padding: 0.7rem 1.2rem;
font-size: 0.85rem;
white-space: nowrap;
}
.inventory-toolbar {
flex-direction: column;
align-items: stretch;
}
.search-box {
max-width: none;
}
.result-stats {
flex-direction: column;
gap: 0.5rem;
}
}

1
dashboard/bodega.min.js vendored Normal file

File diff suppressed because one or more lines are too long

660
dashboard/captura.min.css vendored Normal file
View File

@@ -0,0 +1,660 @@
/* ============================================================
captura.css -- Styles for Nexus Autoparts Data Entry
============================================================ */
/* --- Tabs --- */
.captura-tabs {
display: flex;
gap: 0;
border-bottom: 2px solid var(--border);
margin-bottom: 1.5rem;
}
.captura-tab {
padding: 0.8rem 1.8rem;
background: transparent;
border: none;
color: var(--text-secondary);
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
border-bottom: 3px solid transparent;
transition: all 0.2s;
position: relative;
bottom: -2px;
}
.captura-tab:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.captura-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.captura-tab .tab-badge {
background: var(--accent);
color: #fff;
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 10px;
margin-left: 0.5rem;
font-weight: 700;
}
.captura-section {
display: none;
}
.captura-section.active {
display: block;
}
/* --- Vehicle Selector (Section 1) --- */
.vehicle-filters {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
align-items: flex-end;
}
.vehicle-filters .filter-group {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.vehicle-filters label {
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.vehicle-filters select,
.vehicle-filters input {
padding: 0.5rem 0.8rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.9rem;
min-width: 160px;
}
.vehicle-filters select:focus,
.vehicle-filters input:focus {
outline: none;
border-color: var(--accent);
}
/* --- Vehicle List --- */
.vehicle-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 0.8rem;
max-height: 400px;
overflow-y: auto;
padding-right: 0.5rem;
}
.vehicle-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
}
.vehicle-card:hover {
border-color: var(--accent);
background: var(--bg-hover);
}
.vehicle-card .vc-brand {
font-weight: 700;
font-size: 0.95rem;
color: var(--accent);
}
.vehicle-card .vc-model {
font-size: 1.1rem;
font-weight: 600;
margin: 0.2rem 0;
}
.vehicle-card .vc-details {
font-size: 0.8rem;
color: var(--text-secondary);
}
.vehicle-card .vc-parts-count {
margin-top: 0.5rem;
font-size: 0.75rem;
color: var(--success);
}
/* --- Vehicle Header (when editing) --- */
.vehicle-header {
background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-hover) 100%);
border: 1px solid var(--accent);
border-radius: 12px;
padding: 1rem 1.5rem;
margin-bottom: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.vehicle-header .vh-info {
display: flex;
gap: 1.5rem;
align-items: center;
flex-wrap: wrap;
}
.vehicle-header .vh-label {
font-size: 0.7rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.vehicle-header .vh-value {
font-size: 1.1rem;
font-weight: 700;
}
.vehicle-header .vh-brand { color: var(--accent); }
.vehicle-header .vh-actions {
display: flex;
gap: 0.5rem;
}
/* --- Part Groups Table --- */
.category-section {
margin-bottom: 1.5rem;
}
.category-header {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px 8px 0 0;
padding: 0.6rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
user-select: none;
}
.category-header:hover {
background: var(--bg-hover);
}
.category-header h3 {
font-size: 0.9rem;
font-weight: 700;
color: var(--accent);
}
.category-header .cat-toggle {
font-size: 0.8rem;
color: var(--text-secondary);
transition: transform 0.2s;
}
.category-header.collapsed .cat-toggle {
transform: rotate(-90deg);
}
.category-body {
border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 8px 8px;
}
.category-body.collapsed {
display: none;
}
.group-section {
border-bottom: 1px solid var(--border);
padding: 0.8rem 1rem;
}
.group-section:last-child {
border-bottom: none;
}
.group-name {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
/* --- Part Rows --- */
.part-rows {
display: flex;
flex-direction: column;
gap: 0.4rem;
margin-bottom: 0.4rem;
}
.part-row {
display: flex;
gap: 0.5rem;
align-items: center;
}
.part-row input {
padding: 0.4rem 0.6rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-size: 0.85rem;
}
.part-row input:focus {
outline: none;
border-color: var(--accent);
}
.part-row .pr-oem {
width: 160px;
font-family: monospace;
}
.part-row .pr-name {
flex: 1;
min-width: 150px;
}
.part-row .pr-qty {
width: 50px;
text-align: center;
}
.part-row .pr-btn {
padding: 0.3rem 0.6rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.8rem;
font-weight: 600;
transition: all 0.2s;
}
.part-row .pr-save {
background: var(--success);
color: #fff;
}
.part-row .pr-save:hover { background: #1ea34e; }
.part-row .pr-delete {
background: var(--danger);
color: #fff;
}
.part-row .pr-delete:hover { background: #cc3333; }
.part-row.saved {
background: rgba(34, 197, 94, 0.08);
border-radius: 6px;
padding: 0.2rem 0.4rem;
}
.part-row.saved input {
background: transparent;
border-color: var(--success);
color: var(--success);
}
.btn-add-part {
background: transparent;
border: 1px dashed var(--border);
border-radius: 6px;
padding: 0.3rem 0.8rem;
color: var(--text-secondary);
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.btn-add-part:hover {
border-color: var(--accent);
color: var(--accent);
}
/* --- Progress Bar --- */
.progress-bar {
background: var(--bg-secondary);
border-radius: 10px;
height: 8px;
overflow: hidden;
margin: 0.5rem 0;
}
.progress-bar .progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent), var(--success));
border-radius: 10px;
transition: width 0.3s;
}
.progress-text {
font-size: 0.75rem;
color: var(--text-secondary);
}
/* --- Section 2: Intercambios --- */
.part-detail-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1rem;
margin-bottom: 1rem;
}
.part-detail-card .pdc-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.8rem;
}
.part-detail-card .pdc-oem {
font-family: monospace;
font-size: 1rem;
font-weight: 700;
color: var(--accent);
}
.part-detail-card .pdc-name {
font-size: 0.85rem;
color: var(--text-secondary);
}
.part-detail-card .pdc-group {
font-size: 0.75rem;
color: var(--text-secondary);
background: var(--bg-hover);
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
.aftermarket-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.aftermarket-table th {
text-align: left;
padding: 0.5rem;
border-bottom: 1px solid var(--border);
color: var(--text-secondary);
font-size: 0.75rem;
text-transform: uppercase;
}
.aftermarket-table td {
padding: 0.4rem 0.5rem;
border-bottom: 1px solid rgba(42, 42, 58, 0.5);
}
.aftermarket-form {
display: flex;
gap: 0.5rem;
align-items: flex-end;
flex-wrap: wrap;
margin-top: 0.8rem;
padding-top: 0.8rem;
border-top: 1px dashed var(--border);
}
.aftermarket-form .af-field {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.aftermarket-form label {
font-size: 0.7rem;
color: var(--text-secondary);
text-transform: uppercase;
}
.aftermarket-form select,
.aftermarket-form input {
padding: 0.4rem 0.6rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-size: 0.85rem;
}
.aftermarket-form select:focus,
.aftermarket-form input:focus {
outline: none;
border-color: var(--accent);
}
/* --- Section 3: Imágenes --- */
.image-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1rem;
display: flex;
gap: 1rem;
align-items: center;
margin-bottom: 0.8rem;
}
.image-card .ic-preview {
width: 100px;
height: 100px;
background: var(--bg-secondary);
border: 2px dashed var(--border);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 0.75rem;
overflow: hidden;
flex-shrink: 0;
}
.image-card .ic-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.image-card .ic-info {
flex: 1;
}
.image-card .ic-oem {
font-family: monospace;
font-weight: 700;
color: var(--accent);
}
.image-card .ic-name {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.image-card .ic-upload {
display: flex;
gap: 0.5rem;
align-items: center;
}
.image-card .ic-upload input[type="file"] {
font-size: 0.8rem;
color: var(--text-secondary);
}
/* --- Search bar --- */
.captura-search {
display: flex;
gap: 0.8rem;
margin-bottom: 1rem;
align-items: center;
}
.captura-search input {
padding: 0.5rem 0.8rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.9rem;
flex: 1;
max-width: 400px;
}
.captura-search input:focus {
outline: none;
border-color: var(--accent);
}
/* --- Pagination --- */
.captura-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 1rem;
padding: 0.5rem;
}
.captura-pagination button {
padding: 0.4rem 0.8rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
cursor: pointer;
font-size: 0.85rem;
}
.captura-pagination button:hover {
border-color: var(--accent);
}
.captura-pagination button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.captura-pagination .page-info {
font-size: 0.8rem;
color: var(--text-secondary);
}
/* --- Empty state --- */
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-secondary);
}
.empty-state .es-icon {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.empty-state .es-text {
font-size: 0.9rem;
}
/* --- Toast notifications --- */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
padding: 0.8rem 1.5rem;
border-radius: 10px;
color: #fff;
font-weight: 600;
font-size: 0.9rem;
z-index: 9999;
animation: toastIn 0.3s ease;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}
.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
@keyframes toastIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* --- Loading spinner --- */
.loading {
display: flex;
justify-content: center;
padding: 2rem;
}
.spinner {
width: 30px;
height: 30px;
border: 3px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* --- Layout --- */
.captura-container {
max-width: 1200px;
margin: 0 auto;
padding: 5rem 2rem 2rem;
}
/* --- Status tabs for vehicles --- */
.status-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.status-tab {
padding: 0.4rem 1rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 20px;
color: var(--text-secondary);
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.status-tab:hover { border-color: var(--accent); }
.status-tab.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}

1
dashboard/captura.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/catalog-public.min.js vendored Normal file

File diff suppressed because one or more lines are too long

237
dashboard/chat-public.min.css vendored Normal file
View File

@@ -0,0 +1,237 @@
/* ==========================================================================
NEXUS — Public Catalog Chat Widget
Reuses design tokens from tokens.css
========================================================================== */
.chat-fab {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 8000;
width: 52px;
height: 52px;
border-radius: var(--radius-full, 50%);
border: none;
cursor: pointer;
background: var(--color-accent, #F5A623);
color: #fff;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--shadow-lg, 0 4px 12px rgba(0,0,0,0.3));
transition: transform 0.2s ease, background 0.2s ease;
}
.chat-fab:hover {
transform: scale(1.08);
background: var(--color-primary-hover, #e5952f);
}
.chat-panel {
position: fixed;
bottom: 90px;
right: 24px;
z-index: 8001;
width: 400px;
height: 520px;
max-height: calc(100vh - 100px);
display: flex;
flex-direction: column;
background: var(--color-bg-elevated, #1a1a1a);
border: 1px solid var(--color-border, #333);
border-radius: var(--radius-xl, 16px);
box-shadow: var(--shadow-xl, 0 8px 32px rgba(0,0,0,0.4));
overflow: hidden;
transform: translateY(20px) scale(0.95);
opacity: 0;
pointer-events: none;
transition: transform 0.25s ease, opacity 0.25s ease;
}
.chat-panel.open {
transform: translateY(0) scale(1);
opacity: 1;
pointer-events: all;
}
.chat-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--color-accent, #F5A623);
color: #fff;
flex-shrink: 0;
}
.chat-header h3 {
font-family: var(--font-heading, sans-serif);
font-size: 0.95rem;
font-weight: 600;
margin: 0;
}
.chat-header-close {
background: none;
border: none;
color: #fff;
font-size: 1.2rem;
cursor: pointer;
padding: 4px;
line-height: 1;
opacity: 0.8;
}
.chat-header-close:hover { opacity: 1; }
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.chat-msg {
max-width: 85%;
padding: 8px 12px;
border-radius: 12px;
font-size: 0.875rem;
line-height: 1.45;
word-wrap: break-word;
}
.chat-msg.user {
align-self: flex-end;
background: var(--color-accent, #F5A623);
color: #fff;
border-bottom-right-radius: 4px;
}
.chat-msg.ai {
align-self: flex-start;
background: var(--color-surface-2, rgba(255,255,255,0.06));
color: var(--color-text-primary, #fff);
border-bottom-left-radius: 4px;
}
.chat-typing {
align-self: flex-start;
display: none;
gap: 4px;
padding: 8px 12px;
background: var(--color-surface-2, rgba(255,255,255,0.06));
border-radius: 12px;
border-bottom-left-radius: 4px;
}
.chat-typing.visible { display: flex; }
.chat-typing span {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--color-text-muted, #888);
animation: chatBounce 1.2s infinite;
}
.chat-typing span:nth-child(2) { animation-delay: 0.2s; }
.chat-typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes chatBounce {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-5px); }
}
.chat-parts {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
}
.chat-part-card {
background: var(--color-bg-elevated, #1a1a1a);
border: 1px solid var(--color-border, #333);
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
transition: border-color 0.15s ease, background 0.15s ease;
}
.chat-part-card:hover {
border-color: var(--color-accent, #F5A623);
background: var(--color-bg-base, #111);
}
.chat-part-card .part-number {
font-family: var(--font-mono, monospace);
font-size: 0.75rem;
color: var(--color-accent, #F5A623);
font-weight: 600;
}
.chat-part-card .part-name {
font-size: 0.875rem;
color: var(--color-text-primary, #fff);
margin-top: 2px;
}
.chat-input-area {
display: flex;
gap: 8px;
padding: 12px;
border-top: 1px solid var(--color-border, #333);
flex-shrink: 0;
}
.chat-input {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--color-border, #333);
border-radius: 8px;
background: var(--color-bg-base, #111);
color: var(--color-text-primary, #fff);
font-size: 0.875rem;
font-family: var(--font-body, sans-serif);
resize: none;
outline: none;
min-height: 38px;
max-height: 80px;
}
.chat-input:focus {
border-color: var(--color-accent, #F5A623);
}
.chat-input::placeholder {
color: var(--color-text-muted, #888);
}
.chat-send-btn {
width: 38px;
height: 38px;
border-radius: 8px;
border: none;
background: var(--color-accent, #F5A623);
color: #fff;
font-size: 1.1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.15s ease;
}
.chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
@media (max-width: 480px) {
.chat-panel {
width: calc(100vw - 16px);
right: 8px;
height: 60vh;
}
}

1
dashboard/chat-public.min.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(){"use strict";var e=!1,t=!1,n=[];function a(){var e=document.createElement("button");e.className="chat-fab",e.id="chatFab",e.title="Asistente IA",e.innerHTML="&#x1F4AC;",e.setAttribute("aria-label","Abrir asistente IA");var t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML='<div class="chat-header"><h3>Asistente — Buscar partes</h3><button class="chat-header-close" id="chatClose" aria-label="Cerrar">&times;</button></div><div class="chat-messages" id="chatMessages"><div class="chat-msg ai">Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.</div><div class="chat-typing" id="chatTyping"><span></span><span></span><span></span></div></div><div class="chat-input-area"><textarea class="chat-input" id="chatInput" placeholder="Ej: Balatas para Tsuru 2015..." rows="1"></textarea><button class="chat-send-btn" id="chatSend" aria-label="Enviar">&#9654;</button></div>',document.body.appendChild(e),document.body.appendChild(t),e.addEventListener("click",s),document.getElementById("chatClose").addEventListener("click",s),document.getElementById("chatSend").addEventListener("click",c),document.getElementById("chatInput").addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),c())})),document.getElementById("chatInput").addEventListener("input",(function(){this.style.height="auto",this.style.height=Math.min(this.scrollHeight,80)+"px"}))}function s(){e=!e;var t=document.getElementById("chatPanel"),n=document.getElementById("chatFab");e?(t.classList.add("open"),n.style.display="none",document.getElementById("chatInput").focus()):(t.classList.remove("open"),n.style.display="flex")}function c(){if(!t){var e=document.getElementById("chatInput"),a=e.value.trim();a&&(e.value="",e.style.height="auto",i(a,"user"),n.push({role:"user",content:a}),n.length>20&&n.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,d(!0),fetch("/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:a,history:n.slice(-10)})}).then((function(e){return e.json()})).then((function(e){if(e.error)i("Error: "+e.error,"ai");else{var t,a,c,d,l=e.response||"Sin respuesta.";i(l,"ai"),n.push({role:"assistant",content:l}),e.search_results&&e.search_results.length>0&&(t=e.search_results,a=document.getElementById("chatMessages"),c=document.getElementById("chatTyping"),(d=document.createElement("div")).className="chat-parts",t.slice(0,8).forEach((function(e){var t=document.createElement("div");t.className="chat-part-card";var n=e.name_es||e.name_part||"",a=e.oem_part_number||e.part_number||"",c=e.brand||"";t.innerHTML='<div class="part-number">'+o(a)+'</div><div class="part-name">'+o(n)+(c?' <span style="color:var(--color-text-muted);">('+o(c)+")</span>":"")+"</div>",t.style.cursor="pointer",t.addEventListener("click",(function(){var e=document.getElementById("searchInput");e&&a&&(e.value=a,"function"==typeof window.doSearch&&window.doSearch(),s())})),d.appendChild(t)})),a.insertBefore(d,c),r())}})).catch((function(e){i("Error de conexion: "+e.message,"ai")})).finally((function(){t=!1,document.getElementById("chatSend").disabled=!1,d(!1)})))}}function i(e,t){var n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg "+t,s.textContent=e,n.insertBefore(s,a),r()}function d(e){var t=document.getElementById("chatTyping");t&&t.classList.toggle("visible",e),e&&r()}function r(){var e=document.getElementById("chatMessages");e&&(e.scrollTop=e.scrollHeight)}function o(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",a):a()}();

282
dashboard/cuentas.min.css vendored Normal file
View File

@@ -0,0 +1,282 @@
/* ============================================================
cuentas.css -- Accounts receivable styles
============================================================ */
.cuentas-container {
max-width: 1200px;
margin: 0 auto;
padding: 5rem 2rem 2rem;
}
/* --- Customer list --- */
.cuentas-search {
display: flex;
gap: 0.8rem;
margin-bottom: 1rem;
}
.cuentas-search input {
flex: 1;
max-width: 400px;
padding: 0.5rem 0.8rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.9rem;
}
.cuentas-search input:focus {
outline: none;
border-color: var(--accent);
}
.customer-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 0.8rem;
margin-bottom: 1.5rem;
}
.customer-card-item {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
}
.customer-card-item:hover {
border-color: var(--accent);
background: var(--bg-hover);
}
.cci-name {
font-weight: 700;
font-size: 1rem;
margin-bottom: 0.2rem;
}
.cci-rfc {
font-family: monospace;
font-size: 0.8rem;
color: var(--text-secondary);
}
.cci-balance-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.5rem;
}
.cci-balance {
font-size: 1.1rem;
font-weight: 700;
}
.cci-balance.positive { color: var(--danger); }
.cci-balance.zero { color: var(--success); }
.cci-limit {
font-size: 0.75rem;
color: var(--text-secondary);
}
/* --- Customer detail view --- */
.detail-view {
display: none;
}
.detail-header {
background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg-hover) 100%);
border: 1px solid var(--accent);
border-radius: 12px;
padding: 1.2rem 1.5rem;
margin-bottom: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.dh-info {
display: flex;
gap: 2rem;
flex-wrap: wrap;
}
.dh-field .dh-label {
font-size: 0.7rem;
color: var(--text-secondary);
text-transform: uppercase;
}
.dh-field .dh-value {
font-size: 1rem;
font-weight: 600;
}
.dh-field .dh-value.accent { color: var(--accent); }
.dh-field .dh-value.danger { color: var(--danger); }
.dh-field .dh-value.success { color: var(--success); }
/* --- Two-column layout for invoices/payments --- */
.detail-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
@media (max-width: 768px) {
.detail-columns { grid-template-columns: 1fr; }
}
.detail-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.detail-card h3 {
padding: 0.8rem 1rem;
border-bottom: 1px solid var(--border);
font-size: 0.9rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.detail-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.detail-table th {
text-align: left;
padding: 0.5rem 0.6rem;
border-bottom: 1px solid var(--border);
color: var(--text-secondary);
font-size: 0.75rem;
text-transform: uppercase;
}
.detail-table td {
padding: 0.4rem 0.6rem;
border-bottom: 1px solid rgba(42, 42, 58, 0.5);
}
.status-badge {
display: inline-block;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
}
.status-badge.pending { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
.status-badge.partial { background: rgba(59, 130, 246, 0.15); color: var(--info); }
.status-badge.paid { background: rgba(34, 197, 94, 0.15); color: var(--success); }
.status-badge.cancelled { background: rgba(255, 68, 68, 0.15); color: var(--danger); }
/* --- Payment form --- */
.payment-form {
padding: 1rem;
border-top: 1px solid var(--border);
}
.payment-form h4 {
font-size: 0.85rem;
color: var(--accent);
margin-bottom: 0.8rem;
}
.pf-row {
display: flex;
gap: 0.5rem;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.pf-field {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.pf-field label {
font-size: 0.7rem;
color: var(--text-secondary);
text-transform: uppercase;
}
.pf-field input,
.pf-field select {
padding: 0.4rem 0.6rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-size: 0.85rem;
}
.pf-field input:focus,
.pf-field select:focus {
outline: none;
border-color: var(--accent);
}
/* --- Toast --- */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
padding: 0.8rem 1.5rem;
border-radius: 10px;
color: #fff;
font-weight: 600;
font-size: 0.9rem;
z-index: 9999;
animation: toastIn 0.3s ease;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}
.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
@keyframes toastIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* --- Pagination --- */
.cuentas-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 1rem;
}
.cuentas-pagination button {
padding: 0.4rem 0.8rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
cursor: pointer;
font-size: 0.85rem;
}
.cuentas-pagination button:hover { border-color: var(--accent); }
.cuentas-pagination button:disabled { opacity: 0.4; cursor: not-allowed; }
.cuentas-pagination .page-info { font-size: 0.8rem; color: var(--text-secondary); }
.empty-state {
text-align: center;
padding: 2rem;
color: var(--text-secondary);
font-size: 0.9rem;
}

1
dashboard/cuentas.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/dashboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/enhanced-search.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dashboard/landing.min.js vendored Normal file

File diff suppressed because one or more lines are too long

211
dashboard/login.min.css vendored Normal file
View File

@@ -0,0 +1,211 @@
/* ============================================================
login.css -- Login / Register page styles
============================================================ */
.login-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: var(--bg-primary);
padding: 2rem;
}
/* --- Card --- */
.login-card {
width: 100%;
max-width: 440px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 2.5rem;
animation: fadeIn 0.4s ease;
}
/* --- Brand header --- */
.login-brand {
text-align: center;
margin-bottom: 2rem;
}
.login-brand .logo-icon {
width: 56px;
height: 56px;
background: linear-gradient(135deg, var(--accent) 0%, #ff4500 100%);
border-radius: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
margin-bottom: 1rem;
box-shadow: 0 4px 20px var(--accent-glow);
}
.login-brand h1 {
font-family: 'Orbitron', sans-serif;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: 2px;
color: var(--text-primary);
margin-bottom: 0.4rem;
}
.login-brand h1 span {
color: var(--accent);
}
.login-brand .slogan {
font-size: 0.85rem;
color: var(--text-secondary);
font-weight: 400;
}
/* --- Form panel visibility --- */
.form-panel {
display: none;
}
.form-panel.active {
display: block;
animation: fadeIn 0.3s ease;
}
/* --- Form title --- */
.form-title {
font-size: 1.15rem;
font-weight: 600;
margin-bottom: 1.5rem;
text-align: center;
color: var(--text-primary);
}
/* --- Select (dropdown) --- */
.form-select {
width: 100%;
padding: 0.75rem 1rem;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.95rem;
transition: border-color 0.2s;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a0a0b0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: var(--accent);
}
.form-select option {
background: var(--bg-secondary);
color: var(--text-primary);
}
/* --- Submit button (full width) --- */
.btn-submit {
width: 100%;
padding: 0.85rem;
margin-top: 0.5rem;
font-size: 1rem;
}
/* --- Toggle link --- */
.toggle-link {
text-align: center;
margin-top: 1.5rem;
font-size: 0.9rem;
color: var(--text-secondary);
}
.toggle-link a {
color: var(--accent);
text-decoration: none;
font-weight: 600;
cursor: pointer;
transition: color 0.2s;
}
.toggle-link a:hover {
color: var(--accent-hover);
text-decoration: underline;
}
/* --- Alert messages --- */
.login-alert {
padding: 0.85rem 1rem;
border-radius: 8px;
margin-bottom: 1.25rem;
font-size: 0.9rem;
display: none;
align-items: center;
gap: 0.5rem;
line-height: 1.4;
}
.login-alert.show {
display: flex;
}
.login-alert.error {
background: rgba(255, 68, 68, 0.1);
border: 1px solid var(--danger);
color: var(--danger);
}
.login-alert.success {
background: rgba(0, 214, 143, 0.1);
border: 1px solid var(--success);
color: var(--success);
}
/* --- Loading spinner on button --- */
.btn-submit.loading {
pointer-events: none;
opacity: 0.7;
}
.btn-submit .spinner {
display: none;
width: 18px;
height: 18px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
.btn-submit.loading .spinner {
display: inline-block;
}
.btn-submit.loading .btn-label {
display: none;
}
/* --- Row layout for two fields side by side --- */
.form-row {
display: flex;
gap: 1rem;
}
.form-row .form-group {
flex: 1;
}
/* --- Responsive --- */
@media (max-width: 500px) {
.login-card {
padding: 1.75rem 1.5rem;
}
.form-row {
flex-direction: column;
gap: 0;
}
}

1
dashboard/login.min.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(){"use strict";const e=document.getElementById("loginPanel"),t=document.getElementById("registerPanel"),o=document.getElementById("loginForm"),n=document.getElementById("registerForm"),r=document.getElementById("alert"),a={ADMIN:"/demo",OWNER:"/demo",BODEGA:"/bodega",TALLER:"/demo"};function s(e,t){r.textContent=e,r.className="login-alert show "+t}function c(){r.className="login-alert",r.textContent=""}function l(e,t){e.classList.toggle("loading",t)}!function(){const e=localStorage.getItem("access_token"),t=localStorage.getItem("user_role");if(e&&t){const e=a[t]||"/index.html";window.location.replace(e)}}(),window.showPanel=function(o){c(),"register"===o?(e.classList.remove("active"),t.classList.add("active")):(t.classList.remove("active"),e.classList.add("active"))},o.addEventListener("submit",(async function(e){e.preventDefault(),c();const t=document.getElementById("loginEmail").value.trim(),n=document.getElementById("loginPassword").value,r=o.querySelector(".btn-submit");if(t&&n){l(r,!0);try{const e=await fetch("/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t,password:n})}),o=await e.json();if(!e.ok)return void s(o.error||o.message||"Credenciales incorrectas.","error");localStorage.setItem("access_token",o.access_token),localStorage.setItem("refresh_token",o.refresh_token||""),localStorage.setItem("user_role",o.role||o.user?.role||""),localStorage.setItem("user_name",o.name||o.user?.name||"");const r=(o.role||o.user?.role||"").toUpperCase(),c=a[r]||"/index.html";window.location.replace(c)}catch(e){s("Error de conexion. Intenta de nuevo.","error")}finally{l(r,!1)}}else s("Completa todos los campos.","error")})),n.addEventListener("submit",(async function(e){e.preventDefault(),c();const t=document.getElementById("regName").value.trim(),o=document.getElementById("regEmail").value.trim(),r=document.getElementById("regPassword").value,a=document.getElementById("regConfirm").value,i=document.getElementById("regBusiness").value.trim(),m=document.getElementById("regPhone").value.trim(),d=document.getElementById("regRole").value,u=n.querySelector(".btn-submit");if(t&&o&&r&&a&&i&&m)if(r.length<8)s("La contrasena debe tener al menos 8 caracteres.","error");else if(r===a){l(u,!0);try{const e=await fetch("/api/auth/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,email:o,password:r,role:d,business_name:i,phone:m})}),a=await e.json();if(!e.ok)return void s(a.error||a.message||"Error al crear la cuenta.","error");s("Cuenta creada. Pendiente de aprobacion por administrador.","success"),n.reset()}catch(e){s("Error de conexion. Intenta de nuevo.","error")}finally{l(u,!1)}}else s("Las contrasenas no coinciden.","error");else s("Completa todos los campos.","error")})),window.authFetch=async function(e,t={}){const o=localStorage.getItem("access_token");if(!o)return void window.location.replace("/login.html");const n=Object.assign({},t.headers||{},{Authorization:"Bearer "+o});let r=await fetch(e,Object.assign({},t,{headers:n}));if(401===r.status){const o=await async function(){const e=localStorage.getItem("refresh_token");if(!e)return!1;try{const t=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e})});if(!t.ok)return!1;const o=await t.json();return localStorage.setItem("access_token",o.access_token),o.refresh_token&&localStorage.setItem("refresh_token",o.refresh_token),!0}catch(e){return!1}}();if(!o)return void logout();n.Authorization="Bearer "+localStorage.getItem("access_token"),r=await fetch(e,Object.assign({},t,{headers:n}))}return r},window.logout=function(){localStorage.removeItem("access_token"),localStorage.removeItem("refresh_token"),localStorage.removeItem("user_role"),localStorage.removeItem("user_name"),window.location.replace("/login.html")}}();

1
dashboard/nav.min.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(){"use strict";var e=window.location.pathname;function t(t){var a=t.replace(/\/+$/,"")||"/",n=e.replace(/\/+$/,"")||"/";return a===n||(!("/"!==a&&"/index.html"!==a||"/"!==n&&"/index.html"!==n)||(!("/admin.html"!==a&&"/admin"!==a||"/admin.html"!==n&&"/admin"!==n)||(!("/diagramas"!==a&&"/diagrams.html"!==a||"/diagramas"!==n&&"/diagrams.html"!==n)||("/customer-landing.html"===a&&"/customer-landing.html"===n||("/captura"===a&&"/captura"===n||("/pos"===a&&"/pos"===n||("/cuentas"===a&&"/cuentas"===n||("/tienda"===a&&"/tienda"===n||("/bodega"===a&&"/bodega"===n||!("/demo"!==a&&"/demo.html"!==a||"/demo"!==n&&"/demo.html"!==n))))))))))}var a='<header id="shared-nav-header" style="background: rgba(18, 18, 26, 0.95);backdrop-filter: blur(20px);-webkit-backdrop-filter: blur(20px);border-bottom: 1px solid var(--border);padding: 1rem 2rem;position: fixed;top: 0; left: 0; right: 0;z-index: 1000;"><div style="max-width: 1600px;margin: 0 auto;display: flex;justify-content: space-between;align-items: center;gap: 2rem;"><a href="/" style="display: flex;align-items: center;gap: 0.75rem;text-decoration: none;flex-shrink: 0;"><div style="width: 42px; height: 42px;background: linear-gradient(135deg, var(--accent) 0%, #ff4500 100%);border-radius: 10px;display: flex; align-items: center; justify-content: center;font-size: 1.5rem;box-shadow: 0 4px 20px var(--accent-glow);">⚙️</div><span style="font-family: Orbitron, sans-serif;font-size: 1.3rem;font-weight: 700;background: linear-gradient(135deg, #fff 0%, var(--accent) 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;">NEXUS AUTOPARTS</span></a><div id="shared-nav-extra" style="display: contents;"></div><nav id="shared-nav-links" style="display: flex;gap: 1.5rem;align-items: center;flex-shrink: 0;">'+[{label:"Demo",href:"/demo"},{label:"Tienda",href:"/tienda"},{label:"Catálogo",href:"/index.html"},{label:"Captura",href:"/captura"},{label:"POS",href:"/pos"},{label:"Cuentas",href:"/cuentas"},{label:"Bodega",href:"/bodega"},{label:"Admin",href:"/admin"}].map((function(e){var a="text-decoration: none; font-size: 0.9rem; font-weight: 500; transition: color 0.2s;";return t(e.href)?a+=" color: var(--accent);":a+=" color: var(--text-secondary);",'<a href="'+e.href+'" style="'+a+'" onmouseover="this.style.color=\'var(--accent)\'" onmouseout="'+(t(e.href)?"":"this.style.color='var(--text-secondary)'")+'">'+e.label+"</a>"})).join("")+'</nav><div id="nav-auth" style="display:flex;align-items:center;gap:0.75rem;flex-shrink:0;"><span id="nav-user-name" style="color:var(--text-secondary);font-size:0.85rem;"></span><a id="nav-auth-btn" href="/login.html" style="text-decoration:none;font-size:0.85rem;font-weight:500;color:var(--bg);background:var(--accent);padding:0.4rem 1rem;border-radius:6px;transition:opacity 0.2s;">Iniciar Sesión</a></div></div></header>',n=document.getElementById("shared-nav");n&&(n.innerHTML=a);var r=localStorage.getItem("access_token");if(r)try{var i=JSON.parse(atob(r.split(".")[1])),o=document.getElementById("nav-user-name"),l=document.getElementById("nav-auth-btn");o&&i.business_name?o.textContent=i.business_name:o&&(o.textContent=i.role||""),l&&(l.textContent="Salir",l.href="#",l.onclick=function(e){e.preventDefault(),localStorage.removeItem("access_token"),localStorage.removeItem("refresh_token"),window.location.href="/login.html"})}catch(e){}}();

418
dashboard/pos.min.css vendored Normal file
View File

@@ -0,0 +1,418 @@
/* ============================================================
pos.css -- Point of Sale styles
============================================================ */
.pos-container {
max-width: 1400px;
margin: 0 auto;
padding: 5rem 2rem 2rem;
}
/* --- Layout: 2 columns --- */
.pos-layout {
display: grid;
grid-template-columns: 1fr 360px;
gap: 1.5rem;
align-items: start;
}
/* --- Left: Search + Cart --- */
.pos-main {
display: flex;
flex-direction: column;
gap: 1rem;
}
/* --- Customer bar --- */
.customer-bar {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1rem;
display: flex;
gap: 1rem;
align-items: center;
}
.customer-bar .cb-search {
flex: 1;
padding: 0.5rem 0.8rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.9rem;
}
.customer-bar .cb-search:focus {
outline: none;
border-color: var(--accent);
}
.customer-bar .cb-selected {
display: flex;
align-items: center;
gap: 0.8rem;
flex: 1;
}
.customer-bar .cb-name {
font-weight: 700;
font-size: 1rem;
}
.customer-bar .cb-rfc {
font-size: 0.8rem;
color: var(--text-secondary);
font-family: monospace;
}
.customer-bar .cb-balance {
font-size: 0.85rem;
padding: 0.2rem 0.6rem;
border-radius: 6px;
}
.cb-balance.positive { background: rgba(255, 68, 68, 0.15); color: var(--danger); }
.cb-balance.zero { background: rgba(34, 197, 94, 0.15); color: var(--success); }
/* --- Customer dropdown --- */
.customer-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 0 0 8px 8px;
max-height: 250px;
overflow-y: auto;
z-index: 100;
box-shadow: 0 8px 30px rgba(0,0,0,0.4);
}
.customer-dropdown-item {
padding: 0.6rem 1rem;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
}
.customer-dropdown-item:hover {
background: var(--bg-hover);
}
.customer-dropdown-item .cdi-name { font-weight: 600; }
.customer-dropdown-item .cdi-rfc { font-size: 0.8rem; color: var(--text-secondary); }
/* --- Part search --- */
.part-search-wrap {
position: relative;
}
.part-search {
width: 100%;
padding: 0.7rem 1rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-primary);
font-size: 1rem;
}
.part-search:focus {
outline: none;
border-color: var(--accent);
}
.part-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 0 0 10px 10px;
max-height: 300px;
overflow-y: auto;
z-index: 100;
box-shadow: 0 8px 30px rgba(0,0,0,0.4);
display: none;
}
.part-result-item {
padding: 0.6rem 1rem;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
}
.part-result-item:hover,
.part-result-item.part-result-active {
background: var(--bg-hover);
border-left: 3px solid var(--accent);
}
.part-result-item .pri-number {
font-family: monospace;
font-weight: 600;
color: var(--accent);
}
.part-result-item .pri-name {
font-size: 0.85rem;
color: var(--text-secondary);
margin-left: 0.5rem;
}
.part-result-item .pri-type {
font-size: 0.7rem;
padding: 0.15rem 0.4rem;
border-radius: 4px;
text-transform: uppercase;
}
.pri-type.oem { background: rgba(59, 130, 246, 0.15); color: var(--info); }
.pri-type.aftermarket { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
/* --- Cart table --- */
.cart-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.cart-card h3 {
padding: 0.8rem 1rem;
border-bottom: 1px solid var(--border);
font-size: 0.9rem;
color: var(--text-secondary);
}
.cart-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.cart-table th {
text-align: left;
padding: 0.5rem 0.6rem;
border-bottom: 1px solid var(--border);
color: var(--text-secondary);
font-size: 0.75rem;
text-transform: uppercase;
}
.cart-table td {
padding: 0.5rem 0.6rem;
border-bottom: 1px solid rgba(42, 42, 58, 0.5);
vertical-align: middle;
}
.cart-table input {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-primary);
padding: 0.3rem 0.4rem;
font-size: 0.85rem;
width: 70px;
text-align: right;
}
.cart-table input:focus {
outline: none;
border-color: var(--accent);
}
.cart-table .cart-desc { max-width: 250px; }
.cart-table .cart-qty { width: 45px; text-align: center; }
.cart-table .cart-cost { width: 80px; }
.cart-table .cart-margin { width: 55px; }
.cart-table .cart-price { width: 80px; }
.cart-table .cart-remove {
background: none;
border: none;
color: var(--danger);
cursor: pointer;
font-size: 1rem;
padding: 0.2rem;
}
.cart-empty {
text-align: center;
padding: 2rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
/* --- Right sidebar: Invoice summary --- */
.pos-sidebar {
position: sticky;
top: 5rem;
}
.invoice-summary {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.2rem;
}
.invoice-summary h3 {
font-size: 0.9rem;
color: var(--text-secondary);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.summary-row {
display: flex;
justify-content: space-between;
padding: 0.4rem 0;
font-size: 0.9rem;
}
.summary-row.total {
border-top: 2px solid var(--accent);
margin-top: 0.5rem;
padding-top: 0.8rem;
font-size: 1.2rem;
font-weight: 700;
}
.summary-row .sr-label { color: var(--text-secondary); }
.summary-row .sr-value { font-weight: 600; }
.summary-row.total .sr-value { color: var(--accent); }
.btn-facturar {
width: 100%;
margin-top: 1.2rem;
padding: 0.9rem;
font-size: 1rem;
background: linear-gradient(135deg, var(--accent), #ff4500);
border: none;
border-radius: 10px;
color: #fff;
font-weight: 700;
cursor: pointer;
transition: all 0.3s;
}
.btn-facturar:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px var(--accent-glow);
}
.btn-facturar:disabled {
opacity: 0.4;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.invoice-notes {
width: 100%;
margin-top: 0.8rem;
padding: 0.5rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-size: 0.85rem;
resize: vertical;
min-height: 60px;
}
.invoice-notes:focus {
outline: none;
border-color: var(--accent);
}
/* --- New customer modal --- */
.modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
width: 450px;
max-width: 95vw;
}
.modal-content h3 {
margin-bottom: 1rem;
font-size: 1.1rem;
}
.modal-field {
margin-bottom: 0.8rem;
}
.modal-field label {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
margin-bottom: 0.2rem;
}
.modal-field input {
width: 100%;
padding: 0.5rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-size: 0.9rem;
}
.modal-field input:focus {
outline: none;
border-color: var(--accent);
}
.modal-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
/* --- Toast (reuse from captura) --- */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
padding: 0.8rem 1.5rem;
border-radius: 10px;
color: #fff;
font-weight: 600;
font-size: 0.9rem;
z-index: 9999;
animation: toastIn 0.3s ease;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}
.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
@keyframes toastIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}

1
dashboard/pos.min.js vendored Normal file

File diff suppressed because one or more lines are too long

262
dashboard/shared.min.css vendored Normal file
View File

@@ -0,0 +1,262 @@
/* ============================================================
shared.css -- Common styles for all Nexus Autoparts pages
============================================================ */
/* --- Reset --- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* --- CSS Variables (union of all pages) --- */
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-card: #1a1a24;
--bg-hover: #252532;
--bg-tertiary: #1a1a25;
--accent: #ff6b35;
--accent-hover: #ff8555;
--accent-glow: rgba(255, 107, 53, 0.3);
--text-primary: #ffffff;
--text-secondary: #a0a0b0;
--border: #2a2a3a;
--success: #22c55e;
--warning: #f59e0b;
--info: #3b82f6;
--danger: #ff4444;
}
/* --- Base body --- */
body {
font-family: 'Inter', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
}
/* --- Shared Button Styles --- */
.btn {
padding: 0.7rem 1.5rem;
border-radius: 10px;
border: none;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9rem;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent) 0%, #ff4500 100%);
color: white;
box-shadow: 0 4px 15px var(--accent-glow);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px var(--accent-glow);
}
.btn-secondary {
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-primary);
}
.btn-secondary:hover {
border-color: var(--accent);
color: var(--accent);
}
.btn-back {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1.2rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-primary);
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 1.5rem;
}
.btn-back:hover {
border-color: var(--accent);
color: var(--accent);
}
/* --- Shared Animations --- */
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* --- Loading & Empty States --- */
.state-container {
text-align: center;
padding: 4rem 2rem;
color: var(--text-secondary);
}
.state-container i {
font-size: 4rem;
margin-bottom: 1rem;
color: var(--text-secondary);
}
.state-container h4 {
color: var(--text-primary);
margin-bottom: 0.5rem;
}
/* --- Scrollbar Styling --- */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
/* --- Skip Link (accessibility) --- */
.skip-link {
position: absolute;
top: -50px;
left: 0;
background: var(--accent);
color: white;
padding: 0.75rem 1.5rem;
z-index: 3000;
text-decoration: none;
font-weight: 600;
border-radius: 0 0 8px 0;
}
.skip-link:focus {
top: 0;
}
/* --- Screen Reader Only --- */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* --- Alert / Toast Styles --- */
.alert {
padding: 1rem 1.5rem;
border-radius: 8px;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.alert-success {
background: rgba(0, 214, 143, 0.1);
border: 1px solid var(--success);
color: var(--success);
}
.alert-error {
background: rgba(255, 68, 68, 0.1);
border: 1px solid var(--danger);
color: var(--danger);
}
/* --- Modal Base Styles --- */
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 2000;
align-items: center;
justify-content: center;
padding: 2rem;
}
.modal-overlay.active {
display: flex;
}
/* --- Form Styles --- */
.form-group {
margin-bottom: 1.25rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
color: var(--text-secondary);
}
.form-input {
width: 100%;
padding: 0.75rem 1rem;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-primary);
font-size: 0.95rem;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: var(--accent);
}
.form-input::placeholder {
color: var(--text-secondary);
}
/* --- Quality Badges --- */
.quality-badge {
display: inline-block;
padding: 0.25rem 0.6rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.quality-economy { background: var(--warning); color: #000; }
.quality-standard { background: var(--info); color: white; }
.quality-premium { background: var(--success); color: white; }
.quality-oem { background: #9b59b6; color: white; }

678
dashboard/tienda.min.css vendored Normal file
View File

@@ -0,0 +1,678 @@
/* ============================================================
tienda.css -- Store / Tablet dashboard styles
Nexus Autoparts — tablet-first, touch-friendly
============================================================ */
/* --- Base overrides for tienda page --- */
body {
margin: 0;
padding: 0;
font-family: 'DM Sans', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
overscroll-behavior: none;
}
/* --- Header --- */
.t-header {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.6rem 1.2rem;
background: rgba(18, 18, 26, 0.92);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border-bottom: 1px solid var(--border);
}
.t-header-left {
display: flex;
align-items: center;
gap: 0.6rem;
flex-shrink: 0;
}
.t-logo-mark {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--accent) 0%, #ff4500 100%);
border-radius: 9px;
box-shadow: 0 3px 14px var(--accent-glow);
display: flex;
align-items: center;
justify-content: center;
}
.t-logo-mark::after {
content: '\2699\FE0F';
font-size: 1.2rem;
}
.t-brand {
display: flex;
flex-direction: column;
line-height: 1.1;
}
.t-brand-name {
font-family: 'Outfit', sans-serif;
font-weight: 800;
font-size: 1.1rem;
background: linear-gradient(135deg, #fff 0%, var(--accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.t-brand-sub {
font-size: 0.55rem;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.15em;
text-transform: uppercase;
}
/* --- Header center: search --- */
.t-header-center {
flex: 1;
max-width: 420px;
margin: 0 1rem;
}
.t-search-box {
position: relative;
display: flex;
align-items: center;
}
.t-search-icon {
position: absolute;
left: 0.7rem;
width: 18px;
height: 18px;
color: var(--text-secondary);
pointer-events: none;
}
.t-search-box input {
width: 100%;
padding: 0.55rem 0.8rem 0.55rem 2.2rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-primary);
font-family: 'DM Sans', sans-serif;
font-size: 0.85rem;
outline: none;
transition: border-color 0.2s;
}
.t-search-box input:focus {
border-color: var(--accent);
}
.t-search-box input::placeholder {
color: var(--text-secondary);
}
.t-search-results {
position: absolute;
top: calc(100% + 4px);
left: 0; right: 0;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
max-height: 300px;
overflow-y: auto;
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
display: none;
z-index: 200;
}
.t-search-results.active {
display: block;
}
.t-search-result-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.6rem 0.8rem;
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background 0.15s;
}
.t-search-result-item:last-child {
border-bottom: none;
}
.t-search-result-item:hover,
.t-search-result-item:active {
background: var(--bg-hover);
}
.t-search-result-item .sri-number {
font-family: 'JetBrains Mono', monospace;
font-weight: 600;
font-size: 0.85rem;
color: var(--accent);
}
.t-search-result-item .sri-name {
font-size: 0.8rem;
color: var(--text-secondary);
margin-left: 0.4rem;
}
/* --- Header right: clock --- */
.t-header-right {
flex-shrink: 0;
}
.t-clock {
font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem;
font-weight: 500;
color: var(--text-secondary);
letter-spacing: 0.03em;
}
/* --- Main --- */
.t-main {
padding: 4.2rem 1rem 1.5rem;
max-width: 1200px;
margin: 0 auto;
}
/* --- KPI Row --- */
.t-kpi-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.8rem;
margin-bottom: 1rem;
}
.t-kpi {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 0.9rem 1rem;
display: flex;
align-items: center;
gap: 0.8rem;
position: relative;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.t-kpi:active {
transform: scale(0.98);
}
/* Colored left accent bar */
.t-kpi::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
border-radius: 3px 0 0 3px;
}
.t-kpi[data-color="accent"]::before { background: var(--accent); }
.t-kpi[data-color="success"]::before { background: var(--success); }
.t-kpi[data-color="info"]::before { background: var(--info); }
.t-kpi[data-color="warning"]::before { background: var(--warning); }
.t-kpi-icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.t-kpi-icon svg {
width: 22px;
height: 22px;
}
.t-kpi[data-color="accent"] .t-kpi-icon { background: rgba(255, 107, 53, 0.12); color: var(--accent); }
.t-kpi[data-color="success"] .t-kpi-icon { background: rgba(34, 197, 94, 0.12); color: var(--success); }
.t-kpi[data-color="info"] .t-kpi-icon { background: rgba(59, 130, 246, 0.12); color: var(--info); }
.t-kpi[data-color="warning"] .t-kpi-icon { background: rgba(245, 158, 11, 0.12); color: var(--warning); }
.t-kpi-data {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.t-kpi-value {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: 1.3rem;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.t-kpi-label {
font-size: 0.72rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 500;
}
.t-kpi-count {
font-size: 0.65rem;
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
white-space: nowrap;
align-self: flex-start;
margin-top: 0.2rem;
}
/* --- Content Grid --- */
.t-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.8rem;
}
/* --- Cards --- */
.t-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1rem;
}
.t-card-full {
min-height: 0;
}
.t-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.8rem;
}
.t-card-title {
font-family: 'DM Sans', sans-serif;
font-size: 0.85rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.8rem;
}
.t-card-header .t-card-title {
margin-bottom: 0;
}
.t-see-all {
font-size: 0.75rem;
color: var(--accent);
text-decoration: none;
font-weight: 600;
padding: 0.3rem 0.6rem;
border-radius: 6px;
transition: background 0.2s;
}
.t-see-all:hover,
.t-see-all:active {
background: rgba(255, 107, 53, 0.1);
}
/* --- Quick Actions Grid --- */
.t-actions-card {
padding-bottom: 0.8rem;
}
.t-actions-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.6rem;
}
.t-action {
display: flex;
align-items: center;
gap: 0.7rem;
padding: 0.8rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 10px;
text-decoration: none;
color: var(--text-primary);
font-size: 0.85rem;
font-weight: 600;
transition: transform 0.15s, background 0.2s, border-color 0.2s;
-webkit-tap-highlight-color: transparent;
}
.t-action:active {
transform: scale(0.96);
}
.t-action:hover {
background: var(--bg-hover);
}
.t-action-icon {
width: 36px;
height: 36px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.t-action-icon svg {
width: 20px;
height: 20px;
}
.t-action[data-color="accent"] .t-action-icon { background: rgba(255, 107, 53, 0.12); color: var(--accent); }
.t-action[data-color="accent"]:hover { border-color: var(--accent); }
.t-action[data-color="info"] .t-action-icon { background: rgba(59, 130, 246, 0.12); color: var(--info); }
.t-action[data-color="info"]:hover { border-color: var(--info); }
.t-action[data-color="success"] .t-action-icon { background: rgba(34, 197, 94, 0.12); color: var(--success); }
.t-action[data-color="success"]:hover { border-color: var(--success); }
.t-action[data-color="warning"] .t-action-icon { background: rgba(245, 158, 11, 0.12); color: var(--warning); }
.t-action[data-color="warning"]:hover { border-color: var(--warning); }
/* --- Debtors List --- */
.t-debtors-list {
display: flex;
flex-direction: column;
gap: 0.4rem;
max-height: 280px;
overflow-y: auto;
}
.t-debtor {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.6rem 0.7rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
.t-debtor:hover,
.t-debtor:active {
background: var(--bg-hover);
border-color: var(--danger);
}
.t-debtor-name {
font-weight: 600;
font-size: 0.85rem;
}
.t-debtor-invoices {
font-size: 0.7rem;
color: var(--text-secondary);
}
.t-debtor-amount {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
font-size: 0.9rem;
color: var(--danger);
}
/* --- Invoice List --- */
.t-invoice-list {
display: flex;
flex-direction: column;
gap: 0.4rem;
max-height: 320px;
overflow-y: auto;
}
.t-invoice {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.6rem 0.7rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
transition: background 0.15s;
}
.t-invoice:hover,
.t-invoice:active {
background: var(--bg-hover);
}
.t-invoice-left {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.t-invoice-folio {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
font-size: 0.85rem;
color: var(--accent);
}
.t-invoice-customer {
font-size: 0.75rem;
color: var(--text-secondary);
}
.t-invoice-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.15rem;
}
.t-invoice-total {
font-family: 'JetBrains Mono', monospace;
font-weight: 600;
font-size: 0.85rem;
}
.t-invoice-status {
font-size: 0.65rem;
font-weight: 600;
padding: 0.15rem 0.45rem;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.t-invoice-status.paid {
background: rgba(34, 197, 94, 0.15);
color: var(--success);
}
.t-invoice-status.pending {
background: rgba(245, 158, 11, 0.15);
color: var(--warning);
}
.t-invoice-status.partial {
background: rgba(59, 130, 246, 0.15);
color: var(--info);
}
.t-invoice-status.cancelled {
background: rgba(255, 68, 68, 0.15);
color: var(--danger);
}
/* --- Today's Payments card --- */
.t-today-payments {
text-align: center;
padding: 0.5rem 0;
}
.t-today-amount {
font-family: 'Outfit', sans-serif;
font-weight: 800;
font-size: 2rem;
color: var(--success);
line-height: 1.2;
}
.t-today-count {
font-size: 0.8rem;
color: var(--text-secondary);
margin-top: 0.3rem;
}
/* --- Empty state --- */
.t-empty {
text-align: center;
padding: 1.5rem;
color: var(--text-secondary);
font-size: 0.85rem;
}
/* --- Scrollbar (minimal for touch) --- */
.t-debtors-list::-webkit-scrollbar,
.t-invoice-list::-webkit-scrollbar,
.t-search-results::-webkit-scrollbar {
width: 4px;
}
.t-debtors-list::-webkit-scrollbar-track,
.t-invoice-list::-webkit-scrollbar-track,
.t-search-results::-webkit-scrollbar-track {
background: transparent;
}
.t-debtors-list::-webkit-scrollbar-thumb,
.t-invoice-list::-webkit-scrollbar-thumb,
.t-search-results::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 2px;
}
/* --- Responsive --- */
/* Tablet landscape (default target) */
@media (max-width: 1024px) {
.t-main {
padding: 4rem 0.8rem 1.2rem;
}
.t-kpi-row {
grid-template-columns: repeat(2, 1fr);
}
}
/* Tablet portrait / large phone */
@media (max-width: 768px) {
.t-header-center {
display: none;
}
.t-main {
padding: 3.8rem 0.6rem 1rem;
}
.t-content {
grid-template-columns: 1fr;
}
.t-kpi-row {
grid-template-columns: repeat(2, 1fr);
gap: 0.6rem;
}
.t-kpi {
padding: 0.7rem 0.8rem;
}
.t-kpi-value {
font-size: 1.1rem;
}
.t-kpi-count {
display: none;
}
.t-actions-grid {
grid-template-columns: 1fr 1fr;
}
}
/* Small phone */
@media (max-width: 480px) {
.t-kpi-row {
grid-template-columns: 1fr 1fr;
}
.t-kpi-icon {
width: 32px;
height: 32px;
}
.t-kpi-icon svg {
width: 18px;
height: 18px;
}
.t-kpi-value {
font-size: 1rem;
}
.t-actions-grid {
grid-template-columns: 1fr;
}
}
/* --- Fade-in animation for cards --- */
@keyframes t-fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.t-kpi {
animation: t-fadeIn 0.4s ease both;
}
.t-kpi:nth-child(1) { animation-delay: 0.05s; }
.t-kpi:nth-child(2) { animation-delay: 0.1s; }
.t-kpi:nth-child(3) { animation-delay: 0.15s; }
.t-kpi:nth-child(4) { animation-delay: 0.2s; }
.t-card {
animation: t-fadeIn 0.4s ease both;
animation-delay: 0.25s;
}
.t-content .t-col:nth-child(2) .t-card {
animation-delay: 0.3s;
}
.t-content .t-col:nth-child(2) .t-card:nth-child(2) {
animation-delay: 0.35s;
}

1
dashboard/tienda.min.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(){"use strict";function t(t){return"$"+(parseFloat(t)||0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")}function e(t){if(!t)return"";var e=document.createElement("div");return e.textContent=t,e.innerHTML}function n(){var t=new Date,e=t.getHours(),n=String(t.getMinutes()).padStart(2,"0"),i=e>=12?"PM":"AM";e=e%12||12,document.getElementById("clock").textContent=e+":"+n+" "+i}function i(){fetch("/api/tienda/stats").then((function(t){return t.json()})).then((function(n){var i=n.sales_today||{},a=n.sales_month||{},o=n.payments_today||{};document.getElementById("kpi-sales-today").textContent=t(i.total),document.getElementById("kpi-sales-count").textContent=(i.count||0)+" facturas",document.getElementById("kpi-month").textContent=t(a.total),document.getElementById("kpi-month-count").textContent=(a.count||0)+" facturas",document.getElementById("kpi-customers").textContent=n.total_customers||0,document.getElementById("kpi-parts-count").textContent=(n.total_parts||0)+" partes",document.getElementById("kpi-pending").textContent=t(n.pending_balance||0),document.getElementById("kpi-pending-count").textContent=(n.pending_invoices||0)+" facturas",document.getElementById("payments-today-amount").textContent=t(o.total),document.getElementById("payments-today-count").textContent=(o.count||0)+" pagos registrados",function(n){var i=document.getElementById("debtors-list");if(0===n.length)return void(i.innerHTML='<div class="t-empty">Sin cuentas pendientes</div>');i.innerHTML=n.map((function(n){var i=n.credit_limit>0?Math.round(n.balance/n.credit_limit*100):0;return'<a href="/cuentas" class="t-debtor"><div><div class="t-debtor-name">'+e(n.name)+"</div>"+(n.credit_limit>0?'<div class="t-debtor-invoices">'+i+"% de límite</div>":"")+'</div><span class="t-debtor-amount">'+t(n.balance)+"</span></a>"})).join("")}(n.top_debtors||[]),function(n){var i=document.getElementById("recent-invoices");if(0===n.length)return void(i.innerHTML='<div class="t-empty">Sin facturas recientes</div>');i.innerHTML=n.map((function(n){var i=n.status||"pending",a={pending:"Pendiente",paid:"Pagada",partial:"Parcial",cancelled:"Cancelada"};return'<div class="t-invoice"><div class="t-invoice-left"><span class="t-invoice-folio">'+e(n.folio)+'</span><span class="t-invoice-customer">'+e(n.customer_name)+'</span></div><div class="t-invoice-right"><span class="t-invoice-total">'+t(n.total)+'</span><span class="t-invoice-status '+i+'">'+(a[i]||i)+"</span></div></div>"})).join("")}(n.recent_invoices||[])})).catch((function(t){console.error("Error loading stats:",t)}))}n(),setInterval(n,3e4);var a=null,o=document.getElementById("global-search"),s=document.getElementById("global-results");o&&(o.addEventListener("input",(function(){clearTimeout(a);var t=this.value.trim();if(t.length<2)return s.classList.remove("active"),void(s.innerHTML="");a=setTimeout((function(){fetch("/api/pos/search-parts?q="+encodeURIComponent(t)).then((function(t){return t.json()})).then((function(n){0===n.length?s.innerHTML='<div style="padding:0.8rem;color:var(--text-secondary);font-size:0.85rem">Sin resultados para "'+e(t)+'"</div>':s.innerHTML=n.slice(0,8).map((function(t){return'<div class="t-search-result-item"><div><span class="sri-number">'+e(t.oem_part_number)+'</span><span class="sri-name">'+e(t.name_part)+"</span></div></div>"})).join(""),s.classList.add("active")}))}),250)})),o.addEventListener("blur",(function(){setTimeout((function(){s.classList.remove("active")}),200)})),o.addEventListener("focus",(function(){s.innerHTML.trim()&&s.classList.add("active")}))),i(),setInterval(i,12e4)}();