Merge branch 'main' into desarrollo_hector
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/accounting.css">
|
||||
</head>
|
||||
@@ -188,10 +190,10 @@
|
||||
<!-- Tabs -->
|
||||
<div class="tabs-row" role="tablist">
|
||||
<button class="tab-btn is-active" role="tab" aria-selected="true" onclick="switchTab('cxc')">
|
||||
Ctas. por Cobrar <span class="tab-btn__badge">23</span>
|
||||
Ctas. por Cobrar <span class="tab-btn__badge" id="badge-cxc">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" onclick="switchTab('cxp')">
|
||||
Ctas. por Pagar <span class="tab-btn__badge--alert tab-btn__badge">3</span>
|
||||
Ctas. por Pagar <span class="tab-btn__badge--alert tab-btn__badge" id="badge-cxp">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" onclick="switchTab('balance')">
|
||||
Balance General
|
||||
@@ -490,7 +492,8 @@
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/accounting.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
@@ -498,5 +501,31 @@
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
<script src="/pos/static/js/chat.js" defer></script>
|
||||
|
||||
<script>
|
||||
// Load accounting stats for tab badges
|
||||
async function loadAccountingStats() {
|
||||
const token = localStorage.getItem('pos_token') || '';
|
||||
try {
|
||||
const res = await fetch('/pos/api/accounting/stats', {
|
||||
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const map = {
|
||||
'badge-cxc': data.cuentas_cobrar,
|
||||
'badge-cxp': data.cuentas_pagar
|
||||
};
|
||||
Object.entries(map).forEach(function([id, val]) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = val || 0;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to load accounting stats:', e);
|
||||
}
|
||||
}
|
||||
window.loadAccountingStats = loadAccountingStats;
|
||||
loadAccountingStats();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -7,12 +7,14 @@
|
||||
<title>Catalogo — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
<script src="/pos/static/js/native-bridge.js" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/catalog.css">
|
||||
@@ -66,6 +68,10 @@
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 7h8M8 12h8M8 17h5"/></svg>
|
||||
Catalogo
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/supplier-catalog" role="menuitem">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M2 3h6l2 3h12v14H2V3z"/></svg>
|
||||
Cat. Proveedores
|
||||
</a>
|
||||
<div class="nav-section-label" style="margin-top: var(--space-2);">Gestion</div>
|
||||
<a class="nav-item" href="/pos/customers" role="menuitem">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
|
||||
@@ -107,6 +113,9 @@
|
||||
<span class="breadcrumb__current">Catalogo</span>
|
||||
</nav>
|
||||
<div class="header-actions" style="position:relative;">
|
||||
<button class="btn btn--sm" id="uploadPricesBtn" onclick="CatalogApp.openUploadPricesModal()" title="Subir precios de proveedor" style="margin-right:var(--space-2);display:none;">
|
||||
💰 Precios proveedor
|
||||
</button>
|
||||
<div class="mode-toggle" id="modeToggle" title="Cambiar entre catalogo OEM (TecDoc), marcas locales, por marca de vehiculo y consumibles">
|
||||
<button data-mode="oem" onclick="CatalogApp.setMode('oem')" disabled style="opacity:0.5;cursor:not-allowed;" title="Próximamente">OEM 🔒</button>
|
||||
<button data-mode="local" onclick="CatalogApp.setMode('local')">Local</button>
|
||||
@@ -269,6 +278,29 @@
|
||||
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Upload Supplier Prices Modal -->
|
||||
<div id="uploadPricesModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;z-index:9500;background:rgba(0,0,0,0.6);align-items:center;justify-content:center;padding:var(--space-4);">
|
||||
<div style="background:var(--color-surface-1);border:1px solid var(--color-border);border-radius:var(--radius-md);max-width:520px;width:100%;max-height:90vh;overflow:auto;padding:var(--space-5);">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4);">
|
||||
<h2 style="margin:0;font-family:var(--font-heading);font-size:var(--text-h4);">Subir precios de proveedor</h2>
|
||||
<button onclick="CatalogApp.closeUploadPricesModal()" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--color-text-secondary);">✕</button>
|
||||
</div>
|
||||
<p style="color:var(--color-text-secondary);font-size:var(--text-body-sm);margin-bottom:var(--space-3);">
|
||||
Sube un CSV o Excel con las columnas: <code>supplier_name, sku, price, currency, effective_from</code>.<br>
|
||||
El precio se mostrará en el catálogo junto a cada parte del proveedor.
|
||||
</p>
|
||||
<div style="margin-bottom:var(--space-3);">
|
||||
<label style="display:block;font-size:var(--text-caption);color:var(--color-text-muted);margin-bottom:4px;">Archivo CSV / Excel</label>
|
||||
<input type="file" id="uploadPricesFile" accept=".csv,.xlsx,.xls" style="width:100%;" />
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-2);justify-content:flex-end;">
|
||||
<a href="/pos/api/supplier-catalog/prices/template" class="btn btn--ghost" style="text-decoration:none;">Descargar plantilla</a>
|
||||
<button class="btn btn-primary" onclick="CatalogApp.submitUploadPrices()">Subir precios</button>
|
||||
</div>
|
||||
<div id="uploadPricesStatus" style="margin-top:var(--space-3);font-size:var(--text-body-sm);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Brand Catalog Overlay (full-screen overlay for brand-first browsing) -->
|
||||
<div id="brandCatalogOverlay" style="display:none;position:relative;top:0;left:0;right:0;bottom:0;background:var(--color-bg-base);overflow:auto;padding:var(--space-4);">
|
||||
<div style="max-width:1200px;margin:0 auto;">
|
||||
@@ -286,9 +318,10 @@
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/kiosk.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/catalog.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/catalog.js?v=6" defer></script>
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
<script src="/pos/static/js/chat.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/config.css?v=2">
|
||||
</head>
|
||||
@@ -249,7 +251,68 @@
|
||||
</div>
|
||||
|
||||
<!-- ===============================================================
|
||||
SECTION 3: USUARIOS Y PERMISOS
|
||||
SECTION 3: MÓDULOS E INTEGRACIONES
|
||||
=============================================================== -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-section__header">
|
||||
<div class="settings-section__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="settings-section__title">Módulos e Integraciones</div>
|
||||
<div class="settings-section__desc">Activa o desactiva funcionalidades del menú para este tenant</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="toggle-row">
|
||||
<div class="toggle-row__info">
|
||||
<span class="toggle-row__label">WhatsApp</span>
|
||||
<span class="toggle-row__desc">Mostrar el menú de WhatsApp Bridge y chat</span>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="cfg-module-whatsapp" checked />
|
||||
<span class="toggle__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="toggle-row">
|
||||
<div class="toggle-row__info">
|
||||
<span class="toggle-row__label">Marketplace</span>
|
||||
<span class="toggle-row__desc">Mostrar el menú de Marketplace interno</span>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="cfg-module-marketplace" checked />
|
||||
<span class="toggle__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="toggle-row">
|
||||
<div class="toggle-row__info">
|
||||
<span class="toggle-row__label">MercadoLibre</span>
|
||||
<span class="toggle-row__desc">Mostrar el menú de integración con MercadoLibre</span>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="cfg-module-meli" checked />
|
||||
<span class="toggle__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="toggle-row">
|
||||
<div class="toggle-row__info">
|
||||
<span class="toggle-row__label">Catálogo</span>
|
||||
<span class="toggle-row__desc">Mostrar el menú de Catálogo de productos</span>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="cfg-module-catalog" checked />
|
||||
<span class="toggle__slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="margin-top:var(--space-4);text-align:right;">
|
||||
<button class="btn btn--primary" onclick="Config.saveModules()">Guardar módulos</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===============================================================
|
||||
SECTION 4: USUARIOS Y PERMISOS
|
||||
=============================================================== -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-section__header">
|
||||
@@ -655,19 +718,56 @@
|
||||
MODALS
|
||||
===================================================================== -->
|
||||
|
||||
<!-- Modal: Nueva Sucursal -->
|
||||
<!-- Modal: Nueva / Editar Sucursal -->
|
||||
<div class="cfg-modal-overlay" id="modal-branch" style="display:none;">
|
||||
<div class="cfg-modal">
|
||||
<div class="cfg-modal" style="max-width:640px;">
|
||||
<div class="cfg-modal__header">
|
||||
<h3 class="cfg-modal__title">Nueva Sucursal</h3>
|
||||
<h3 class="cfg-modal__title" id="branch-modal-title">Nueva Sucursal</h3>
|
||||
<button class="cfg-modal__close" onclick="Config.closeModal('modal-branch')">×</button>
|
||||
</div>
|
||||
<div class="cfg-modal__body">
|
||||
<input type="hidden" id="branch-id" value="" />
|
||||
<div class="form-grid">
|
||||
<div class="form-group form-group--full">
|
||||
<label class="form-label">Nombre</label>
|
||||
<label class="form-label">Nombre *</label>
|
||||
<input class="form-input" id="branch-name" type="text" placeholder="Ej. Sucursal Norte" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">RFC</label>
|
||||
<input class="form-input" id="branch-rfc" type="text" placeholder="ABC010101ABC" maxlength="13" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Razon Social</label>
|
||||
<input class="form-input" id="branch-razon" type="text" placeholder="Razon social fiscal" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Regimen Fiscal</label>
|
||||
<input class="form-input" id="branch-regimen" type="text" placeholder="601" maxlength="10" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Codigo Postal</label>
|
||||
<input class="form-input" id="branch-cp" type="text" placeholder="00000" maxlength="5" />
|
||||
</div>
|
||||
<div class="form-group form-group--full">
|
||||
<label class="form-label">Direccion Fiscal</label>
|
||||
<input class="form-input" id="branch-direccion-fiscal" type="text" placeholder="Calle, Colonia, Ciudad" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Serie CFDI</label>
|
||||
<input class="form-input" id="branch-serie" type="text" placeholder="A" maxlength="10" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Folio Inicio</label>
|
||||
<input class="form-input" id="branch-folio-inicio" type="number" placeholder="1" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Folio Actual</label>
|
||||
<input class="form-input" id="branch-folio-actual" type="number" placeholder="1" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email</label>
|
||||
<input class="form-input" id="branch-email" type="email" placeholder="sucursal@empresa.com" />
|
||||
</div>
|
||||
<div class="form-group form-group--full">
|
||||
<label class="form-label">Direccion</label>
|
||||
<input class="form-input" id="branch-address" type="text" placeholder="Calle, Colonia, Ciudad" />
|
||||
@@ -676,6 +776,12 @@
|
||||
<label class="form-label">Telefono</label>
|
||||
<input class="form-input" id="branch-phone" type="tel" placeholder="(55) 1234-5678" />
|
||||
</div>
|
||||
<div class="form-group form-group--full">
|
||||
<label class="form-check" style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
||||
<input type="checkbox" id="branch-main" style="width:auto;" />
|
||||
<span>Sucursal principal (datos fiscales por defecto)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-modal__footer">
|
||||
@@ -741,10 +847,11 @@
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/kiosk.js" defer></script>
|
||||
<script src="/pos/static/js/config.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/config.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/customers.css">
|
||||
</head>
|
||||
@@ -304,9 +306,11 @@
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-wrap themed-scrollbar">
|
||||
<div id="customersBulkToolbar"></div>
|
||||
<table class="customers-table" id="customersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAllCustomers" onclick="Customers.toggleSelectAll()"></th>
|
||||
<th>#</th>
|
||||
<th>Nombre</th>
|
||||
<th class="hide-mobile">RFC</th>
|
||||
@@ -380,10 +384,18 @@
|
||||
<div class="detail-section">
|
||||
<div class="detail-section__title">Información de Contacto</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-row info-row--full">
|
||||
<span class="info-label">Razón Social</span>
|
||||
<span class="info-value" id="detailRazonSocial">—</span>
|
||||
</div>
|
||||
<div class="info-row info-row--full">
|
||||
<span class="info-label">Dirección</span>
|
||||
<span class="info-value" id="detailAddress">Av. Insurgentes Sur 1602, Col. Crédito Constructor, CDMX</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">CP</span>
|
||||
<span class="info-value info-value--mono" id="detailCp">—</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Teléfono</span>
|
||||
<span class="info-value info-value--mono" id="detailPhone">+52 55 1234-5678</span>
|
||||
@@ -420,6 +432,10 @@
|
||||
<div class="credit-metric__label">Utilizado</div>
|
||||
<div class="credit-metric__value used" id="detailCreditUsed">$31,500</div>
|
||||
</div>
|
||||
<div class="credit-metric">
|
||||
<div class="credit-metric__label">Descuento Max</div>
|
||||
<div class="credit-metric__value" id="detailMaxDiscount">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="credit-progress">
|
||||
<div class="credit-progress__labels">
|
||||
@@ -611,16 +627,17 @@
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button class="btn-edit" onclick="if(typeof Customers!=='undefined') Customers.editCurrent();">Editar Cliente</button>
|
||||
<button class="btn-sale" onclick="window.location.href='/pos/';">Nueva Venta</button>
|
||||
<button class="btn-sale" onclick="window.location.href='/pos/sale';">Nueva Venta</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/virtual-scroll.js" defer></script>
|
||||
<script src="/pos/static/js/customers.js" defer></script>
|
||||
<script src="/pos/static/js/customers.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,12 +8,14 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/dashboard.css">
|
||||
<link rel="stylesheet" href="/pos/static/css/dashboard.css?v=3">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -254,9 +256,9 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="kpi-card__value" id="kpi-ventas-total">--</div>
|
||||
<div class="kpi-card__value" id="kpi-ventas-total"><div class="skeleton skeleton--text" style="width:60%;"></div></div>
|
||||
<div class="kpi-card__meta" id="kpi-ventas-meta">
|
||||
<span class="kpi-tag kpi-tag--neutral">Cargando...</span>
|
||||
<div class="skeleton skeleton--text-sm" style="width:80%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -272,9 +274,9 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="kpi-card__value" id="kpi-tickets-count">--</div>
|
||||
<div class="kpi-card__value" id="kpi-tickets-count"><div class="skeleton skeleton--text" style="width:40%;"></div></div>
|
||||
<div class="kpi-card__meta" id="kpi-tickets-meta">
|
||||
<span class="kpi-tag kpi-tag--neutral">Cargando...</span>
|
||||
<div class="skeleton skeleton--text-sm" style="width:70%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -290,9 +292,9 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="kpi-card__value" id="kpi-promedio-value">--</div>
|
||||
<div class="kpi-card__value" id="kpi-promedio-value"><div class="skeleton skeleton--text" style="width:60%;"></div></div>
|
||||
<div class="kpi-card__meta" id="kpi-promedio-meta">
|
||||
<span class="kpi-tag kpi-tag--neutral">Cargando...</span>
|
||||
<div class="skeleton skeleton--text-sm" style="width:75%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,9 +309,9 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="kpi-card__value" id="kpi-cajas-count">--</div>
|
||||
<div class="kpi-card__value" id="kpi-cajas-count"><div class="skeleton skeleton--text" style="width:30%;"></div></div>
|
||||
<div class="kpi-card__meta" id="kpi-cajas-meta">
|
||||
<span class="kpi-tag kpi-tag--neutral">Cargando...</span>
|
||||
<div class="skeleton skeleton--text-sm" style="width:80%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -366,7 +368,7 @@
|
||||
Ventas por Hora
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="hourlySalesChart" height="180"></canvas>
|
||||
<div class="chart-canvas-wrap"><canvas id="hourlySalesChart"></canvas></div>
|
||||
</div>
|
||||
<div class="rank-card">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4);">
|
||||
@@ -374,7 +376,7 @@
|
||||
Top Productos (Hoy)
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="topProductsChart" height="180"></canvas>
|
||||
<div class="chart-canvas-wrap"><canvas id="topProductsChart"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -398,7 +400,9 @@
|
||||
<a href="/pos/inventory" style="font-size:var(--text-caption);color:var(--color-primary);font-weight:var(--font-weight-semibold);cursor:pointer;text-decoration:none;">Ver todos</a>
|
||||
</div>
|
||||
<div class="rank-list" id="top-products-list">
|
||||
<div class="rank-item" style="justify-content:center;color:var(--color-text-muted);font-size:var(--text-caption);">Cargando...</div>
|
||||
<div class="rank-item"><div class="skeleton skeleton--text" style="width:90%;"></div></div>
|
||||
<div class="rank-item"><div class="skeleton skeleton--text" style="width:70%;"></div></div>
|
||||
<div class="rank-item"><div class="skeleton skeleton--text" style="width:80%;"></div></div>
|
||||
</div>
|
||||
</div><!-- end Top Productos -->
|
||||
|
||||
@@ -411,7 +415,8 @@
|
||||
<a href="/pos/config" style="font-size:var(--text-caption);color:var(--color-primary);font-weight:var(--font-weight-semibold);cursor:pointer;text-decoration:none;">Gestionar</a>
|
||||
</div>
|
||||
<div class="rank-list" id="registers-list">
|
||||
<div class="rank-item" style="justify-content:center;color:var(--color-text-muted);font-size:var(--text-caption);">Cargando...</div>
|
||||
<div class="rank-item"><div class="skeleton skeleton--text" style="width:85%;"></div></div>
|
||||
<div class="rank-item"><div class="skeleton skeleton--text" style="width:65%;"></div></div>
|
||||
</div>
|
||||
</div><!-- end Cajas del Día -->
|
||||
|
||||
@@ -428,7 +433,8 @@
|
||||
<span class="section-action">Gestionar todo →</span>
|
||||
</div>
|
||||
<div class="alerts-grid" id="alerts-grid">
|
||||
<div class="alert-item" style="justify-content:center;color:var(--color-text-muted);font-size:var(--text-caption);border-left:none;">Cargando alertas...</div>
|
||||
<div class="alert-item" style="border-left:none;"><div class="skeleton skeleton--text" style="width:100%;"></div></div>
|
||||
<div class="alert-item" style="border-left:none;"><div class="skeleton skeleton--text" style="width:80%;"></div></div>
|
||||
</div><!-- end alerts-grid -->
|
||||
</section>
|
||||
|
||||
@@ -466,7 +472,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="recent-sales-tbody">
|
||||
<tr><td colspan="5" style="text-align:center;color:var(--color-text-muted);font-size:var(--text-caption);">Cargando ventas recientes...</td></tr>
|
||||
<tr><td colspan="5"><div class="skeleton skeleton--text" style="width:100%;"></div></td></tr>
|
||||
<tr><td colspan="5"><div class="skeleton skeleton--text" style="width:80%;"></div></td></tr>
|
||||
<tr><td colspan="5"><div class="skeleton skeleton--text" style="width:90%;"></div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -483,10 +491,11 @@
|
||||
<script src="/pos/static/js/chart.umd.min.js" defer></script>
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/dashboard-stats.js" defer></script>
|
||||
<script src="/pos/static/js/dashboard.js" defer></script>
|
||||
<script src="/pos/static/js/dashboard-stats.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/dashboard.js?v=3" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,11 +8,13 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
<script src="/pos/static/js/native-bridge.js" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/diagrams.css">
|
||||
@@ -150,7 +152,8 @@
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/kiosk.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/diagrams.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/fleet.css">
|
||||
</head>
|
||||
@@ -303,7 +305,8 @@
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/fleet.js" defer></script>
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,12 +8,14 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/inventory.css">
|
||||
<link rel="stylesheet" href="/pos/static/css/inventory.css?v=8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -32,6 +34,16 @@
|
||||
<span class="theme-bar__label">Sucursal Centro — Usuario: H. García</span>
|
||||
</div>
|
||||
<div class="theme-bar__right">
|
||||
<button class="icon-btn" id="notifDropdownBtn" title="Notificaciones" onclick="NotificationsDropdown.toggle()" style="margin-right:8px;">
|
||||
<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="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
<span class="notif-dot" id="headerNotifDot"></span>
|
||||
</button>
|
||||
<button class="icon-btn" title="Modo táctil" onclick="TouchModeToggle.set(document.documentElement.getAttribute('data-touch') !== 'true')" style="margin-right:4px;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
||||
</button>
|
||||
<button class="icon-btn" title="Densidad" onclick="DensityToggle.set(document.documentElement.getAttribute('data-density') === 'compact' ? 'normal' : 'compact')" style="margin-right:8px;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
|
||||
</button>
|
||||
<span class="theme-bar__label">Tema:</span>
|
||||
<button class="theme-btn theme-btn--industrial is-active" data-theme-target="industrial" onclick="setTheme('industrial')">
|
||||
<span class="theme-btn__swatch"></span>
|
||||
@@ -171,6 +183,10 @@
|
||||
<h1 class="page-header__title">Inventario</h1>
|
||||
</div>
|
||||
<div class="page-header__actions">
|
||||
<button class="btn btn--ghost" onclick="document.getElementById('bulkImportModal').classList.add('is-open')">
|
||||
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
Importar CSV
|
||||
</button>
|
||||
<button class="btn btn--ghost" onclick="exportVisibleTableCSV('inventario')">
|
||||
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
Exportar CSV
|
||||
@@ -268,6 +284,7 @@
|
||||
=================================================================== -->
|
||||
<div class="tab-panel is-active" id="panel-stock" role="tabpanel">
|
||||
|
||||
<div id="savedFiltersContainer" style="margin-bottom:var(--space-2);"></div>
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
@@ -306,10 +323,18 @@
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
<button class="btn btn--ghost btn--sm" onclick="showTierDiscountModal()">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="6" x2="12" y2="12"/><line x1="16.24" y1="16.24" x2="12" y2="12"/></svg>
|
||||
<span id="tierDiscountBadge">Taller -15% · Mayoreo -25%</span>
|
||||
</button>
|
||||
<button class="btn btn--primary btn--sm" onclick="showCreateModal()">
|
||||
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
Nuevo Producto
|
||||
</button>
|
||||
<button class="btn btn--sm btn--meli" id="btnPublishML" style="display:none;" onclick="openMeliPublishModal()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
||||
Publicar en ML <span id="meliSelectedCountBadge" style="background:#2D3277;color:#FFE600;border-radius:10px;padding:0 6px;font-size:11px;margin-left:4px;">0</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
@@ -317,6 +342,7 @@
|
||||
<table class="data-table" id="stockTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:32px;"><input type="checkbox" id="selectAllItems" onclick="toggleSelectAllItems()" title="Seleccionar todos" /></th>
|
||||
<th style="font-size:var(--text-caption);color:var(--color-text-muted);">ID</th>
|
||||
<th>Barcode</th>
|
||||
<th>No. Parte</th>
|
||||
@@ -324,9 +350,9 @@
|
||||
<th>Marca</th>
|
||||
<th style="text-align:right">Stock</th>
|
||||
<th style="text-align:right">Costo</th>
|
||||
<th style="text-align:right">Precio 1</th>
|
||||
<th style="text-align:right">Precio 2</th>
|
||||
<th style="text-align:right">Precio 3</th>
|
||||
<th style="text-align:right">Mostrador</th>
|
||||
<th style="text-align:right">Taller</th>
|
||||
<th style="text-align:right">Mayoreo</th>
|
||||
<th>Ubicación</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
@@ -686,12 +712,22 @@
|
||||
<div class="inv-form-grid">
|
||||
<div class="inv-field"><label>No. Parte *</label><input type="text" id="newPartNumber" placeholder="Ej: GAT-50104" /></div>
|
||||
<div class="inv-field"><label>Nombre *</label><input type="text" id="newName" placeholder="Nombre del producto" /></div>
|
||||
<div class="inv-field"><label>Marca</label><input type="text" id="newBrand" placeholder="Marca" /></div>
|
||||
<div class="inv-field"><label>Marca</label><input type="text" id="newBrand" placeholder="Marca del fabricante" /></div>
|
||||
<div class="inv-field"><label>Categoría</label>
|
||||
<select class="select-filter" id="newCategory" onchange="onCategoryChange(this.value)" style="width:100%;">
|
||||
<option value="">Selecciona categoría</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="inv-field"><label>Subcategoría</label>
|
||||
<select class="select-filter" id="newSubcategory" style="width:100%;" disabled>
|
||||
<option value="">Selecciona categoría primero</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="inv-field"><label>Barcode</label><input type="text" id="newBarcode" placeholder="Auto-generado si vacío" /></div>
|
||||
<div class="inv-field"><label>SKU Alternativo 1</label><input type="text" id="newSku2" placeholder="Ej: SKU-Bodega-A" /></div>
|
||||
<div class="inv-field"><label>SKU Alternativo 2</label><input type="text" id="newSku3" placeholder="Ej: SKU-Bodega-B" /></div>
|
||||
<div class="inv-field"><label>Costo</label><input type="number" id="newCost" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Precio 1</label><input type="number" id="newPrice1" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Precio 2</label><input type="number" id="newPrice2" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Precio 3</label><input type="number" id="newPrice3" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Precio Mostrador</label><input type="number" id="newPrice1" step="0.01" placeholder="0.00" /></div>
|
||||
<div class="inv-field"><label>Stock Mínimo</label><input type="number" id="newMinStock" placeholder="0" /></div>
|
||||
<div class="inv-field"><label>Stock Inicial</label><input type="number" id="newInitialStock" placeholder="0" /></div>
|
||||
<div class="inv-field"><label>Ubicación</label><input type="text" id="newLocation" placeholder="Ej: A-12-3" /></div>
|
||||
@@ -809,6 +845,191 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tier Discounts Modal -->
|
||||
<div class="inv-modal-overlay" id="tierDiscountModal">
|
||||
<div class="inv-modal">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Descuentos por Tipo de Cliente</h3>
|
||||
<button class="inv-modal__close" onclick="closeTierDiscountModal()">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body">
|
||||
<p style="font-size:var(--text-caption);color:var(--color-text-muted);margin-bottom:var(--space-4);">Estos descuentos se aplican automáticamente a todos los productos al calcular precios de Taller y Mayoreo.</p>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div class="inv-field"><label>Descuento Taller (%)</label><input type="number" id="tierDisc2" step="0.1" min="0" max="100" placeholder="15" /></div>
|
||||
<div class="inv-field"><label>Descuento Mayoreo (%)</label><input type="number" id="tierDisc3" step="0.1" min="0" max="100" placeholder="25" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inv-modal__footer">
|
||||
<button class="btn btn--ghost" onclick="closeTierDiscountModal()">Cancelar</button>
|
||||
<button class="btn btn--primary" onclick="saveTierDiscounts()">Guardar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ Publicar en MercadoLibre Modal ══════════ -->
|
||||
<div class="inv-modal-overlay" id="meliPublishModal">
|
||||
<div class="inv-modal inv-modal--wide">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Publicar en MercadoLibre</h3>
|
||||
<button class="inv-modal__close" onclick="closeMeliPublishModal()">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body">
|
||||
<div id="meliPublishSelectedCount" style="font-size:var(--text-caption);color:var(--color-text-muted);margin-bottom:var(--space-3);">0 productos seleccionados</div>
|
||||
|
||||
<!-- Pre-flight checks & editable preview -->
|
||||
<div id="meliPublishItemsPreview" style="max-height:320px;overflow-y:auto;margin-bottom:var(--space-4);">
|
||||
<p style="color:var(--color-text-muted);font-size:var(--text-caption);">Cargando preview...</p>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:var(--space-4);">
|
||||
<div class="inv-field">
|
||||
<label>Categoría ML *</label>
|
||||
<div style="position:relative;">
|
||||
<input type="text" id="meliCategorySearch" placeholder="Buscar categoría..." oninput="searchMeliCategories()" onkeydown="handleMeliCatKeydown(event)" autocomplete="off" />
|
||||
<div id="meliCategoryResults"></div>
|
||||
</div>
|
||||
<input type="hidden" id="meliCategoryId" />
|
||||
</div>
|
||||
<div class="inv-field">
|
||||
<label>Tipo de Publicación</label>
|
||||
<select id="meliListingType">
|
||||
<option value="gold_special">Gold Special</option>
|
||||
<option value="gold_pro">Gold Pro</option>
|
||||
<option value="bronze">Bronce (gratis)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="inv-field">
|
||||
<label>Modo de Envío</label>
|
||||
<select id="meliShippingMode" onchange="onMeliShippingChange()">
|
||||
<option value="me2" selected>MercadoEnvíos (me2)</option>
|
||||
<option value="custom">Propio (custom)</option>
|
||||
<option value="not_specified">No especificado</option>
|
||||
</select>
|
||||
<small style="color:var(--color-text-muted);font-size:var(--text-caption);">Si no puedes activar ME1/ME2 en ML, prueba 'Propio' o 'No especificado'.</small>
|
||||
</div>
|
||||
<div class="inv-field" id="meliShippingCostField" style="display:none;">
|
||||
<label>Costo de Envío (MXN)</label>
|
||||
<input type="number" id="meliShippingCost" value="150" min="0" step="1" />
|
||||
</div>
|
||||
<div class="inv-field" style="display:flex;align-items:center;gap:8px;margin-top:var(--space-2);">
|
||||
<input type="checkbox" id="meliSkipValidate" style="width:auto;height:auto;" />
|
||||
<label for="meliSkipValidate" style="margin:0;font-size:var(--text-caption);color:var(--color-text-muted);text-transform:none;">Saltear validación y publicar directamente (si la validación falla por config de cuenta)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic required attributes section -->
|
||||
<div id="meliAttrsSection" style="display:none;">
|
||||
<div class="meli-attrs-section">
|
||||
<strong style="font-size:var(--text-body-sm);">Atributos requeridos para esta categoría</strong>
|
||||
<div id="meliAttrsGrid" class="meli-attrs-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="meliPublishResult" style="min-height:1.5em;margin-top:var(--space-3);"></div>
|
||||
</div>
|
||||
<div class="inv-modal__footer">
|
||||
<button class="btn btn--ghost" onclick="closeMeliPublishModal()">Cancelar</button>
|
||||
<button class="btn btn--secondary" id="meliValidateBtn" onclick="validateMeliPublish()">Validar con ML</button>
|
||||
<button class="btn btn--meli" id="meliPublishBtn" onclick="executeMeliPublish()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg> Publicar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ Product Timeline Modal ══════════ -->
|
||||
<div class="inv-modal-overlay" id="productTimelineModal">
|
||||
<div class="inv-modal inv-modal--wide">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Timeline del Producto</h3>
|
||||
<button class="inv-modal__close" onclick="document.getElementById('productTimelineModal').classList.remove('is-open')">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body" id="productTimelineBody">
|
||||
<div class="timeline">
|
||||
<div class="timeline__item"><div class="timeline__dot"></div><div class="timeline__content"><div class="timeline__date">--</div><div class="timeline__title">Producto creado</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inv-modal__footer">
|
||||
<button class="btn btn--ghost" onclick="document.getElementById('productTimelineModal').classList.remove('is-open')">Cerrar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ Image Comparator Modal ══════════ -->
|
||||
<div class="inv-modal-overlay" id="imageCompareModal">
|
||||
<div class="inv-modal">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Comparar Imágenes</h3>
|
||||
<button class="inv-modal__close" onclick="document.getElementById('imageCompareModal').classList.remove('is-open')">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body">
|
||||
<div class="img-compare" id="imgCompareContainer" style="max-height:400px;">
|
||||
<img class="img-compare__img" src="" id="imgCompareNew" alt="Nueva" />
|
||||
<div class="img-compare__overlay">
|
||||
<img src="" id="imgCompareOld" alt="Anterior" />
|
||||
</div>
|
||||
<div class="img-compare__handle">↔</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ Ticket Preview Modal ══════════ -->
|
||||
<div class="inv-modal-overlay" id="ticketPreviewModal">
|
||||
<div class="inv-modal" style="max-width:400px;">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Vista previa del ticket</h3>
|
||||
<button class="inv-modal__close" onclick="document.getElementById('ticketPreviewModal').classList.remove('is-open')">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body" id="ticketPreviewBody">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
<div class="inv-modal__footer">
|
||||
<button class="btn btn--ghost" onclick="document.getElementById('ticketPreviewModal').classList.remove('is-open')">Cerrar</button>
|
||||
<button class="btn btn--primary" onclick="window.print()">Imprimir</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ Bulk Import Modal ══════════ -->
|
||||
<div class="inv-modal-overlay" id="bulkImportModal">
|
||||
<div class="inv-modal" style="max-width:520px;">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Importar Productos Masivamente</h3>
|
||||
<button class="inv-modal__close" onclick="document.getElementById('bulkImportModal').classList.remove('is-open')">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body">
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="display:block;margin-bottom:4px;font-size:var(--text-caption);color:var(--color-text-muted);">Archivo CSV o Excel</label>
|
||||
<input type="file" id="bulkImportFile" accept=".csv,.xlsx,.xls" style="width:100%;padding:8px;border:1px dashed var(--color-border);border-radius:6px;background:var(--color-surface);color:var(--color-text);" />
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="display:block;margin-bottom:4px;font-size:var(--text-caption);color:var(--color-text-muted);">Modo de importación</label>
|
||||
<select id="bulkImportMode" class="select-filter" style="width:100%;">
|
||||
<option value="strict">Estricto — abortar al primer error</option>
|
||||
<option value="lenient" selected>Permisivo — saltar filas con error</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="display:block;margin-bottom:4px;font-size:var(--text-caption);color:var(--color-text-muted);">Compatibilidad de vehículo faltante</label>
|
||||
<select id="bulkImportStrategy" class="select-filter" style="width:100%;">
|
||||
<option value="qwen" selected>Auto-generar con IA (QWEN)</option>
|
||||
<option value="skip">Omitir compatibilidad</option>
|
||||
<option value="reject">Rechazar filas sin compatibilidad</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="font-size:var(--text-caption);color:var(--color-text-muted);background:var(--color-surface);padding:10px;border-radius:6px;">
|
||||
<strong>Columnas esperadas:</strong>
|
||||
<code style="display:block;margin-top:4px;word-break:break-all;">sku, name, brand, price, stock, cost, location, description, category, make, model, year, engine, engine_code</code>
|
||||
<span style="display:block;margin-top:4px;">También se aceptan sinónimos en español: <em>numero_de_parte, nombre, marca, precio, cantidad, costo, ubicacion, categoria, fabricante, modelo, anio, motor, codigo_motor</em></span>
|
||||
</div>
|
||||
<div id="bulkImportResult" style="margin-top:12px;display:none;"></div>
|
||||
</div>
|
||||
<div class="inv-modal__footer">
|
||||
<button class="btn btn--ghost" onclick="document.getElementById('bulkImportModal').classList.remove('is-open')">Cancelar</button>
|
||||
<button class="btn btn--primary" onclick="submitBulkImport()">Importar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Offline Banner -->
|
||||
<div id="offlineBanner" class="banner banner--warning" style="display:none;position:fixed;top:0;left:0;right:0;z-index:9999;border-radius:0;animation:none;">
|
||||
<span class="banner__icon"></span>
|
||||
@@ -818,10 +1039,11 @@
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/virtual-scroll.js" defer></script>
|
||||
<script src="/pos/static/js/inventory.js?v=5" defer></script>
|
||||
<script src="/pos/static/js/virtual-scroll.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/inventory.js?v=17" defer></script>
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/invoicing.css">
|
||||
</head>
|
||||
@@ -301,16 +303,16 @@
|
||||
<!-- Tabs Row -->
|
||||
<div class="tabs-row" role="tablist" aria-label="Módulos de Facturación">
|
||||
<button class="tab-btn is-active" role="tab" aria-selected="true" aria-controls="panel-facturas" id="tab-facturas" onclick="switchTab('facturas')">
|
||||
Facturas <span class="tab-btn__badge">247</span>
|
||||
Facturas <span class="tab-btn__badge" id="badge-facturas">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-notas" id="tab-notas" onclick="switchTab('notas')">
|
||||
Notas de Crédito <span class="tab-btn__badge">8</span>
|
||||
Notas de Crédito <span class="tab-btn__badge" id="badge-notas-credito">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-complementos" id="tab-complementos" onclick="switchTab('complementos')">
|
||||
Complementos de Pago <span class="tab-btn__badge">12</span>
|
||||
Complementos de Pago <span class="tab-btn__badge" id="badge-complementos">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-cancelaciones" id="tab-cancelaciones" onclick="switchTab('cancelaciones')">
|
||||
Cancelaciones <span class="tab-btn__badge tab-btn__badge--warn">6</span>
|
||||
Cancelaciones <span class="tab-btn__badge tab-btn__badge--warn" id="badge-cancelaciones">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-config" id="tab-config" onclick="switchTab('config')">
|
||||
Configuración CFDI
|
||||
@@ -354,6 +356,16 @@
|
||||
|
||||
<div class="toolbar__spacer"></div>
|
||||
|
||||
<button class="btn btn--primary" onclick="Invoicing.openGlobalInvoiceModal()">
|
||||
<svg viewBox="0 0 24 24" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
||||
<line x1="16" y1="2" x2="16" y2="6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="6"/>
|
||||
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||
</svg>
|
||||
Factura Global
|
||||
</button>
|
||||
|
||||
<button class="btn btn--ghost">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
@@ -1052,13 +1064,77 @@
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/invoicing.js" defer></script>
|
||||
<script src="/pos/static/js/invoicing.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
|
||||
<script src="/pos/static/js/chat.js" defer></script>
|
||||
|
||||
<script>
|
||||
// Load invoicing stats for tab badges
|
||||
async function loadInvoicingStats() {
|
||||
const token = localStorage.getItem('pos_token') || '';
|
||||
try {
|
||||
const res = await fetch('/pos/api/invoicing/stats', {
|
||||
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const map = {
|
||||
'badge-facturas': data.facturas,
|
||||
'badge-notas-credito': data.notas_credito,
|
||||
'badge-complementos': data.complementos,
|
||||
'badge-cancelaciones': data.cancelaciones
|
||||
};
|
||||
Object.entries(map).forEach(function([id, val]) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = val || 0;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to load invoicing stats:', e);
|
||||
}
|
||||
}
|
||||
window.loadInvoicingStats = loadInvoicingStats;
|
||||
loadInvoicingStats();
|
||||
</script>
|
||||
|
||||
<!-- =====================================================================
|
||||
MODAL: FACTURA GLOBAL
|
||||
===================================================================== -->
|
||||
<div class="modal-overlay" id="modalGlobalInvoice" style="display:none; position:fixed; inset:0; z-index:var(--z-modal); background:var(--overlay-backdrop); align-items:center; justify-content:center;">
|
||||
<div class="modal-card" style="background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); width:480px; max-width:95vw; box-shadow:var(--shadow-xl);">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; padding:var(--space-5) var(--space-6); border-bottom:1px solid var(--color-border);">
|
||||
<div>
|
||||
<div style="font-weight:var(--font-weight-semibold); font-size:var(--text-body-lg);">Generar Factura Global</div>
|
||||
<div style="font-size:var(--text-caption); color:var(--color-text-muted); margin-top:2px;">Agrupa ventas de contado no facturadas</div>
|
||||
</div>
|
||||
<button onclick="document.getElementById('modalGlobalInvoice').style.display='none'" style="background:none; border:none; color:var(--color-text-muted); font-size:1.4rem; cursor:pointer; padding:var(--space-2);">✕</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-5) var(--space-6);">
|
||||
<div style="display:flex; gap:var(--space-3); margin-bottom:var(--space-4);">
|
||||
<div style="flex:1;">
|
||||
<label style="font-size:var(--text-caption); color:var(--color-text-muted); display:block; margin-bottom:var(--space-1);">Año</label>
|
||||
<input type="number" id="global-year" class="form-input" style="width:100%;" />
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<label style="font-size:var(--text-caption); color:var(--color-text-muted); display:block; margin-bottom:var(--space-1);">Mes</label>
|
||||
<input type="number" id="global-month" class="form-input" style="width:100%;" min="1" max="12" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="global-preview" style="background:var(--color-surface); border-radius:var(--radius-md); padding:var(--space-3); font-size:var(--text-caption); color:var(--color-text-muted);">
|
||||
Presiona "Vista previa" para ver ventas elegibles.
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:var(--space-3); justify-content:flex-end; padding:var(--space-4) var(--space-6); border-top:1px solid var(--color-border);">
|
||||
<button onclick="document.getElementById('modalGlobalInvoice').style.display='none'" class="btn btn--ghost">Cancelar</button>
|
||||
<button onclick="Invoicing.previewGlobalInvoice()" class="btn btn--ghost">Vista previa</button>
|
||||
<button onclick="Invoicing.generateGlobalInvoice()" class="btn btn--primary">Generar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -291,6 +291,13 @@
|
||||
btnLogin.disabled = !canLogin;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
LOGIN BUTTON CLICK
|
||||
------------------------------------------------------------------ */
|
||||
btnLogin.addEventListener('click', function() {
|
||||
triggerLogin();
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
TRIGGER LOGIN (demo)
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/marketplace.css">
|
||||
</head>
|
||||
@@ -57,10 +59,11 @@
|
||||
<h2 style="margin-bottom:var(--space-4);font-family:var(--font-heading);">Mi Inventario</h2>
|
||||
<div class="upload-box">
|
||||
<label>Cargar inventario via CSV</label>
|
||||
<textarea id="csvText" placeholder="part_number,stock,price AB-123,5,150.50 CD-456,12,89.00"></textarea>
|
||||
<textarea id="csvText" placeholder="part_number,stock,price,name AB-123,5,150.50,Filtro de aceite CD-456,12,89.00,Balata delantera"></textarea>
|
||||
<div class="hint">
|
||||
Columnas requeridas: <code>part_number, stock, price</code>.
|
||||
Opcionales: <code>min_order, warehouse_location, currency</code>.
|
||||
Opcionales: <code>name, min_order, warehouse_location, currency</code>.
|
||||
<br>Si la parte no existe en el catálogo, se crea automáticamente como <em>seller listing</em>.
|
||||
</div>
|
||||
<button style="margin-top:var(--space-3);padding:var(--space-3) var(--space-6);background:var(--gradient-accent);color:var(--btn-primary-text);border:none;border-radius:var(--radius-md);font-weight:bold;cursor:pointer;" onclick="uploadCSV()">Subir CSV</button>
|
||||
<div id="uploadResult" style="margin-top:var(--space-3);font-size:var(--text-body-sm);"></div>
|
||||
@@ -205,8 +208,18 @@
|
||||
var priceStr = p.min_price === p.max_price
|
||||
? '$' + fmt(p.min_price)
|
||||
: '$' + fmt(p.min_price) + ' – $' + fmt(p.max_price);
|
||||
return '<div class="part-card" onclick="openPartDetail(' + p.id_part + ')">' +
|
||||
'<div class="part-card__oem">' + esc(p.oem_part_number) + '</div>' +
|
||||
var isOem = p.listing_type === 'oem';
|
||||
var badge = isOem
|
||||
? '<span style="background:#3FB95020;color:#3FB950;padding:2px 6px;border-radius:4px;font-size:var(--text-caption);">Catálogo</span>'
|
||||
: '<span style="background:#F5A62320;color:#F5A623;padding:2px 6px;border-radius:4px;font-size:var(--text-caption);">Listing</span>';
|
||||
var onclick = isOem
|
||||
? 'openPartDetail(' + p.id + ')'
|
||||
: 'openListingDetail(' + p.id + ')';
|
||||
return '<div class="part-card" onclick="' + onclick + '">' +
|
||||
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-1);">' +
|
||||
'<div class="part-card__oem">' + esc(p.part_number) + '</div>' +
|
||||
badge +
|
||||
'</div>' +
|
||||
'<div class="part-card__name">' + esc(p.name) + '</div>' +
|
||||
'<div class="part-card__meta">' +
|
||||
'<span class="price-range">' + priceStr + '</span>' +
|
||||
@@ -248,7 +261,7 @@
|
||||
'</div>' +
|
||||
'<div style="text-align:right;">' +
|
||||
'<div class="price-range">$' + fmt(b.price) + '</div>' +
|
||||
'<button style="margin-top:var(--space-1);padding:var(--space-1) var(--space-3);background:var(--gradient-accent);color:var(--btn-primary-text);border:none;border-radius:var(--radius-sm);font-size:var(--text-caption);cursor:pointer;" onclick="createOrderFor(' + partId + ', ' + b.id_bodega + ', \'' + esc(b.name).replace(/\'/g, '\\\'') + '\', ' + (b.price || 0) + ')">Ordenar</button>' +
|
||||
'<button style="margin-top:var(--space-1);padding:var(--space-1) var(--space-3);background:var(--gradient-accent);color:var(--btn-primary-text);border:none;border-radius:var(--radius-sm);font-size:var(--text-caption);cursor:pointer;" onclick="createOrderFor(' + partId + ', null, ' + b.id_bodega + ', \'' + esc(b.name).replace(/\'/g, '\\\'') + '\', ' + (b.price || 0) + ')">Ordenar</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
});
|
||||
@@ -257,7 +270,38 @@
|
||||
});
|
||||
};
|
||||
|
||||
window.createOrderFor = function (partId, bodegaId, bodegaName, price) {
|
||||
window.openListingDetail = function (wiId) {
|
||||
var modal = document.getElementById('partModal');
|
||||
modal.classList.add('open');
|
||||
document.getElementById('partModalBody').innerHTML = 'Cargando bodegas...';
|
||||
|
||||
apiFetch('/inventory/listing/' + wiId).then(function (resp) {
|
||||
if (!resp || !resp.data) return;
|
||||
var bodegas = resp.data;
|
||||
if (bodegas.length === 0) {
|
||||
document.getElementById('partModalBody').innerHTML = '<div class="empty-state">Ninguna bodega tiene esta parte en stock.</div>';
|
||||
return;
|
||||
}
|
||||
var html = '<p style="color:var(--color-text-muted);margin-bottom:var(--space-3);">Elige una bodega para ordenar:</p>';
|
||||
html += '<div style="display:flex;flex-direction:column;gap:var(--space-2);">';
|
||||
bodegas.forEach(function (b) {
|
||||
html += '<div style="padding:var(--space-3);border:1px solid var(--glass-border);border-radius:var(--radius-md);display:flex;justify-content:space-between;align-items:center;">' +
|
||||
'<div>' +
|
||||
'<strong>' + esc(b.name) + '</strong>' +
|
||||
'<div style="color:var(--color-text-muted);font-size:var(--text-caption);">' + esc(b.city || '') + ' · ' + esc(b.stock_hint) + '</div>' +
|
||||
'</div>' +
|
||||
'<div style="text-align:right;">' +
|
||||
'<div class="price-range">$' + fmt(b.price) + '</div>' +
|
||||
'<button style="margin-top:var(--space-1);padding:var(--space-1) var(--space-3);background:var(--gradient-accent);color:var(--btn-primary-text);border:none;border-radius:var(--radius-sm);font-size:var(--text-caption);cursor:pointer;" onclick="createOrderFor(null, ' + wiId + ', ' + b.id_bodega + ', \'' + esc(b.name).replace(/\'/g, '\\\'') + '\', ' + (b.price || 0) + ')">Ordenar</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
document.getElementById('partModalBody').innerHTML = html;
|
||||
});
|
||||
};
|
||||
|
||||
window.createOrderFor = function (partId, wiId, bodegaId, bodegaName, price) {
|
||||
var qty = prompt('Cantidad a ordenar para "' + bodegaName + '":', '1');
|
||||
if (!qty) return;
|
||||
qty = parseInt(qty);
|
||||
@@ -265,12 +309,22 @@
|
||||
|
||||
var notes = prompt('Notas para la bodega (opcional):', '') || '';
|
||||
|
||||
var item = { quantity: qty, unit_price: price };
|
||||
if (partId) {
|
||||
item.part_id = partId;
|
||||
} else if (wiId) {
|
||||
item.wi_id = wiId;
|
||||
} else {
|
||||
alert('Error: se requiere part_id o wi_id');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create PO draft
|
||||
apiFetch('/orders', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
bodega_id: bodegaId,
|
||||
items: [{ part_id: partId, quantity: qty, unit_price: price }],
|
||||
items: [item],
|
||||
delivery_method: 'pickup',
|
||||
buyer_notes: notes,
|
||||
}),
|
||||
@@ -482,6 +536,10 @@
|
||||
r.updated + ' actualizados';
|
||||
if (r.skipped > 0) msg += ', ' + r.skipped + ' omitidos';
|
||||
msg += '</span>';
|
||||
if (r.oem_count !== undefined || r.seller_count !== undefined) {
|
||||
msg += '<div style="margin-top:var(--space-2);font-size:var(--text-caption);color:var(--color-text-muted);">' +
|
||||
(r.oem_count || 0) + ' catálogo OEM · ' + (r.seller_count || 0) + ' seller listings</div>';
|
||||
}
|
||||
if (r.errors && r.errors.length) {
|
||||
msg += '<div style="margin-top:var(--space-2);color:var(--color-text-muted);font-size:var(--text-caption);">';
|
||||
r.errors.slice(0, 5).forEach(function (e) {
|
||||
|
||||
354
pos/templates/marketplace_external.html
Normal file
354
pos/templates/marketplace_external.html
Normal file
@@ -0,0 +1,354 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MercadoLibre — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/inventory.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
<style>
|
||||
.meli-status { display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600; }
|
||||
.meli-status--active { background:#d4edda;color:#155724; }
|
||||
.meli-status--paused { background:#fff3cd;color:#856404; }
|
||||
.meli-status--closed { background:#f8d7da;color:#721c24; }
|
||||
.meli-status--pending { background:#e2e3e5;color:#383d41; }
|
||||
.meli-card { background:var(--color-surface-1);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-4); }
|
||||
.meli-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:var(--space-4); }
|
||||
.meli-connect-btn { display:inline-flex;align-items:center;gap:8px;padding:10px 20px;background:#FFE600;color:#2D3277;border:none;border-radius:var(--radius-md);font-weight:700;cursor:pointer; }
|
||||
.meli-connect-btn:hover { filter:brightness(0.95); }
|
||||
.meli-config-row { display:flex;gap:var(--space-4);flex-wrap:wrap;margin-bottom:var(--space-4); }
|
||||
.meli-config-row label { display:block;font-size:var(--text-caption);color:var(--color-text-muted);margin-bottom:4px; }
|
||||
.meli-config-row input, .meli-config-row select { padding:8px 12px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface-0);color:var(--color-text-primary); }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- =========================================================================
|
||||
THEME SWITCHER BAR
|
||||
========================================================================= -->
|
||||
|
||||
<header class="theme-bar" role="banner">
|
||||
<div class="theme-bar__left">
|
||||
<div class="theme-bar__store">
|
||||
<span class="theme-bar__dot"></span>
|
||||
Nexus Autoparts
|
||||
</div>
|
||||
<div class="theme-bar__sep"></div>
|
||||
<span class="theme-bar__label">MercadoLibre — Integración</span>
|
||||
</div>
|
||||
<div class="theme-bar__right">
|
||||
<span class="theme-bar__label">Tema:</span>
|
||||
<button class="theme-btn theme-btn--industrial is-active" data-theme-target="industrial" onclick="setTheme('industrial')">
|
||||
<span class="theme-btn__swatch"></span>
|
||||
Industrial
|
||||
</button>
|
||||
<button class="theme-btn theme-btn--modern" data-theme-target="modern" onclick="setTheme('modern')">
|
||||
<span class="theme-btn__swatch"></span>
|
||||
Moderno
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- =========================================================================
|
||||
APP SHELL
|
||||
========================================================================= -->
|
||||
|
||||
<div class="app-shell">
|
||||
|
||||
<!-- -----------------------------------------------------------------------
|
||||
SIDEBAR NAVIGATION
|
||||
----------------------------------------------------------------------- -->
|
||||
|
||||
<aside class="sidebar" role="navigation" aria-label="Navegación principal">
|
||||
<div class="sidebar__brand">
|
||||
<div class="brand-logo">NA</div>
|
||||
<div class="brand-name">
|
||||
<span class="brand-name__primary">Nexus</span>
|
||||
<span class="brand-name__sub">Autoparts POS</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="sidebar__nav">
|
||||
<div class="nav-section-label">Principal</div>
|
||||
<a class="nav-item" href="/pos/dashboard">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
|
||||
</svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/sale">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
|
||||
</svg>
|
||||
<span>POS</span>
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/catalog">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
|
||||
</svg>
|
||||
<span>Catálogo</span>
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/inventory">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>
|
||||
</svg>
|
||||
<span>Inventario</span>
|
||||
</a>
|
||||
<div class="nav-section-label">Gestión</div>
|
||||
<a class="nav-item" href="/pos/customers">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/>
|
||||
</svg>
|
||||
<span>Clientes</span>
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/marketplace">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<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>Marketplace B2B</span>
|
||||
</a>
|
||||
<a class="nav-item is-active" href="/pos/marketplace-external" aria-current="page">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
|
||||
</svg>
|
||||
<span>MercadoLibre</span>
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/invoicing">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||||
<polyline points="14 2 14 8 20 8"/>
|
||||
<line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
|
||||
<polyline points="10 9 9 9 8 9"/>
|
||||
</svg>
|
||||
<span>Facturación</span>
|
||||
</a>
|
||||
<a class="nav-item" href="/pos/config">
|
||||
<svg class="nav-item__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/>
|
||||
</svg>
|
||||
<span>Configuración</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
<div class="sidebar__user-avatar" id="sidebarAvatar">U</div>
|
||||
<div class="sidebar__user-info">
|
||||
<div class="sidebar__user-name" id="sidebarName">Usuario</div>
|
||||
<div class="sidebar__user-role" id="sidebarRole">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- -----------------------------------------------------------------------
|
||||
MAIN CONTENT
|
||||
----------------------------------------------------------------------- -->
|
||||
|
||||
<main class="main" role="main">
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="page-header__title-group">
|
||||
<span class="page-header__eyebrow">Marketplace</span>
|
||||
<h1 class="page-header__title">MercadoLibre</h1>
|
||||
</div>
|
||||
<div class="page-header__actions">
|
||||
<a class="btn btn--ghost" href="/pos/dashboard">
|
||||
<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn is-active" role="tab" aria-selected="true" aria-controls="panel-config" onclick="switchTab('config')">
|
||||
Configuración
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-listings" onclick="switchTab('listings')">
|
||||
Publicaciones
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-orders" onclick="switchTab('orders')">
|
||||
Órdenes
|
||||
</button>
|
||||
<button class="tab-btn" role="tab" aria-selected="false" aria-controls="panel-questions" onclick="switchTab('questions')">
|
||||
Preguntas
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Panels -->
|
||||
<div class="tab-panels" id="tab-panels">
|
||||
|
||||
<!-- ══════════ TAB: Configuración ══════════ -->
|
||||
<div class="tab-panel is-active" id="panel-config" role="tabpanel">
|
||||
<div class="meli-card" style="max-width:600px;">
|
||||
<h3 style="margin:0 0 var(--space-4);font-family:var(--font-heading);">Conexión con MercadoLibre</h3>
|
||||
<div id="configStatus">
|
||||
<div class="skeleton skeleton--text" style="width:120px;"></div>
|
||||
</div>
|
||||
<div id="configForm" style="display:none;margin-top:var(--space-4);">
|
||||
<p style="margin-bottom:var(--space-3);font-size:var(--text-body-sm);color:var(--color-text-secondary);">
|
||||
Para conectar, necesitas una <strong>aplicación de MercadoLibre</strong>.
|
||||
Ve a <a href="https://developers.mercadolibre.com.mx" target="_blank">developers.mercadolibre.com.mx</a>
|
||||
y crea una app. Luego pega los datos aquí:
|
||||
</p>
|
||||
<div class="meli-config-row">
|
||||
<div>
|
||||
<label>Client ID</label>
|
||||
<input type="text" id="cfgClientId" placeholder="Tu App ID" style="width:200px;" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Client Secret</label>
|
||||
<input type="password" id="cfgClientSecret" placeholder="Tu Secret Key" style="width:200px;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="meli-config-row">
|
||||
<div>
|
||||
<label>Categoría Default</label>
|
||||
<input type="text" id="cfgCategory" placeholder="MLM1747" style="width:150px;" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Modo de Envío</label>
|
||||
<select id="cfgShipping">
|
||||
<option value="me2">MercadoEnvíos (me2)</option>
|
||||
<option value="custom">Propio (custom)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="meli-connect-btn" onclick="startOAuth()">🔗 Conectar con MercadoLibre</button>
|
||||
</div>
|
||||
<div id="configConnected" style="display:none;margin-top:var(--space-4);">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:var(--space-3);">
|
||||
<div style="width:48px;height:48px;border-radius:50%;background:#FFE600;display:flex;align-items:center;justify-content:center;font-weight:800;color:#2D3277;">ML</div>
|
||||
<div>
|
||||
<div style="font-weight:700;" id="connectedNickname">Usuario ML</div>
|
||||
<div style="font-size:var(--text-caption);color:var(--color-text-muted);" id="connectedSite">MLM</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn--danger btn--sm" onclick="disconnectMeli()">Desconectar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ TAB: Publicaciones ══════════ -->
|
||||
<div class="tab-panel" id="panel-listings" role="tabpanel">
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
<input type="text" id="listingSearch" placeholder="Buscar publicación..." oninput="filterListings()" />
|
||||
</div>
|
||||
<select class="select-filter" id="listingStatusFilter" onchange="filterListings()">
|
||||
<option value="">Todos los estados</option>
|
||||
<option value="active">Activas</option>
|
||||
<option value="paused">Pausadas</option>
|
||||
<option value="closed">Cerradas</option>
|
||||
</select>
|
||||
<div class="toolbar__spacer"></div>
|
||||
<button class="btn btn--ghost btn--sm" onclick="loadMeliStats()">📊 Resumen</button>
|
||||
<button class="btn btn--primary" onclick="loadListings()">🔄 Actualizar</button>
|
||||
</div>
|
||||
<div id="meliStatsBar" style="margin-bottom:var(--space-4);"></div>
|
||||
<div id="listingsContainer" class="meli-grid"></div>
|
||||
<div id="listingsPagination" class="table-footer" style="margin-top:var(--space-4);"></div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ TAB: Preguntas ══════════ -->
|
||||
<div class="tab-panel" id="panel-questions" role="tabpanel">
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
<input type="text" id="questionSearch" placeholder="Buscar pregunta..." oninput="filterQuestions()" />
|
||||
</div>
|
||||
<select class="select-filter" id="questionStatusFilter" onchange="filterQuestions()">
|
||||
<option value="">Todas</option>
|
||||
<option value="unanswered">Sin responder</option>
|
||||
<option value="answered">Respondidas</option>
|
||||
<option value="closed">Cerradas</option>
|
||||
</select>
|
||||
<div class="toolbar__spacer"></div>
|
||||
<button class="btn btn--ghost btn--sm" onclick="syncQuestions()">🔄 Sincronizar con ML</button>
|
||||
<button class="btn btn--primary" onclick="loadQuestions()">🔄 Actualizar</button>
|
||||
</div>
|
||||
<div id="questionsStatsBar" style="margin-bottom:var(--space-4);"></div>
|
||||
<div id="questionsContainer" class="meli-grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════ TAB: Órdenes ══════════ -->
|
||||
<div class="tab-panel" id="panel-orders" role="tabpanel">
|
||||
<div class="toolbar">
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
<input type="text" id="orderSearch" placeholder="Buscar orden..." oninput="filterOrders()" />
|
||||
</div>
|
||||
<select class="select-filter" id="orderStatusFilter" onchange="filterOrders()">
|
||||
<option value="">Todos los estados</option>
|
||||
<option value="pending">Pendientes</option>
|
||||
<option value="confirmed">Confirmadas</option>
|
||||
<option value="packed">Empacadas</option>
|
||||
<option value="shipped">Enviadas</option>
|
||||
<option value="delivered">Entregadas</option>
|
||||
<option value="cancelled">Canceladas</option>
|
||||
</select>
|
||||
<div class="toolbar__spacer"></div>
|
||||
<button class="btn btn--ghost btn--sm" id="btnKanbanView" onclick="toggleOrderView()">📋 Kanban</button>
|
||||
<button class="btn btn--primary" onclick="loadOrders()">🔄 Actualizar</button>
|
||||
</div>
|
||||
<div id="ordersKanbanView" style="display:none;"></div>
|
||||
<div class="table-wrapper" id="ordersTableView">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Orden ML</th>
|
||||
<th>Comprador</th>
|
||||
<th style="text-align:right">Total</th>
|
||||
<th>Estado Nexus</th>
|
||||
<th>Fecha</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ordersTableBody"></tbody>
|
||||
</table>
|
||||
<div id="ordersPagination" class="table-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /tab-panels -->
|
||||
|
||||
</main>
|
||||
|
||||
</div><!-- /app-shell -->
|
||||
|
||||
<!-- ══════════ Order Detail Modal ══════════ -->
|
||||
<div class="inv-modal-overlay" id="orderModal">
|
||||
<div class="inv-modal inv-modal--wide">
|
||||
<div class="inv-modal__header">
|
||||
<h3>Detalle de Orden ML</h3>
|
||||
<button class="inv-modal__close" onclick="closeModal('orderModal')">×</button>
|
||||
</div>
|
||||
<div class="inv-modal__body" id="orderModalBody">cargando...</div>
|
||||
<div class="inv-modal__footer" id="orderModalFooter"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/marketplace_external.js?v=4" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -7,11 +7,13 @@
|
||||
<title>Nexus Autoparts — Punto de Venta</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
<script src="/pos/static/js/native-bridge.js" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/pos.css?v=4">
|
||||
@@ -32,6 +34,10 @@
|
||||
<span id="statusClock"></span>
|
||||
</div>
|
||||
<div class="status-bar__right">
|
||||
<a href="/pos/dashboard" class="btn btn--ghost btn--sm" id="backToSystemBtn" style="margin-right:8px;display:none;">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
Sistema
|
||||
</a>
|
||||
<div class="status-bar__user" aria-label="Usuario activo">
|
||||
<div class="status-bar__user-avatar" aria-hidden="true">--</div>
|
||||
<span id="employeeName">Empleado</span>
|
||||
@@ -200,10 +206,11 @@
|
||||
|
||||
<!-- Secondary Actions -->
|
||||
<div class="secondary-actions" role="toolbar" aria-label="Acciones secundarias">
|
||||
<button class="btn-secondary-action" onclick="POS.modifyPrice()" title="Modificar precio">Mod.Precio</button>
|
||||
<button class="btn-secondary-action" onclick="POS.saveQuotation()" title="Cotizacion (F4)">Cotizar</button>
|
||||
<button class="btn-secondary-action" onclick="POS.showLastSale()" title="Ultima venta (F5)">Ult.Venta</button>
|
||||
<button class="btn-secondary-action" onclick="POS.showCutZModal()" title="Corte Z - Cerrar caja">Corte Z</button>
|
||||
<button class="btn-secondary-action danger" id="btnCancelSale" title="Cancelar (Esc)">Cancelar</button>
|
||||
<button class="btn-secondary-action danger" id="btnCancelSale" onclick="POS.openCancelModal()" title="Cancelar (Esc)">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -233,14 +240,14 @@
|
||||
<span class="fkey-key">F6</span><span class="fkey-label">Cajon</span>
|
||||
</div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" title="Cantidad +/-">
|
||||
<div class="fkey" onclick="POS.changeQuantity()" title="Cantidad +/-">
|
||||
<span class="fkey-key">+/-</span><span class="fkey-label">Cantidad</span>
|
||||
</div>
|
||||
<div class="fkey" title="Descuento">
|
||||
<div class="fkey" onclick="POS.applyDiscount()" title="Descuento">
|
||||
<span class="fkey-key">*</span><span class="fkey-label">Descuento</span>
|
||||
</div>
|
||||
<div class="fkey-sep"></div>
|
||||
<div class="fkey" id="fkeyEsc" title="Cancelar">
|
||||
<div class="fkey" id="fkeyEsc" onclick="POS.openCancelModal()" title="Cancelar">
|
||||
<span class="fkey-key">Esc</span><span class="fkey-label">Cancelar</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -559,11 +566,12 @@
|
||||
================================================================ -->
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/kiosk.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/push.js" defer></script>
|
||||
<script src="/pos/static/js/printer.js" defer></script>
|
||||
<script src="/pos/static/js/pos.js?v=4" defer></script>
|
||||
<script src="/pos/static/js/pos.js?v=7" defer></script>
|
||||
|
||||
<script>
|
||||
// Cancel sale button wiring
|
||||
@@ -577,11 +585,7 @@
|
||||
closeCancelModal();
|
||||
// Clear cart via POS module
|
||||
if (typeof POS !== 'undefined') {
|
||||
// Clear each item
|
||||
const tbody = document.getElementById('cartBody');
|
||||
while (tbody && tbody.firstChild) {
|
||||
POS.removeFromCart(0);
|
||||
}
|
||||
POS.clearCart();
|
||||
POS.clearCustomer();
|
||||
}
|
||||
});
|
||||
@@ -614,6 +618,20 @@
|
||||
}
|
||||
updateClock();
|
||||
setInterval(updateClock, 30000);
|
||||
|
||||
// Show "Back to System" button when NOT in kiosk mode
|
||||
(function checkKiosk() {
|
||||
var btn = document.getElementById('backToSystemBtn');
|
||||
if (!btn) return;
|
||||
function showIfNotKiosk() {
|
||||
try {
|
||||
var isKiosk = window.isKioskEnabled && window.isKioskEnabled();
|
||||
btn.style.display = isKiosk ? 'none' : 'inline-flex';
|
||||
} catch (e) { btn.style.display = 'inline-flex'; }
|
||||
}
|
||||
showIfNotKiosk();
|
||||
setInterval(showIfNotKiosk, 2000);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src="/pos/static/js/chat.js" defer></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,14 +8,17 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/quotations.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
|
||||
<div class="page">
|
||||
<h1 class="page-title">Cotizaciones</h1>
|
||||
@@ -65,7 +68,9 @@
|
||||
html += '<td onclick="openQuote(' + q.id + ')" style="cursor:pointer;font-family:var(--font-mono);font-weight:700;">$' + fmt(q.total) + '</td>';
|
||||
html += '<td onclick="openQuote(' + q.id + ')" style="cursor:pointer;">' + statusBadge + '</td>';
|
||||
html += '<td onclick="openQuote(' + q.id + ')" style="cursor:pointer;color:var(--color-text-muted);">' + dateStr + '</td>';
|
||||
html += '<td><button onclick="deleteQuote(' + q.id + ', event)" style="background:none;border:none;color:var(--color-text-muted);cursor:pointer;font-size:16px;padding:4px 8px;border-radius:4px;" onmouseover="this.style.color=\'#F85149\';this.style.background=\'rgba(248,81,73,0.1)\'" onmouseout="this.style.color=\'var(--color-text-muted)\';this.style.background=\'none\'">🗑️</button></td>';
|
||||
html += '<td><button onclick="deleteQuote(' + q.id + ', event)" style="background:var(--color-bg-overlay);border:1.5px solid var(--color-border);color:var(--color-text-muted);cursor:pointer;font-size:13px;padding:6px 10px;border-radius:var(--radius-md);display:inline-flex;align-items:center;gap:4px;transition:var(--transition-fast);" onmouseover="this.style.color=\'var(--color-error)\';this.style.borderColor=\'var(--color-error)\';this.style.background=\'rgba(248,81,73,0.08)\'" onmouseout="this.style.color=\'var(--color-text-muted)\';this.style.borderColor=\'var(--color-border)\';this.style.background=\'var(--color-bg-overlay)\'">';
|
||||
html += '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
||||
html += 'Eliminar</button></td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
@@ -114,15 +119,33 @@
|
||||
html += '<div style="font-size:var(--text-h5);font-weight:700;color:var(--color-text-accent);">Total: $' + fmt(q.total) + '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div style="margin-top:var(--space-5);display:flex;gap:var(--space-3);justify-content:flex-end;flex-wrap:wrap;">';
|
||||
html += '<div style="margin-top:var(--space-5);">';
|
||||
// Primary actions
|
||||
if (q.status === 'active') {
|
||||
html += '<button class="btn btn--ghost" onclick="editQuote(' + q.id + ')" style="color:#4f46e5;">Editar</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="convertQuote(' + q.id + ')" style="color:#059669;">Convertir a venta</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="shareQuote(' + q.id + ')">Compartir link</button>';
|
||||
html += '<div style="display:flex;gap:var(--space-3);flex-wrap:wrap;margin-bottom:var(--space-3);">';
|
||||
html += '<button class="btn btn--primary" onclick="convertQuote(' + q.id + ')" style="padding:10px 20px;font-size:var(--text-body);display:inline-flex;align-items:center;gap:8px;">';
|
||||
html += '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>';
|
||||
html += 'Convertir a Venta</button>';
|
||||
html += '<button class="btn btn--secondary" onclick="editQuote(' + q.id + ')" style="padding:10px 20px;font-size:var(--text-body);display:inline-flex;align-items:center;gap:8px;">';
|
||||
html += '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
|
||||
html += 'Editar</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="shareQuote(' + q.id + ')" style="padding:10px 20px;font-size:var(--text-body);display:inline-flex;align-items:center;gap:8px;border:1.5px solid var(--color-border);">';
|
||||
html += '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>';
|
||||
html += 'Compartir</button>';
|
||||
html += '</div>';
|
||||
}
|
||||
html += '<button class="btn btn--ghost" onclick="deleteQuote(' + q.id + ')" style="color:#F85149;">Eliminar</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="exportVisibleTableCSV(\'cotizacion_' + q.id + '\')">Exportar CSV</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="window.print()">Imprimir</button>';
|
||||
// Secondary actions
|
||||
html += '<div style="display:flex;gap:var(--space-3);flex-wrap:wrap;justify-content:flex-end;">';
|
||||
html += '<button class="btn btn--ghost" onclick="window.print()" style="padding:8px 16px;font-size:var(--text-body-sm);display:inline-flex;align-items:center;gap:6px;">';
|
||||
html += '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>';
|
||||
html += 'Imprimir</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="exportVisibleTableCSV(\'cotizacion_' + q.id + '\')" style="padding:8px 16px;font-size:var(--text-body-sm);display:inline-flex;align-items:center;gap:6px;">';
|
||||
html += '<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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
|
||||
html += 'Exportar CSV</button>';
|
||||
html += '<button class="btn btn--ghost" onclick="deleteQuote(' + q.id + ')" style="padding:8px 16px;font-size:var(--text-body-sm);display:inline-flex;align-items:center;gap:6px;color:var(--color-error);">';
|
||||
html += '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
||||
html += 'Eliminar</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
document.getElementById('quoteDetail').innerHTML = html;
|
||||
@@ -142,69 +165,83 @@
|
||||
} else {
|
||||
alert('Error: ' + (d.error || 'desconocido'));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function(err) { alert('Error de red al eliminar: ' + err.message); });
|
||||
};
|
||||
|
||||
window.editQuote = function(id) {
|
||||
fetch(API + '/quotations/' + id, { headers: headers() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(q) {
|
||||
if (!q.items) { alert('Error cargando cotización'); return; }
|
||||
var cartItems = q.items.map(function(it) {
|
||||
return {
|
||||
inventory_id: it.inventory_id,
|
||||
part_number: it.part_number,
|
||||
name: it.name,
|
||||
quantity: it.quantity,
|
||||
unit_price: it.unit_price,
|
||||
discount_pct: it.discount_pct,
|
||||
tax_rate: it.tax_rate
|
||||
};
|
||||
});
|
||||
localStorage.setItem('pos_edit_quote_id', id);
|
||||
localStorage.setItem('pos_edit_quote_customer_id', q.customer_id || '');
|
||||
localStorage.setItem('pos_edit_quote_notes', q.notes || '');
|
||||
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
|
||||
window.location.href = '/pos';
|
||||
});
|
||||
try {
|
||||
fetch(API + '/quotations/' + id, { headers: headers() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(q) {
|
||||
if (!q.items) { alert('Error cargando cotización'); return; }
|
||||
var cartItems = q.items.map(function(it) {
|
||||
return {
|
||||
inventory_id: it.inventory_id,
|
||||
part_number: it.part_number,
|
||||
name: it.name,
|
||||
quantity: it.quantity,
|
||||
unit_price: it.unit_price,
|
||||
discount_pct: it.discount_pct,
|
||||
tax_rate: it.tax_rate
|
||||
};
|
||||
});
|
||||
localStorage.setItem('pos_edit_quote_id', id);
|
||||
localStorage.setItem('pos_edit_quote_customer_id', q.customer_id || '');
|
||||
localStorage.setItem('pos_edit_quote_notes', q.notes || '');
|
||||
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
|
||||
window.location.href = '/pos/sale';
|
||||
})
|
||||
.catch(function(err) { alert('Error al cargar para editar: ' + err.message); });
|
||||
} catch (e) { alert('Excepcion en editQuote: ' + e.message); }
|
||||
};
|
||||
|
||||
window.convertQuote = function(id) {
|
||||
fetch(API + '/quotations/' + id, { headers: headers() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(q) {
|
||||
if (!q.items) { alert('Error cargando cotización'); return; }
|
||||
var cartItems = q.items.map(function(it) {
|
||||
return {
|
||||
inventory_id: it.inventory_id,
|
||||
part_number: it.part_number,
|
||||
name: it.name,
|
||||
quantity: it.quantity,
|
||||
unit_price: it.unit_price,
|
||||
discount_pct: it.discount_pct,
|
||||
tax_rate: it.tax_rate
|
||||
};
|
||||
});
|
||||
localStorage.setItem('pos_convert_quote_id', id);
|
||||
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
|
||||
window.location.href = '/pos';
|
||||
});
|
||||
try {
|
||||
fetch(API + '/quotations/' + id, { headers: headers() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(q) {
|
||||
if (!q.items) { alert('Error cargando cotización'); return; }
|
||||
var cartItems = q.items.map(function(it) {
|
||||
return {
|
||||
inventory_id: it.inventory_id,
|
||||
part_number: it.part_number,
|
||||
name: it.name,
|
||||
quantity: it.quantity,
|
||||
unit_price: it.unit_price,
|
||||
discount_pct: it.discount_pct,
|
||||
tax_rate: it.tax_rate
|
||||
};
|
||||
});
|
||||
localStorage.setItem('pos_convert_quote_id', id);
|
||||
localStorage.setItem('pos_cart', JSON.stringify(cartItems));
|
||||
window.location.href = '/pos/sale';
|
||||
})
|
||||
.catch(function(err) { alert('Error al cargar para convertir: ' + err.message); });
|
||||
} catch (e) { alert('Excepcion en convertQuote: ' + e.message); }
|
||||
};
|
||||
|
||||
window.shareQuote = function(id) {
|
||||
fetch(API + '/quotations/' + id + '/share', { method: 'POST', headers: headers() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.url) {
|
||||
navigator.clipboard.writeText(d.url).then(function() {
|
||||
alert('Link copiado al portapapeles:\n' + d.url);
|
||||
}).catch(function() {
|
||||
prompt('Copia este link:', d.url);
|
||||
});
|
||||
} else {
|
||||
alert('Error: ' + (d.error || 'desconocido'));
|
||||
}
|
||||
});
|
||||
try {
|
||||
fetch(API + '/quotations/' + id + '/share', { method: 'POST', headers: headers() })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.url) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(d.url).then(function() {
|
||||
alert('Link copiado al portapapeles:\n' + d.url);
|
||||
}).catch(function() {
|
||||
prompt('Copia este link:', d.url);
|
||||
});
|
||||
} else {
|
||||
prompt('Copia este link:', d.url);
|
||||
}
|
||||
} else {
|
||||
alert('Error del servidor: ' + (d.error || 'desconocido'));
|
||||
}
|
||||
})
|
||||
.catch(function(err) { alert('Error de red al compartir: ' + err.message); });
|
||||
} catch (e) { alert('Excepcion en shareQuote: ' + e.message); }
|
||||
};
|
||||
|
||||
// Close modal on outside click
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -8,10 +8,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/reports.css">
|
||||
</head>
|
||||
@@ -318,7 +320,8 @@
|
||||
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/reports.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
|
||||
135
pos/templates/supplier_catalog.html
Normal file
135
pos/templates/supplier_catalog.html
Normal file
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Catalogo de Proveedores — Nexus Autoparts POS</title>
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
<style>
|
||||
.supplier-catalog { padding: var(--space-5); max-width: 1400px; margin: 0 auto; }
|
||||
.sc-header { display:flex; align-items:center; justify-content:space-between; gap:var(--space-4); margin-bottom:var(--space-5); flex-wrap:wrap; }
|
||||
.sc-search { display:flex; gap:var(--space-3); flex:1; min-width:280px; }
|
||||
.sc-search input { flex:1; padding:var(--space-3) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-bg-elevated); color:var(--color-text-primary); font-size:var(--text-body); }
|
||||
.sc-filters { display:flex; gap:var(--space-3); flex-wrap:wrap; margin-bottom:var(--space-5); }
|
||||
.sc-filters select { padding:var(--space-2) var(--space-3); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-bg-elevated); color:var(--color-text-primary); min-width:140px; }
|
||||
.sc-categories { display:grid; grid-template-columns:repeat(auto-fill, minmax(160px, 1fr)); gap:var(--space-3); margin-bottom:var(--space-5); }
|
||||
.sc-cat-card { background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); padding:var(--space-4); cursor:pointer; transition:all .15s; text-align:center; }
|
||||
.sc-cat-card:hover, .sc-cat-card.active { border-color:var(--color-primary); box-shadow:var(--shadow-sm); }
|
||||
.sc-cat-card .count { font-size:var(--text-caption); color:var(--color-text-muted); margin-top:2px; }
|
||||
.sc-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:var(--space-4); }
|
||||
.sc-card { background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-lg); padding:var(--space-4); cursor:pointer; transition:all .15s; display:flex; flex-direction:column; gap:var(--space-2); }
|
||||
.sc-card:hover { border-color:var(--color-primary); transform:translateY(-2px); box-shadow:var(--shadow-sm); }
|
||||
.sc-card__sku { font-family:var(--font-mono); font-size:var(--text-caption); color:var(--color-primary); font-weight:var(--font-weight-bold); }
|
||||
.sc-card__name { font-weight:var(--font-weight-semibold); color:var(--color-text-primary); line-height:1.3; }
|
||||
.sc-card__meta { font-size:var(--text-caption); color:var(--color-text-muted); margin-top:auto; }
|
||||
.sc-card__badge { display:inline-block; padding:2px 8px; border-radius:var(--radius-full); background:var(--color-primary-muted); color:var(--color-primary); font-size:10px; font-weight:var(--font-weight-bold); text-transform:uppercase; }
|
||||
.sc-empty { text-align:center; padding:var(--space-8); color:var(--color-text-muted); }
|
||||
.sc-pagination { display:flex; justify-content:center; align-items:center; gap:var(--space-3); margin-top:var(--space-6); }
|
||||
.sc-pagination button { padding:var(--space-2) var(--space-4); border:1px solid var(--color-border); border-radius:var(--radius-md); background:var(--color-bg-elevated); color:var(--color-text-primary); cursor:pointer; }
|
||||
.sc-pagination button:disabled { opacity:.4; cursor:not-allowed; }
|
||||
.sc-pagination span { font-size:var(--text-caption); color:var(--color-text-muted); }
|
||||
|
||||
/* Modal */
|
||||
.sc-modal-overlay { position:fixed; inset:0; background:var(--overlay-backdrop); z-index:var(--z-modal); display:none; align-items:center; justify-content:center; padding:var(--space-4); }
|
||||
.sc-modal-overlay.open { display:flex; }
|
||||
.sc-modal { background:var(--color-bg-elevated); border:1px solid var(--color-border); border-radius:var(--radius-xl); width:100%; max-width:720px; max-height:90vh; overflow-y:auto; display:flex; flex-direction:column; }
|
||||
.sc-modal__header { display:flex; align-items:center; justify-content:space-between; padding:var(--space-4) var(--space-5); border-bottom:1px solid var(--color-border); }
|
||||
.sc-modal__body { padding:var(--space-5); display:flex; flex-direction:column; gap:var(--space-4); }
|
||||
.sc-modal__section h4 { font-size:var(--text-body-sm); font-weight:var(--font-weight-bold); color:var(--color-text-secondary); margin-bottom:var(--space-2); text-transform:uppercase; letter-spacing:var(--tracking-wider); }
|
||||
.sc-compat-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:var(--space-2); }
|
||||
.sc-compat-item { background:var(--color-surface-1); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:var(--space-2) var(--space-3); font-size:var(--text-caption); }
|
||||
.sc-interchange-list { display:flex; flex-wrap:wrap; gap:var(--space-2); }
|
||||
.sc-interchange-chip { background:var(--color-surface-2); border:1px solid var(--color-border); border-radius:var(--radius-full); padding:2px 10px; font-size:var(--text-caption); }
|
||||
.sc-close { background:none; border:none; font-size:20px; color:var(--color-text-muted); cursor:pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Theme bar -->
|
||||
<div class="theme-bar">
|
||||
<span class="theme-bar__label">Tema:</span>
|
||||
<button class="theme-btn" id="btn-industrial" onclick="setTheme('industrial')">Industrial</button>
|
||||
<button class="theme-btn" id="btn-modern" onclick="setTheme('modern')">Moderno</button>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-overlay" id="sidebar-overlay" onclick="closeSidebar()"></div>
|
||||
|
||||
<div class="app-shell">
|
||||
<nav class="sidebar themed-scrollbar" id="sidebar">
|
||||
<div class="sidebar__logo">
|
||||
<div class="sidebar__logo-text">Nexus</div>
|
||||
<div class="sidebar__logo-sub">Autoparts POS</div>
|
||||
</div>
|
||||
<div class="sidebar__nav">
|
||||
<div class="sidebar__section-label">Principal</div>
|
||||
<a href="/pos/dashboard" class="nav-link"><span class="nav-link__icon">📊</span> Dashboard</a>
|
||||
<a href="/pos/sale" class="nav-link"><span class="nav-link__icon">🛒</span> POS</a>
|
||||
<a href="/pos/catalog" class="nav-link"><span class="nav-link__icon">📁</span> Catalogo</a>
|
||||
<a href="/pos/supplier-catalog" class="nav-link active"><span class="nav-link__icon">🏭</span> Cat. Proveedores</a>
|
||||
<a href="/pos/inventory" class="nav-link"><span class="nav-link__icon">📦</span> Inventario</a>
|
||||
<a href="/pos/config" class="nav-link"><span class="nav-link__icon">⚙️</span> Configuracion</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="main">
|
||||
<header class="header">
|
||||
<div class="header__left">
|
||||
<button class="hamburger-btn" onclick="toggleSidebar()">☰</button>
|
||||
<div class="header__greeting">
|
||||
<div class="header__title">Catalogo de Proveedores</div>
|
||||
<div class="header__subtitle">Busca por vehiculo, SKU o nombre de parte</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="supplier-catalog themed-scrollbar">
|
||||
<div class="sc-header">
|
||||
<div class="sc-search">
|
||||
<input type="text" id="searchInput" placeholder="Buscar SKU, nombre o intercambio..." onkeydown="if(event.key==='Enter') doSearch()" />
|
||||
<button class="btn btn--primary" onclick="doSearch()">Buscar</button>
|
||||
<button class="btn btn--secondary" onclick="clearFilters()">Limpiar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sc-filters">
|
||||
<select id="filterMake" onchange="onMakeChange()"><option value="">Marca vehiculo</option></select>
|
||||
<select id="filterModel" onchange="onModelChange()" disabled><option value="">Modelo</option></select>
|
||||
<select id="filterYear" onchange="onYearChange()" disabled><option value="">Año</option></select>
|
||||
<select id="filterEngine" onchange="doSearch()" disabled><option value="">Motorizacion</option></select>
|
||||
</div>
|
||||
|
||||
<div class="sc-categories" id="categoriesGrid"></div>
|
||||
|
||||
<div id="resultsArea">
|
||||
<div class="sc-grid" id="partsGrid"></div>
|
||||
<div class="sc-pagination" id="pagination"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail Modal -->
|
||||
<div class="sc-modal-overlay" id="detailModal" onclick="closeModal(event)">
|
||||
<div class="sc-modal" onclick="event.stopPropagation()">
|
||||
<div class="sc-modal__header">
|
||||
<h3 id="modalTitle">Detalle</h3>
|
||||
<button class="sc-close" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div class="sc-modal__body" id="modalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
<script src="/pos/static/js/supplier_catalog.js?v=2" defer></script>
|
||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-theme="industrial">
|
||||
<html lang="es">
|
||||
<head>
|
||||
<script>/*pos_theme_early*/(function(){var t=localStorage.getItem("pos_theme")||"industrial";document.documentElement.setAttribute("data-theme",t);})()</script>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -11,10 +11,12 @@
|
||||
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/common.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-ui.css?v=2" />
|
||||
<link rel="stylesheet" href="/pos/static/css/sidebar.css" />
|
||||
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
|
||||
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||
<meta name="theme-color" content="#F5A623" />
|
||||
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
|
||||
|
||||
<link rel="stylesheet" href="/pos/static/css/whatsapp.css">
|
||||
</head>
|
||||
@@ -131,8 +133,10 @@ function posLogout(){localStorage.removeItem('pos_token');window.location.href='
|
||||
|
||||
<!-- Sidebar -->
|
||||
<script src="/pos/static/js/i18n.js" defer></script>
|
||||
<script src="/pos/static/js/whatsapp2.js" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js" defer></script>
|
||||
<script src="/pos/static/js/app-init.js" defer></script>
|
||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||
<script src="/pos/static/js/whatsapp2.js?v=5" defer></script>
|
||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||
|
||||
<script src="/pos/static/js/chat.js" defer></script>
|
||||
|
||||
Reference in New Issue
Block a user