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:
2026-03-18 22:25:32 +00:00
parent b1adf536f6
commit fe6542c45c
12 changed files with 4037 additions and 0 deletions

222
dashboard/cuentas.js Normal file
View 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) + '">&laquo;</button>' +
'<span class="page-info">Pag ' + pag.page + '/' + pag.total_pages + '</span>' +
'<button ' + (pag.page >= pag.total_pages ? 'disabled' : '') + ' data-p="' + (pag.page + 1) + '">&raquo;</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();
})();