-- ============================================================================= -- ERP TRANSPORTISTAS - Schema Transport DDL -- ============================================================================= -- Archivo: 01-transport-schema-ddl.sql -- Version: 1.0.0 -- Fecha: 2026-01-25 -- Descripcion: Ordenes de transporte, embarques, viajes, paradas, POD -- ============================================================================= -- ============================================================================= -- TIPOS ENUMERADOS ADICIONALES -- ============================================================================= CREATE TYPE transport.estado_orden AS ENUM ( 'BORRADOR', 'CONFIRMADA', 'ASIGNADA', 'EN_PROCESO', 'COMPLETADA', 'FACTURADA', 'CANCELADA' ); CREATE TYPE transport.tipo_carga AS ENUM ( 'GENERAL', 'PELIGROSA', 'REFRIGERADA', 'SOBREDIMENSIONADA', 'GRANEL', 'LIQUIDOS', 'CONTENEDOR', 'AUTOMOVILES' ); CREATE TYPE transport.estado_pod AS ENUM ( 'PENDIENTE', 'PARCIAL', 'COMPLETO', 'RECHAZADO' ); -- ============================================================================= -- TABLA: ordenes_transporte (OT) -- ============================================================================= CREATE TABLE transport.ordenes_transporte ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Identificación codigo VARCHAR(50) NOT NULL, referencia_cliente VARCHAR(100), -- Cliente (Shipper) shipper_id UUID NOT NULL, shipper_nombre VARCHAR(200) NOT NULL, -- Destinatario (Consignee) consignee_id UUID NOT NULL, consignee_nombre VARCHAR(200) NOT NULL, -- Origen origen_direccion TEXT NOT NULL, origen_codigo_postal VARCHAR(10), origen_ciudad VARCHAR(100), origen_estado VARCHAR(100), origen_pais VARCHAR(3) DEFAULT 'MEX', origen_latitud DECIMAL(10, 7), origen_longitud DECIMAL(10, 7), origen_contacto VARCHAR(200), origen_telefono VARCHAR(30), -- Destino destino_direccion TEXT NOT NULL, destino_codigo_postal VARCHAR(10), destino_ciudad VARCHAR(100), destino_estado VARCHAR(100), destino_pais VARCHAR(3) DEFAULT 'MEX', destino_latitud DECIMAL(10, 7), destino_longitud DECIMAL(10, 7), destino_contacto VARCHAR(200), destino_telefono VARCHAR(30), -- Fechas programadas fecha_recoleccion_programada TIMESTAMPTZ, fecha_entrega_programada TIMESTAMPTZ, ventana_recoleccion_inicio TIME, ventana_recoleccion_fin TIME, ventana_entrega_inicio TIME, ventana_entrega_fin TIME, -- Carga tipo_carga transport.tipo_carga DEFAULT 'GENERAL', descripcion_carga TEXT, peso_kg DECIMAL(12, 2), volumen_m3 DECIMAL(12, 4), piezas INT, pallets INT, valor_declarado DECIMAL(15, 2), moneda VARCHAR(3) DEFAULT 'MXN', -- Requisitos requiere_temperatura BOOLEAN DEFAULT FALSE, temperatura_min DECIMAL(5, 2), temperatura_max DECIMAL(5, 2), requiere_gps BOOLEAN DEFAULT FALSE, requiere_escolta BOOLEAN DEFAULT FALSE, requiere_cita BOOLEAN DEFAULT FALSE, instrucciones_especiales TEXT, -- Servicio y tarifa modalidad_servicio transport.modalidad_servicio DEFAULT 'FTL', tarifa_id UUID, tarifa_base DECIMAL(15, 2), recargos DECIMAL(15, 2) DEFAULT 0, descuentos DECIMAL(15, 2) DEFAULT 0, subtotal DECIMAL(15, 2), iva DECIMAL(15, 2), total DECIMAL(15, 2), -- Estado estado transport.estado_orden DEFAULT 'BORRADOR', -- Asignación viaje_id UUID, embarque_id UUID, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW(), updated_by_id UUID, deleted_at TIMESTAMPTZ, CONSTRAINT uq_ot_tenant_codigo UNIQUE (tenant_id, codigo) ); CREATE INDEX idx_ot_tenant ON transport.ordenes_transporte(tenant_id); CREATE INDEX idx_ot_estado ON transport.ordenes_transporte(tenant_id, estado); CREATE INDEX idx_ot_shipper ON transport.ordenes_transporte(tenant_id, shipper_id); CREATE INDEX idx_ot_fechas ON transport.ordenes_transporte(tenant_id, fecha_recoleccion_programada); CREATE INDEX idx_ot_viaje ON transport.ordenes_transporte(viaje_id) WHERE viaje_id IS NOT NULL; -- ============================================================================= -- TABLA: embarques (Agrupación de OTs) -- ============================================================================= CREATE TABLE transport.embarques ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), codigo VARCHAR(50) NOT NULL, descripcion VARCHAR(500), -- Cliente principal cliente_id UUID NOT NULL, -- Totales consolidados total_ots INT DEFAULT 0, peso_total_kg DECIMAL(12, 2), volumen_total_m3 DECIMAL(12, 4), -- Estado estado VARCHAR(20) DEFAULT 'ABIERTO', -- Viaje asignado viaje_id UUID, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT uq_embarque_tenant_codigo UNIQUE (tenant_id, codigo) ); CREATE INDEX idx_embarque_tenant ON transport.embarques(tenant_id); CREATE INDEX idx_embarque_cliente ON transport.embarques(tenant_id, cliente_id); -- ============================================================================= -- TABLA: viajes -- ============================================================================= CREATE TABLE transport.viajes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Identificación codigo VARCHAR(50) NOT NULL, -- Unidad y operador (referencias a fleet schema) unidad_id UUID NOT NULL, remolque_id UUID, operador_id UUID NOT NULL, -- Ruta origen_principal VARCHAR(200), destino_principal VARCHAR(200), distancia_estimada_km DECIMAL(10, 2), tiempo_estimado_horas DECIMAL(6, 2), -- Fechas programadas fecha_salida_programada TIMESTAMPTZ, fecha_llegada_programada TIMESTAMPTZ, -- Fechas reales fecha_salida_real TIMESTAMPTZ, fecha_llegada_real TIMESTAMPTZ, -- Kilometraje km_inicio INT, km_fin INT, km_recorridos INT GENERATED ALWAYS AS (km_fin - km_inicio) STORED, -- Estado estado transport.estado_viaje DEFAULT 'BORRADOR', -- Checklist pre-viaje checklist_completado BOOLEAN DEFAULT FALSE, checklist_fecha TIMESTAMPTZ, checklist_observaciones TEXT, -- Sellos sellos_salida JSONB, sellos_llegada JSONB, -- Costos costo_combustible DECIMAL(15, 2) DEFAULT 0, costo_peajes DECIMAL(15, 2) DEFAULT 0, costo_viaticos DECIMAL(15, 2) DEFAULT 0, costo_otros DECIMAL(15, 2) DEFAULT 0, costo_total DECIMAL(15, 2) DEFAULT 0, -- Ingresos ingreso_total DECIMAL(15, 2) DEFAULT 0, margen DECIMAL(15, 2) GENERATED ALWAYS AS (ingreso_total - costo_total) STORED, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW(), updated_by_id UUID, CONSTRAINT uq_viaje_tenant_codigo UNIQUE (tenant_id, codigo) ); CREATE INDEX idx_viaje_tenant ON transport.viajes(tenant_id); CREATE INDEX idx_viaje_estado ON transport.viajes(tenant_id, estado); CREATE INDEX idx_viaje_unidad ON transport.viajes(tenant_id, unidad_id); CREATE INDEX idx_viaje_operador ON transport.viajes(tenant_id, operador_id); CREATE INDEX idx_viaje_fechas ON transport.viajes(tenant_id, fecha_salida_programada); -- ============================================================================= -- TABLA: paradas_viaje (Multi-paradas) -- ============================================================================= CREATE TABLE transport.paradas_viaje ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), viaje_id UUID NOT NULL REFERENCES transport.viajes(id), -- Secuencia secuencia INT NOT NULL, -- Tipo de parada tipo VARCHAR(20) NOT NULL, -- 'RECOLECCION', 'ENTREGA', 'ESCALA' -- Ubicación direccion TEXT NOT NULL, codigo_postal VARCHAR(10), ciudad VARCHAR(100), estado VARCHAR(100), latitud DECIMAL(10, 7), longitud DECIMAL(10, 7), -- Contacto contacto_nombre VARCHAR(200), contacto_telefono VARCHAR(30), -- Programación hora_programada_llegada TIMESTAMPTZ, hora_programada_salida TIMESTAMPTZ, -- Real hora_real_llegada TIMESTAMPTZ, hora_real_salida TIMESTAMPTZ, -- OTs asociadas ots_ids UUID[], -- Estado estado VARCHAR(20) DEFAULT 'PENDIENTE', -- Observaciones observaciones TEXT, CONSTRAINT uq_parada_viaje_secuencia UNIQUE (viaje_id, secuencia) ); CREATE INDEX idx_parada_viaje ON transport.paradas_viaje(viaje_id); -- ============================================================================= -- TABLA: pod (Proof of Delivery) -- ============================================================================= CREATE TABLE transport.pod ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), viaje_id UUID NOT NULL REFERENCES transport.viajes(id), parada_id UUID REFERENCES transport.paradas_viaje(id), ot_id UUID REFERENCES transport.ordenes_transporte(id), -- Estado POD estado transport.estado_pod DEFAULT 'PENDIENTE', -- Recepción receptor_nombre VARCHAR(200), receptor_identificacion VARCHAR(50), fecha_recepcion TIMESTAMPTZ, -- Firma digital firma_digital TEXT, -- Base64 de la imagen de firma -- Evidencias (URLs o IDs de archivos) fotos_entrega JSONB, -- Cantidades piezas_entregadas INT, piezas_rechazadas INT, piezas_danadas INT, -- Observaciones observaciones TEXT, motivo_rechazo TEXT, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID, CONSTRAINT uq_pod_ot UNIQUE (ot_id) ); CREATE INDEX idx_pod_viaje ON transport.pod(viaje_id); CREATE INDEX idx_pod_estado ON transport.pod(tenant_id, estado); -- ============================================================================= -- TABLA: incidencias -- ============================================================================= CREATE TABLE transport.incidencias ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Referencias viaje_id UUID REFERENCES transport.viajes(id), ot_id UUID REFERENCES transport.ordenes_transporte(id), unidad_id UUID, operador_id UUID, -- Incidencia codigo VARCHAR(50) NOT NULL, tipo transport.tipo_incidencia NOT NULL, descripcion TEXT NOT NULL, -- Ubicación latitud DECIMAL(10, 7), longitud DECIMAL(10, 7), direccion TEXT, -- Fecha/hora fecha_incidencia TIMESTAMPTZ NOT NULL, fecha_reporte TIMESTAMPTZ DEFAULT NOW(), -- Impacto tiempo_perdido_horas DECIMAL(6, 2), costo_estimado DECIMAL(15, 2), -- Resolución estado VARCHAR(20) DEFAULT 'ABIERTA', fecha_resolucion TIMESTAMPTZ, resolucion TEXT, responsable_id UUID, -- Evidencias evidencias JSONB, -- Auditoría created_at TIMESTAMPTZ DEFAULT NOW(), created_by_id UUID NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT uq_incidencia_codigo UNIQUE (tenant_id, codigo) ); CREATE INDEX idx_incidencia_tenant ON transport.incidencias(tenant_id); CREATE INDEX idx_incidencia_viaje ON transport.incidencias(viaje_id) WHERE viaje_id IS NOT NULL; CREATE INDEX idx_incidencia_estado ON transport.incidencias(tenant_id, estado); -- ============================================================================= -- RLS POLICIES -- ============================================================================= ALTER TABLE transport.ordenes_transporte ENABLE ROW LEVEL SECURITY; ALTER TABLE transport.embarques ENABLE ROW LEVEL SECURITY; ALTER TABLE transport.viajes ENABLE ROW LEVEL SECURITY; ALTER TABLE transport.paradas_viaje ENABLE ROW LEVEL SECURITY; ALTER TABLE transport.pod ENABLE ROW LEVEL SECURITY; ALTER TABLE transport.incidencias ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_ot ON transport.ordenes_transporte USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_embarques ON transport.embarques USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_viajes ON transport.viajes USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_paradas ON transport.paradas_viaje USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_pod ON transport.pod USING (tenant_id = current_setting('app.tenant_id')::uuid); CREATE POLICY tenant_isolation_incidencias ON transport.incidencias USING (tenant_id = current_setting('app.tenant_id')::uuid); -- ============================================================================= -- COMENTARIOS -- ============================================================================= COMMENT ON TABLE transport.ordenes_transporte IS 'Ordenes de transporte (OT) - solicitudes de servicio'; COMMENT ON TABLE transport.embarques IS 'Agrupación de OTs para consolidación'; COMMENT ON TABLE transport.viajes IS 'Ejecución operativa de transporte'; COMMENT ON TABLE transport.paradas_viaje IS 'Paradas programadas en un viaje (multi-drop)'; COMMENT ON TABLE transport.pod IS 'Proof of Delivery - evidencia de entrega'; COMMENT ON TABLE transport.incidencias IS 'Registro de incidencias durante el transporte'; -- ============================================================================= -- FIN DDL TRANSPORT -- =============================================================================