-- ============================================================= -- ARCHIVO: 24-invoices.sql -- DESCRIPCION: Facturas de venta/compra y pagos -- VERSION: 1.0.0 -- PROYECTO: ERP-Core V2 -- FECHA: 2026-01-13 -- DEPENDE DE: 16-partners.sql, 22-sales.sql, 23-purchases.sql -- ============================================================= -- ===================== -- SCHEMA: billing -- ===================== CREATE SCHEMA IF NOT EXISTS billing; -- ===================== -- TABLA: invoices -- Facturas de venta y compra -- ===================== CREATE TABLE IF NOT EXISTS billing.invoices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, -- Identificacion invoice_number VARCHAR(30) NOT NULL, invoice_type VARCHAR(20) NOT NULL DEFAULT 'sale', -- sale (venta), purchase (compra), credit_note, debit_note -- Referencia a origen sales_order_id UUID REFERENCES sales.sales_orders(id), purchase_order_id UUID REFERENCES purchases.purchase_orders(id), -- Partner partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE RESTRICT, partner_name VARCHAR(200), partner_tax_id VARCHAR(50), -- Direcciones billing_address JSONB, -- Fechas invoice_date DATE NOT NULL DEFAULT CURRENT_DATE, due_date DATE, payment_date DATE, -- Fecha real de pago -- Totales currency VARCHAR(3) DEFAULT 'MXN', exchange_rate DECIMAL(10, 6) DEFAULT 1, subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0, tax_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, withholding_tax DECIMAL(15, 2) DEFAULT 0, -- Retenciones discount_amount DECIMAL(15, 2) NOT NULL DEFAULT 0, total DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Pagos amount_paid DECIMAL(15, 2) DEFAULT 0, amount_due DECIMAL(15, 2) GENERATED ALWAYS AS (total - COALESCE(amount_paid, 0)) STORED, -- Terminos payment_term_days INTEGER DEFAULT 0, payment_method VARCHAR(50), -- Estado status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, validated, sent, partial, paid, cancelled, voided -- CFDI (Facturacion electronica Mexico) cfdi_uuid VARCHAR(40), -- UUID del CFDI cfdi_status VARCHAR(20), -- pending, stamped, cancelled cfdi_xml TEXT, -- XML del CFDI cfdi_pdf_url VARCHAR(500), -- Notas notes TEXT, internal_notes TEXT, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_by UUID REFERENCES auth.users(id), deleted_at TIMESTAMPTZ, UNIQUE(tenant_id, invoice_number) ); -- Indices para invoices CREATE INDEX IF NOT EXISTS idx_invoices_tenant ON billing.invoices(tenant_id); CREATE INDEX IF NOT EXISTS idx_invoices_number ON billing.invoices(invoice_number); CREATE INDEX IF NOT EXISTS idx_invoices_type ON billing.invoices(invoice_type); CREATE INDEX IF NOT EXISTS idx_invoices_partner ON billing.invoices(partner_id); CREATE INDEX IF NOT EXISTS idx_invoices_sales_order ON billing.invoices(sales_order_id); CREATE INDEX IF NOT EXISTS idx_invoices_purchase_order ON billing.invoices(purchase_order_id); CREATE INDEX IF NOT EXISTS idx_invoices_status ON billing.invoices(status); CREATE INDEX IF NOT EXISTS idx_invoices_date ON billing.invoices(invoice_date); CREATE INDEX IF NOT EXISTS idx_invoices_due_date ON billing.invoices(due_date); CREATE INDEX IF NOT EXISTS idx_invoices_cfdi ON billing.invoices(cfdi_uuid); CREATE INDEX IF NOT EXISTS idx_invoices_unpaid ON billing.invoices(status) WHERE status IN ('validated', 'sent', 'partial'); -- ===================== -- TABLA: invoice_items -- Lineas de factura -- ===================== CREATE TABLE IF NOT EXISTS billing.invoice_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE, product_id UUID REFERENCES products.products(id) ON DELETE SET NULL, -- Linea line_number INTEGER NOT NULL DEFAULT 1, -- Producto product_sku VARCHAR(50), product_name VARCHAR(200) NOT NULL, description TEXT, -- SAT (Mexico) sat_product_code VARCHAR(20), -- Clave de producto SAT sat_unit_code VARCHAR(10), -- Clave de unidad SAT -- Cantidad y precio quantity DECIMAL(15, 4) NOT NULL DEFAULT 1, uom VARCHAR(20) DEFAULT 'PZA', unit_price DECIMAL(15, 4) NOT NULL DEFAULT 0, -- Descuentos discount_percent DECIMAL(5, 2) DEFAULT 0, discount_amount DECIMAL(15, 2) DEFAULT 0, -- Impuestos tax_rate DECIMAL(5, 2) DEFAULT 16.00, tax_amount DECIMAL(15, 2) DEFAULT 0, withholding_rate DECIMAL(5, 2) DEFAULT 0, withholding_amount DECIMAL(15, 2) DEFAULT 0, -- Totales subtotal DECIMAL(15, 2) NOT NULL DEFAULT 0, total DECIMAL(15, 2) NOT NULL DEFAULT 0, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Indices para invoice_items CREATE INDEX IF NOT EXISTS idx_invoice_items_invoice ON billing.invoice_items(invoice_id); CREATE INDEX IF NOT EXISTS idx_invoice_items_product ON billing.invoice_items(product_id); CREATE INDEX IF NOT EXISTS idx_invoice_items_line ON billing.invoice_items(invoice_id, line_number); -- ===================== -- TABLA: payments -- Pagos recibidos y realizados -- ===================== CREATE TABLE IF NOT EXISTS billing.payments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, -- Identificacion payment_number VARCHAR(30) NOT NULL, payment_type VARCHAR(20) NOT NULL DEFAULT 'received', -- received (cobro), made (pago) -- Partner partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE RESTRICT, partner_name VARCHAR(200), -- Monto currency VARCHAR(3) DEFAULT 'MXN', amount DECIMAL(15, 2) NOT NULL, exchange_rate DECIMAL(10, 6) DEFAULT 1, -- Fecha payment_date DATE NOT NULL DEFAULT CURRENT_DATE, -- Metodo de pago payment_method VARCHAR(50) NOT NULL, -- cash, transfer, check, credit_card, debit_card reference VARCHAR(100), -- Numero de referencia, cheque, etc. -- Cuenta bancaria bank_account_id UUID REFERENCES partners.partner_bank_accounts(id), -- Estado status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, confirmed, reconciled, cancelled -- Notas notes TEXT, -- CFDI de pago (Mexico) cfdi_uuid VARCHAR(40), cfdi_status VARCHAR(20), -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMPTZ, UNIQUE(tenant_id, payment_number) ); -- Indices para payments CREATE INDEX IF NOT EXISTS idx_payments_tenant ON billing.payments(tenant_id); CREATE INDEX IF NOT EXISTS idx_payments_number ON billing.payments(payment_number); CREATE INDEX IF NOT EXISTS idx_payments_type ON billing.payments(payment_type); CREATE INDEX IF NOT EXISTS idx_payments_partner ON billing.payments(partner_id); CREATE INDEX IF NOT EXISTS idx_payments_status ON billing.payments(status); CREATE INDEX IF NOT EXISTS idx_payments_date ON billing.payments(payment_date); CREATE INDEX IF NOT EXISTS idx_payments_method ON billing.payments(payment_method); -- ===================== -- TABLA: payment_allocations -- Aplicacion de pagos a facturas -- ===================== CREATE TABLE IF NOT EXISTS billing.payment_allocations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), payment_id UUID NOT NULL REFERENCES billing.payments(id) ON DELETE CASCADE, invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE, -- Monto aplicado amount DECIMAL(15, 2) NOT NULL, -- Fecha de aplicacion allocation_date DATE NOT NULL DEFAULT CURRENT_DATE, -- Metadata created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_by UUID REFERENCES auth.users(id), UNIQUE(payment_id, invoice_id) ); -- Indices para payment_allocations CREATE INDEX IF NOT EXISTS idx_payment_allocations_payment ON billing.payment_allocations(payment_id); CREATE INDEX IF NOT EXISTS idx_payment_allocations_invoice ON billing.payment_allocations(invoice_id); -- ===================== -- COMENTARIOS -- ===================== COMMENT ON TABLE billing.invoices IS 'Facturas de venta y compra'; COMMENT ON COLUMN billing.invoices.invoice_type IS 'Tipo: sale (venta), purchase (compra), credit_note (nota credito), debit_note (nota debito)'; COMMENT ON COLUMN billing.invoices.status IS 'Estado: draft, validated, sent, partial (pago parcial), paid, cancelled, voided'; COMMENT ON COLUMN billing.invoices.cfdi_uuid IS 'UUID del CFDI para facturacion electronica en Mexico'; COMMENT ON COLUMN billing.invoices.amount_due IS 'Saldo pendiente de pago (calculado)'; COMMENT ON TABLE billing.invoice_items IS 'Lineas de detalle de facturas'; COMMENT ON COLUMN billing.invoice_items.sat_product_code IS 'Clave de producto del catalogo SAT (Mexico)'; COMMENT ON COLUMN billing.invoice_items.sat_unit_code IS 'Clave de unidad del catalogo SAT (Mexico)'; COMMENT ON TABLE billing.payments IS 'Registro de pagos recibidos y realizados'; COMMENT ON COLUMN billing.payments.payment_type IS 'Tipo: received (cobro a cliente), made (pago a proveedor)'; COMMENT ON COLUMN billing.payments.status IS 'Estado: draft, confirmed, reconciled, cancelled'; COMMENT ON TABLE billing.payment_allocations IS 'Aplicacion de pagos a facturas especificas'; COMMENT ON COLUMN billing.payment_allocations.amount IS 'Monto del pago aplicado a esta factura';