Files
Autoparts-DB/dashboard/captura.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
16 KiB
JavaScript

!function(){"use strict";var e=null,t=null,a=[],n=[],r="pending",i=1;function o(e,t){var a=document.createElement("div");a.className="toast "+(t||"success"),a.textContent=e,document.body.appendChild(a),setTimeout((function(){a.remove()}),3e3)}function c(e,t){return t=t||{},fetch(""+e,t).then((function(e){return e.ok?e.json():e.json().then((function(e){throw new Error(e.error||"Error")}))}))}function d(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}document.querySelectorAll(".captura-tab").forEach((function(e){e.addEventListener("click",(function(){document.querySelectorAll(".captura-tab").forEach((function(e){e.classList.remove("active")})),document.querySelectorAll(".captura-section").forEach((function(e){e.classList.remove("active")})),e.classList.add("active");var t=e.getAttribute("data-tab");document.getElementById("section-"+t).classList.add("active"),"aftermarket"===t&&g(),"images"===t&&h()}))})),document.querySelectorAll(".status-tab").forEach((function(e){e.addEventListener("click",(function(){document.querySelectorAll(".status-tab").forEach((function(e){e.classList.remove("active")})),e.classList.add("active"),r=e.getAttribute("data-status"),i=1,s()}))})),document.getElementById("oem-brand-filter").addEventListener("change",(function(){i=1,s()}));var l=null;function s(){var n=document.getElementById("oem-brand-filter").value,l=document.getElementById("oem-model-filter").value,g=document.getElementById("oem-vehicle-list");g.innerHTML='<div class="loading"><div class="spinner"></div></div>';var y="pending"===r?"/api/captura/vehicles/pending":"/api/captura/vehicles/in-progress",h="?page="+i+"&per_page=30";n&&(h+="&brand="+encodeURIComponent(n)),l&&(h+="&model="+encodeURIComponent(l)),c(y+h).then((function(n){var l=n.data||[];if(0===l.length)return g.innerHTML='<div class="empty-state"><div class="es-icon">&#128203;</div><div class="es-text">No hay vehiculos '+("pending"===r?"pendientes":"en progreso")+"</div></div>",void(document.getElementById("oem-vehicle-pagination").innerHTML="");g.innerHTML=l.map((function(e){return'<div class="vehicle-card" data-mye="'+e.id_mye+'"><div class="vc-brand">'+d(e.brand)+'</div><div class="vc-model">'+d(e.model)+'</div><div class="vc-details">'+e.year+" &middot; "+d(e.engine)+(e.trim_level?" &middot; "+d(e.trim_level):"")+"</div>"+(e.parts_count?'<div class="vc-parts-count">'+e.parts_count+" partes registradas</div>":"")+"</div>"})).join(""),g.querySelectorAll(".vehicle-card").forEach((function(n){n.addEventListener("click",(function(){var r;r=parseInt(n.getAttribute("data-mye")),e=r,document.getElementById("oem-vehicle-select").style.display="none",document.getElementById("oem-part-entry").style.display="block",c("/api/captura/vehicles/"+r+"/status",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:"in_progress"})}),function(n){c("/api/captura/vehicles/"+n+"/parts").then((function(n){t=n.vehicle,a=n.parts||[],document.getElementById("oem-vehicle-header").innerHTML='<div class="vh-info"><div><div class="vh-label">Marca</div><div class="vh-value vh-brand">'+d(t.brand)+'</div></div><div><div class="vh-label">Modelo</div><div class="vh-value">'+d(t.model)+'</div></div><div><div class="vh-label">Ano</div><div class="vh-value">'+t.year+'</div></div><div><div class="vh-label">Motor</div><div class="vh-value">'+d(t.engine)+"</div></div>"+(t.trim_level?'<div><div class="vh-label">Trim</div><div class="vh-value">'+d(t.trim_level)+"</div></div>":"")+'</div><div class="vh-actions"><button class="btn btn-secondary" id="btn-back-vehicles">&#9664; Volver</button><button class="btn btn-primary" id="btn-complete-vehicle">Terminado &#10003;</button></div>',document.getElementById("btn-back-vehicles").addEventListener("click",p),document.getElementById("btn-complete-vehicle").addEventListener("click",m),function(t,n){var r=document.getElementById("oem-groups-container"),i={};t.forEach((function(e){i[e.category]||(i[e.category]={id:e.id_part_category,groups:[]}),i[e.category].groups.push(e)}));var l="";Object.keys(i).forEach((function(e){var t=i[e],a=n.filter((function(e){return t.groups.some((function(t){return t.id_part_group===e.group_id}))}));l+='<div class="category-section"><div class="category-header" data-cat="'+t.id+'"><h3>'+d(e)+" ("+a.length+')</h3><span class="cat-toggle">&#9660;</span></div><div class="category-body" data-cat-body="'+t.id+'">',t.groups.forEach((function(e){var t=n.filter((function(t){return t.group_id===e.id_part_group}));l+='<div class="group-section" data-group="'+e.id_part_group+'"><div class="group-name">'+d(e.group_name)+'</div><div class="part-rows" data-group-parts="'+e.id_part_group+'">',t.forEach((function(e){l+=v(e)})),l+='</div><button class="btn-add-part" data-group-id="'+e.id_part_group+'">+ Agregar pieza</button></div>'})),l+="</div></div>"})),r.innerHTML=l,r.querySelectorAll(".category-header").forEach((function(e){e.addEventListener("click",(function(){var t=e.getAttribute("data-cat"),a=r.querySelector('[data-cat-body="'+t+'"]');e.classList.toggle("collapsed"),a.classList.toggle("collapsed")}))})),r.querySelectorAll(".btn-add-part").forEach((function(t){t.addEventListener("click",(function(){var n,r,i;n=parseInt(t.getAttribute("data-group-id")),r=document.querySelector('[data-group-parts="'+n+'"]'),(i=document.createElement("div")).className="part-row",i.innerHTML='<input class="pr-oem" placeholder="# OEM" data-group="'+n+'"><input class="pr-name" placeholder="Nombre pieza"><input class="pr-qty" value="1" type="number" min="1"><button class="pr-btn pr-save" title="Guardar">&#10003;</button><button class="pr-btn pr-delete" title="Quitar">&#10005;</button>',r.appendChild(i),i.querySelector(".pr-oem").focus(),i.querySelector(".pr-oem").addEventListener("blur",(function(){var e=this.value.trim();e&&c("/api/captura/parts/check-oem?oem="+encodeURIComponent(e)).then((function(e){e.exists&&(i.querySelector(".pr-name").value=e.part.name_part||"",i.querySelector(".pr-name").style.borderColor="var(--success)",i.dataset.existingPartId=e.part.id_part)}))})),i.querySelector(".pr-save").addEventListener("click",(function(){!function(t,n){var r=t.querySelector(".pr-oem").value.trim(),i=t.querySelector(".pr-name").value.trim(),d=parseInt(t.querySelector(".pr-qty").value)||1;if(!r)return o("Ingresa el numero OEM","error"),void t.querySelector(".pr-oem").focus();var l=t.querySelector(".pr-save");l.disabled=!0,l.textContent="...";var s=t.dataset.existingPartId;function u(s){c("/api/admin/fitment",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model_year_engine_id:e,part_id:s,quantity_required:d})}).then((function(e){var l={id_vehicle_part:e.id,part_id:s,oem_part_number:r,name_part:i,quantity_required:d,group_id:n};a.push(l),t.outerHTML=v(l),f(),o("Parte guardada: "+r),document.querySelectorAll(".part-row.saved .pr-delete").forEach((function(e){e.onclick=function(){var t=e.closest(".part-row"),n=t.getAttribute("data-fitment-id");n?c("/api/admin/fitment/"+n,{method:"DELETE"}).then((function(){a=a.filter((function(e){return e.id_vehicle_part!==parseInt(n)})),t.remove(),f(),o("Parte eliminada")})).catch((function(e){o(e.message,"error")})):t.remove()}}))})).catch((function(e){o(e.message,"error"),l.disabled=!1,l.textContent="✓"}))}s?u(parseInt(s)):c("/api/admin/parts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oem_part_number:r,name:i||r,group_id:n})}).then((function(e){u(e.id)})).catch((function(e){o(e.message,"error"),l.disabled=!1,l.textContent="✓"}))}(i,n)})),i.querySelector(".pr-delete").addEventListener("click",(function(){i.remove()}))}))}))}(n.groups,a),f()}))}(r)}))})),u("oem-vehicle-pagination",n.pagination,(function(e){i=e,s()}))}))}function u(e,t,a){var n=document.getElementById(e);!t||t.total_pages<=1?n.innerHTML="":(n.innerHTML="<button "+(t.page<=1?"disabled":"")+' data-p="'+(t.page-1)+'">&laquo; Anterior</button><span class="page-info">Pag '+t.page+" de "+t.total_pages+" ("+t.total+" total)</span><button "+(t.page>=t.total_pages?"disabled":"")+' data-p="'+(t.page+1)+'">Siguiente &raquo;</button>',n.querySelectorAll("button").forEach((function(e){e.addEventListener("click",(function(){a(parseInt(e.getAttribute("data-p")))}))})))}function p(){document.getElementById("oem-vehicle-select").style.display="block",document.getElementById("oem-part-entry").style.display="none",e=null,s()}function m(){0!==a.length?c("/api/captura/vehicles/"+e+"/status",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:"completed"})}).then((function(){o("Vehiculo completado"),p()})):o("Registra al menos una parte antes de marcar como terminado","error")}function v(e){return'<div class="part-row saved" data-fitment-id="'+e.id_vehicle_part+'"><input class="pr-oem" value="'+d(e.oem_part_number)+'" readonly><input class="pr-name" value="'+d(e.name_part||"")+'" readonly><input class="pr-qty" value="'+(e.quantity_required||1)+'" readonly><button class="pr-btn pr-delete" title="Eliminar">&#10005;</button></div>'}function f(){var e=a.length,t=Math.min(100,Math.round(e/63*100));document.getElementById("oem-progress-fill").style.width=t+"%",document.getElementById("oem-progress-text").textContent=e+" partes registradas",document.querySelectorAll(".category-header h3").forEach((function(e){var t=e.closest(".category-section").querySelectorAll(".part-row.saved"),a=e.textContent.replace(/\s*\(\d+\)$/,"");e.textContent=a+" ("+t.length+")"}))}document.getElementById("oem-model-filter").addEventListener("input",(function(){clearTimeout(l),l=setTimeout((function(){i=1,s()}),400)}));function g(e){e=e||1;var t=document.getElementById("aftermarket-search").value,a=document.getElementById("aftermarket-list");a.innerHTML='<div class="loading"><div class="spinner"></div></div>';var r="?page="+e+"&per_page=20";t&&(r+="&search="+encodeURIComponent(t)),c("/api/captura/parts/without-aftermarket"+r).then((function(e){var t=e.data||[];if(0===t.length)return a.innerHTML='<div class="empty-state"><div class="es-icon">&#9989;</div><div class="es-text">No hay piezas sin intercambios</div></div>',void(document.getElementById("aftermarket-pagination").innerHTML="");a.innerHTML=t.map((function(e){return'<div class="part-detail-card" data-part-id="'+e.id_part+'"><div class="pdc-header"><div><span class="pdc-oem">'+d(e.oem_part_number)+'</span> <span class="pdc-name">'+d(e.name_part)+'</span></div><span class="pdc-group">'+d(e.category)+" &rsaquo; "+d(e.group_name)+'</span></div><div class="aftermarket-existing" data-af-list="'+e.id_part+'"></div><div class="aftermarket-form" data-af-form="'+e.id_part+'"><div class="af-field"><label>Fabricante</label><select class="af-manufacturer">'+n.map((function(e){return'<option value="'+e.id+'">'+d(e.name)+"</option>"})).join("")+'</select></div><div class="af-field"><label># Aftermarket</label><input class="af-partnum" placeholder="Ej: MK1234"></div><div class="af-field"><label>Nombre</label><input class="af-name" placeholder="Nombre pieza"></div><div class="af-field"><label>Calidad</label><select class="af-quality"><option value="standard">Standard</option><option value="economy">Economy</option><option value="oem">OEM</option><option value="premium">Premium</option></select></div><div class="af-field"><label>Precio USD</label><input class="af-price" type="number" step="0.01" placeholder="0.00" style="width:80px"></div><div class="af-field"><label>Garantia (meses)</label><input class="af-warranty" type="number" placeholder="12" style="width:70px"></div><button class="btn btn-primary af-save-btn" style="padding:0.4rem 1rem">+ Agregar</button></div></div>'})).join(""),t.forEach((function(e){y(e.id_part)})),a.querySelectorAll(".af-save-btn").forEach((function(e){e.addEventListener("click",(function(){!function(e){var t=e.getAttribute("data-part-id"),a=e.querySelector(".af-manufacturer").value,n=e.querySelector(".af-partnum").value.trim(),r=e.querySelector(".af-name").value.trim(),i=e.querySelector(".af-quality").value,d=e.querySelector(".af-price").value,l=e.querySelector(".af-warranty").value;if(!n)return void o("Ingresa el numero de parte aftermarket","error");c("/api/admin/aftermarket",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oem_part_id:parseInt(t),manufacturer_id:parseInt(a),part_number:n,name:r,quality_tier:i,price_usd:d?parseFloat(d):null,warranty_months:l?parseInt(l):null})}).then((function(){o("Intercambio guardado: "+n),e.querySelector(".af-partnum").value="",e.querySelector(".af-name").value="",e.querySelector(".af-price").value="",e.querySelector(".af-warranty").value="",y(parseInt(t))})).catch((function(e){o(e.message,"error")}))}(e.closest(".part-detail-card"))}))})),u("aftermarket-pagination",e.pagination,(function(e){g(e)}))}))}function y(e){c("/api/captura/parts/"+e+"/aftermarket").then((function(t){var a=document.querySelector('[data-af-list="'+e+'"]');if(0!==t.length){var n='<table class="aftermarket-table"><thead><tr><th>Fabricante</th><th># Parte</th><th>Nombre</th><th>Calidad</th><th>Precio</th><th>Garantia</th></tr></thead><tbody>';t.forEach((function(e){n+="<tr><td>"+d(e.manufacturer)+"</td><td>"+d(e.part_number)+"</td><td>"+d(e.name||"")+"</td><td>"+d(e.quality||"")+"</td><td>"+(e.price_usd?"$"+e.price_usd:"")+"</td><td>"+(e.warranty_months||"")+"</td></tr>"})),n+="</tbody></table>",a.innerHTML=n}else a.innerHTML='<p style="font-size:0.8rem;color:var(--text-secondary);margin-bottom:0.5rem">Sin intercambios registrados</p>'}))}function h(e){e=e||1;var t=document.getElementById("image-search").value,a=document.getElementById("image-list");a.innerHTML='<div class="loading"><div class="spinner"></div></div>';var n="?page="+e+"&per_page=20";t&&(n+="&search="+encodeURIComponent(t)),c("/api/captura/parts/without-image"+n).then((function(e){var t=e.data||[];if(0===t.length)return a.innerHTML='<div class="empty-state"><div class="es-icon">&#128247;</div><div class="es-text">No hay piezas sin imagen</div></div>',void(document.getElementById("image-pagination").innerHTML="");a.innerHTML=t.map((function(e){return'<div class="image-card" data-part-id="'+e.id_part+'"><div class="ic-preview"><span>Sin imagen</span></div><div class="ic-info"><div class="ic-oem">'+d(e.oem_part_number)+'</div><div class="ic-name">'+d(e.name_part)+" &middot; "+d(e.group_name)+'</div><div class="ic-upload"><input type="file" accept="image/jpeg,image/png,image/webp" class="ic-file-input"><button class="btn btn-primary ic-upload-btn" style="padding:0.3rem 0.8rem;font-size:0.8rem" disabled>Subir</button></div></div></div>'})).join(""),a.querySelectorAll(".ic-file-input").forEach((function(e){e.addEventListener("change",(function(){var t=e.closest(".image-card"),a=t.querySelector(".ic-upload-btn"),n=t.querySelector(".ic-preview");if(e.files&&e.files[0]){a.disabled=!1;var r=new FileReader;r.onload=function(e){n.innerHTML='<img src="'+e.target.result+'">'},r.readAsDataURL(e.files[0])}}))})),a.querySelectorAll(".ic-upload-btn").forEach((function(e){e.addEventListener("click",(function(){!function(e){var t=e.getAttribute("data-part-id"),a=e.querySelector(".ic-file-input"),n=e.querySelector(".ic-upload-btn");if(!a.files||!a.files[0])return;n.disabled=!0,n.textContent="Subiendo...";var r=new FormData;r.append("image",a.files[0]),fetch("/api/captura/parts/"+t+"/image",{method:"POST",body:r}).then((function(e){return e.json()})).then((function(t){if(t.error)throw new Error(t.error);o("Imagen subida correctamente"),e.style.opacity="0.3",setTimeout((function(){e.remove()}),500)})).catch((function(e){o(e.message,"error"),n.disabled=!1,n.textContent="Subir"}))}(e.closest(".image-card"))}))})),u("image-pagination",e.pagination,(function(e){h(e)}))}))}window.loadPartsWithoutAftermarket=g,window.loadPartsWithoutImage=h,c("/api/brands").then((function(e){var t=document.getElementById("oem-brand-filter");e.forEach((function(e){var a=document.createElement("option");a.value=e,a.textContent=e,t.appendChild(a)}))})),s(),c("/api/captura/manufacturers").then((function(e){n=e}))}();