FASE 7: Quick Wins de Performance — Optimización Fase 1

Cambios implementados:

1. Nginx:
   - gzip on (compresión JS/CSS/JSON)
   - Cache headers para assets estáticos (6M)
   - Proxy buffer tuning (10s connect, 30s read)

2. Frontend catalog.js:
   - Reemplazados 8x innerHTML += en loops por map+join
   - Event delegation en breadcrumb y cart (elimina memory leak)
   - AbortController en apiFetch (evita race conditions)
   - sessionStorage cache para years-all y brands por modo

3. Frontend templates HTML:
   - defer en todos los scripts POS (mejora First Paint)

4. Dashboard JS:
   - innerHTML += fix en dashboard.js y cuentas.js

5. Base de datos:
   - Índice parcial idx_wi_part_stock_positive en warehouse_inventory

6. Documentación:
   - docs/performance_audit_2026.md con análisis completo y roadmap

Tests: 73/73 pasando (compat + fase3 + fase5 + fase6)
This commit is contained in:
2026-04-27 07:19:37 +00:00
parent efbd763e43
commit 175dda6212
19 changed files with 813 additions and 146 deletions

View File

@@ -186,8 +186,20 @@
var cartItems = JSON.parse(localStorage.getItem('pos_cart') || '[]');
// ─── API helper ───
var currentAbort = null;
function apiFetch(url) {
return fetch(url, { headers: headers })
// Cancel previous navigation/search GETs to avoid race conditions
if (currentAbort) {
currentAbort.abort();
currentAbort = null;
}
var opts = { headers: headers };
if (url.indexOf('/pos/api/') === 0 && url.indexOf('mode=') !== -1 || url.indexOf('/years') !== -1 || url.indexOf('/brands') !== -1 || url.indexOf('/models') !== -1 || url.indexOf('/engines') !== -1 || url.indexOf('/categories') !== -1 || url.indexOf('/groups') !== -1 || url.indexOf('/part-types') !== -1 || url.indexOf('/parts') !== -1 || url.indexOf('/search') !== -1) {
currentAbort = new AbortController();
opts.signal = currentAbort.signal;
}
return fetch(url, opts)
.then(function (resp) {
if (resp.status === 401) {
localStorage.removeItem('pos_token');
@@ -197,6 +209,7 @@
return resp.json();
})
.catch(function (e) {
if (e.name === 'AbortError') return null;
console.error('API error:', e);
return null;
});
@@ -333,8 +346,18 @@
setupLevelFilter(true);
showLoading();
var cacheKey = 'nexus:brands:' + catalogMode;
var cached = sessionStorage.getItem(cacheKey);
if (cached) {
hideLoading();
var data = JSON.parse(cached);
renderBrands(data);
return;
}
apiFetch(API + '/brands?mode=' + catalogMode).then(function (data) {
hideLoading();
if (data && data.data) sessionStorage.setItem(cacheKey, JSON.stringify(data));
if (!data || !data.data || !data.data.length) {
if (!data) {
enterOfflineMode();
@@ -359,6 +382,30 @@
});
}
function renderBrands(data) {
if (!data || !data.data || !data.data.length) {
if (!data) {
enterOfflineMode();
return;
}
showEmpty('Sin marcas', 'El catalogo no tiene marcas con partes disponibles.');
return;
}
navGrid.className = 'nav-grid';
navGrid.innerHTML = data.data.map(function (b) {
return '<div class="nav-card" role="listitem" data-brand-id="' + b.id_brand + '" data-name="' + esc(b.name_brand) + '">' +
'<div class="nav-card__name">' + esc(b.name_brand) + '</div>' +
'</div>';
}).join('');
navGrid.querySelectorAll('.nav-card').forEach(function (card) {
card.addEventListener('click', function () {
nav.brand = { id: parseInt(this.dataset.brandId), name: this.dataset.name };
loadModels();
});
});
}
function loadModels() {
nav.level = 'models';
pushNavState();
@@ -1463,18 +1510,19 @@
cartTaxEl.textContent = '$' + fmt(tax);
cartTotalEl.textContent = '$' + fmt(subtotal + tax);
// Wire cart buttons
cartItemsEl.querySelectorAll('[data-cart-action]').forEach(function (btn) {
btn.addEventListener('click', function () {
var idx = parseInt(this.dataset.idx);
var action = this.dataset.cartAction;
if (action === 'dec') updateQuantity(idx, cartItems[idx].quantity - 1);
else if (action === 'inc') updateQuantity(idx, cartItems[idx].quantity + 1);
else if (action === 'remove') removeFromCart(idx);
});
});
}
// Event delegation for cart buttons (attached once)
cartItemsEl.addEventListener('click', function (e) {
var btn = e.target.closest('[data-cart-action]');
if (!btn) return;
var idx = parseInt(btn.dataset.idx);
var action = btn.dataset.cartAction;
if (action === 'dec') updateQuantity(idx, cartItems[idx].quantity - 1);
else if (action === 'inc') updateQuantity(idx, cartItems[idx].quantity + 1);
else if (action === 'remove') removeFromCart(idx);
});
function toggleCart() {
var isOpen = cartSidebar.classList.toggle('open');
cartOverlay.classList.toggle('open', isOpen);
@@ -1565,6 +1613,22 @@
// Load years on init
function vsLoadYears() {
var cacheKey = 'nexus:years-all';
var cached = sessionStorage.getItem(cacheKey);
if (cached) {
var data = JSON.parse(cached);
var years = data.data || data || [];
if (!years.length) {
years = [];
for (var y = 2026; y >= 1990; y--) years.push({ id_year: y, year_car: y });
}
vsYear.innerHTML = '<option value="">Año...</option>' +
years.map(function (y) {
return '<option value="' + y.id_year + '">' + y.year_car + '</option>';
}).join('');
return;
}
apiFetch(API + '/years-all').then(function (data) {
if (!data) return;
var years = data.data || data;
@@ -1573,16 +1637,19 @@
years = [];
for (var y = 2026; y >= 1990; y--) years.push({ id_year: y, year_car: y });
}
vsYear.innerHTML = '<option value="">Año...</option>';
years.forEach(function (y) {
vsYear.innerHTML += '<option value="' + y.id_year + '">' + y.year_car + '</option>';
});
sessionStorage.setItem(cacheKey, JSON.stringify(data));
vsYear.innerHTML = '<option value="">Año...</option>' +
years.map(function (y) {
return '<option value="' + y.id_year + '">' + y.year_car + '</option>';
}).join('');
}).catch(function () {
// Fallback: generate years statically
vsYear.innerHTML = '<option value="">Año...</option>';
for (var y = 2026; y >= 1990; y--) {
vsYear.innerHTML += '<option value="' + y + '">' + y + '</option>';
}
var fallbackYears = [];
for (var y = 2026; y >= 1990; y--) fallbackYears.push(y);
vsYear.innerHTML = '<option value="">Año...</option>' +
fallbackYears.map(function (y) {
return '<option value="' + y + '">' + y + '</option>';
}).join('');
});
}
@@ -1603,10 +1670,10 @@
apiFetch(API + '/brands?year_id=' + yearId + '&mode=' + catalogMode).then(function (data) {
var brands = data.data || data;
if (!brands) return;
vsBrand.innerHTML = '<option value="">Marca...</option>';
brands.forEach(function (b) {
vsBrand.innerHTML += '<option value="' + b.id_brand + '">' + esc(b.name_brand) + '</option>';
});
vsBrand.innerHTML = '<option value="">Marca...</option>' +
brands.map(function (b) {
return '<option value="' + b.id_brand + '">' + esc(b.name_brand) + '</option>';
}).join('');
});
}
@@ -1625,10 +1692,10 @@
apiFetch(API + '/models?brand_id=' + brandId + (yearId ? '&year_id=' + yearId : '')).then(function (data) {
var models = data.data || data;
if (!models) return;
vsModel.innerHTML = '<option value="">Modelo...</option>';
models.forEach(function (m) {
vsModel.innerHTML += '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
});
vsModel.innerHTML = '<option value="">Modelo...</option>' +
models.map(function (m) {
return '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
}).join('');
});
}
@@ -1644,11 +1711,11 @@
apiFetch(API + '/engines?model_id=' + modelId + '&year_id=' + yearVal).then(function (data) {
var engines = data.data || data;
if (!engines) return;
vsEngine.innerHTML = '<option value="">Motor...</option>';
engines.forEach(function (e) {
var label = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : '');
vsEngine.innerHTML += '<option value="' + e.id_mye + '">' + esc(label) + '</option>';
});
vsEngine.innerHTML = '<option value="">Motor...</option>' +
engines.map(function (e) {
var label = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : '');
return '<option value="' + e.id_mye + '">' + esc(label) + '</option>';
}).join('');
// If only 1 engine, auto-select
if (engines.length === 1) {
vsEngine.value = engines[0].id_mye;
@@ -1841,10 +1908,10 @@
apiFetch(API + '/brands?year_id=' + match.year_id).then(function (brandData) {
var brands = brandData && (brandData.data || brandData);
if (!brands) return;
vsBrand.innerHTML = '<option value="">Marca...</option>';
brands.forEach(function (b) {
vsBrand.innerHTML += '<option value="' + b.id_brand + '">' + esc(b.name_brand) + '</option>';
});
vsBrand.innerHTML = '<option value="">Marca...</option>' +
brands.map(function (b) {
return '<option value="' + b.id_brand + '">' + esc(b.name_brand) + '</option>';
}).join('');
vsBrand.disabled = false;
vsClear.style.display = '';
@@ -1854,10 +1921,10 @@
apiFetch(API + '/models?brand_id=' + match.brand_id + '&year_id=' + match.year_id).then(function (modelData) {
var models = modelData && (modelData.data || modelData);
if (!models) return;
vsModel.innerHTML = '<option value="">Modelo...</option>';
models.forEach(function (m) {
vsModel.innerHTML += '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
});
vsModel.innerHTML = '<option value="">Modelo...</option>' +
models.map(function (m) {
return '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
}).join('');
vsModel.disabled = false;
if (match.model_id) {
@@ -1866,11 +1933,11 @@
apiFetch(API + '/engines?model_id=' + match.model_id + '&year_id=' + match.year_id).then(function (engData) {
var engines = engData && (engData.data || engData);
if (!engines) return;
vsEngine.innerHTML = '<option value="">Motor...</option>';
engines.forEach(function (e) {
var elabel = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : '');
vsEngine.innerHTML += '<option value="' + e.id_mye + '">' + esc(elabel) + '</option>';
});
vsEngine.innerHTML = '<option value="">Motor...</option>' +
engines.map(function (e) {
var elabel = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : '');
return '<option value="' + e.id_mye + '">' + esc(elabel) + '</option>';
}).join('');
vsEngine.disabled = false;
// Auto-select engine if only one or if match specifies it

View File

@@ -1731,12 +1731,12 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/accounting.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/accounting.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>
</body>

View File

@@ -11,7 +11,7 @@
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<script src="/pos/static/js/native-bridge.js"></script>
<script src="/pos/static/js/native-bridge.js" defer></script>
<style>
/* =========================================================================
@@ -820,16 +820,16 @@
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">&times;</button>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/kiosk.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/catalog.js"></script>
<script src="/pos/static/js/offline-banner.js"></script>
<script src="/pos/static/js/chat.js"></script>
<script src="/pos/static/js/sync-engine.js"></script>
<script src="/pos/static/js/onboarding.js"></script>
<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/sidebar.js" defer></script>
<script src="/pos/static/js/catalog.js" 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>
<script src="/pos/static/js/onboarding.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
</body>
</html>

View File

@@ -1880,13 +1880,13 @@
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
</style>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/kiosk.js"></script>
<script src="/pos/static/js/config.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/kiosk.js" defer></script>
<script src="/pos/static/js/config.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>
</body>

View File

@@ -2164,13 +2164,13 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/customers.js"></script>
<script src="/pos/static/js/offline-banner.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/customers.js" 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>
</body>
</html>

View File

@@ -1686,12 +1686,12 @@
</div><!-- end app-shell -->
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/dashboard.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/dashboard.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>
</body>

View File

@@ -11,7 +11,7 @@
<link rel="stylesheet" href="/pos/static/css/onboarding.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<script src="/pos/static/js/native-bridge.js"></script>
<script src="/pos/static/js/native-bridge.js" defer></script>
<style>
/* =========================================================================
@@ -604,12 +604,12 @@
</main>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/kiosk.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/diagrams.js"></script>
<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/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>
</body>
</html>

View File

@@ -963,12 +963,12 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/fleet.js"></script>
<script src="/pos/static/js/offline-banner.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/fleet.js" defer></script>
<script src="/pos/static/js/offline-banner.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
</body>

View File

@@ -2106,13 +2106,13 @@
<button class="banner__dismiss" onclick="document.getElementById('offlineBanner').style.display='none'" aria-label="Cerrar">&times;</button>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/inventory.js"></script>
<script src="/pos/static/js/offline-banner.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/inventory.js" 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>
</body>
</html>

View File

@@ -2386,12 +2386,12 @@
</div>
</div>
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/invoicing.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/invoicing.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>
</body>

View File

@@ -1362,8 +1362,8 @@
</script>
<script src="/pos/static/js/kiosk.js"></script>
<script src="/pos/static/js/sync-engine.js"></script>
<script src="/pos/static/js/kiosk.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>
</body>

View File

@@ -10,7 +10,7 @@
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" />
<script src="/pos/static/js/native-bridge.js"></script>
<script src="/pos/static/js/native-bridge.js" defer></script>
<style>
/* =====================================================================
@@ -1480,13 +1480,13 @@
<!-- ================================================================
JAVASCRIPT
================================================================ -->
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/kiosk.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/push.js"></script>
<script src="/pos/static/js/printer.js"></script>
<script src="/pos/static/js/pos.js"></script>
<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/app-init.js" 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" defer></script>
<script>
// Cancel sale button wiring
@@ -1539,8 +1539,8 @@
setInterval(updateClock, 30000);
</script>
<script src="/pos/static/js/chat.js"></script>
<script src="/pos/static/js/sync-engine.js"></script>
<script src="/pos/static/js/chat.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>
</body>
</html>

View File

@@ -36,9 +36,9 @@
</style>
</head>
<body>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/app-init.js" defer></script>
<div class="page">
<h1 class="page-title">Cotizaciones</h1>

View File

@@ -1847,12 +1847,12 @@
</div>
<!-- End app-shell -->
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/app-init.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/reports.js"></script>
<script src="/pos/static/js/sync-engine.js"></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/sidebar.js" defer></script>
<script src="/pos/static/js/reports.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>
</body>

View File

@@ -628,10 +628,10 @@ function posLogout(){localStorage.removeItem('pos_token');window.location.href='
</script>
<!-- Sidebar -->
<script src="/pos/static/js/i18n.js"></script>
<script src="/pos/static/js/whatsapp.js"></script>
<script src="/pos/static/js/pos-utils.js"></script>
<script src="/pos/static/js/sidebar.js"></script>
<script src="/pos/static/js/i18n.js" defer></script>
<script src="/pos/static/js/whatsapp.js" defer></script>
<script src="/pos/static/js/pos-utils.js" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script>
</body>
</html>