-- ============================================================================= -- ERP TRANSPORTISTAS - Schema Compliance DDL -- ============================================================================= -- Archivo: 08-compliance-schema-ddl.sql -- Version: 1.0.0 -- Fecha: 2026-01-25 -- Descripcion: Carta Porte CFDI 3.1, HOS, inspecciones, NOM-087/068 -- ============================================================================= -- ============================================================================= -- TIPOS ENUMERADOS -- ============================================================================= CREATE TYPE compliance.estado_carta_porte AS ENUM ( 'BORRADOR', 'VALIDADA', 'TIMBRADA', 'CANCELADA' ); CREATE TYPE compliance.tipo_cfdi_carta_porte AS ENUM ( 'INGRESO', -- Servicio de transporte 'TRASLADO' -- Traslado propio ); CREATE TYPE compliance.estado_hos AS ENUM ( 'DRIVING', -- Conduciendo 'ON_DUTY', -- En servicio (no conduciendo) 'SLEEPER', -- En litera 'OFF_DUTY' -- Fuera de servicio ); -- ============================================================================= -- TABLA: cartas_porte -- ============================================================================= CREATE TABLE compliance.cartas_porte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Viaje relacionado viaje_id UUID NOT NULL, -- CFDI tipo_cfdi compliance.tipo_cfdi_carta_porte NOT NULL, version_carta_porte VARCHAR(10) DEFAULT '3.1', -- Identificación CFDI serie VARCHAR(10), folio VARCHAR(20), uuid_cfdi UUID, fecha_timbrado TIMESTAMPTZ, -- Emisor emisor_rfc VARCHAR(13) NOT NULL, emisor_nombre VARCHAR(200) NOT NULL, emisor_regimen_fiscal VARCHAR(10), -- Receptor (para Ingreso) / Propietario (para Traslado) receptor_rfc VARCHAR(13) NOT NULL, receptor_nombre VARCHAR(200) NOT NULL, receptor_uso_cfdi VARCHAR(10), receptor_domicilio_fiscal_cp VARCHAR(10), -- Totales CFDI subtotal DECIMAL(15, 2), total DECIMAL(15, 2), moneda VARCHAR(3) DEFAULT 'MXN', -- Datos transporte federal transporte_internacional BOOLEAN DEFAULT FALSE, entrada_salida_merc VARCHAR(10), -- 'Entrada' o 'Salida' pais_origen_destino VARCHAR(3), -- Datos específicos autotransporte permiso_sct VARCHAR(50), num_permiso_sct VARCHAR(50), config_vehicular VARCHAR(10), -- C2, C3, T3S2, etc. peso_bruto_total DECIMAL(12, 3), unidad_peso VARCHAR(10) DEFAULT 'KGM', num_total_mercancias INT, -- Seguro asegura_resp_civil VARCHAR(100), poliza_resp_civil VARCHAR(50), asegura_med_ambiente VARCHAR(100), poliza_med_ambiente VARCHAR(50), asegura_carga VARCHAR(100), poliza_carga VARCHAR(50), prima_seguro DECIMAL(15, 2), -- Estado estado compliance.estado_carta_porte DEFAULT 'BORRADOR', -- XML y PDF xml_cfdi TEXT, xml_carta_porte TEXT, pdf_url TEXT, qr_url TEXT, -- Cancelación fecha_cancelacion TIMESTAMPTZ, motivo_cancelacion TEXT, uuid_sustitucion UUID, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_carta_porte_tenant ON compliance.cartas_porte(tenant_id); CREATE INDEX idx_carta_porte_viaje ON compliance.cartas_porte(viaje_id); CREATE INDEX idx_carta_porte_uuid ON compliance.cartas_porte(uuid_cfdi); CREATE INDEX idx_carta_porte_estado ON compliance.cartas_porte(tenant_id, estado); -- ============================================================================= -- TABLA: ubicaciones_carta_porte -- ============================================================================= CREATE TABLE compliance.ubicaciones_carta_porte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), carta_porte_id UUID NOT NULL REFERENCES compliance.cartas_porte(id), -- Tipo tipo_ubicacion VARCHAR(10) NOT NULL, -- 'Origen' o 'Destino' -- ID Ubicación (catálogo SAT) id_ubicacion VARCHAR(10), -- RFC rfc_remitente_destinatario VARCHAR(13), nombre_remitente_destinatario VARCHAR(200), -- Domicilio pais VARCHAR(3) DEFAULT 'MEX', estado VARCHAR(10), -- Clave SAT municipio VARCHAR(10), -- Clave SAT localidad VARCHAR(10), codigo_postal VARCHAR(10) NOT NULL, colonia VARCHAR(10), calle VARCHAR(200), numero_exterior VARCHAR(50), numero_interior VARCHAR(50), referencia VARCHAR(500), -- Fechas fecha_hora_salida_llegada TIMESTAMPTZ, -- Distancia distancia_recorrida DECIMAL(10, 2), -- Solo para destinos -- Secuencia (orden en la ruta) secuencia INT NOT NULL ); CREATE INDEX idx_ubicacion_carta ON compliance.ubicaciones_carta_porte(carta_porte_id); -- ============================================================================= -- TABLA: mercancias_carta_porte -- ============================================================================= CREATE TABLE compliance.mercancias_carta_porte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), carta_porte_id UUID NOT NULL REFERENCES compliance.cartas_porte(id), -- Bienes transportados bienes_transp VARCHAR(10) NOT NULL, -- Clave SAT descripcion VARCHAR(1000) NOT NULL, cantidad DECIMAL(14, 3) NOT NULL, clave_unidad VARCHAR(10) NOT NULL, -- Clave SAT unidad VARCHAR(50), -- Dimensiones peso_en_kg DECIMAL(14, 3) NOT NULL, largo_cm DECIMAL(10, 2), ancho_cm DECIMAL(10, 2), alto_cm DECIMAL(10, 2), -- Valor valor_mercancia DECIMAL(15, 2), moneda VARCHAR(3) DEFAULT 'MXN', -- Material peligroso material_peligroso BOOLEAN DEFAULT FALSE, cve_material_peligroso VARCHAR(10), tipo_embalaje VARCHAR(10), descripcion_embalaje VARCHAR(200), -- Fracción arancelaria (comercio exterior) fraccion_arancelaria VARCHAR(10), uuid_comercio_ext UUID, -- Pedimentos (comercio exterior) pedimentos TEXT[], -- Array de números de pedimento -- Guías (paquetería) guias TEXT[], -- Array de números de guía -- Secuencia secuencia INT NOT NULL ); CREATE INDEX idx_mercancia_carta ON compliance.mercancias_carta_porte(carta_porte_id); -- ============================================================================= -- TABLA: figuras_transporte (Operador, propietario, arrendatario) -- ============================================================================= CREATE TABLE compliance.figuras_transporte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), carta_porte_id UUID NOT NULL REFERENCES compliance.cartas_porte(id), -- Tipo de figura tipo_figura VARCHAR(10) NOT NULL, -- '01'=Operador, '02'=Propietario, '03'=Arrendador -- Datos rfc_figura VARCHAR(13), nombre_figura VARCHAR(200), num_licencia VARCHAR(50), -- Solo para operadores -- Domicilio (opcional) pais VARCHAR(3), estado VARCHAR(10), codigo_postal VARCHAR(10), calle VARCHAR(200), -- Partes transporte (solo para Propietario/Arrendador) partes_transporte JSONB -- Array de {parte_transporte: string} ); CREATE INDEX idx_figura_carta ON compliance.figuras_transporte(carta_porte_id); -- ============================================================================= -- TABLA: autotransporte_carta_porte -- ============================================================================= CREATE TABLE compliance.autotransporte_carta_porte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), carta_porte_id UUID NOT NULL REFERENCES compliance.cartas_porte(id), -- Permiso perm_sct VARCHAR(10) NOT NULL, -- Tipo permiso SAT num_permiso_sct VARCHAR(50) NOT NULL, -- Identificación vehicular tractora config_vehicular VARCHAR(10) NOT NULL, -- C2, C3, T3S2, etc. placa_vm VARCHAR(15) NOT NULL, anio_modelo_vm INT, -- Remolques (puede haber hasta 2) remolques JSONB -- Array de {sub_tipo_rem, placa} ); CREATE INDEX idx_autotransporte_carta ON compliance.autotransporte_carta_porte(carta_porte_id); -- ============================================================================= -- TABLA: hos_logs (Hours of Service - NOM-087) -- ============================================================================= CREATE TABLE compliance.hos_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Operador y viaje operador_id UUID NOT NULL, viaje_id UUID, -- Log fecha DATE NOT NULL, hora_inicio TIME NOT NULL, hora_fin TIME, duracion_minutos INT, -- Estado estado compliance.estado_hos NOT NULL, -- Ubicación latitud DECIMAL(10, 7), longitud DECIMAL(10, 7), ubicacion_descripcion VARCHAR(200), -- Odómetro odometro_inicio INT, odometro_fin INT, -- Observaciones observaciones TEXT, -- Certificado certificado_por_operador BOOLEAN DEFAULT FALSE, certificado_fecha TIMESTAMPTZ, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_hos_operador ON compliance.hos_logs(operador_id); CREATE INDEX idx_hos_fecha ON compliance.hos_logs(tenant_id, fecha); CREATE INDEX idx_hos_viaje ON compliance.hos_logs(viaje_id); -- ============================================================================= -- TABLA: hos_resumen_diario -- ============================================================================= CREATE TABLE compliance.hos_resumen_diario ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), operador_id UUID NOT NULL, fecha DATE NOT NULL, -- Horas por estado horas_driving DECIMAL(4, 2) DEFAULT 0, horas_on_duty DECIMAL(4, 2) DEFAULT 0, horas_sleeper DECIMAL(4, 2) DEFAULT 0, horas_off_duty DECIMAL(4, 2) DEFAULT 0, horas_totales DECIMAL(4, 2) DEFAULT 0, -- Cumplimiento NOM-087 horas_conduccion_disponibles DECIMAL(4, 2), horas_servicio_disponibles DECIMAL(4, 2), en_cumplimiento BOOLEAN DEFAULT TRUE, violaciones TEXT[], -- Acumulados (ciclo de 7 días) horas_conduccion_ciclo DECIMAL(5, 2), horas_servicio_ciclo DECIMAL(5, 2), -- Certificación certificado BOOLEAN DEFAULT FALSE, certificado_fecha TIMESTAMPTZ, CONSTRAINT uq_hos_resumen UNIQUE (operador_id, fecha) ); CREATE INDEX idx_hos_resumen_operador ON compliance.hos_resumen_diario(operador_id); CREATE INDEX idx_hos_resumen_fecha ON compliance.hos_resumen_diario(tenant_id, fecha); -- ============================================================================= -- TABLA: inspecciones_pre_viaje -- ============================================================================= CREATE TABLE compliance.inspecciones_pre_viaje ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Viaje y unidad viaje_id UUID NOT NULL, unidad_id UUID NOT NULL, remolque_id UUID, operador_id UUID NOT NULL, -- Fecha fecha_inspeccion TIMESTAMPTZ NOT NULL, -- Resultado general aprobada BOOLEAN DEFAULT FALSE, -- Items checklist (JSON con resultados) checklist_items JSONB NOT NULL, -- Ejemplo: [{ "item": "Frenos", "estado": "OK", "observacion": null }, ...] -- Defectos encontrados defectos_encontrados TEXT[], defectos_criticos INT DEFAULT 0, defectos_menores INT DEFAULT 0, -- Firma firma_operador TEXT, -- Base64 firma_fecha TIMESTAMPTZ, -- Evidencias fotos JSONB, -- URLs de fotos -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_inspeccion_viaje ON compliance.inspecciones_pre_viaje(viaje_id); CREATE INDEX idx_inspeccion_unidad ON compliance.inspecciones_pre_viaje(unidad_id); CREATE INDEX idx_inspeccion_fecha ON compliance.inspecciones_pre_viaje(tenant_id, fecha_inspeccion); -- ============================================================================= -- RLS POLICIES -- ============================================================================= ALTER TABLE compliance.cartas_porte ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.ubicaciones_carta_porte ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.mercancias_carta_porte ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.figuras_transporte ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.autotransporte_carta_porte ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.hos_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.hos_resumen_diario ENABLE ROW LEVEL SECURITY; ALTER TABLE compliance.inspecciones_pre_viaje ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_cartas ON compliance.cartas_porte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_ubicaciones ON compliance.ubicaciones_carta_porte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_mercancias ON compliance.mercancias_carta_porte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_figuras ON compliance.figuras_transporte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_autotransporte ON compliance.autotransporte_carta_porte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_hos ON compliance.hos_logs USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_hos_resumen ON compliance.hos_resumen_diario USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_inspecciones ON compliance.inspecciones_pre_viaje USING (tenant_id = current_setting('app.tenant_id')::uuid); -- ============================================================================= -- COMENTARIOS -- ============================================================================= COMMENT ON TABLE compliance.cartas_porte IS 'CFDI con complemento Carta Porte 3.1'; COMMENT ON TABLE compliance.ubicaciones_carta_porte IS 'Ubicaciones origen/destino de la carta porte'; COMMENT ON TABLE compliance.mercancias_carta_porte IS 'Mercancías transportadas en la carta porte'; COMMENT ON TABLE compliance.figuras_transporte IS 'Figuras de transporte (operador, propietario, arrendador)'; COMMENT ON TABLE compliance.autotransporte_carta_porte IS 'Datos del autotransporte federal'; COMMENT ON TABLE compliance.hos_logs IS 'Registros de horas de servicio (NOM-087)'; COMMENT ON TABLE compliance.hos_resumen_diario IS 'Resumen diario de HOS por operador'; COMMENT ON TABLE compliance.inspecciones_pre_viaje IS 'Inspecciones pre-viaje de unidades'; -- ============================================================================= -- FIN DDL COMPLIANCE -- =============================================================================