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

Cambios implementados:

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

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

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

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

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

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

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