Files
Autoparts-DB/dashboard/bodega.min.js
consultoria-as 21959f1b37 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
2026-04-27 08:34:24 +00:00

1 line
10 KiB
JavaScript

!function(){"use strict";var e=null,t=1,n="";function r(){return localStorage.getItem("access_token")||""}function o(e){var t={Authorization:"Bearer "+r()};if(e)for(var n in e)t[n]=e[n];return t}function a(){var e=localStorage.getItem("refresh_token");return e?fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e})}).then((function(e){if(!e.ok)throw new Error("Refresh failed");return e.json()})).then((function(e){return e.access_token&&localStorage.setItem("access_token",e.access_token),e})):Promise.reject(new Error("No refresh token"))}function i(e,t){return(t=t||{}).headers||(t.headers={}),t.headers.Authorization="Bearer "+r(),fetch(""+e,t).then((function(n){return 401===n.status?a().then((function(){return t.headers.Authorization="Bearer "+r(),fetch(""+e,t)})).then((function(e){return e.ok?e.json():e.json().then((function(e){throw new Error(e.error||"Error")}))})):n.ok?n.json():n.json().then((function(e){throw new Error(e.error||"Error")}))}))}function c(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}function s(e,t){var n=document.getElementById("toast-container"),r=document.createElement("div");r.className="toast "+(t||"success"),r.textContent=e,n.appendChild(r),setTimeout((function(){r.remove()}),3500)}function d(e){return e?new Date(e).toLocaleDateString("es-MX",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"—"}document.querySelectorAll(".bodega-tab").forEach((function(e){e.addEventListener("click",(function(){document.querySelectorAll(".bodega-tab").forEach((function(e){e.classList.remove("active")})),document.querySelectorAll(".bodega-section").forEach((function(e){e.classList.remove("active")})),e.classList.add("active");var t=document.getElementById("section-"+e.getAttribute("data-tab"));t&&t.classList.add("active");var n=e.getAttribute("data-tab");"subir"===n&&p(),"inventario"===n&&g()}))})),document.getElementById("btn-save-mapping").addEventListener("click",(function(){var e=document.getElementById("map-part-number").value.trim(),t=document.getElementById("map-price").value.trim(),n=document.getElementById("map-stock").value.trim(),r=document.getElementById("map-location").value.trim(),a=document.getElementById("mapping-status");if(!e||!t||!n)return a.textContent="Completa los campos obligatorios.",void(a.className="status-msg error");var c={part_number:e,price:t,stock:n,location:r||null};a.textContent="Guardando...",a.className="status-msg",i("/api/inventory/mapping",{method:"PUT",headers:o({"Content-Type":"application/json"}),body:JSON.stringify(c)}).then((function(){a.textContent="Mapeo guardado correctamente.",a.className="status-msg success",s("Mapeo guardado","success")})).catch((function(e){a.textContent=e.message||"Error al guardar.",a.className="status-msg error"}))}));var l=document.getElementById("drop-zone"),u=document.getElementById("file-input");function m(t){var n=(t.name||"").split(".").pop().toLowerCase();-1!==["text/csv","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"].indexOf(t.type)||-1!==["csv","xls","xlsx"].indexOf(n)?t.size>10485760?s("El archivo excede 10MB.","error"):(e=t,document.getElementById("selected-file-name").textContent=t.name+" ("+(t.size/1024).toFixed(1)+" KB)",document.getElementById("selected-file").style.display="flex",document.getElementById("btn-upload").disabled=!1):s("Formato no soportado. Usa CSV o Excel.","error")}function p(){i("/api/inventory/uploads").then((function(e){var t=e.uploads||e.data||e||[],n=document.getElementById("history-body");Array.isArray(t)&&0!==t.length?n.innerHTML=t.map((function(e){return"<tr><td>"+c(e.filename||e.archivo||"—")+"</td><td>"+(t=e.status||e.estado,'<span class="badge '+(n={completed:{cls:"badge-success",label:"Completado"},success:{cls:"badge-success",label:"Completado"},error:{cls:"badge-error",label:"Error"},failed:{cls:"badge-error",label:"Fallido"},pending:{cls:"badge-pending",label:"Pendiente"},processing:{cls:"badge-processing",label:"Procesando"}}[(t||"").toLowerCase()]||{cls:"badge-pending",label:t||"Desconocido"}).cls+'">'+c(n.label)+"</span></td><td>")+(null!=e.imported_count?e.imported_count:null!=e.importados?e.importados:"—")+"</td><td>"+(null!=e.error_count?e.error_count:null!=e.errores?e.errores:"—")+"</td><td>"+d(e.created_at||e.fecha)+"</td></tr>";var t,n})).join(""):n.innerHTML='<tr><td colspan="5" class="empty-row">Sin cargas previas</td></tr>'})).catch((function(){document.getElementById("history-body").innerHTML='<tr><td colspan="5" class="empty-row">Error al cargar historial</td></tr>'}))}function g(){var e="?page="+t;n&&(e+="&q="+encodeURIComponent(n)),i("/api/inventory/items"+e).then((function(e){var t=e.data||e.items||[],n=e.pagination||{},r=document.getElementById("inv-body");if(!Array.isArray(t)||0===t.length)return r.innerHTML='<tr><td colspan="6" class="empty-row">Sin articulos en inventario</td></tr>',void f(n);r.innerHTML=t.map((function(e){return"<tr><td><strong>"+c(e.part_number)+"</strong></td><td>"+c(e.name||e.nombre||"—")+"</td><td>"+(t=e.price||e.precio,n=parseFloat(t),(isNaN(n)?"—":"$"+n.toLocaleString("es-MX",{minimumFractionDigits:2,maximumFractionDigits:2}))+"</td><td>")+(null!=e.stock?e.stock:null!=e.existencias?e.existencias:"—")+"</td><td>"+c(e.location||e.ubicacion||"—")+"</td><td>"+d(e.updated_at||e.actualizado)+"</td></tr>";var t,n})).join(""),f(n)})).catch((function(){document.getElementById("inv-body").innerHTML='<tr><td colspan="6" class="empty-row">Error al cargar inventario</td></tr>'}))}function f(e){var n=document.getElementById("inv-pagination");if(!e||!e.total_pages||e.total_pages<=1)n.innerHTML="";else{var r=e.page||e.current_page||1,o=e.total_pages,a="";a+="<button "+(r<=1?"disabled":"")+' data-page="'+(r-1)+'">Anterior</button>';var i=Math.max(1,r-2),c=Math.min(o,r+2);i>1&&(a+='<button data-page="1">1</button>',i>2&&(a+="<button disabled>...</button>"));for(var s=i;s<=c;s++)a+='<button data-page="'+s+'"'+(s===r?' class="active"':"")+">"+s+"</button>";c<o&&(c<o-1&&(a+="<button disabled>...</button>"),a+='<button data-page="'+o+'">'+o+"</button>"),a+="<button "+(r>=o?"disabled":"")+' data-page="'+(r+1)+'">Siguiente</button>',n.innerHTML=a,n.querySelectorAll("button[data-page]").forEach((function(e){e.addEventListener("click",(function(){t=parseInt(e.getAttribute("data-page"),10),g()}))}))}}l.addEventListener("click",(function(){u.click()})),l.addEventListener("dragover",(function(e){e.preventDefault(),l.classList.add("dragover")})),l.addEventListener("dragleave",(function(){l.classList.remove("dragover")})),l.addEventListener("drop",(function(e){e.preventDefault(),l.classList.remove("dragover"),e.dataTransfer.files.length&&m(e.dataTransfer.files[0])})),u.addEventListener("change",(function(){u.files.length&&m(u.files[0])})),document.getElementById("btn-clear-file").addEventListener("click",(function(){e=null,u.value="",document.getElementById("selected-file").style.display="none",document.getElementById("btn-upload").disabled=!0})),document.getElementById("btn-upload").addEventListener("click",(function(){if(e){var t=document.getElementById("btn-upload"),n=document.getElementById("upload-status");t.disabled=!0,n.textContent="Subiendo...",n.className="status-msg";var o=new FormData;o.append("file",e),fetch("/api/inventory/upload",{method:"POST",headers:{Authorization:"Bearer "+r()},body:o}).then((function(e){return 401===e.status?a().then((function(){return fetch("/api/inventory/upload",{method:"POST",headers:{Authorization:"Bearer "+r()},body:o})})):e})).then((function(e){return e.json().then((function(t){if(!e.ok)throw new Error(t.error||"Error al subir");return t}))})).then((function(r){n.textContent="",function(e){var t=document.getElementById("upload-result"),n=e.imported||e.imported_count||0,r=e.errors||e.error_count||0,o=e.error_samples||[],a="<h4>Resultado de la Carga</h4>";a+='<div class="result-stats">',a+='<span class="result-stat ok">Importados: '+n+"</span>",a+='<span class="result-stat err">Errores: '+r+"</span>",a+="</div>",o.length&&(a+='<div class="error-samples">',a+="<strong>Ejemplos de errores:</strong>",o.forEach((function(e){a+="<p>"+c("string"==typeof e?e:JSON.stringify(e))+"</p>"})),a+="</div>");t.innerHTML=a,t.style.display="block"}(r),s("Archivo procesado correctamente.","success"),p(),e=null,u.value="",document.getElementById("selected-file").style.display="none",t.disabled=!0})).catch((function(e){n.textContent=e.message||"Error al subir archivo.",n.className="status-msg error",t.disabled=!1}))}})),document.getElementById("btn-inv-search").addEventListener("click",(function(){n=document.getElementById("inv-search").value.trim(),t=1,g()})),document.getElementById("inv-search").addEventListener("keydown",(function(e){"Enter"===e.key&&(n=this.value.trim(),t=1,g())})),document.getElementById("btn-clear-all").addEventListener("click",(function(){var e,t,n;e="Limpiar Inventario",t="Se eliminaran todos los articulos de tu inventario. Esta accion no se puede deshacer.",n=function(){i("/api/inventory/items",{method:"DELETE",headers:o()}).then((function(){s("Inventario limpiado correctamente.","success"),g()})).catch((function(e){s(e.message||"Error al limpiar inventario.","error")}))},document.getElementById("confirm-title").textContent=e,document.getElementById("confirm-msg").textContent=t,document.getElementById("confirm-modal").classList.add("active"),y=n}));var v,h,y=null;document.getElementById("confirm-cancel").addEventListener("click",(function(){document.getElementById("confirm-modal").classList.remove("active"),y=null})),document.getElementById("confirm-ok").addEventListener("click",(function(){document.getElementById("confirm-modal").classList.remove("active"),y&&(y(),y=null)})),document.getElementById("confirm-modal").addEventListener("click",(function(e){e.target===this&&(this.classList.remove("active"),y=null)})),v=r(),h=function(){var e=r();if(!e)return null;try{return JSON.parse(atob(e.split(".")[1])).role||null}catch(e){return null}}(),(v&&("BODEGA"===h||"ADMIN"===h)||(window.location.href="/login.html",0))&&i("/api/inventory/mapping").then((function(e){e.part_number&&(document.getElementById("map-part-number").value=e.part_number),e.price&&(document.getElementById("map-price").value=e.price),e.stock&&(document.getElementById("map-stock").value=e.stock),e.location&&(document.getElementById("map-location").value=e.location)})).catch((function(){}))}();