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
1 line
5.1 KiB
JavaScript
1 line
5.1 KiB
JavaScript
!function(){"use strict";var e=null,t=1;function n(e,t){var n=document.createElement("div");n.className="toast "+(t||"success"),n.textContent=e,document.body.appendChild(n),setTimeout((function(){n.remove()}),3e3)}function a(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 o(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}function i(e){return"$"+(parseFloat(e)||0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")}function c(e){return e?new Date(e).toLocaleDateString("es-MX",{day:"2-digit",month:"short",year:"numeric"}):""}var d=null;function r(){var e=document.getElementById("customer-search").value,n=document.getElementById("customer-grid");n.innerHTML='<div class="empty-state">Cargando...</div>';var c="?page="+t+"&per_page=30";e&&(c+="&search="+encodeURIComponent(e)),a("/api/pos/customers"+c).then((function(e){var a=e.data||[];if(0===a.length)return n.innerHTML='<div class="empty-state">No se encontraron clientes</div>',void(document.getElementById("customer-pagination").innerHTML="");n.innerHTML=a.map((function(e){return'<div class="customer-card-item" data-id="'+e.id_customer+'"><div class="cci-name">'+o(e.name)+'</div><div class="cci-rfc">'+o(e.rfc||"Sin RFC")+'</div><div class="cci-balance-row"><span class="cci-balance '+(e.balance>0?"positive":"zero")+'">'+i(e.balance)+'</span><span class="cci-limit">Limite: '+i(e.credit_limit)+"</span></div></div>"})).join(""),n.querySelectorAll(".customer-card-item").forEach((function(e){e.addEventListener("click",(function(){s(parseInt(e.getAttribute("data-id")))}))}));var c=e.pagination,d=document.getElementById("customer-pagination");c.total_pages<=1?d.innerHTML="":(d.innerHTML="<button "+(c.page<=1?"disabled":"")+' data-p="'+(c.page-1)+'">«</button><span class="page-info">Pag '+c.page+"/"+c.total_pages+"</span><button "+(c.page>=c.total_pages?"disabled":"")+' data-p="'+(c.page+1)+'">»</button>',d.querySelectorAll("button").forEach((function(e){e.addEventListener("click",(function(){t=parseInt(e.getAttribute("data-p")),r()}))})))})).catch((function(e){console.error("Error loading customers:",e),n.innerHTML='<div class="empty-state">Error al cargar clientes</div>'}))}function s(t){e=t,document.getElementById("list-view").style.display="none",document.getElementById("detail-view").style.display="block",a("/api/pos/customers/"+t+"/statement").then((function(e){var t=e.customer;document.getElementById("dh-name").textContent=t.name,document.getElementById("dh-rfc").textContent=t.rfc||"Sin RFC";var n=document.getElementById("dh-balance");n.textContent=i(t.balance),n.className="dh-value "+(t.balance>0?"danger":"success"),document.getElementById("dh-limit").textContent=i(t.credit_limit),document.getElementById("dh-terms").textContent=t.payment_terms+" dias";var a=document.getElementById("invoice-list");0===e.invoices.length?a.innerHTML='<tr><td colspan="5" class="empty-state">Sin facturas</td></tr>':a.innerHTML=e.invoices.map((function(e){return'<tr><td style="font-family:monospace;font-weight:600">'+o(e.folio)+"</td><td>"+c(e.date_issued)+"</td><td>"+i(e.total)+"</td><td>"+i(e.amount_paid)+'</td><td><span class="status-badge '+e.status+'">'+e.status+"</span></td></tr>"})).join("");var d=document.getElementById("payment-list");0===e.payments.length?d.innerHTML='<tr><td colspan="5" class="empty-state">Sin pagos</td></tr>':d.innerHTML=e.payments.map((function(e){return"<tr><td>"+c(e.date_payment)+'</td><td style="font-weight:600;color:var(--success)">'+i(e.amount)+"</td><td>"+o(e.payment_method)+"</td><td>"+o(e.reference||"")+"</td><td>"+o(e.invoice_folio||"General")+"</td></tr>"})).join("");var r=document.getElementById("pay-invoice"),s=e.invoices.filter((function(e){return"paid"!==e.status&&"cancelled"!==e.status})).map((function(e){return'<option value="'+e.id_invoice+'">'+e.folio+" — "+i(e.total-e.amount_paid)+" pendiente</option>"})).join("");r.innerHTML='<option value="">Abono general</option>'+s}))}document.getElementById("customer-search").addEventListener("input",(function(){clearTimeout(d),d=setTimeout((function(){t=1,r()}),400)})),document.getElementById("btn-back-list").addEventListener("click",(function(){document.getElementById("detail-view").style.display="none",document.getElementById("list-view").style.display="block",e=null,r()})),document.getElementById("btn-pay").addEventListener("click",(function(){var t=parseFloat(document.getElementById("pay-amount").value);if(!t||t<=0)n("Ingresa un monto valido","error");else{var o=document.getElementById("pay-invoice").value;a("/api/pos/payments",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({customer_id:e,amount:t,payment_method:document.getElementById("pay-method").value,reference:document.getElementById("pay-reference").value.trim()||null,invoice_id:o?parseInt(o):null,notes:document.getElementById("pay-notes").value.trim()||null})}).then((function(){n("Pago de "+i(t)+" registrado"),document.getElementById("pay-amount").value="",document.getElementById("pay-reference").value="",document.getElementById("pay-notes").value="",s(e)})).catch((function(e){n(e.message,"error")}))}})),r()}(); |