Files
Autoparts-DB/pos/static/js/customers.min.js
consultoria-as b803950fae chore(assets): regenerate minified JS bundles
- customers.min.js, fleet.min.js, inventory.min.js, pos-utils.min.js,
  sidebar.min.js, virtual-scroll.min.js
2026-04-29 06:31:25 +00:00

1 line
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const Customers=(()=>{let e=localStorage.getItem("pos_token")||"",t=1,n=1,o=null,a=null;const i=e=>"$"+parseFloat(e||0).toLocaleString("es-MX",{minimumFractionDigits:2,maximumFractionDigits:2});async function l(t,n={}){n.headers={"Content-Type":"application/json",Authorization:"Bearer "+e};const o=await fetch(t,n);if(401===o.status)return void(window.location.href="/pos/login");const a=await o.json();if(!o.ok)throw new Error(a.error||`HTTP ${o.status}`);return a}const d={1:"Mostrador",2:"Taller",3:"Mayoreo"},r={1:"mostrador",2:"taller",3:"mayoreo"};function s(e){return e.credit_balance>0&&e.credit_limit>0&&e.credit_balance>e.credit_limit?'<span class="badge badge--warning"><span class="badge-dot"></span>Mora</span>':'<span class="badge badge--active"><span class="badge-dot"></span>Activo</span>'}function c(e){if(!e)return"--";const t=e.trim().split(/\s+/);return t.length>=2?(t[0][0]+t[1][0]).toUpperCase():e.substring(0,2).toUpperCase()}function m(e){if(!e)return"-";try{return new Date(e).toLocaleDateString("es-MX",{day:"2-digit",month:"short",year:"numeric"})}catch(t){return e}}async function p(e,o){e=e||t;const a=document.getElementById("searchInput");o=void 0!==o?o:a&&a.value||"";try{const a=new URLSearchParams({page:e,per_page:50});o&&a.append("q",o);const i=await l(`/pos/api/customers?${a}`);!function(e){const t=document.getElementById("customersBody");if(!t)return;if(!e||0===e.length)return void(t.innerHTML='<tr><td colspan="9" style="text-align:center;padding:var(--space-8);color:var(--color-text-muted);">Sin resultados.</td></tr>');g||(g=new VirtualScroll({container:t,rowHeight:52,buffer:3,renderRow:u,emptyHtml:'<tr><td colspan="9" style="text-align:center;padding:var(--space-8);color:var(--color-text-muted);">Sin resultados.</td></tr>'}));g.setData(e)}(i.data||[]),function(e){const o=document.querySelector(".pagination"),a=document.getElementById("tableInfo");if(!e||!e.total_pages)return o&&(o.innerHTML=""),void(a&&(a.textContent=""));n=e.total_pages,t=e.page;const i=e.total||0,l=e.per_page||50,d=(e.page-1)*l+1,r=Math.min(e.page*l,i);a&&(a.textContent=`Mostrando ${d}${r} de ${i.toLocaleString("es-MX")} clientes`);if(!o)return;if(n<=1)return void(o.innerHTML="");let s="";s+=`<button class="page-btn" ${e.page<=1?"disabled":""} onclick="Customers.goToPage(${e.page-1})">&#8249;</button>`;const c=7;let m=Math.max(1,e.page-3),p=Math.min(n,m+c-1);p-m<c-1&&(m=Math.max(1,p-c+1));for(let t=m;t<=p;t++)s+=`<button class="page-btn ${t===e.page?"active":""}" onclick="Customers.goToPage(${t})">${t}</button>`;p<n&&(s+='<span style="color:var(--color-text-muted);font-size:var(--text-caption);padding:0 4px;">...</span>',s+=`<button class="page-btn" onclick="Customers.goToPage(${n})">${n}</button>`);s+=`<button class="page-btn" ${e.page>=n?"disabled":""} onclick="Customers.goToPage(${e.page+1})">&#8250;</button>`,o.innerHTML=s}(i.pagination||{})}catch(e){console.error("Load customers failed:",e)}}function u(e){const t=d[e.price_tier]||"Mostrador",n=r[e.price_tier]||"mostrador",a=parseFloat(e.credit_limit||0),l=parseFloat(e.credit_balance||0),c=Math.max(0,a-l),p=a>0?Math.round(l/a*100):0,u=p>=80?"none":p>=60?"low":"",g=String(e.id).padStart(5,"0");return'<tr class="'+(o&&o.id===e.id?"selected":"")+'" onclick="selectCustomer('+e.id+')"><td class="cell-num">'+g+'</td><td><div class="cell-name">'+(e.name||"")+'</div><div class="cell-name-sub hide-mobile">'+(e.email||"")+'</div></td><td class="cell-rfc hide-mobile">'+(e.rfc||"-")+'</td><td class="hide-mobile">'+(e.phone||"-")+'</td><td class="hide-mobile" style="font-size:var(--text-caption);color:var(--color-text-secondary);">'+(e.email||"-")+'</td><td><span class="tipo-chip tipo-chip--'+n+'">'+t+'</span></td><td class="cell-credit '+u+'">'+i(c)+'</td><td class="cell-date hide-mobile">'+m(e.last_purchase||e.created_at)+"</td><td>"+s(e)+"</td></tr>"}var g=null;function v(){clearTimeout(a),a=setTimeout((()=>{t=1,p(1)}),300)}async function f(e){try{const n=await l(`/pos/api/customers/${e}`);o=n;const a=document.getElementById("detailEmpty"),u=document.getElementById("detailContent");a&&(a.style.display="none"),u&&(u.style.display="flex");const g=document.getElementById("detailAvatar");g&&(g.textContent=c(n.name));const v=document.getElementById("detailName");v&&(v.textContent=(n.name||"").toUpperCase());const f=document.getElementById("detailRFC");f&&(f.textContent=n.rfc||"-");const y=document.getElementById("detailTipo");if(y){const e=d[n.price_tier]||"Mostrador",t=r[n.price_tier]||"mostrador";y.textContent=e,y.className=`tipo-chip tipo-chip--${t}`}const b=document.getElementById("detailStatus");b&&(b.innerHTML=s(n));const h=(e,t)=>{const n=document.getElementById(e);n&&(n.textContent=t||"-")};h("detailAddress",n.address),h("detailPhone",n.phone),h("detailEmail",n.email),h("detailSince",m(n.created_at)),h("detailLastPurchase",m(n.last_purchase));const x=parseFloat(n.credit_limit||0),C=parseFloat(n.credit_balance||0),E=Math.max(0,x-C),I=x>0?Math.round(C/x*100):0;h("detailCreditLimit",i(x)),h("detailCreditAvail",i(E)),h("detailCreditUsed",i(C)),h("detailCreditPct",I+"%");const B=document.getElementById("detailCreditBar");B&&(B.style.width=I+"%",B.className="progress-bar__fill "+(I<40?"low":I>75?"high":""));const w=document.getElementById("historyBody");if(w){const e=n.recent_purchases||[];0===e.length?w.innerHTML='<tr><td colspan="4" style="text-align:center;color:var(--color-text-muted);padding:var(--space-4);">Sin compras recientes</td></tr>':(w.innerHTML="",e.forEach((e=>{const t="paid"===e.status?"mbadge--paid":"overdue"===e.status?"mbadge--overdue":"mbadge--pending",n="paid"===e.status?"Pagado":"overdue"===e.status?"Vencido":"Pendiente",o=document.createElement("tr");o.innerHTML=`\n <td class="date">${m(e.created_at)}</td>\n <td class="folio">NX-${String(e.id).padStart(5,"0")}</td>\n <td class="total">${i(e.total)}</td>\n <td><span class="mbadge ${t}">${n}</span></td>\n `,w.appendChild(o)})))}!function(e){const t=(e,t)=>{const n=document.getElementById(e);n&&(n.textContent=t||"--")};t("panelAvatar",c(e.name)),t("panelName",e.name),t("panelRfc","RFC: "+(e.rfc||"-"));const n=parseFloat(e.credit_limit||0),o=parseFloat(e.credit_balance||0),a=Math.max(0,n-o),l=n>0?Math.round(o/n*100):0;t("panelCreditLimit",i(n)),t("panelCreditUsed",i(o)),t("panelCreditAvail",i(a));const d=document.getElementById("panelCreditBar");d&&(d.style.width=l+"%");const r=document.getElementById("panelVehicles");if(r){const t=e.vehicle_info||[];0===t.length?r.innerHTML='<span style="color:var(--color-text-muted);font-size:var(--text-body-sm);">Sin vehiculos registrados</span>':r.innerHTML=t.map((e=>`<div style="padding:4px 0;border-bottom:1px solid var(--color-border);">\n <strong>${e.make||""} ${e.model||""} ${e.year||""}</strong>\n ${e.plates?` <span style="color:var(--color-text-muted);">Placas: ${e.plates}</span>`:""}\n </div>`)).join("")}const s=document.getElementById("panelPurchases");if(s){const t=e.recent_purchases||[];0===t.length?s.innerHTML='<span style="color:var(--color-text-muted);font-size:var(--text-body-sm);">Sin compras recientes</span>':s.innerHTML=t.slice(0,5).map((e=>`<div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--color-border);font-size:var(--text-body-sm);">\n <span>NX-${String(e.id).padStart(5,"0")}${m(e.created_at)}</span>\n <span>${i(e.total)}</span>\n </div>`)).join("")}}(n),p(t)}catch(e){console.error("Error loading customer:",e)}}function y(){const e=document.getElementById("customerModal");e&&(document.getElementById("modalTitle").textContent="Nuevo Cliente",document.getElementById("editId").value="",document.getElementById("fName").value="",document.getElementById("fRfc").value="",document.getElementById("fRazonSocial").value="",document.getElementById("fRegimenFiscal").value="",document.getElementById("fUsoCfdi").value="G03",document.getElementById("fCp").value="",document.getElementById("fPhone").value="",document.getElementById("fEmail").value="",document.getElementById("fAddress").value="",document.getElementById("fPriceTier").value="1",document.getElementById("fCreditLimit").value="0",e.classList.add("active"),document.getElementById("fName").focus())}function b(){if(!o)return;const e=o,t=document.getElementById("customerModal");t&&(document.getElementById("modalTitle").textContent="Editar Cliente",document.getElementById("editId").value=e.id,document.getElementById("fName").value=e.name||"",document.getElementById("fRfc").value=e.rfc||"",document.getElementById("fRazonSocial").value=e.razon_social||"",document.getElementById("fRegimenFiscal").value=e.regimen_fiscal||"",document.getElementById("fUsoCfdi").value=e.uso_cfdi||"G03",document.getElementById("fCp").value=e.cp||"",document.getElementById("fPhone").value=e.phone||"",document.getElementById("fEmail").value=e.email||"",document.getElementById("fAddress").value=e.address||"",document.getElementById("fPriceTier").value=e.price_tier||"1",document.getElementById("fCreditLimit").value=e.credit_limit||0,t.classList.add("active"))}function h(){const e=document.getElementById("customerModal");e&&e.classList.remove("active")}async function x(){if(!o)return;const e=document.getElementById("statementModal");if(!e)return;const t=document.getElementById("statementName");t&&(t.textContent=o.name);const n=document.getElementById("statementContent");n&&(n.innerHTML='<div style="text-align:center;padding:20px;color:var(--color-text-muted);">Cargando...</div>'),e.classList.add("active");try{const e=await l(`/pos/api/customers/${o.id}/statement`);let t=`<div style="margin-bottom:12px;font-size:14px;">\n <strong>Saldo actual: ${i(e.balance)}</strong> |\n Limite: ${i(e.customer?e.customer.credit_limit:0)}\n </div>`;e.entries&&0!==e.entries.length?(t+='<table style="width:100%;border-collapse:collapse;font-size:13px;">',t+='<tr style="background:var(--color-surface-2);"><th style="padding:8px;text-align:left;">Fecha</th><th style="text-align:left;">Concepto</th><th style="text-align:right;padding:8px;">Cargo</th><th style="text-align:right;padding:8px;">Abono</th><th style="text-align:right;padding:8px;">Saldo</th></tr>',e.entries.forEach((e=>{t+=`<tr style="border-bottom:1px solid var(--color-border);">\n <td style="padding:6px 8px;">${m(e.date)}</td>\n <td>${e.description||""}</td>\n <td style="text-align:right;padding:6px 8px;">${"charge"===e.type?i(e.amount):""}</td>\n <td style="text-align:right;padding:6px 8px;">${"payment"===e.type?i(e.amount):""}</td>\n <td style="text-align:right;padding:6px 8px;">${i(e.running_balance)}</td>\n </tr>`})),t+="</table>"):t+='<div style="color:var(--color-text-muted);padding:20px;text-align:center;">Sin movimientos</div>',n&&(n.innerHTML=t)}catch(e){n&&(n.innerHTML=`<div style="color:var(--color-error);padding:20px;text-align:center;">Error: ${e.message}</div>`)}}function C(){const e=document.getElementById("paymentModal");e&&e.classList.remove("active")}function E(e){f(e),"function"==typeof openSlidePanel&&openSlidePanel()}return window.filterCustomers=function(){v()},e?(window.openNewCustomerModal=y,function(){const e=document.querySelectorAll(".quick-actions .action-btn");e.length>=1&&(e[0].onclick=()=>{o&&(window.location.href="/pos/?customer="+o.id)}),e.length>=2&&(e[1].onclick=()=>b()),e.length>=3&&(e[2].onclick=()=>x()),e.length>=4&&(e[3].onclick=()=>{o&&f(o.id)})}(),window.viewCustomer=E,function(){if(!document.getElementById("customerModal")){const e=document.createElement("div");e.innerHTML='\n <div id="customerModal" class="modal-overlay" style="display:none;">\n <div class="modal-box">\n <div class="modal-header">\n <h3 id="modalTitle">Nuevo Cliente</h3>\n <button class="btn-close" onclick="Customers.closeModal()">&times;</button>\n </div>\n <div class="modal-body">\n <input type="hidden" id="editId" />\n <div class="form-row">\n <div class="form-group"><label>Nombre *</label><input type="text" id="fName" class="form-input" placeholder="Nombre del cliente" /></div>\n <div class="form-group"><label>RFC</label><input type="text" id="fRfc" class="form-input" placeholder="RFC" maxlength="13" /></div>\n </div>\n <div class="form-row">\n <div class="form-group"><label>Razon Social</label><input type="text" id="fRazonSocial" class="form-input" placeholder="Razon Social" /></div>\n <div class="form-group"><label>Regimen Fiscal</label>\n <select id="fRegimenFiscal" class="form-input">\n <option value="">-- Seleccionar --</option>\n <option value="601">601 - General de Ley PM</option>\n <option value="603">603 - Personas Morales Fines No Lucrativos</option>\n <option value="605">605 - Sueldos y Salarios</option>\n <option value="606">606 - Arrendamiento</option>\n <option value="608">608 - Demas Ingresos</option>\n <option value="612">612 - Personas Fisicas Empresariales</option>\n <option value="616">616 - Sin Obligaciones Fiscales</option>\n <option value="621">621 - Incorporacion Fiscal</option>\n <option value="625">625 - RESICO</option>\n <option value="626">626 - RESICO PM</option>\n </select>\n </div>\n </div>\n <div class="form-row">\n <div class="form-group"><label>Uso CFDI</label>\n <select id="fUsoCfdi" class="form-input">\n <option value="G01">G01 - Adquisicion de mercancias</option>\n <option value="G03" selected>G03 - Gastos en general</option>\n <option value="I01">I01 - Construcciones</option>\n <option value="I08">I08 - Otra maquinaria</option>\n <option value="P01">P01 - Por definir</option>\n <option value="S01">S01 - Sin efectos fiscales</option>\n </select>\n </div>\n <div class="form-group"><label>Codigo Postal</label><input type="text" id="fCp" class="form-input" placeholder="C.P." maxlength="5" /></div>\n </div>\n <div class="form-row">\n <div class="form-group"><label>Telefono</label><input type="text" id="fPhone" class="form-input" placeholder="Telefono" /></div>\n <div class="form-group"><label>Email</label><input type="email" id="fEmail" class="form-input" placeholder="correo@ejemplo.com" /></div>\n </div>\n <div class="form-group"><label>Direccion</label><input type="text" id="fAddress" class="form-input" placeholder="Direccion" /></div>\n <div class="form-row">\n <div class="form-group"><label>Tipo Precio</label>\n <select id="fPriceTier" class="form-input">\n <option value="1">Mostrador</option>\n <option value="2">Taller</option>\n <option value="3">Mayoreo</option>\n </select>\n </div>\n <div class="form-group"><label>Limite de Credito</label><input type="number" id="fCreditLimit" class="form-input" value="0" min="0" step="1000" /></div>\n </div>\n </div>\n <div class="modal-footer">\n <button class="btn btn-secondary" onclick="Customers.closeModal()">Cancelar</button>\n <button class="btn btn-primary" onclick="Customers.save()">Guardar</button>\n </div>\n </div>\n </div>',document.body.appendChild(e)}if(!document.getElementById("statementModal")){const e=document.createElement("div");e.innerHTML='\n <div id="statementModal" class="modal-overlay" style="display:none;">\n <div class="modal-box modal-box--wide">\n <div class="modal-header">\n <h3>Estado de Cuenta — <span id="statementName"></span></h3>\n <button class="btn-close" onclick="Customers.closeStatement()">&times;</button>\n </div>\n <div class="modal-body" id="statementContent" style="max-height:60vh;overflow-y:auto;">\n </div>\n <div class="modal-footer">\n <button class="btn btn-primary" onclick="Customers.showPaymentModal()">Registrar Abono</button>\n <button class="btn btn-secondary" onclick="Customers.closeStatement()">Cerrar</button>\n </div>\n </div>\n </div>',document.body.appendChild(e)}if(!document.getElementById("paymentModal")){const e=document.createElement("div");e.innerHTML='\n <div id="paymentModal" class="modal-overlay" style="display:none;">\n <div class="modal-box">\n <div class="modal-header">\n <h3>Registrar Abono</h3>\n <button class="btn-close" onclick="Customers.closePayment()">&times;</button>\n </div>\n <div class="modal-body">\n <p style="margin-bottom:var(--space-4);color:var(--color-text-secondary);">Cliente: <strong id="paymentCustomerName"></strong></p>\n <div class="form-row">\n <div class="form-group"><label>Monto</label><input type="number" id="paymentAmount" class="form-input" placeholder="0.00" min="0" step="0.01" /></div>\n <div class="form-group"><label>Metodo</label>\n <select id="paymentMethod" class="form-input">\n <option value="cash">Efectivo</option>\n <option value="transfer">Transferencia</option>\n <option value="card">Tarjeta</option>\n <option value="check">Cheque</option>\n </select>\n </div>\n </div>\n <div class="form-group"><label>Referencia</label><input type="text" id="paymentRef" class="form-input" placeholder="Num. referencia (opcional)" /></div>\n </div>\n <div class="modal-footer">\n <button class="btn btn-secondary" onclick="Customers.closePayment()">Cancelar</button>\n <button class="btn btn-primary" onclick="Customers.recordPayment()">Registrar</button>\n </div>\n </div>\n </div>',document.body.appendChild(e)}if(!document.getElementById("modalStyles")){const e=document.createElement("style");e.id="modalStyles",e.textContent="\n .modal-overlay {\n position: fixed; top: 0; left: 0; right: 0; bottom: 0;\n background: rgba(0,0,0,0.5); z-index: 9000;\n display: flex; align-items: center; justify-content: center;\n backdrop-filter: blur(2px);\n }\n .modal-overlay.active { display: flex !important; }\n .modal-box {\n background: var(--color-bg-elevated); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); width: 90%; max-width: 600px;\n max-height: 85vh; overflow-y: auto;\n box-shadow: var(--shadow-xl);\n }\n .modal-box--wide { max-width: 800px; }\n .modal-header {\n display: flex; align-items: center; justify-content: space-between;\n padding: var(--space-4) var(--space-5);\n border-bottom: 1px solid var(--color-border);\n }\n .modal-header h3 {\n font-family: var(--font-heading); font-size: var(--text-h6);\n font-weight: var(--heading-weight-primary); color: var(--color-text-primary);\n letter-spacing: var(--tracking-wide); text-transform: uppercase;\n }\n .modal-body { padding: var(--space-5); }\n .modal-footer {\n display: flex; justify-content: flex-end; gap: var(--space-3);\n padding: var(--space-4) var(--space-5);\n border-top: 1px solid var(--color-border);\n }\n .form-row { display: flex; gap: var(--space-4); margin-bottom: var(--space-4); }\n .form-row > .form-group { flex: 1; }\n .form-group { margin-bottom: var(--space-4); }\n .form-group label {\n display: block; font-size: var(--text-caption); font-weight: var(--font-weight-semibold);\n color: var(--color-text-muted); text-transform: uppercase; letter-spacing: var(--tracking-wider);\n margin-bottom: var(--space-1);\n }\n .form-input {\n width: 100%; height: 38px; padding: 0 var(--space-3);\n background: var(--color-bg-overlay); border: 1.5px solid var(--color-border);\n border-radius: var(--radius-md); font-family: var(--font-body);\n font-size: var(--text-body-sm); color: var(--color-text-primary); outline: none;\n transition: var(--transition-fast);\n }\n .form-input:focus { border-color: var(--color-border-focus); box-shadow: var(--shadow-focus); }\n select.form-input { cursor: pointer; }\n ",document.head.appendChild(e)}}(),p(),async function(){try{const e=await l("/pos/api/customers?page=1&per_page=1"),t=e.pagination?e.pagination.total:0,n=document.querySelectorAll(".summary-card__value");n.length>=1&&(n[0].textContent=t.toLocaleString("es-MX"))}catch(e){}}()):window.location.href="/pos/login",{search:v,goToPage:function(e){e<1||e>n||(t=e,p(e))},loadCustomers:p,showDetail:E,selectCustomer:f,closeDetail:function(){o=null;const e=document.getElementById("detailEmpty"),n=document.getElementById("detailContent");e&&(e.style.display="flex"),n&&(n.style.display="none"),p(t)},showCreateModal:y,editCurrent:b,closeModal:h,save:async function(){const e=document.getElementById("fName"),t=e?e.value.trim():"";if(!t)return void alert("Nombre es requerido");const n=e=>{const t=document.getElementById(e);return t?t.value.trim():""},a={name:t,rfc:n("fRfc")||null,razon_social:n("fRazonSocial")||null,regimen_fiscal:n("fRegimenFiscal")||null,uso_cfdi:n("fUsoCfdi")||"G03",cp:n("fCp")||null,phone:n("fPhone")||null,email:n("fEmail")||null,address:n("fAddress")||null,price_tier:parseInt(n("fPriceTier"))||1,credit_limit:parseFloat(n("fCreditLimit"))||0},i=n("editId");try{i?await l(`/pos/api/customers/${i}`,{method:"PUT",body:JSON.stringify(a)}):await l("/pos/api/customers",{method:"POST",body:JSON.stringify(a)}),h(),p(),i&&o&&f(i)}catch(e){alert("Error: "+e.message)}},showStatement:x,closeStatement:function(){const e=document.getElementById("statementModal");e&&e.classList.remove("active")},showPaymentModal:function(){if(!o)return;const e=document.getElementById("paymentModal");e&&(document.getElementById("paymentCustomerName").textContent=o.name,document.getElementById("paymentAmount").value="",document.getElementById("paymentMethod").value="cash",document.getElementById("paymentRef").value="",e.classList.add("active"),document.getElementById("paymentAmount").focus())},closePayment:C,recordPayment:async function(){if(!o)return;const e=parseFloat(document.getElementById("paymentAmount").value);if(!e||e<=0)return void alert("Ingresa un monto valido");const t=document.getElementById("paymentMethod").value,n=document.getElementById("paymentRef").value.trim();try{await l(`/pos/api/customers/${o.id}/payment`,{method:"POST",body:JSON.stringify({amount:e,method:t,reference:n})}),C(),f(o.id)}catch(e){alert("Error: "+e.message)}}}})();