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
8.5 KiB
JavaScript
1 line
8.5 KiB
JavaScript
!function(){"use strict";var e=localStorage.getItem("pos_token");if(e){var t=null,n=null,o=null,a=document.getElementById("convList"),s=document.getElementById("chatMessages"),i=document.getElementById("chatHeaderPhone"),r=document.getElementById("chatInput"),c=document.getElementById("sendBtn"),l=document.getElementById("newChatBtn"),d=document.getElementById("emptyState"),u=document.getElementById("chatPanel"),p=document.getElementById("statusDot"),v=document.getElementById("statusText"),m=document.getElementById("connectSection"),h=document.getElementById("messengerArea"),y=document.getElementById("qrImg"),f=document.getElementById("qrPlaceholder"),g=document.getElementById("connectBtn"),E=document.getElementById("disconnectBtn"),b=document.getElementById("refreshQrBtn");g.addEventListener("click",(function(){g.disabled=!0,g.textContent="Creando instancia...",B("POST","/connect").then((function(e){g.disabled=!1,g.textContent="Conectar WhatsApp",e.error?alert("Error: "+(e.error.message||e.error)):w()})).catch((function(){g.disabled=!1,g.textContent="Conectar WhatsApp",alert("Error de red al crear instancia")}))})),E.addEventListener("click",(function(){confirm("Desconectar WhatsApp?")&&B("POST","/logout").then((function(){k("close"),q()}))})),b.addEventListener("click",w),window.deleteAllConversations=function(){confirm("Borrar TODAS las conversaciones? Esta accion no se puede deshacer.")&&B("DELETE","/conversations").then((function(e){e.ok&&(t=null,u.style.display="none",d.style.display="",D())}))};var x="";c.addEventListener("click",P),r.addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),P())})),l.addEventListener("click",(function(){var e=prompt("Numero de telefono (formato: 5215512345678):");e&&(O(e=e.replace(/[\s\-\+\(\)]/g,"")),D())}));var I=document.getElementById("sendQuoteBtn");I&&I.addEventListener("click",(function(){t?fetch("/pos/api/quotations?per_page=20",{headers:C()}).then((function(e){return e.json()})).then((function(e){var n=(e.data||[]).filter((function(e){return"active"===e.status}));if(0!==n.length){var o="Cotizaciones activas:\n";n.forEach((function(e){o+="#"+e.id+" — $"+e.total.toFixed(2)+" ("+(e.customer_name||e.source||"sin cliente")+")\n"}));var a=prompt(o+"\nEscribe el ID de la cotizacion a enviar:");a&&fetch("/pos/api/quotations/"+a,{headers:C()}).then((function(e){return e.json()})).then((function(e){if(e.error)alert("Error: "+e.error);else{var n=["📄 *COTIZACIÓN #"+e.id+"*",""];(e.items||[]).forEach((function(e,t){n.push(t+1+". "+e.name),n.push(" #"+e.part_number+" × "+e.quantity+" = $"+e.subtotal.toFixed(2))})),n.push("─────────────"),n.push("Subtotal: $"+e.subtotal.toFixed(2)),n.push("IVA: $"+e.tax_total.toFixed(2)),n.push("*TOTAL: $"+e.total.toFixed(2)+"*");var o=n.join("\n");B("POST","/send",{phone:t,message:o}).then((function(e){e.error?alert("Error enviando: "+e.error):(N(t),D())}))}}))}else alert("No hay cotizaciones activas. Crea una desde el POS (F4) o via WhatsApp.")})):alert("Selecciona una conversacion primero")})),A(),setInterval(A,3e4);try{var _=JSON.parse(atob(e.split(".")[1]));window.POS_USER={name:_.name||"Usuario",roleLabel:(_.role||"").charAt(0).toUpperCase()+(_.role||"").slice(1),initials:(_.name||"U").split(" ").map((function(e){return e[0]})).join("").slice(0,2).toUpperCase()}}catch(e){}}else window.location.href="/pos/login";function C(){return{Authorization:"Bearer "+e,"Content-Type":"application/json"}}function B(e,t,n){var o={method:e,headers:C()};return n&&(o.body=JSON.stringify(n)),fetch("/pos/api/whatsapp"+t,o).then((function(e){return 401===e.status&&(window.location.href="/pos/login"),e.json()}))}function S(e){var t=document.createElement("div");return t.textContent=e||"",t.innerHTML}function T(e){if(!e)return"";var t=new Date(e),n=new Date;return t.toDateString()===n.toDateString()?t.toLocaleTimeString("es-MX",{hour:"2-digit",minute:"2-digit"}):t.toLocaleDateString("es-MX",{day:"2-digit",month:"short"})+" "+t.toLocaleTimeString("es-MX",{hour:"2-digit",minute:"2-digit"})}function L(e){return e?13===e.length&&e.startsWith("521")?"+52 1 "+e.slice(3,5)+" "+e.slice(5,9)+" "+e.slice(9):12===e.length&&e.startsWith("52")?"+52 "+e.slice(2,4)+" "+e.slice(4,8)+" "+e.slice(8):"+"+e:""}function A(){B("GET","/status").then((function(e){k((e.instance||e).state||e.state||"close")})).catch((function(){k("close")}))}function k(e){"open"===e?(p.className="status-dot status-dot--ok",v.textContent="Conectado",m.style.display="none",h.style.display="flex",E.style.display="",g.style.display="none",D(),M()):"connecting"===e?(p.className="status-dot status-dot--warn",v.textContent="Escaneando QR...",m.style.display="flex",h.style.display="none",E.style.display="none",g.style.display="none",b.style.display=""):(p.className="status-dot status-dot--error",v.textContent="Desconectado",m.style.display="flex",h.style.display="none",E.style.display="none",g.style.display="",b.style.display="none",y.style.display="none",f.style.display="")}function w(){f.textContent="Generando QR...",B("GET","/qr").then((function(e){var t=e.qr||e.base64||e.qrcode||"";t?(y.src=t.startsWith("data:")?t:"data:image/png;base64,"+t,y.style.display="block",f.style.display="none",b.style.display="",k("connecting"),q(),o=setInterval((function(){B("GET","/status").then((function(e){"open"===((e.instance||e).state||e.state||"close")&&(k("open"),q(),D(),M())}))}),3e3)):e.instance&&"open"===e.instance.state||"open"===e.state?(k("open"),D()):(f.textContent="No se pudo generar el QR. Intenta de nuevo.",f.style.display="",y.style.display="none")})).catch((function(){f.textContent="Error al obtener QR"}))}function q(){o&&(clearInterval(o),o=null)}function D(){B("GET","/conversations").then((function(e){var n=e.conversations||[];if(0!==n.length){var o="";n.forEach((function(e){var n=e.phone===t,a="outgoing"===e.last_direction?"↗ ":"↙ ",s=e.contact_name||"";s||(s=e.phone.length>13||!/^(52|1|44|34)/.test(e.phone)?"Contacto WhatsApp":L(e.phone));o+='<div class="conv-item'+(n?" is-active":"")+'" data-phone="'+S(e.phone)+'"><div class="conv-item__phone">'+S(s)+'</div><div class="conv-item__preview">'+a+S(e.last_message||"(sin texto)")+'</div><div class="conv-item__time">'+T(e.last_at)+'</div><button class="conv-item__delete" data-del-phone="'+S(e.phone)+'" title="Borrar conversacion">×</button></div>'})),o+='<div style="padding:8px;text-align:center;"><button class="conv-delete-all" style="background:none;border:1px dashed var(--color-border,#444);color:var(--color-text-muted);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:11px;" onclick="deleteAllConversations()">Borrar todas las conversaciones</button></div>',a.innerHTML=o,a.querySelectorAll(".conv-item").forEach((function(e){e.addEventListener("click",(function(t){if(!t.target.classList.contains("conv-item__delete")){var n=e.querySelector(".conv-item__phone")?e.querySelector(".conv-item__phone").textContent:"";O(e.getAttribute("data-phone"),n)}}))})),a.querySelectorAll(".conv-item__delete").forEach((function(e){e.addEventListener("click",(function(n){n.stopPropagation();var o=e.getAttribute("data-del-phone");confirm("Borrar conversacion con "+L(o)+"?")&&function(e){B("DELETE","/conversations/"+encodeURIComponent(e)).then((function(n){n.ok?(t===e&&(t=null,u.style.display="none",d.style.display=""),D()):alert("Error: "+(n.error||"unknown"))}))}(o)}))}))}else a.innerHTML='<div class="conv-empty">No hay conversaciones</div>'})).catch((function(){a.innerHTML='<div class="conv-empty">Error cargando conversaciones</div>'}))}function O(e,n){t=e;var o=e.length>13||!/^(52|1|44|34)/.test(e);x=n||"",i.textContent=x||(o?"Contacto WhatsApp":L(e)),d.style.display="none",u.style.display="flex",a.querySelectorAll(".conv-item").forEach((function(t){t.classList.toggle("is-active",t.getAttribute("data-phone")===e)})),N(e),M()}function N(e){B("GET","/conversations/"+encodeURIComponent(e)).then((function(e){!function(e){var t="";e.forEach((function(e){var n="outgoing"===e.direction?"msg-bubble--out":"msg-bubble--in",o=e.message_text||e.text||"",a=e.created_at||e.date||"";t+='<div class="msg-bubble '+n+'"><div class="msg-bubble__text">'+S(o).replace(/\n/g,"<br>")+'</div><div class="msg-bubble__meta">'+T(a)+"</div></div>"})),s.innerHTML=t||'<div class="chat-empty">Sin mensajes</div>',s.scrollTop=s.scrollHeight}(e.messages||[])}))}function P(){var e=r.value.trim();e&&t&&(r.value="",c.disabled=!0,B("POST","/send",{phone:t,message:e}).then((function(e){c.disabled=!1,e.error?alert("Error: "+e.error):(N(t),D())})).catch((function(){c.disabled=!1,alert("Error de red al enviar mensaje")})))}function M(){n&&clearInterval(n),n=setInterval((function(){t&&N(t),D()}),1e4)}}(); |