-- ============================================================================= -- ERP TRANSPORTISTAS - Schema Billing Transport DDL -- ============================================================================= -- Archivo: 07-billing-transport-ddl.sql -- Version: 1.0.0 -- Fecha: 2026-01-25 -- Descripcion: Tarifas, facturacion, recargos especificos de transporte -- ============================================================================= -- ============================================================================= -- TIPOS ENUMERADOS -- ============================================================================= CREATE TYPE billing.tipo_tarifa AS ENUM ( 'POR_VIAJE', 'POR_KM', 'POR_TONELADA', 'POR_M3', 'POR_PALLET', 'POR_HORA', 'MIXTA' ); CREATE TYPE billing.tipo_recargo AS ENUM ( 'FUEL_SURCHARGE', 'DETENTION', 'MANIOBRAS', 'CUSTODIA', 'ESCOLTA', 'PERNOCTA', 'ESTADIAS', 'FALSO_FLETE', 'SEGURO_ADICIONAL', 'OTRO' ); CREATE TYPE billing.estado_factura AS ENUM ( 'BORRADOR', 'EMITIDA', 'ENVIADA', 'PAGADA', 'PARCIAL', 'VENCIDA', 'CANCELADA' ); -- ============================================================================= -- TABLA: lanes (Rutas origen-destino) -- ============================================================================= CREATE TABLE billing.lanes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Identificación codigo VARCHAR(50) NOT NULL, nombre VARCHAR(200) NOT NULL, -- Origen origen_ciudad VARCHAR(100) NOT NULL, origen_estado VARCHAR(100) NOT NULL, origen_codigo_postal VARCHAR(10), -- Destino destino_ciudad VARCHAR(100) NOT NULL, destino_estado VARCHAR(100) NOT NULL, destino_codigo_postal VARCHAR(10), -- Distancia distancia_km DECIMAL(10, 2), tiempo_estimado_horas DECIMAL(6, 2), -- Estado activo BOOLEAN DEFAULT TRUE, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL ); CREATE INDEX idx_lane_tenant ON billing.lanes(tenant_id); CREATE UNIQUE INDEX idx_lane_codigo ON billing.lanes(tenant_id, codigo); -- ============================================================================= -- TABLA: tarifas -- ============================================================================= CREATE TABLE billing.tarifas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Identificación codigo VARCHAR(50) NOT NULL, nombre VARCHAR(200) NOT NULL, descripcion TEXT, -- Cliente (opcional para tarifas generales) cliente_id UUID, -- Lane (opcional) lane_id UUID REFERENCES billing.lanes(id), -- Tipo de servicio modalidad_servicio transport.modalidad_servicio, tipo_equipo fleet.tipo_unidad, -- Tipo de tarifa tipo_tarifa billing.tipo_tarifa NOT NULL, -- Precios tarifa_base DECIMAL(15, 2) NOT NULL, tarifa_km DECIMAL(10, 4), tarifa_tonelada DECIMAL(10, 4), tarifa_m3 DECIMAL(10, 4), tarifa_pallet DECIMAL(10, 4), tarifa_hora DECIMAL(10, 4), -- Mínimos minimo_facturar DECIMAL(15, 2), peso_minimo_kg DECIMAL(10, 2), -- Moneda moneda VARCHAR(3) DEFAULT 'MXN', -- Vigencia fecha_inicio DATE NOT NULL, fecha_fin DATE, -- Estado activa BOOLEAN DEFAULT TRUE, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_tarifa_tenant ON billing.tarifas(tenant_id); CREATE INDEX idx_tarifa_cliente ON billing.tarifas(cliente_id); CREATE INDEX idx_tarifa_lane ON billing.tarifas(lane_id); CREATE INDEX idx_tarifa_activa ON billing.tarifas(tenant_id, activa, fecha_inicio); -- ============================================================================= -- TABLA: recargos_catalogo -- ============================================================================= CREATE TABLE billing.recargos_catalogo ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Identificación codigo VARCHAR(50) NOT NULL, nombre VARCHAR(200) NOT NULL, tipo billing.tipo_recargo NOT NULL, descripcion TEXT, -- Monto es_porcentaje BOOLEAN DEFAULT FALSE, monto DECIMAL(15, 4) NOT NULL, -- Si es_porcentaje=true, es porcentaje; sino monto fijo moneda VARCHAR(3) DEFAULT 'MXN', -- Aplicación aplica_automatico BOOLEAN DEFAULT FALSE, condicion_aplicacion TEXT, -- Descripción de cuándo aplica -- Estado activo BOOLEAN DEFAULT TRUE, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL ); CREATE INDEX idx_recargo_tenant ON billing.recargos_catalogo(tenant_id); CREATE UNIQUE INDEX idx_recargo_codigo ON billing.recargos_catalogo(tenant_id, codigo); -- ============================================================================= -- TABLA: facturas_transporte -- ============================================================================= CREATE TABLE billing.facturas_transporte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Identificación serie VARCHAR(10), folio VARCHAR(20) NOT NULL, uuid_cfdi UUID, -- UUID del CFDI timbrado -- Cliente cliente_id UUID NOT NULL, cliente_rfc VARCHAR(13) NOT NULL, cliente_razon_social VARCHAR(200) NOT NULL, cliente_uso_cfdi VARCHAR(10), -- Fechas fecha_emision TIMESTAMPTZ NOT NULL, fecha_vencimiento DATE, -- Totales subtotal DECIMAL(15, 2) NOT NULL, descuento DECIMAL(15, 2) DEFAULT 0, iva DECIMAL(15, 2) DEFAULT 0, retencion_iva DECIMAL(15, 2) DEFAULT 0, retencion_isr DECIMAL(15, 2) DEFAULT 0, total DECIMAL(15, 2) NOT NULL, moneda VARCHAR(3) DEFAULT 'MXN', tipo_cambio DECIMAL(10, 4) DEFAULT 1, -- Pago forma_pago VARCHAR(10), metodo_pago VARCHAR(10), condiciones_pago VARCHAR(200), -- Relacionados viaje_ids UUID[], ot_ids UUID[], -- CFDI xml_cfdi TEXT, pdf_url TEXT, -- Estado estado billing.estado_factura DEFAULT 'BORRADOR', -- Pago monto_pagado DECIMAL(15, 2) DEFAULT 0, fecha_pago TIMESTAMPTZ, -- Cancelación fecha_cancelacion TIMESTAMPTZ, motivo_cancelacion TEXT, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_factura_tenant ON billing.facturas_transporte(tenant_id); CREATE INDEX idx_factura_cliente ON billing.facturas_transporte(cliente_id); CREATE INDEX idx_factura_estado ON billing.facturas_transporte(tenant_id, estado); CREATE INDEX idx_factura_fecha ON billing.facturas_transporte(fecha_emision); CREATE UNIQUE INDEX idx_factura_folio ON billing.facturas_transporte(tenant_id, serie, folio); -- ============================================================================= -- TABLA: lineas_factura -- ============================================================================= CREATE TABLE billing.lineas_factura ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), factura_id UUID NOT NULL REFERENCES billing.facturas_transporte(id), -- Secuencia linea INT NOT NULL, -- Concepto descripcion TEXT NOT NULL, clave_producto_sat VARCHAR(10), -- Catálogo SAT unidad_sat VARCHAR(10), -- Cantidades cantidad DECIMAL(12, 4) NOT NULL, precio_unitario DECIMAL(15, 4) NOT NULL, descuento DECIMAL(15, 2) DEFAULT 0, importe DECIMAL(15, 2) NOT NULL, -- Impuestos iva_tasa DECIMAL(5, 2) DEFAULT 16, iva_monto DECIMAL(15, 2), -- Referencia viaje_id UUID, ot_id UUID, recargo_id UUID REFERENCES billing.recargos_catalogo(id) ); CREATE INDEX idx_linea_factura ON billing.lineas_factura(factura_id); -- ============================================================================= -- TABLA: fuel_surcharge (Índice de combustible) -- ============================================================================= CREATE TABLE billing.fuel_surcharge ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Período fecha_inicio DATE NOT NULL, fecha_fin DATE NOT NULL, -- Precios de referencia precio_diesel_referencia DECIMAL(10, 4), -- Precio base precio_diesel_actual DECIMAL(10, 4), -- Surcharge porcentaje_surcharge DECIMAL(5, 2) NOT NULL, -- Estado activo BOOLEAN DEFAULT TRUE, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL ); CREATE INDEX idx_fuel_surcharge_fecha ON billing.fuel_surcharge(tenant_id, fecha_inicio, fecha_fin); -- ============================================================================= -- RLS POLICIES -- ============================================================================= ALTER TABLE billing.lanes ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.tarifas ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.recargos_catalogo ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.facturas_transporte ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.lineas_factura ENABLE ROW LEVEL SECURITY; ALTER TABLE billing.fuel_surcharge ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_lanes ON billing.lanes USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_tarifas ON billing.tarifas USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_recargos ON billing.recargos_catalogo USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_facturas ON billing.facturas_transporte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_lineas ON billing.lineas_factura USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_fuel ON billing.fuel_surcharge USING (tenant_id = current_setting('app.tenant_id')::uuid); -- ============================================================================= -- COMENTARIOS -- ============================================================================= COMMENT ON TABLE billing.lanes IS 'Rutas origen-destino para tarifas'; COMMENT ON TABLE billing.tarifas IS 'Catálogo de tarifas de transporte'; COMMENT ON TABLE billing.recargos_catalogo IS 'Catálogo de recargos aplicables'; COMMENT ON TABLE billing.facturas_transporte IS 'Facturas emitidas a clientes'; COMMENT ON TABLE billing.lineas_factura IS 'Detalle de líneas de factura'; COMMENT ON TABLE billing.fuel_surcharge IS 'Índices de fuel surcharge por período'; -- ============================================================================= -- FIN DDL BILLING -- =============================================================================