feat: Fase 1-3 completas - precios proveedor, multi-sucursal, factura global

Fase 1: Lista de precios de proveedor
- Tabla supplier_catalog_prices en master DB
- Endpoints GET/POST/PUT/DELETE /supplier-catalog/prices
- Upload CSV/Excel de precios de proveedor
- Visualizacion de supplier_price en catalogo y POS

Fase 2: Multi-sucursal completo
- Migracion v4.0: inventory.branch_id=NULL, tabla inventory_stock
- Campos fiscales en branches (RFC, regimen, CP, serie CFDI, certificados)
- Trigger trg_update_inventory_stock para sincronizar stock por sucursal
- Backend config_bp.py con CRUD de sucursales fiscales
- Backend inventory_bp.py y pos_bp.py refactorizados para inventario compartido
- Backend invoicing_bp.py usa datos fiscales de la sucursal de la venta
- Frontend config.html/js con modal de sucursales expandido

Fase 3: Factura global mensual
- Migracion v4.1: tablas global_invoice_sales, sales.global_invoiced_at
- build_global_invoice_xml() con InformacionGlobal SAT-compliant
- Servicio global_invoice.py para agrupar ventas PUE <=000
- Endpoints POST/GET /global-invoice y /global-invoice/eligible-sales
- Frontend invoicing.html/js con boton y modal de factura global
This commit is contained in:
2026-06-11 08:59:56 +00:00
parent ea29cc31c0
commit 2b73c2c6db
23 changed files with 1665 additions and 230 deletions

View File

@@ -478,6 +478,51 @@ const Invoicing = (() => {
alert('Nota de credito: proximamente');
}
// ---- Global Invoice ----
function openGlobalInvoiceModal() {
const now = new Date();
document.getElementById('global-year').value = now.getFullYear();
document.getElementById('global-month').value = now.getMonth() + 1;
document.getElementById('global-preview').innerHTML = 'Presiona "Vista previa" para ver ventas elegibles.';
document.getElementById('modalGlobalInvoice').style.display = 'flex';
}
async function previewGlobalInvoice() {
const year = document.getElementById('global-year').value;
const month = document.getElementById('global-month').value;
const preview = document.getElementById('global-preview');
preview.innerHTML = 'Cargando...';
try {
const res = await api(`/global-invoice/eligible-sales?year=${year}&month=${month}`);
preview.innerHTML = `<strong>${res.count} ventas elegibles</strong> — Total: $${fmt(res.total)}<br><small>${res.sales.map(s => '#' + s.id).join(', ')}</small>`;
} catch (e) {
preview.innerHTML = '<span style="color:var(--color-error);">Error: ' + e.message + '</span>';
}
}
async function generateGlobalInvoice() {
const year = parseInt(document.getElementById('global-year').value, 10);
const month = parseInt(document.getElementById('global-month').value, 10);
const btn = document.querySelector('#modalGlobalInvoice .btn--primary');
const originalText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Generando...';
try {
const res = await api('/global-invoice', {
method: 'POST',
body: JSON.stringify({ year, month })
});
alert(`Factura global generada: ${res.provisional_folio} (${res.sales_count} ventas, $${fmt(res.total)})`);
document.getElementById('modalGlobalInvoice').style.display = 'none';
loadFacturas();
} catch (e) {
alert('Error: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = originalText;
}
}
// Expose switchTab globally for onclick handlers in HTML
window.switchTab = switchTab;
window.showNewInvoiceModal = showNewInvoiceModal;
@@ -489,6 +534,7 @@ const Invoicing = (() => {
switchTab, loadFacturas, loadNotas, loadComplementos, loadCancelaciones,
showDetail, showCancelModal, confirmCancel, processQueue,
showNewInvoiceModal, closeNewInvoiceModal, submitNewInvoice, notaCreditoPlaceholder,
openGlobalInvoiceModal, previewGlobalInvoice, generateGlobalInvoice,
};
// Register Cmd+K items
if (typeof registerCmdKItem === "function") {