// /home/Autopartes/pos/static/js/customers.js /** * Customers management frontend. * Communicates with /pos/api/customers (customers_bp). */ const Customers = (() => { let token = localStorage.getItem('pos_token') || ''; let currentPage = 1; let currentCustomer = null; let searchTimeout = null; const fmt = (n) => '$' + parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); function headers() { return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }; } async function api(url, options = {}) { options.headers = headers(); const res = await fetch(url, options); const data = await res.json(); if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`); return data; } // ─── List ──────────────────────────── async function loadCustomers(page, q) { page = page || currentPage; q = q !== undefined ? q : (document.getElementById('searchInput').value || ''); try { const params = new URLSearchParams({ page, per_page: 50 }); if (q) params.append('q', q); const data = await api(`/pos/api/customers?${params}`); renderTable(data.data); renderPagination(data.pagination); } catch (e) { console.error('Load customers failed:', e); } } function renderTable(customers) { const tbody = document.getElementById('customersBody'); const tiers = { 1: ['Mostrador', 'tier-1'], 2: ['Taller', 'tier-2'], 3: ['Mayoreo', 'tier-3'] }; let html = ''; customers.forEach(c => { const [tierName, tierClass] = tiers[c.price_tier] || ['P1', 'tier-1']; const limit = c.credit_limit || 0; const balance = c.credit_balance || 0; const usagePct = limit > 0 ? Math.min(100, (balance / limit) * 100) : 0; const fillClass = usagePct > 90 ? 'danger' : usagePct > 70 ? 'warning' : ''; html += ` ${c.name} ${c.rfc || '-'} ${c.phone || '-'} ${tierName} ${fmt(limit)} ${limit > 0 ? `
` : ''} ${balance > 0 ? fmt(balance) : '-'} `; }); if (customers.length === 0) { html = 'Sin resultados'; } tbody.innerHTML = html; } function renderPagination(pag) { const container = document.getElementById('pagination'); if (pag.total_pages <= 1) { container.innerHTML = ''; return; } let html = ''; if (pag.page > 1) { html += ``; } for (let i = 1; i <= Math.min(pag.total_pages, 10); i++) { html += ``; } if (pag.page < pag.total_pages) { html += ``; } container.innerHTML = html; } function goToPage(page) { currentPage = page; loadCustomers(page); } function search() { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { currentPage = 1; loadCustomers(1); }, 300); } // ─── Detail ────────────────────────── async function showDetail(id) { try { const c = await api(`/pos/api/customers/${id}`); currentCustomer = c; document.getElementById('detailName').textContent = c.name; // Credit const available = (c.credit_limit || 0) - (c.credit_balance || 0); document.getElementById('detailCreditAvailable').textContent = fmt(available); document.getElementById('detailCreditLimit').textContent = fmt(c.credit_limit); document.getElementById('detailCreditBalance').textContent = fmt(c.credit_balance); // Fiscal let fiscalHtml = ''; fiscalHtml += `
RFC${c.rfc || '-'}
`; fiscalHtml += `
Razon Social${c.razon_social || '-'}
`; fiscalHtml += `
Regimen${c.regimen_fiscal || '-'}
`; fiscalHtml += `
Uso CFDI${c.uso_cfdi || '-'}
`; fiscalHtml += `
CP${c.cp || '-'}
`; document.getElementById('detailFiscal').innerHTML = fiscalHtml; // Contact let contactHtml = ''; contactHtml += `
Telefono${c.phone || '-'}
`; contactHtml += `
Email${c.email || '-'}
`; contactHtml += `
Direccion${c.address || '-'}
`; document.getElementById('detailContact').innerHTML = contactHtml; // Vehicles const vehicles = c.vehicle_info || []; if (vehicles.length === 0) { document.getElementById('detailVehicles').innerHTML = '
Sin vehiculos registrados
'; } else { document.getElementById('detailVehicles').innerHTML = vehicles.map(v => `
${v.make || ''} ${v.model || ''} ${v.year || ''} ${v.plates ? `Placas: ${v.plates}` : ''} ${v.vin ? `
VIN: ${v.vin}
` : ''}
` ).join(''); } // Recent purchases const purchases = c.recent_purchases || []; if (purchases.length === 0) { document.getElementById('detailPurchases').innerHTML = '
Sin compras recientes
'; } else { document.getElementById('detailPurchases').innerHTML = purchases.map(p => `
Venta #${p.id} - ${new Date(p.created_at).toLocaleDateString('es-MX')} ${fmt(p.total)}
` ).join(''); } document.getElementById('detailPanel').classList.add('active'); } catch (e) { alert('Error: ' + e.message); } } function closeDetail() { document.getElementById('detailPanel').classList.remove('active'); currentCustomer = null; } // ─── Create/Edit Modal ─────────────── function showCreateModal() { 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'; document.getElementById('customerModal').classList.add('active'); document.getElementById('fName').focus(); } function editCurrent() { if (!currentCustomer) return; const c = currentCustomer; document.getElementById('modalTitle').textContent = 'Editar Cliente'; document.getElementById('editId').value = c.id; document.getElementById('fName').value = c.name || ''; document.getElementById('fRfc').value = c.rfc || ''; document.getElementById('fRazonSocial').value = c.razon_social || ''; document.getElementById('fRegimenFiscal').value = c.regimen_fiscal || ''; document.getElementById('fUsoCfdi').value = c.uso_cfdi || 'G03'; document.getElementById('fCp').value = c.cp || ''; document.getElementById('fPhone').value = c.phone || ''; document.getElementById('fEmail').value = c.email || ''; document.getElementById('fAddress').value = c.address || ''; document.getElementById('fPriceTier').value = c.price_tier || '1'; document.getElementById('fCreditLimit').value = c.credit_limit || 0; document.getElementById('customerModal').classList.add('active'); } function closeModal() { document.getElementById('customerModal').classList.remove('active'); } async function save() { const name = document.getElementById('fName').value.trim(); if (!name) { alert('Nombre es requerido'); return; } const body = { name: name, rfc: document.getElementById('fRfc').value.trim() || null, razon_social: document.getElementById('fRazonSocial').value.trim() || null, regimen_fiscal: document.getElementById('fRegimenFiscal').value || null, uso_cfdi: document.getElementById('fUsoCfdi').value || 'G03', cp: document.getElementById('fCp').value.trim() || null, phone: document.getElementById('fPhone').value.trim() || null, email: document.getElementById('fEmail').value.trim() || null, address: document.getElementById('fAddress').value.trim() || null, price_tier: parseInt(document.getElementById('fPriceTier').value) || 1, credit_limit: parseFloat(document.getElementById('fCreditLimit').value) || 0, }; const editId = document.getElementById('editId').value; try { if (editId) { await api(`/pos/api/customers/${editId}`, { method: 'PUT', body: JSON.stringify(body), }); } else { await api('/pos/api/customers', { method: 'POST', body: JSON.stringify(body), }); } closeModal(); loadCustomers(); if (editId && currentCustomer) { showDetail(editId); } } catch (e) { alert('Error: ' + e.message); } } // ─── Statement ─────────────────────── async function showStatement() { if (!currentCustomer) return; document.getElementById('statementName').textContent = currentCustomer.name; try { const data = await api(`/pos/api/customers/${currentCustomer.id}/statement`); let html = `
Saldo actual: ${fmt(data.balance)} | Limite: ${fmt(data.customer.credit_limit)}
`; if (data.entries.length === 0) { html += '
Sin movimientos
'; } else { html += ''; html += ''; data.entries.forEach(e => { const dateStr = new Date(e.date).toLocaleDateString('es-MX'); html += ``; }); html += '
FechaConceptoCargoAbonoSaldo
${dateStr} ${e.description} ${e.type === 'charge' ? fmt(e.amount) : ''} ${e.type === 'payment' ? fmt(e.amount) : ''} ${fmt(e.running_balance)}
'; } document.getElementById('statementContent').innerHTML = html; document.getElementById('statementModal').classList.add('active'); } catch (e) { alert('Error: ' + e.message); } } // ─── Init ──────────────────────────── loadCustomers(); return { search, goToPage, loadCustomers, showDetail, closeDetail, showCreateModal, editCurrent, closeModal, save, showStatement, }; })();