fix(audit): corrige errores criticos y mayores, mejora UX/accesibilidad y optimiza rendimiento
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- Arregla @require_auth, permisos, race conditions, locks de caja/stock
- Elimina N+1 en layaway, flotilla, dashboard y global_invoice
- Asegura folios atomicos para CFDI, ordenes de servicio y polizas
- Protege client_secret de MercadoLibre en backend
- Conecta botones/filtros de config, customers, accounting e invoicing
- Mejora accesibilidad (labels/aria-label) y estados de carga/vacio
- Limpia accounting.js obsoleto y consolida accounting.v9.js
- Actualiza cache busting a v32 y Service Worker a v32
- Documenta todo en docs/AUDIT_Y_MEJORAS_2026-06-15.md

Tests: 35 passed
This commit is contained in:
2026-06-29 23:54:58 +00:00
parent 59a4893e84
commit 2bdeb2973a
61 changed files with 2879 additions and 706 deletions

View File

@@ -43,6 +43,9 @@ const Customers = (() => {
const tierClass = { 1: 'mostrador', 2: 'taller', 3: 'mayoreo' };
function statusBadge(c) {
if (c.is_active === false) {
return '<span class="badge badge--inactive"><span class="badge-dot"></span>Inactivo</span>';
}
// Derive status: if credit_balance > credit_limit => Mora, else Activo
if (c.credit_balance > 0 && c.credit_limit > 0 && c.credit_balance > c.credit_limit) {
return '<span class="badge badge--warning"><span class="badge-dot"></span>Mora</span>';
@@ -82,14 +85,27 @@ const Customers = (() => {
const searchEl = document.getElementById('searchInput');
q = q !== undefined ? q : (searchEl ? searchEl.value || '' : '');
const tipoEl = document.getElementById('tipoFilter');
const estadoEl = document.getElementById('estadoFilter');
const tipo = tipoEl ? tipoEl.value : '';
// Map UI status labels to backend values
const estadoMap = { 'Activo': 'active', 'Inactivo': 'inactive', 'Mora': 'overdue' };
const estado = estadoEl ? (estadoMap[estadoEl.value] || 'all') : 'active';
const tbody = document.getElementById('customersBody');
if (tbody) tbody.innerHTML = '<tr><td colspan="11">' + renderLoadingState({ message: 'Cargando clientes...' }) + '</td></tr>';
try {
const params = new URLSearchParams({ page, per_page: 50 });
if (q) params.append('q', q);
if (tipo) params.append('price_tier', tipo);
if (estado) params.append('status', estado);
const data = await api(`/pos/api/customers?${params}`);
renderTable(data.data || []);
renderPagination(data.pagination || {});
} catch (e) {
if (tbody) tbody.innerHTML = '<tr><td colspan="11">' + renderEmptyState({ title: 'Error', subtitle: 'No se pudieron cargar los clientes.' }) + '</td></tr>';
console.error('Load customers failed:', e);
}
}
@@ -136,7 +152,7 @@ const Customers = (() => {
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
title: 'Sin clientes',
subtitle: 'No se encontraron clientes registrados.',
action: '<button class="btn btn--primary btn--sm" onclick="Customers.openCreateModal()">Nuevo cliente</button>'
action: '<button class="btn btn--primary btn--sm" onclick="Customers.showCreateModal()">Nuevo cliente</button>'
}) + '</td></tr>';
return;
}
@@ -650,7 +666,7 @@ const Customers = (() => {
try {
await api(`/pos/api/customers/${currentCustomer.id}/payment`, {
method: 'POST',
body: JSON.stringify({ amount, method, reference }),
body: JSON.stringify({ amount, payment_method: method, reference }),
});
closePayment();
selectCustomer(currentCustomer.id);
@@ -892,6 +908,14 @@ const Customers = (() => {
showCustomerHistory, closeCustomerHistoryModal,
};
// Register Cmd+K items
if (typeof registerCmdKItem === "function") {
registerCmdKItem({ group: "Principal", label: "POS Ventas", href: "/pos/sale", icon: "🛒" });
registerCmdKItem({ group: "Principal", label: "Catálogo", href: "/pos/catalog", icon: "📁" });
registerCmdKItem({ group: "Principal", label: "Clientes", href: "/pos/customers", icon: "👤" });
registerCmdKItem({ group: "Principal", label: "Dashboard", href: "/pos/dashboard", icon: "📊" });
}
// Bulk selection
publicApi.toggleCustomerSelection = function(id) {
if (selectedCustomers.has(id)) selectedCustomers.delete(id);
@@ -927,15 +951,15 @@ const Customers = (() => {
updateBulkToolbar();
};
publicApi.featureProximamente = function(name) {
if (typeof window.featureProximamente === 'function') {
window.featureProximamente(name);
} else {
alert(name + ' — próximamente');
}
};
// Expose globally for inline HTML onclick handlers
window.Customers = publicApi;
return publicApi;
// Register Cmd+K items
if (typeof registerCmdKItem === "function") {
registerCmdKItem({ group: "Principal", label: "POS Ventas", href: "/pos/sale", icon: "🛒" });
registerCmdKItem({ group: "Principal", label: "Catálogo", href: "/pos/catalog", icon: "📁" });
registerCmdKItem({ group: "Principal", label: "Clientes", href: "/pos/customers", icon: "👤" });
registerCmdKItem({ group: "Principal", label: "Dashboard", href: "/pos/dashboard", icon: "📊" });
}
})();