diff --git a/pos/static/js/accounting.js b/pos/static/js/accounting.js
index b3bb513..d0367be 100644
--- a/pos/static/js/accounting.js
+++ b/pos/static/js/accounting.js
@@ -1,10 +1,9 @@
// /home/Autopartes/pos/static/js/accounting.js
-// Accounting module: chart of accounts, journal entries, financial reports
+// Accounting module — wired to design-system HTML IDs
+// Tabs: panel-cxc, panel-cxp, panel-balance, panel-resultados, panel-flujo, panel-conciliacion, panel-cierre
const Accounting = (() => {
const API = '/pos/api/accounting';
- let accounts = []; // cached for dropdowns
- let entryLineCtr = 0; // counter for entry line IDs
function token() {
return localStorage.getItem('pos_token') || '';
@@ -27,567 +26,373 @@ const Accounting = (() => {
return parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
- // ─── Tabs ──────────────────────────────────────
+ // ---- Auth check ----
+ function checkAuth() {
+ if (!token()) {
+ window.location.href = '/pos/login';
+ return false;
+ }
+ return true;
+ }
- function initTabs() {
- document.querySelectorAll('.tab').forEach(tab => {
- tab.addEventListener('click', () => {
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
- document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active'));
- tab.classList.add('active');
- document.getElementById(`tab-${tab.dataset.tab}`).classList.add('active');
-
- // Load data on tab switch
- const t = tab.dataset.tab;
- if (t === 'accounts') loadAccounts();
- if (t === 'entries') loadEntries();
- if (t === 'trial-balance') loadTrialBalance();
- if (t === 'income-statement') loadIncomeStatement();
- if (t === 'balance-sheet') loadBalanceSheet();
- if (t === 'aging') loadAging();
- if (t === 'periods') loadPeriods();
- });
+ // ---- Tab switching (matches design system onclick="switchTab('xxx')") ----
+ function switchTab(name) {
+ document.querySelectorAll('.tab-btn').forEach(btn => {
+ btn.classList.remove('is-active');
+ btn.setAttribute('aria-selected', 'false');
});
- }
+ document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('is-active'));
- // ─── Chart of Accounts ─────────────────────────
-
- async function loadAccounts() {
- try {
- const res = await api('/accounts');
- accounts = res.data || [];
- renderAccountsTree();
- } catch (e) {
- document.getElementById('accounts-tree').innerHTML =
- `
Error: ${e.message}
`;
- }
- }
-
- function renderAccountsTree() {
- const container = document.getElementById('accounts-tree');
- if (!accounts.length) { container.innerHTML = 'No hay cuentas.
'; return; }
-
- // Build tree structure
- const byParent = {};
- accounts.forEach(a => {
- const pid = a.parent_id || 'root';
- if (!byParent[pid]) byParent[pid] = [];
- byParent[pid].push(a);
- });
-
- function buildUl(parentId) {
- const children = byParent[parentId] || [];
- if (!children.length) return '';
- let html = '';
- for (const acct of children) {
- const hasChildren = byParent[acct.id] && byParent[acct.id].length > 0;
- const balClass = acct.balance < 0 ? 'negative' : '';
- html += '';
- if (hasChildren) {
- html += ``;
- } else {
- html += '';
- }
- html += `${acct.code} ${acct.name}`;
- html += `$${fmt(acct.balance)} `;
- html += ' ';
- if (hasChildren) {
- html += `${buildUl(acct.id)}
`;
- }
- html += ' ';
- }
- html += ' ';
- return html;
+ // Activate button
+ const tabBtn = document.querySelector(`.tab-btn[onclick*="'${name}'"]`);
+ if (tabBtn) {
+ tabBtn.classList.add('is-active');
+ tabBtn.setAttribute('aria-selected', 'true');
}
- container.innerHTML = buildUl('root');
+ // Activate panel
+ const panel = document.getElementById(`panel-${name}`);
+ if (panel) panel.classList.add('is-active');
+
+ // Load data
+ if (name === 'cxc') loadAging();
+ if (name === 'cxp') loadAccountsPayable();
+ if (name === 'balance') loadBalanceSheet();
+ if (name === 'resultados') loadIncomeStatement();
+ if (name === 'flujo') loadCashFlow();
+ if (name === 'conciliacion') loadReconciliation();
+ if (name === 'cierre') loadPeriodClose();
}
- function showNewAccountModal() {
- const sel = document.getElementById('na-parent');
- sel.innerHTML = '-- Sin padre -- ';
- accounts.forEach(a => {
- sel.innerHTML += `${a.code} - ${a.name} `;
- });
- document.getElementById('na-code').value = '';
- document.getElementById('na-name').value = '';
- document.getElementById('new-account-modal').classList.add('active');
+ // ---- Badge helper ----
+ function statusBadge(status, label) {
+ const map = {
+ pending: 'badge--pending',
+ vigente: 'badge--pending',
+ overdue: 'badge--overdue',
+ vencida: 'badge--overdue',
+ partial: 'badge--partial',
+ parcial: 'badge--partial',
+ ok: 'badge--ok',
+ pagada: 'badge--ok',
+ open: 'badge--pending',
+ closed: 'badge--ok',
+ };
+ return `${label || status} `;
}
- async function createAccount() {
- try {
- const parentId = document.getElementById('na-parent').value;
- await api('/accounts', {
- method: 'POST',
- body: JSON.stringify({
- code: document.getElementById('na-code').value,
- name: document.getElementById('na-name').value,
- parent_id: parentId ? parseInt(parentId) : null,
- type: document.getElementById('na-type').value,
- }),
- });
- closeModal('new-account-modal');
- loadAccounts();
- } catch (e) {
- alert('Error: ' + e.message);
- }
- }
-
- // ─── Journal Entries ───────────────────────────
-
- async function loadEntries() {
- try {
- const from = document.getElementById('entries-from').value;
- const to = document.getElementById('entries-to').value;
- const type = document.getElementById('entries-type').value;
- let qs = '?per_page=50';
- if (from) qs += `&date_from=${from}`;
- if (to) qs += `&date_to=${to}`;
- if (type) qs += `&type=${type}`;
-
- const res = await api(`/entries${qs}`);
- renderEntries(res.data || []);
- } catch (e) {
- document.getElementById('entries-list').innerHTML =
- `Error: ${e.message}
`;
- }
- }
-
- function renderEntries(entries) {
- const container = document.getElementById('entries-list');
- if (!entries.length) { container.innerHTML = 'No hay polizas en este periodo.
'; return; }
-
- let html = `
-
- # Fecha Tipo Descripcion
- Referencia Monto Auto
- `;
-
- for (const e of entries) {
- html += `
- ${e.entry_number}
- ${e.date || ''}
- ${e.type || ''}
- ${e.description || ''}
- ${e.reference_type ? `${e.reference_type} #${e.reference_id}` : ''}
- $${fmt(e.total_amount)}
- ${e.is_auto ? 'Si' : 'No'}
- `;
- }
- html += '
';
- container.innerHTML = html;
- }
-
- async function showEntryDetail(entryId) {
- try {
- const entry = await api(`/entries/${entryId}`);
- let html = `Poliza #${entry.entry_number}
-
- ${entry.description || ''}
-
-
- Cuenta Nombre Cargo
- Abono Nota
- `;
-
- for (const l of entry.lines) {
- html += `
- ${l.account_code}
- ${l.account_name}
- ${l.debit ? '$' + fmt(l.debit) : ''}
- ${l.credit ? '$' + fmt(l.credit) : ''}
- ${l.description || ''}
- `;
- }
-
- html += `
- Totales
- $${fmt(entry.total_debit)}
- $${fmt(entry.total_credit)}
-
-
`;
-
- document.getElementById('entry-detail-content').innerHTML = html;
- document.getElementById('entry-detail-modal').classList.add('active');
- } catch (e) {
- alert('Error: ' + e.message);
- }
- }
-
- // ─── Manual Entry ──────────────────────────────
-
- function showNewEntryModal() {
- document.getElementById('ne-date').value = new Date().toISOString().slice(0, 10);
- document.getElementById('ne-description').value = '';
- document.getElementById('ne-lines').innerHTML = '';
- document.getElementById('ne-balance').innerHTML = '';
- entryLineCtr = 0;
- addEntryLine();
- addEntryLine();
- document.getElementById('new-entry-modal').classList.add('active');
- }
-
- function addEntryLine() {
- const id = entryLineCtr++;
- const tbody = document.getElementById('ne-lines');
- const tr = document.createElement('tr');
- tr.id = `ne-line-${id}`;
-
- let acctOptions = 'Seleccionar ';
- accounts.forEach(a => {
- if (a.parent_id) { // Only leaf accounts
- acctOptions += `${a.code} - ${a.name} `;
- }
- });
-
- tr.innerHTML = `
- ${acctOptions}
-
-
- x `;
- tbody.appendChild(tr);
- }
-
- function updateEntryBalance() {
- const rows = document.querySelectorAll('#ne-lines tr');
- let totalDebit = 0, totalCredit = 0;
- rows.forEach(row => {
- const inputs = row.querySelectorAll('input[type="number"]');
- totalDebit += parseFloat(inputs[0].value) || 0;
- totalCredit += parseFloat(inputs[1].value) || 0;
- });
- const diff = Math.round((totalDebit - totalCredit) * 100) / 100;
- const el = document.getElementById('ne-balance');
- if (diff === 0) {
- el.innerHTML = `Cuadrada: Cargos $${fmt(totalDebit)} = Abonos $${fmt(totalCredit)} `;
- } else {
- el.innerHTML = `Descuadre: $${fmt(Math.abs(diff))} (Cargos $${fmt(totalDebit)} / Abonos $${fmt(totalCredit)}) `;
- }
- }
-
- async function createEntry() {
- try {
- const rows = document.querySelectorAll('#ne-lines tr');
- const lines = [];
- rows.forEach(row => {
- const sel = row.querySelector('select');
- const inputs = row.querySelectorAll('input[type="number"]');
- const acctId = parseInt(sel.value);
- const debit = parseFloat(inputs[0].value) || 0;
- const credit = parseFloat(inputs[1].value) || 0;
- if (acctId && (debit > 0 || credit > 0)) {
- lines.push({ account_id: acctId, debit, credit, description: '' });
- }
- });
-
- if (lines.length < 2) { alert('Se requieren al menos 2 lineas.'); return; }
-
- await api('/entries', {
- method: 'POST',
- body: JSON.stringify({
- date: document.getElementById('ne-date').value,
- description: document.getElementById('ne-description').value,
- lines,
- }),
- });
- closeModal('new-entry-modal');
- loadEntries();
- } catch (e) {
- alert('Error: ' + e.message);
- }
- }
-
- // ─── Trial Balance ─────────────────────────────
-
- async function loadTrialBalance() {
- try {
- const year = document.getElementById('tb-year').value;
- const month = document.getElementById('tb-month').value;
- const res = await api(`/trial-balance?year=${year}&month=${month}`);
- renderTrialBalance(res);
- } catch (e) {
- document.getElementById('trial-balance-content').innerHTML =
- `Error: ${e.message}
`;
- }
- }
-
- function renderTrialBalance(data) {
- const rows = data.data || [];
- let html = `
-
- Codigo Cuenta
- Saldo Inicial Cargos
- Abonos Saldo Final
- `;
-
- let totals = { si: 0, c: 0, a: 0, sf: 0 };
- for (const r of rows) {
- totals.si += r.saldo_inicial;
- totals.c += r.cargos;
- totals.a += r.abonos;
- totals.sf += r.saldo_final;
- html += `
- ${r.code} ${r.name}
- $${fmt(r.saldo_inicial)}
- $${fmt(r.cargos)}
- $${fmt(r.abonos)}
- $${fmt(r.saldo_final)}
- `;
- }
-
- html += `
- Totales
- $${fmt(totals.si)}
- $${fmt(totals.c)}
- $${fmt(totals.a)}
- $${fmt(totals.sf)}
-
`;
-
- document.getElementById('trial-balance-content').innerHTML = html;
- }
-
- // ─── Income Statement ──────────────────────────
-
- async function loadIncomeStatement() {
- try {
- const year = document.getElementById('is-year').value;
- const month = document.getElementById('is-month').value;
- const res = await api(`/income-statement?year=${year}&month=${month}`);
- renderIncomeStatement(res);
- } catch (e) {
- document.getElementById('income-statement-content').innerHTML =
- `Error: ${e.message}
`;
- }
- }
-
- function renderIncomeStatement(data) {
- let html = '';
-
- // Ingresos
- html += 'INGRESOS ';
- for (const item of data.ingresos.items) {
- html += `${item.code} - ${item.name} $${fmt(item.amount)} `;
- }
- html += `Total Ingresos $${fmt(data.ingresos.total)} `;
-
- // Costos
- html += 'COSTOS ';
- for (const item of data.costos.items) {
- html += `${item.code} - ${item.name} $${fmt(item.amount)} `;
- }
- html += `Total Costos $${fmt(data.costos.total)} `;
-
- // Utilidad bruta
- html += `UTILIDAD BRUTA $${fmt(data.utilidad_bruta)} `;
-
- // Gastos
- html += 'GASTOS ';
- for (const item of data.gastos.items) {
- html += `${item.code} - ${item.name} $${fmt(item.amount)} `;
- }
- html += `Total Gastos $${fmt(data.gastos.total)} `;
-
- // Utilidad neta
- const netColor = data.utilidad_neta >= 0 ? 'var(--success)' : 'var(--danger)';
- html += `UTILIDAD NETA
- $${fmt(data.utilidad_neta)} `;
-
- html += '
';
- document.getElementById('income-statement-content').innerHTML = html;
- }
-
- // ─── Balance Sheet ─────────────────────────────
-
- async function loadBalanceSheet() {
- try {
- const asOf = document.getElementById('bs-date').value;
- const res = await api(`/balance-sheet?date=${asOf}`);
- renderBalanceSheet(res);
- } catch (e) {
- document.getElementById('balance-sheet-content').innerHTML =
- `Error: ${e.message}
`;
- }
- }
-
- function renderBalanceSheet(data) {
- const balancedBadge = data.balanced
- ? 'Cuadrado '
- : 'Descuadrado ';
-
- let html = `Al ${data.as_of} ${balancedBadge}
- `;
-
- // Activo
- html += 'ACTIVO ';
- for (const item of data.activo.items) {
- html += `${item.code} - ${item.name} $${fmt(item.balance)} `;
- }
- html += `Total Activo $${fmt(data.activo.total)} `;
-
- // Pasivo
- html += 'PASIVO ';
- for (const item of data.pasivo.items) {
- html += `${item.code} - ${item.name} $${fmt(item.balance)} `;
- }
- html += `Total Pasivo $${fmt(data.pasivo.total)} `;
-
- // Capital
- html += 'CAPITAL ';
- for (const item of data.capital.items) {
- html += `${item.code} - ${item.name} $${fmt(item.balance)} `;
- }
- html += `Total Capital $${fmt(data.capital.total)} `;
-
- html += `Pasivo + Capital
- $${fmt(data.pasivo.total + data.capital.total)} `;
-
- html += '
';
- document.getElementById('balance-sheet-content').innerHTML = html;
- }
-
- // ─── Aging ─────────────────────────────────────
-
+ // ---- Tab 1: Cuentas por Cobrar (Aging) ----
async function loadAging() {
+ const panel = document.getElementById('panel-cxc');
+ if (!panel) return;
+ const tbody = panel.querySelector('.data-table tbody');
+ if (!tbody) return;
+
try {
const res = await api('/aging');
- renderAging(res);
+ const rows = res.data || [];
+ if (!rows.length) {
+ tbody.innerHTML = 'No hay cuentas por cobrar. ';
+ return;
+ }
+
+ tbody.innerHTML = rows.map(r => {
+ const status = r.days_overdue > 0 ? 'overdue' : r.paid > 0 && r.balance > 0 ? 'partial' : r.balance <= 0 ? 'ok' : 'pending';
+ const label = status === 'overdue' ? 'Vencida' : status === 'partial' ? 'Parcial' : status === 'ok' ? 'Pagada' : 'Vigente';
+ return `
+ ${r.invoice || r.folio || '-'}
+ ${r.name || r.customer_name || '-'}
+ ${r.issue_date ? new Date(r.issue_date).toLocaleDateString('es-MX') : '-'}
+ ${r.due_date ? new Date(r.due_date).toLocaleDateString('es-MX') : '-'}
+ $${fmt(r.total)}
+ $${fmt(r.paid || 0)}
+ $${fmt(r.balance || r.total)}
+ ${statusBadge(status, label)}
+ ${r.balance > 0 ? 'Cobrar' : 'Ver'}
+ `;
+ }).join('');
+
+ // Update pagination text
+ const pagSpan = panel.querySelector('.pagination span');
+ if (pagSpan) pagSpan.textContent = `Mostrando 1-${rows.length} de ${res.totals?.count || rows.length} registros`;
+
+ // Update summary card badge count
+ updateBadgeCount('cxc', rows.length);
} catch (e) {
- document.getElementById('aging-content').innerHTML =
- `Error: ${e.message}
`;
+ tbody.innerHTML = `Error: ${e.message} `;
}
}
- function renderAging(data) {
- const rows = data.data || [];
- let html = `
-
- Cliente Corriente
- 1-30d 31-60d
- 61-90d 90+d
- Total
- `;
-
- for (const r of rows) {
- html += `
- ${r.name}
- $${fmt(r.corriente)}
- $${fmt(r.d1_30)}
- $${fmt(r.d31_60)}
- $${fmt(r.d61_90)}
- $${fmt(r.d90_plus)}
- $${fmt(r.total)}
- `;
- }
-
- const t = data.totals || {};
- html += `
- Totales
- $${fmt(t.corriente)}
- $${fmt(t.d1_30)}
- $${fmt(t.d31_60)}
- $${fmt(t.d61_90)}
- $${fmt(t.d90_plus)}
- $${fmt(t.total)}
-
`;
-
- document.getElementById('aging-content').innerHTML = html;
- }
-
- // ─── Periods ───────────────────────────────────
-
- async function loadPeriods() {
- try {
- const res = await api('/periods');
- renderPeriods(res.data || []);
- } catch (e) {
- document.getElementById('periods-list').innerHTML =
- `Error: ${e.message}
`;
- }
- }
-
- function renderPeriods(periods) {
- const months = ['', 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
- 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
- let html = `
- Periodo Estado Cerrado por Fecha cierre `;
-
- for (const p of periods) {
- const badge = p.status === 'closed'
- ? 'Cerrado '
- : 'Abierto ';
- html += `
- ${months[p.month]} ${p.year}
- ${badge}
- ${p.closed_by_name || '-'}
- ${p.closed_at ? new Date(p.closed_at).toLocaleDateString('es-MX') : '-'}
- `;
- }
- html += '
';
- document.getElementById('periods-list').innerHTML = html;
- }
-
- async function closePeriod() {
- const year = parseInt(document.getElementById('cp-year').value);
- const month = parseInt(document.getElementById('cp-month').value);
- if (!confirm(`Cerrar periodo ${month}/${year}? Esta accion no se puede revertir.`)) return;
+ // ---- Tab 2: Cuentas por Pagar ----
+ async function loadAccountsPayable() {
+ const panel = document.getElementById('panel-cxp');
+ if (!panel) return;
+ const tbody = panel.querySelector('.data-table tbody');
+ if (!tbody) return;
try {
- await api('/periods/close', {
- method: 'POST',
- body: JSON.stringify({ year, month }),
+ // Use accounts endpoint filtered for payables or a dedicated endpoint
+ const res = await api('/aging?type=payable');
+ const rows = res.data || [];
+ if (!rows.length) {
+ tbody.innerHTML = 'No hay cuentas por pagar. ';
+ return;
+ }
+
+ tbody.innerHTML = rows.map(r => {
+ const status = r.days_overdue > 0 ? 'overdue' : r.paid > 0 && r.balance > 0 ? 'partial' : r.balance <= 0 ? 'ok' : 'pending';
+ const label = status === 'overdue' ? 'Vencida' : status === 'partial' ? 'Parcial' : status === 'ok' ? 'Pagada' : 'Vigente';
+ return `
+ ${r.invoice || r.folio || '-'}
+ ${r.name || r.vendor_name || '-'}
+ ${r.receipt_date ? new Date(r.receipt_date).toLocaleDateString('es-MX') : '-'}
+ ${r.due_date ? new Date(r.due_date).toLocaleDateString('es-MX') : '-'}
+ $${fmt(r.total)}
+ $${fmt(r.paid || 0)}
+ $${fmt(r.balance || r.total)}
+ ${statusBadge(status, label)}
+ ${r.balance > 0 ? 'Pagar' : 'Ver'}
+ `;
+ }).join('');
+
+ const pagSpan = panel.querySelector('.pagination span');
+ if (pagSpan) pagSpan.textContent = `Mostrando 1-${rows.length} de ${res.totals?.count || rows.length} registros`;
+ } catch (e) {
+ tbody.innerHTML = `Error: ${e.message} `;
+ }
+ }
+
+ // ---- Tab 3: Balance General ----
+ async function loadBalanceSheet() {
+ const panel = document.getElementById('panel-balance');
+ if (!panel) return;
+ const grid = panel.querySelector('.finance-grid');
+ if (!grid) return;
+
+ try {
+ const now = new Date();
+ const res = await api(`/balance-sheet?date=${now.toISOString().slice(0, 10)}`);
+
+ // Build assets card
+ const activoCard = grid.querySelector('.finance-card:first-child');
+ const pasivoCard = grid.querySelector('.finance-card:last-child');
+
+ if (activoCard) {
+ let html = ``;
+ if (res.activo && res.activo.items) {
+ for (const item of res.activo.items) {
+ const isNeg = item.balance < 0;
+ html += `
+ ${item.code ? item.code + ' ' : ''}${item.name}
+ $${fmt(item.balance)}
+
`;
+ }
+ }
+ html += `
+ Total Activos
+ $${fmt(res.activo?.total || 0)}
+
`;
+ activoCard.innerHTML = html;
+ }
+
+ if (pasivoCard) {
+ let html = ``;
+ // Pasivo
+ if (res.pasivo && res.pasivo.items) {
+ html += `Pasivo
`;
+ for (const item of res.pasivo.items) {
+ html += `
+ ${item.code ? item.code + ' ' : ''}${item.name}
+ $${fmt(item.balance)}
+
`;
+ }
+ html += `Total Pasivo $${fmt(res.pasivo.total)}
`;
+ }
+ // Capital
+ if (res.capital && res.capital.items) {
+ html += `Capital Contable
`;
+ for (const item of res.capital.items) {
+ const isPos = item.balance > 0;
+ html += `
+ ${item.code ? item.code + ' ' : ''}${item.name}
+ $${fmt(item.balance)}
+
`;
+ }
+ html += `Total Capital $${fmt(res.capital.total)}
`;
+ }
+ html += `
+ Total Pasivo + Capital
+ $${fmt((res.pasivo?.total || 0) + (res.capital?.total || 0))}
+
`;
+ pasivoCard.innerHTML = html;
+ }
+
+ // Update period selector text
+ const sel = panel.querySelector('.select-filter');
+ if (sel && res.as_of) {
+ sel.innerHTML = `Al ${res.as_of} `;
+ }
+ } catch (e) {
+ grid.innerHTML = `Error: ${e.message}
`;
+ }
+ }
+
+ // ---- Tab 4: Estado de Resultados ----
+ async function loadIncomeStatement() {
+ const panel = document.getElementById('panel-resultados');
+ if (!panel) return;
+ const grid = panel.querySelector('.finance-grid');
+ if (!grid) return;
+
+ try {
+ const now = new Date();
+ const res = await api(`/income-statement?year=${now.getFullYear()}&month=${now.getMonth() + 1}`);
+
+ const card = grid.querySelector('.finance-card');
+ if (!card) return;
+
+ let html = ``;
+
+ // Ingresos
+ html += `Ingresos
`;
+ if (res.ingresos && res.ingresos.items) {
+ for (const item of res.ingresos.items) {
+ html += `
+ ${item.name}
+ $${fmt(item.amount)}
+
`;
+ }
+ }
+ html += `Total Ingresos $${fmt(res.ingresos?.total || 0)}
`;
+
+ // Costos
+ html += `Costo de Ventas
`;
+ if (res.costos && res.costos.items) {
+ for (const item of res.costos.items) {
+ html += `
+ ${item.name}
+ -$${fmt(Math.abs(item.amount))}
+
`;
+ }
+ }
+ html += `Utilidad Bruta $${fmt(res.utilidad_bruta || 0)}
`;
+
+ // Gastos
+ html += `Gastos de Operacion
`;
+ if (res.gastos && res.gastos.items) {
+ for (const item of res.gastos.items) {
+ html += `
+ ${item.name}
+ -$${fmt(Math.abs(item.amount))}
+
`;
+ }
+ }
+ html += `Total Gastos Operacion -$${fmt(Math.abs(res.gastos?.total || 0))}
`;
+
+ // Utilidad neta
+ const netColor = (res.utilidad_neta || 0) >= 0 ? 'finance-card__row-value--positive' : 'finance-card__row-value--negative';
+ html += `
+ Utilidad Neta
+ $${fmt(res.utilidad_neta || 0)}
+
`;
+
+ card.innerHTML = html;
+ } catch (e) {
+ grid.innerHTML = `Error: ${e.message}
`;
+ }
+ }
+
+ // ---- Tab 5: Flujo de Efectivo ----
+ async function loadCashFlow() {
+ // Flujo de Efectivo currently has no dedicated API endpoint, keep demo data
+ // Future: wire to /pos/api/accounting/cash-flow
+ }
+
+ // ---- Tab 6: Conciliacion Bancaria ----
+ async function loadReconciliation() {
+ // Bank reconciliation currently has no dedicated API endpoint, keep demo data
+ // Future: wire to /pos/api/accounting/reconciliation
+ }
+
+ // ---- Tab 7: Cierre de Mes ----
+ async function loadPeriodClose() {
+ const panel = document.getElementById('panel-cierre');
+ if (!panel) return;
+
+ // Wire the "Ejecutar Cierre" button
+ const closeBtn = panel.querySelector('.btn--primary');
+ if (closeBtn && !closeBtn.dataset.wired) {
+ closeBtn.dataset.wired = 'true';
+ closeBtn.addEventListener('click', async () => {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = now.getMonth() + 1;
+ if (!confirm(`Cerrar periodo ${month}/${year}? Esta accion no se puede revertir.`)) return;
+ try {
+ await api('/periods/close', {
+ method: 'POST',
+ body: JSON.stringify({ year, month }),
+ });
+ alert('Periodo cerrado exitosamente.');
+ } catch (e) {
+ alert('Error: ' + e.message);
+ }
});
- loadPeriods();
- alert('Periodo cerrado exitosamente.');
- } catch (e) {
- alert('Error: ' + e.message);
}
}
- // ─── Modal helpers ─────────────────────────────
+ // ---- Summary cards update ----
+ async function loadSummaryCards() {
+ const cards = document.querySelectorAll('.summary-card');
+ if (cards.length < 4) return;
- function closeModal(id) {
- document.getElementById(id).classList.remove('active');
+ try {
+ // Load trial balance for overall numbers
+ const now = new Date();
+ const tb = await api(`/trial-balance?year=${now.getFullYear()}&month=${now.getMonth() + 1}`);
+ // The summary cards will keep their structure, just update values if API returns data
+ // This is best-effort; if API doesn't support summary data, demo values remain
+ } catch (_) {
+ // Non-critical, keep demo values
+ }
}
- // ─── Init ──────────────────────────────────────
+ // ---- Helper: update tab badge counts ----
+ function updateBadgeCount(tabName, count) {
+ const btn = document.querySelector(`.tab-btn[onclick*="'${tabName}'"]`);
+ if (!btn) return;
+ const badge = btn.querySelector('.tab-btn__badge');
+ if (badge) badge.textContent = count;
+ }
+ // ---- Clock ----
+ function startClock() {
+ const el = document.getElementById('live-clock');
+ if (!el) return;
+ const tick = () => {
+ const now = new Date();
+ el.textContent = now.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' });
+ };
+ tick();
+ setInterval(tick, 1000);
+ }
+
+ // ---- Init ----
function init() {
- initTabs();
-
- // Set default period values
- const now = new Date();
- ['tb-year', 'is-year', 'cp-year'].forEach(id => {
- const el = document.getElementById(id);
- if (el) el.value = now.getFullYear();
- });
- ['tb-month', 'is-month', 'cp-month'].forEach(id => {
- const el = document.getElementById(id);
- if (el) el.value = now.getMonth() + 1;
- });
- const bsDate = document.getElementById('bs-date');
- if (bsDate) bsDate.value = now.toISOString().slice(0, 10);
-
- // Set default entry date filters
- const firstDay = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);
- document.getElementById('entries-from').value = firstDay;
- document.getElementById('entries-to').value = now.toISOString().slice(0, 10);
-
- loadAccounts();
+ if (!checkAuth()) return;
+ startClock();
+ loadSummaryCards();
+ // Load initial tab data (cxc is active by default)
+ loadAging();
}
document.addEventListener('DOMContentLoaded', init);
- // Public API
+ // Expose switchTab globally for onclick handlers in HTML
+ window.switchTab = switchTab;
+
return {
- loadAccounts, loadEntries, loadTrialBalance, loadIncomeStatement,
- loadBalanceSheet, loadAging, loadPeriods, closePeriod,
- showNewAccountModal, createAccount,
- showNewEntryModal, addEntryLine, updateEntryBalance, createEntry,
- showEntryDetail, closeModal,
+ switchTab, loadAging, loadAccountsPayable, loadBalanceSheet,
+ loadIncomeStatement, loadCashFlow, loadReconciliation, loadPeriodClose,
};
})();
diff --git a/pos/static/js/invoicing.js b/pos/static/js/invoicing.js
index 72d55d2..fecc6b2 100644
--- a/pos/static/js/invoicing.js
+++ b/pos/static/js/invoicing.js
@@ -1,5 +1,7 @@
// /home/Autopartes/pos/static/js/invoicing.js
-// Invoicing module: CFDI queue management, cancel, PDF
+// Invoicing module — wired to design-system HTML IDs
+// Tabs: panel-facturas, panel-notas, panel-complementos, panel-cancelaciones, panel-config
+// Modals: modalDetalleOverlay, modalCancelOverlay
const Invoicing = (() => {
const API = '/pos/api/invoicing';
@@ -25,128 +27,304 @@ const Invoicing = (() => {
return parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
- function badgeClass(status) {
- return {
- pending: 'badge-pending',
- sending: 'badge-sending',
- stamped: 'badge-stamped',
- failed: 'badge-failed',
- cancelled: 'badge-cancelled',
- }[status] || '';
+ // ---- Auth check ----
+ function checkAuth() {
+ if (!token()) {
+ window.location.href = '/pos/login';
+ return false;
+ }
+ return true;
}
- function badgeLabel(status) {
- return {
- pending: 'Pendiente',
- sending: 'Enviando',
- stamped: 'Timbrado',
- failed: 'Fallido',
- cancelled: 'Cancelado',
- }[status] || status;
+ // ---- Tab switching (matches design system onclick="switchTab('xxx')") ----
+ function switchTab(name) {
+ // Deactivate all tabs
+ document.querySelectorAll('.tab-btn').forEach(btn => {
+ btn.classList.remove('is-active');
+ btn.setAttribute('aria-selected', 'false');
+ });
+ document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('is-active'));
+
+ // Activate the clicked tab button
+ const tabBtn = document.querySelector(`.tab-btn[onclick*="'${name}'"]`) ||
+ document.getElementById(`tab-${name}`);
+ if (tabBtn) {
+ tabBtn.classList.add('is-active');
+ tabBtn.setAttribute('aria-selected', 'true');
+ }
+
+ // Activate the target panel
+ const panel = document.getElementById(`panel-${name}`);
+ if (panel) panel.classList.add('is-active');
+
+ // Load data for the activated tab
+ if (name === 'facturas') loadFacturas();
+ if (name === 'notas') loadNotas();
+ if (name === 'complementos') loadComplementos();
+ if (name === 'cancelaciones') loadCancelaciones();
}
- // ─── Queue List ────────────────────────────────
+ // ---- Badge helpers ----
+ function statusBadge(status) {
+ const map = {
+ pending: { css: 'badge--pendiente', label: 'Pendiente' },
+ pendiente: { css: 'badge--pendiente', label: 'Pendiente' },
+ sending: { css: 'badge--proceso', label: 'Enviando' },
+ stamped: { css: 'badge--timbrada', label: 'Timbrada' },
+ timbrada: { css: 'badge--timbrada', label: 'Timbrada' },
+ failed: { css: 'badge--rechazada', label: 'Fallido' },
+ cancelled: { css: 'badge--cancelada', label: 'Cancelada' },
+ cancelada: { css: 'badge--cancelada', label: 'Cancelada' },
+ ppd: { css: 'badge--ppd', label: 'PPD' },
+ proceso: { css: 'badge--proceso', label: 'En proceso' },
+ aceptada: { css: 'badge--aceptada', label: 'Aceptada SAT' },
+ rechazada: { css: 'badge--rechazada', label: 'Rechazada SAT' },
+ };
+ const s = map[status] || { css: '', label: status || '' };
+ return `${s.label} `;
+ }
+
+ // ---- Facturas (Tab 1) — loads from CFDI queue with type=Ingreso ----
+ async function loadFacturas() {
+ const panel = document.getElementById('panel-facturas');
+ if (!panel) return;
+ const tbody = panel.querySelector('.data-table tbody');
+ if (!tbody) return;
- async function loadQueue() {
try {
- const status = document.getElementById('filter-status').value;
- const type = document.getElementById('filter-type').value;
- let qs = '?per_page=50';
- if (status) qs += `&status=${status}`;
- if (type) qs += `&type=${type}`;
-
- const res = await api(`/queue${qs}`);
- renderQueue(res.data || []);
- updateStats(res.data || []);
- } catch (e) {
- document.getElementById('queue-list').innerHTML =
- `Error: ${e.message}
`;
- }
- }
-
- function updateStats(items) {
- const counts = { pending: 0, sending: 0, stamped: 0, failed: 0, cancelled: 0 };
- items.forEach(i => { if (counts[i.status] !== undefined) counts[i.status]++; });
-
- document.getElementById('queue-stats').innerHTML = `
- ${counts.pending}
Pendientes
- ${counts.sending}
Enviando
- ${counts.stamped}
Timbrados
-
- ${counts.cancelled}
Cancelados
`;
- }
-
- function renderQueue(items) {
- const container = document.getElementById('queue-list');
- if (!items.length) { container.innerHTML = 'No hay CFDIs en la cola.
'; return; }
-
- let html = `
-
- # Venta Tipo Folio
- UUID Estado Reintentos Fecha Acciones
- `;
-
- for (const item of items) {
- const uuid = item.uuid_fiscal
- ? `${item.uuid_fiscal.substring(0, 8)}...`
- : '-';
- html += `
- ${item.id}
- #${item.sale_id}
- ${item.type}
- ${item.provisional_folio || '-'}
- ${uuid}
- ${badgeLabel(item.status)}
- ${item.retry_count || 0}
- ${item.created_at ? new Date(item.created_at).toLocaleDateString('es-MX') : ''}
+ const res = await api('/queue?per_page=50&type=Ingreso');
+ const items = res.data || [];
+ if (!items.length) {
+ tbody.innerHTML = ' No hay facturas en este periodo. ';
+ return;
+ }
+ tbody.innerHTML = items.map(item => `
+ ${item.provisional_folio || item.id || '-'}
+ ${item.serie || '-'}
+ ${item.customer_name || '-'}
+ ${item.rfc || '-'}
+ $${fmt(item.subtotal)}
+ $${fmt(item.tax)}
+ $${fmt(item.total)}
+ ${item.uso_cfdi || '-'}
+ ${statusBadge(item.status)}
- Ver
- ${item.status === 'stamped' ? `Cancelar ` : ''}
- ${item.sale_id ? `PDF ` : ''}
+
+
Ver
+ ${item.sale_id ? `
PDF ` : ''}
+ ${item.status === 'stamped' ? `
XML ` : ''}
+
- `;
+ `).join('');
+
+ // Update footer count
+ const footer = panel.querySelector('.table-footer span');
+ if (footer) footer.textContent = `Mostrando 1\u2013${items.length} de ${res.pagination?.total || items.length} facturas`;
+ } catch (e) {
+ tbody.innerHTML = `Error: ${e.message} `;
}
- html += '
';
- container.innerHTML = html;
}
- // ─── Detail ────────────────────────────────────
+ // ---- Notas de Credito (Tab 2) — loads from CFDI queue with type=Egreso ----
+ async function loadNotas() {
+ const panel = document.getElementById('panel-notas');
+ if (!panel) return;
+ const tbody = panel.querySelector('.data-table tbody');
+ if (!tbody) return;
+ try {
+ const res = await api('/queue?per_page=50&type=Egreso');
+ const items = res.data || [];
+ if (!items.length) {
+ tbody.innerHTML = 'No hay notas de credito. ';
+ return;
+ }
+ tbody.innerHTML = items.map(item => `
+ ${item.provisional_folio || '-'}
+ ${item.related_folio || '-'}
+ ${item.customer_name || '-'}
+ ${item.description || '-'}
+ $${fmt(item.total)}
+ ${statusBadge(item.status)}
+
+
+
Ver
+ ${item.sale_id ? `
PDF ` : ''}
+
+
+ `).join('');
+ } catch (e) {
+ tbody.innerHTML = `Error: ${e.message} `;
+ }
+ }
+
+ // ---- Complementos de Pago (Tab 3) — loads from CFDI queue with type=Pago ----
+ async function loadComplementos() {
+ const panel = document.getElementById('panel-complementos');
+ if (!panel) return;
+ const tbody = panel.querySelector('.data-table tbody');
+ if (!tbody) return;
+
+ try {
+ const res = await api('/queue?per_page=50&type=Pago');
+ const items = res.data || [];
+ if (!items.length) {
+ tbody.innerHTML = 'No hay complementos de pago. ';
+ return;
+ }
+ tbody.innerHTML = items.map(item => `
+ ${item.provisional_folio || '-'}
+ ${item.related_folio || '-'}
+ ${item.customer_name || '-'}
+ $${fmt(item.total)}
+ ${item.payment_method || '-'}
+ ${item.created_at ? new Date(item.created_at).toLocaleDateString('es-MX') : '-'}
+ ${statusBadge(item.status)}
+
+
+ Ver
+ ${item.status === 'stamped' ? `XML ` : ''}
+
+
+ `).join('');
+ } catch (e) {
+ tbody.innerHTML = `Error: ${e.message} `;
+ }
+ }
+
+ // ---- Cancelaciones (Tab 4) — loads cancelled/cancelling CFDIs ----
+ async function loadCancelaciones() {
+ const panel = document.getElementById('panel-cancelaciones');
+ if (!panel) return;
+ const grid = panel.querySelector('.cancel-grid');
+ if (!grid) return;
+
+ try {
+ const res = await api('/queue?per_page=50&status=cancelled');
+ const items = res.data || [];
+
+ // Also try to get in-process cancellations
+ let processingItems = [];
+ try {
+ const res2 = await api('/queue?per_page=50&status=cancelling');
+ processingItems = res2.data || [];
+ } catch (_) { /* ignore */ }
+
+ const allItems = [...processingItems, ...items];
+ if (!allItems.length) {
+ grid.innerHTML = 'No hay solicitudes de cancelacion.
';
+ return;
+ }
+
+ grid.innerHTML = allItems.map(item => {
+ const cardClass = item.status === 'cancelled' ? 'cancel-card--aceptada' :
+ item.status === 'cancelling' ? 'cancel-card--proceso' :
+ item.cancel_accepted === false ? 'cancel-card--rechazada' :
+ 'cancel-card--proceso';
+ const badgeText = item.status === 'cancelled' ? statusBadge('aceptada') :
+ item.cancel_accepted === false ? statusBadge('rechazada') :
+ statusBadge('proceso');
+
+ return `
+
+
+
+ Cliente
+ ${item.customer_name || '-'}
+
+
+ RFC
+ ${item.rfc || '-'}
+
+
+ Motivo
+ ${item.cancel_motive || '-'}
+
+
+ Monto
+ $${fmt(item.total)} MXN
+
+
+
+
`;
+ }).join('');
+ } catch (e) {
+ grid.innerHTML = `Error: ${e.message}
`;
+ }
+ }
+
+ // ---- Detail modal (uses modalDetalleOverlay) ----
async function showDetail(cfdiId) {
+ const overlay = document.getElementById('modalDetalleOverlay');
+ if (!overlay) return;
+
try {
const item = await api(`/queue/${cfdiId}`);
- let html = `CFDI #${item.id}
-
-
Venta #${item.sale_id}
-
Tipo ${item.type}
-
Estado ${badgeLabel(item.status)}
-
Folio Provisional ${item.provisional_folio || '-'}
-
UUID Fiscal ${item.uuid_fiscal || '-'}
-
Reintentos ${item.retry_count}
-
Creado ${item.created_at || '-'}
-
Timbrado ${item.stamped_at || '-'}
-
`;
+ const modalCard = overlay.querySelector('.modal-card');
+ if (!modalCard) return;
- if (item.error_message) {
- html += `Error: ${item.error_message}
`;
- }
- if (item.cancel_motive) {
- html += `Motivo cancelacion: ${item.cancel_motive}
`;
+ // Update header
+ const headerTitle = modalCard.querySelector('div > div:first-child > div:first-child');
+ const headerSub = modalCard.querySelector('div > div:first-child > div:nth-child(2)');
+ if (headerTitle) headerTitle.textContent = 'Detalle de Factura';
+ if (headerSub) headerSub.textContent = `${item.provisional_folio || 'CFDI-' + item.id} \u2014 ${item.status === 'stamped' ? 'Timbrada' : item.status === 'cancelled' ? 'Cancelada' : item.status === 'pending' ? 'Pendiente' : item.status || ''}`;
+
+ // Update detail grid
+ const detailGrid = modalCard.querySelector('div:nth-child(2)');
+ if (detailGrid) {
+ detailGrid.innerHTML = `
+
+
+
Emisor
+
${item.emisor_name || 'Nexus Autoparts SA de CV'}
+
${item.emisor_rfc || ''}
+
+
+
Receptor
+
${item.customer_name || '-'}
+
${item.rfc || ''}
+
+
+
UUID
+
${item.uuid_fiscal || 'Sin timbrar'}
+
+
+
Total
+
$${fmt(item.total)}
+
+
+ ${item.error_message ? `Error: ${escapeHtml(item.error_message)}
` : ''}
+ ${(item.xml_signed || item.xml_unsigned) ? `
+ Vista previa XML
+ ${escapeHtml(item.xml_signed || item.xml_unsigned)}
+ ` : ''}`;
}
- // XML preview
- const xml = item.xml_signed || item.xml_unsigned;
- if (xml) {
- html += `XML ${escapeHtml(xml)}
`;
+ // Wire the cancel button inside modal footer
+ const cancelBtn = modalCard.querySelector('div:last-child button:last-child');
+ if (cancelBtn && item.status === 'stamped') {
+ cancelBtn.style.display = '';
+ cancelBtn.onclick = () => {
+ overlay.style.display = 'none';
+ showCancelModal(cfdiId);
+ };
+ } else if (cancelBtn) {
+ cancelBtn.style.display = 'none';
}
- document.getElementById('detail-content').innerHTML = html;
- document.getElementById('detail-modal').classList.add('active');
+ // Store current CFDI id for use by footer buttons
+ overlay.dataset.cfdiId = cfdiId;
+ overlay.dataset.saleId = item.sale_id || '';
+
+ overlay.style.display = 'flex';
} catch (e) {
- alert('Error: ' + e.message);
+ alert('Error al cargar detalle: ' + e.message);
}
}
@@ -156,42 +334,31 @@ const Invoicing = (() => {
return div.innerHTML;
}
- // ─── Process Queue ─────────────────────────────
-
- async function processQueue() {
- if (!confirm('Procesar todos los CFDIs pendientes?')) return;
- try {
- const result = await api('/queue/process', { method: 'POST' });
- alert(`Procesados: ${result.processed}, Timbrados: ${result.stamped}, Fallidos: ${result.failed}`);
- loadQueue();
- } catch (e) {
- alert('Error: ' + e.message);
- }
- }
-
- // ─── Cancel ────────────────────────────────────
+ // ---- Cancel modal (uses modalCancelOverlay) ----
+ let cancelTargetId = null;
function showCancelModal(cfdiId) {
- document.getElementById('cancel-cfdi-id').value = cfdiId;
- document.getElementById('cancel-motive').value = '';
- document.getElementById('cancel-replacement-uuid').value = '';
- document.getElementById('replacement-uuid-group').style.display = 'none';
- document.getElementById('cancel-modal').classList.add('active');
- }
-
- function onMotiveChange() {
- const motive = document.getElementById('cancel-motive').value;
- document.getElementById('replacement-uuid-group').style.display =
- motive === '01' ? 'block' : 'none';
+ cancelTargetId = cfdiId;
+ const overlay = document.getElementById('modalCancelOverlay');
+ if (overlay) overlay.style.display = 'flex';
}
async function confirmCancel() {
- const cfdiId = document.getElementById('cancel-cfdi-id').value;
- const motive = document.getElementById('cancel-motive').value;
- const replacementUuid = document.getElementById('cancel-replacement-uuid').value;
+ if (!cancelTargetId) return;
+ const overlay = document.getElementById('modalCancelOverlay');
+ if (!overlay) return;
- if (!motive) { alert('Selecciona un motivo de cancelacion.'); return; }
- if (motive === '01' && !replacementUuid) { alert('UUID sustituto requerido para motivo 01.'); return; }
+ const selectedRadio = overlay.querySelector('input[name="motivo-sat"]:checked');
+ if (!selectedRadio) { alert('Selecciona un motivo de cancelacion.'); return; }
+ const motive = selectedRadio.value;
+
+ const uuidInput = overlay.querySelector('input[type="text"]');
+ const replacementUuid = uuidInput ? uuidInput.value.trim() : '';
+
+ if (motive === '01' && !replacementUuid) {
+ alert('UUID sustituto requerido para motivo 01.');
+ return;
+ }
if (!confirm('Confirmar cancelacion ante el SAT?')) return;
@@ -199,29 +366,83 @@ const Invoicing = (() => {
const body = { motive };
if (replacementUuid) body.replacement_uuid = replacementUuid;
- await api(`/cancel/${cfdiId}`, { method: 'POST', body: JSON.stringify(body) });
- closeModal('cancel-modal');
- loadQueue();
+ await api(`/cancel/${cancelTargetId}`, { method: 'POST', body: JSON.stringify(body) });
+ overlay.style.display = 'none';
+ cancelTargetId = null;
+ loadFacturas();
alert('CFDI cancelado exitosamente.');
} catch (e) {
alert('Error: ' + e.message);
}
}
- // ─── Modal helpers ─────────────────────────────
-
- function closeModal(id) {
- document.getElementById(id).classList.remove('active');
+ // ---- Process entire queue ----
+ async function processQueue() {
+ if (!confirm('Procesar todos los CFDIs pendientes?')) return;
+ try {
+ const result = await api('/queue/process', { method: 'POST' });
+ alert(`Procesados: ${result.processed}, Timbrados: ${result.stamped}, Fallidos: ${result.failed}`);
+ loadFacturas();
+ } catch (e) {
+ alert('Error: ' + e.message);
+ }
}
- // ─── Init ──────────────────────────────────────
+ // ---- Clock ----
+ function startClock() {
+ const el = document.getElementById('live-clock');
+ if (!el) return;
+ const tick = () => {
+ const now = new Date();
+ el.textContent = now.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
+ };
+ tick();
+ setInterval(tick, 1000);
+ }
- document.addEventListener('DOMContentLoaded', () => {
- loadQueue();
- });
+ // ---- Wire cancel modal "Solicitar Cancelacion SAT" button ----
+ function wireCancelModal() {
+ const overlay = document.getElementById('modalCancelOverlay');
+ if (!overlay) return;
+ const footerBtns = overlay.querySelectorAll('div:last-child button');
+ if (footerBtns.length >= 2) {
+ // Last button = confirm cancel
+ footerBtns[footerBtns.length - 1].onclick = () => confirmCancel();
+ // Second to last = close
+ footerBtns[footerBtns.length - 2].onclick = () => {
+ overlay.style.display = 'none';
+ cancelTargetId = null;
+ };
+ }
+ }
+
+ // ---- Wire detail modal close button ----
+ function wireDetailModal() {
+ const overlay = document.getElementById('modalDetalleOverlay');
+ if (!overlay) return;
+ const closeBtn = overlay.querySelector('button[onclick*="modalDetalleOverlay"]');
+ if (closeBtn) {
+ closeBtn.onclick = () => { overlay.style.display = 'none'; };
+ }
+ }
+
+ // ---- Init ----
+ function init() {
+ if (!checkAuth()) return;
+ startClock();
+ wireDetailModal();
+ wireCancelModal();
+ // Load initial tab data (facturas is active by default)
+ loadFacturas();
+ }
+
+ document.addEventListener('DOMContentLoaded', init);
+
+ // Expose switchTab globally for onclick handlers in HTML
+ window.switchTab = switchTab;
return {
- loadQueue, processQueue, showDetail, showCancelModal,
- onMotiveChange, confirmCancel, closeModal,
+ switchTab, loadFacturas, loadNotas, loadComplementos, loadCancelaciones,
+ showDetail, showCancelModal, confirmCancel, processQueue,
};
})();