feat: add captura, POS, cuentas, and tienda pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
222
dashboard/cuentas.js
Normal file
222
dashboard/cuentas.js
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* cuentas.js — Accounts receivable logic for Nexus Autoparts
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var API = '';
|
||||
var currentCustomerId = null;
|
||||
var customerPage = 1;
|
||||
|
||||
// ================================================================
|
||||
// Utility
|
||||
// ================================================================
|
||||
|
||||
function toast(msg, type) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'toast ' + (type || 'success');
|
||||
el.textContent = msg;
|
||||
document.body.appendChild(el);
|
||||
setTimeout(function () { el.remove(); }, 3000);
|
||||
}
|
||||
|
||||
function api(path, opts) {
|
||||
opts = opts || {};
|
||||
return fetch(API + path, opts).then(function (r) {
|
||||
if (!r.ok) return r.json().then(function (d) { throw new Error(d.error || 'Error'); });
|
||||
return r.json();
|
||||
});
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
if (!s) return '';
|
||||
var d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function fmt(n) {
|
||||
return '$' + (parseFloat(n) || 0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
function fmtDate(d) {
|
||||
if (!d) return '';
|
||||
var dt = new Date(d);
|
||||
return dt.toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Customer List
|
||||
// ================================================================
|
||||
|
||||
var searchTimer = null;
|
||||
document.getElementById('customer-search').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(function () {
|
||||
customerPage = 1;
|
||||
loadCustomers();
|
||||
}, 400);
|
||||
});
|
||||
|
||||
function loadCustomers() {
|
||||
var search = document.getElementById('customer-search').value;
|
||||
var grid = document.getElementById('customer-grid');
|
||||
grid.innerHTML = '<div class="empty-state">Cargando...</div>';
|
||||
|
||||
var params = '?page=' + customerPage + '&per_page=30';
|
||||
if (search) params += '&search=' + encodeURIComponent(search);
|
||||
|
||||
api('/api/pos/customers' + params).then(function (res) {
|
||||
var data = res.data || [];
|
||||
if (data.length === 0) {
|
||||
grid.innerHTML = '<div class="empty-state">No se encontraron clientes</div>';
|
||||
document.getElementById('customer-pagination').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = data.map(function (c) {
|
||||
return '<div class="customer-card-item" data-id="' + c.id_customer + '">' +
|
||||
'<div class="cci-name">' + esc(c.name) + '</div>' +
|
||||
'<div class="cci-rfc">' + esc(c.rfc || 'Sin RFC') + '</div>' +
|
||||
'<div class="cci-balance-row">' +
|
||||
'<span class="cci-balance ' + (c.balance > 0 ? 'positive' : 'zero') + '">' + fmt(c.balance) + '</span>' +
|
||||
'<span class="cci-limit">Limite: ' + fmt(c.credit_limit) + '</span></div></div>';
|
||||
}).join('');
|
||||
|
||||
grid.querySelectorAll('.customer-card-item').forEach(function (card) {
|
||||
card.addEventListener('click', function () {
|
||||
showCustomerDetail(parseInt(card.getAttribute('data-id')));
|
||||
});
|
||||
});
|
||||
|
||||
// Pagination
|
||||
var pag = res.pagination;
|
||||
var pagEl = document.getElementById('customer-pagination');
|
||||
if (pag.total_pages <= 1) { pagEl.innerHTML = ''; return; }
|
||||
pagEl.innerHTML = '<button ' + (pag.page <= 1 ? 'disabled' : '') + ' data-p="' + (pag.page - 1) + '">«</button>' +
|
||||
'<span class="page-info">Pag ' + pag.page + '/' + pag.total_pages + '</span>' +
|
||||
'<button ' + (pag.page >= pag.total_pages ? 'disabled' : '') + ' data-p="' + (pag.page + 1) + '">»</button>';
|
||||
pagEl.querySelectorAll('button').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
customerPage = parseInt(btn.getAttribute('data-p'));
|
||||
loadCustomers();
|
||||
});
|
||||
});
|
||||
}).catch(function (err) {
|
||||
console.error('Error loading customers:', err);
|
||||
grid.innerHTML = '<div class="empty-state">Error al cargar clientes</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Customer Detail
|
||||
// ================================================================
|
||||
|
||||
function showCustomerDetail(customerId) {
|
||||
currentCustomerId = customerId;
|
||||
document.getElementById('list-view').style.display = 'none';
|
||||
document.getElementById('detail-view').style.display = 'block';
|
||||
|
||||
api('/api/pos/customers/' + customerId + '/statement').then(function (res) {
|
||||
var c = res.customer;
|
||||
document.getElementById('dh-name').textContent = c.name;
|
||||
document.getElementById('dh-rfc').textContent = c.rfc || 'Sin RFC';
|
||||
|
||||
var balEl = document.getElementById('dh-balance');
|
||||
balEl.textContent = fmt(c.balance);
|
||||
balEl.className = 'dh-value ' + (c.balance > 0 ? 'danger' : 'success');
|
||||
|
||||
document.getElementById('dh-limit').textContent = fmt(c.credit_limit);
|
||||
document.getElementById('dh-terms').textContent = c.payment_terms + ' dias';
|
||||
|
||||
// Invoices
|
||||
var invBody = document.getElementById('invoice-list');
|
||||
if (res.invoices.length === 0) {
|
||||
invBody.innerHTML = '<tr><td colspan="5" class="empty-state">Sin facturas</td></tr>';
|
||||
} else {
|
||||
invBody.innerHTML = res.invoices.map(function (i) {
|
||||
return '<tr>' +
|
||||
'<td style="font-family:monospace;font-weight:600">' + esc(i.folio) + '</td>' +
|
||||
'<td>' + fmtDate(i.date_issued) + '</td>' +
|
||||
'<td>' + fmt(i.total) + '</td>' +
|
||||
'<td>' + fmt(i.amount_paid) + '</td>' +
|
||||
'<td><span class="status-badge ' + i.status + '">' + i.status + '</span></td></tr>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Payments
|
||||
var payBody = document.getElementById('payment-list');
|
||||
if (res.payments.length === 0) {
|
||||
payBody.innerHTML = '<tr><td colspan="5" class="empty-state">Sin pagos</td></tr>';
|
||||
} else {
|
||||
payBody.innerHTML = res.payments.map(function (p) {
|
||||
return '<tr>' +
|
||||
'<td>' + fmtDate(p.date_payment) + '</td>' +
|
||||
'<td style="font-weight:600;color:var(--success)">' + fmt(p.amount) + '</td>' +
|
||||
'<td>' + esc(p.payment_method) + '</td>' +
|
||||
'<td>' + esc(p.reference || '') + '</td>' +
|
||||
'<td>' + esc(p.invoice_folio || 'General') + '</td></tr>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Populate invoice dropdown for payment form
|
||||
var invSelect = document.getElementById('pay-invoice');
|
||||
invSelect.innerHTML = '<option value="">Abono general</option>';
|
||||
res.invoices.filter(function (i) { return i.status !== 'paid' && i.status !== 'cancelled'; })
|
||||
.forEach(function (i) {
|
||||
invSelect.innerHTML += '<option value="' + i.id_invoice + '">' +
|
||||
i.folio + ' — ' + fmt(i.total - i.amount_paid) + ' pendiente</option>';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('btn-back-list').addEventListener('click', function () {
|
||||
document.getElementById('detail-view').style.display = 'none';
|
||||
document.getElementById('list-view').style.display = 'block';
|
||||
currentCustomerId = null;
|
||||
loadCustomers();
|
||||
});
|
||||
|
||||
// ================================================================
|
||||
// Register Payment
|
||||
// ================================================================
|
||||
|
||||
document.getElementById('btn-pay').addEventListener('click', function () {
|
||||
var amount = parseFloat(document.getElementById('pay-amount').value);
|
||||
if (!amount || amount <= 0) {
|
||||
toast('Ingresa un monto valido', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var invoiceId = document.getElementById('pay-invoice').value;
|
||||
|
||||
api('/api/pos/payments', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
customer_id: currentCustomerId,
|
||||
amount: amount,
|
||||
payment_method: document.getElementById('pay-method').value,
|
||||
reference: document.getElementById('pay-reference').value.trim() || null,
|
||||
invoice_id: invoiceId ? parseInt(invoiceId) : null,
|
||||
notes: document.getElementById('pay-notes').value.trim() || null
|
||||
})
|
||||
}).then(function () {
|
||||
toast('Pago de ' + fmt(amount) + ' registrado');
|
||||
// Clear form
|
||||
document.getElementById('pay-amount').value = '';
|
||||
document.getElementById('pay-reference').value = '';
|
||||
document.getElementById('pay-notes').value = '';
|
||||
// Refresh detail
|
||||
showCustomerDetail(currentCustomerId);
|
||||
}).catch(function (err) {
|
||||
toast(err.message, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// ================================================================
|
||||
// Init
|
||||
// ================================================================
|
||||
|
||||
loadCustomers();
|
||||
})();
|
||||
Reference in New Issue
Block a user