// /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 += '| Fecha | Concepto | Cargo | Abono | Saldo |
';
data.entries.forEach(e => {
const dateStr = new Date(e.date).toLocaleDateString('es-MX');
html += `
| ${dateStr} |
${e.description} |
${e.type === 'charge' ? fmt(e.amount) : ''} |
${e.type === 'payment' ? fmt(e.amount) : ''} |
${fmt(e.running_balance)} |
`;
});
html += '
';
}
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,
};
})();