fix(pos): rewrite invoicing + accounting JS to match design system HTML

Invoicing JS now targets panel-facturas/notas/complementos/cancelaciones/config
panels, modalDetalleOverlay/modalCancelOverlay modals, and switchTab() calls.
Loads CFDI queue data dynamically into data-table tbodies replacing demo rows.

Accounting JS now targets panel-cxc/cxp/balance/resultados/flujo/conciliacion/cierre
panels and finance-card elements. Wires aging, balance sheet, income statement,
and period close to real API endpoints.

Both files include auth check, live clock, and global switchTab binding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 08:27:59 +00:00
parent 3ea6f181ff
commit 761e56e87f
2 changed files with 704 additions and 678 deletions

View File

@@ -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 `<span class="badge ${s.css}">${s.label}</span>`;
}
// ---- 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 =
`<p style="color:var(--danger);">Error: ${e.message}</p>`;
}
}
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 = `
<div class="stat-card"><div class="number">${counts.pending}</div><div class="label">Pendientes</div></div>
<div class="stat-card"><div class="number">${counts.sending}</div><div class="label">Enviando</div></div>
<div class="stat-card"><div class="number">${counts.stamped}</div><div class="label">Timbrados</div></div>
<div class="stat-card"><div class="number">${counts.failed}</div><div class="label">Fallidos</div></div>
<div class="stat-card"><div class="number">${counts.cancelled}</div><div class="label">Cancelados</div></div>`;
}
function renderQueue(items) {
const container = document.getElementById('queue-list');
if (!items.length) { container.innerHTML = '<p>No hay CFDIs en la cola.</p>'; return; }
let html = `<table class="queue-table">
<thead><tr>
<th>#</th><th>Venta</th><th>Tipo</th><th>Folio</th>
<th>UUID</th><th>Estado</th><th>Reintentos</th><th>Fecha</th><th>Acciones</th>
</tr></thead><tbody>`;
for (const item of items) {
const uuid = item.uuid_fiscal
? `${item.uuid_fiscal.substring(0, 8)}...`
: '-';
html += `<tr>
<td>${item.id}</td>
<td>#${item.sale_id}</td>
<td>${item.type}</td>
<td>${item.provisional_folio || '-'}</td>
<td title="${item.uuid_fiscal || ''}">${uuid}</td>
<td><span class="badge ${badgeClass(item.status)}">${badgeLabel(item.status)}</span></td>
<td>${item.retry_count || 0}</td>
<td>${item.created_at ? new Date(item.created_at).toLocaleDateString('es-MX') : ''}</td>
const res = await api('/queue?per_page=50&type=Ingreso');
const items = res.data || [];
if (!items.length) {
tbody.innerHTML = '<tr><td colspan="10" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">No hay facturas en este periodo.</td></tr>';
return;
}
tbody.innerHTML = items.map(item => `<tr>
<td class="td--mono">${item.provisional_folio || item.id || '-'}</td>
<td class="td--primary">${item.serie || '-'}</td>
<td class="td--primary">${item.customer_name || '-'}</td>
<td class="td--mono">${item.rfc || '-'}</td>
<td class="td--amount">$${fmt(item.subtotal)}</td>
<td class="td--amount">$${fmt(item.tax)}</td>
<td class="td--amount">$${fmt(item.total)}</td>
<td style="font-size:var(--text-caption);">${item.uso_cfdi || '-'}</td>
<td>${statusBadge(item.status)}</td>
<td>
<button class="btn btn-secondary" style="padding:0.2rem 0.4rem;font-size:0.8rem;"
onclick="Invoicing.showDetail(${item.id})">Ver</button>
${item.status === 'stamped' ? `<button class="btn btn-danger" style="padding:0.2rem 0.4rem;font-size:0.8rem;"
onclick="Invoicing.showCancelModal(${item.id})">Cancelar</button>` : ''}
${item.sale_id ? `<a href="/pos/api/invoicing/${item.sale_id}/pdf" target="_blank"
class="btn btn-secondary" style="padding:0.2rem 0.4rem;font-size:0.8rem;">PDF</a>` : ''}
<div style="display:flex;gap:4px;">
<button class="btn btn--ghost btn--sm" onclick="Invoicing.showDetail(${item.id})">Ver</button>
${item.sale_id ? `<a href="${API}/${item.sale_id}/pdf" target="_blank" class="btn btn--ghost btn--sm">PDF</a>` : ''}
${item.status === 'stamped' ? `<button class="btn btn--ghost btn--sm" onclick="Invoicing.showDetail(${item.id})">XML</button>` : ''}
</div>
</td>
</tr>`;
</tr>`).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 = `<tr><td colspan="10" style="color:var(--color-error);padding:var(--space-4);">Error: ${e.message}</td></tr>`;
}
html += '</tbody></table>';
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 = '<tr><td colspan="7" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">No hay notas de credito.</td></tr>';
return;
}
tbody.innerHTML = items.map(item => `<tr>
<td class="td--mono">${item.provisional_folio || '-'}</td>
<td class="td--mono" style="color:var(--color-text-accent);">${item.related_folio || '-'}</td>
<td class="td--primary">${item.customer_name || '-'}</td>
<td>${item.description || '-'}</td>
<td class="td--amount">$${fmt(item.total)}</td>
<td>${statusBadge(item.status)}</td>
<td>
<div style="display:flex;gap:4px;">
<button class="btn btn--ghost btn--sm" onclick="Invoicing.showDetail(${item.id})">Ver</button>
${item.sale_id ? `<a href="${API}/${item.sale_id}/pdf" target="_blank" class="btn btn--ghost btn--sm">PDF</a>` : ''}
</div>
</td>
</tr>`).join('');
} catch (e) {
tbody.innerHTML = `<tr><td colspan="7" style="color:var(--color-error);padding:var(--space-4);">Error: ${e.message}</td></tr>`;
}
}
// ---- 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 = '<tr><td colspan="8" style="text-align:center;padding:var(--space-6);color:var(--color-text-muted);">No hay complementos de pago.</td></tr>';
return;
}
tbody.innerHTML = items.map(item => `<tr>
<td class="td--mono">${item.provisional_folio || '-'}</td>
<td class="td--mono" style="color:var(--color-text-accent);">${item.related_folio || '-'}</td>
<td class="td--primary">${item.customer_name || '-'}</td>
<td class="td--amount">$${fmt(item.total)}</td>
<td style="font-size:var(--text-caption);">${item.payment_method || '-'}</td>
<td>${item.created_at ? new Date(item.created_at).toLocaleDateString('es-MX') : '-'}</td>
<td>${statusBadge(item.status)}</td>
<td>
<div style="display:flex;gap:4px;">
<button class="btn btn--ghost btn--sm" onclick="Invoicing.showDetail(${item.id})">Ver</button>
${item.status === 'stamped' ? `<button class="btn btn--ghost btn--sm" onclick="Invoicing.showDetail(${item.id})">XML</button>` : ''}
</div>
</td>
</tr>`).join('');
} catch (e) {
tbody.innerHTML = `<tr><td colspan="8" style="color:var(--color-error);padding:var(--space-4);">Error: ${e.message}</td></tr>`;
}
}
// ---- 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 = '<p style="padding:var(--space-6);color:var(--color-text-muted);text-align:center;">No hay solicitudes de cancelacion.</p>';
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 `<div class="cancel-card ${cardClass}">
<div class="cancel-card__header">
<span class="cancel-card__folio">${item.provisional_folio || `CFDI-${item.id}`}</span>
${badgeText}
</div>
<div class="cancel-card__body">
<div class="cancel-card__row">
<span class="cancel-card__row-label">Cliente</span>
<span class="cancel-card__row-value">${item.customer_name || '-'}</span>
</div>
<div class="cancel-card__row">
<span class="cancel-card__row-label">RFC</span>
<span class="cancel-card__row-value" style="font-family:var(--font-mono);font-size:0.8rem;">${item.rfc || '-'}</span>
</div>
<div class="cancel-card__row">
<span class="cancel-card__row-label">Motivo</span>
<span class="cancel-card__row-value">${item.cancel_motive || '-'}</span>
</div>
<div class="cancel-card__row">
<span class="cancel-card__row-label">Monto</span>
<span class="cancel-card__row-value" style="font-family:var(--font-mono);font-weight:600;color:var(--color-text-primary);">$${fmt(item.total)} MXN</span>
</div>
</div>
<div class="cancel-card__footer">
<span style="font-size:var(--text-caption);color:var(--color-text-muted);">${item.cancelled_at ? 'Cancelada: ' + new Date(item.cancelled_at).toLocaleDateString('es-MX') : item.created_at ? 'Solicitada: ' + new Date(item.created_at).toLocaleDateString('es-MX') : ''}</span>
<button class="btn btn--ghost btn--sm" onclick="Invoicing.showDetail(${item.id})">Ver detalle</button>
</div>
</div>`;
}).join('');
} catch (e) {
grid.innerHTML = `<p style="color:var(--color-error);padding:var(--space-4);">Error: ${e.message}</p>`;
}
}
// ---- 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 = `<h3>CFDI #${item.id}</h3>
<div class="detail-grid">
<div class="detail-item"><label>Venta</label><span>#${item.sale_id}</span></div>
<div class="detail-item"><label>Tipo</label><span>${item.type}</span></div>
<div class="detail-item"><label>Estado</label><span class="badge ${badgeClass(item.status)}">${badgeLabel(item.status)}</span></div>
<div class="detail-item"><label>Folio Provisional</label><span>${item.provisional_folio || '-'}</span></div>
<div class="detail-item"><label>UUID Fiscal</label><span>${item.uuid_fiscal || '-'}</span></div>
<div class="detail-item"><label>Reintentos</label><span>${item.retry_count}</span></div>
<div class="detail-item"><label>Creado</label><span>${item.created_at || '-'}</span></div>
<div class="detail-item"><label>Timbrado</label><span>${item.stamped_at || '-'}</span></div>
</div>`;
const modalCard = overlay.querySelector('.modal-card');
if (!modalCard) return;
if (item.error_message) {
html += `<p style="color:var(--danger);"><strong>Error:</strong> ${item.error_message}</p>`;
}
if (item.cancel_motive) {
html += `<p><strong>Motivo cancelacion:</strong> ${item.cancel_motive}</p>`;
// 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 = `
<div style="display:grid; grid-template-columns:1fr 1fr; gap:var(--space-4); margin-bottom:var(--space-6);">
<div>
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">Emisor</div>
<div style="font-weight:var(--font-weight-semibold);">${item.emisor_name || 'Nexus Autoparts SA de CV'}</div>
<div style="font-family:var(--font-mono); font-size:var(--text-body-sm); color:var(--color-text-secondary);">${item.emisor_rfc || ''}</div>
</div>
<div>
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">Receptor</div>
<div style="font-weight:var(--font-weight-semibold);">${item.customer_name || '-'}</div>
<div style="font-family:var(--font-mono); font-size:var(--text-body-sm); color:var(--color-text-secondary);">${item.rfc || ''}</div>
</div>
<div>
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">UUID</div>
<div style="font-family:var(--font-mono); font-size:var(--text-caption); color:var(--color-text-accent); word-break:break-all;">${item.uuid_fiscal || 'Sin timbrar'}</div>
</div>
<div>
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-1);">Total</div>
<div style="font-family:var(--font-mono); font-size:var(--text-h4); font-weight:var(--font-weight-bold); color:var(--color-text-primary);">$${fmt(item.total)}</div>
</div>
</div>
${item.error_message ? `<p style="color:var(--color-error);margin-bottom:var(--space-3);"><strong>Error:</strong> ${escapeHtml(item.error_message)}</p>` : ''}
${(item.xml_signed || item.xml_unsigned) ? `
<div style="font-size:var(--text-caption); color:var(--color-text-muted); text-transform:uppercase; letter-spacing:var(--tracking-widest); margin-bottom:var(--space-2);">Vista previa XML</div>
<pre style="background:var(--color-surface-3); border:1px solid var(--color-border); border-radius:var(--radius-md); padding:var(--space-4); font-family:var(--font-mono); font-size:11px; color:var(--color-text-secondary); overflow-x:auto; max-height:200px; line-height:1.6;">${escapeHtml(item.xml_signed || item.xml_unsigned)}</pre>
` : ''}`;
}
// XML preview
const xml = item.xml_signed || item.xml_unsigned;
if (xml) {
html += `<h4>XML</h4><div class="xml-preview">${escapeHtml(xml)}</div>`;
// 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,
};
})();