erp-transportistas-database-v2/ddl/01-transport-schema-ddl.sql
Adrian Flores Cortes 7a91823784 feat: Add complete DDL for all transport schemas (01-08)
DDL files created:
- 01-transport-schema-ddl.sql: OT, embarques, viajes, paradas, POD, incidencias
- 02-fleet-schema-ddl.sql: unidades, remolques, operadores, documentos, asignaciones
- 03-tracking-schema-ddl.sql: posiciones GPS, eventos, geocercas, alertas, ETA
- 04-fuel-schema-ddl.sql: cargas combustible, peajes, gastos, anticipos, rendimiento
- 05-maintenance-schema-ddl.sql: planes, programacion, ordenes trabajo, checklist
- 06-carriers-schema-ddl.sql: carriers, documentos, unidades, operadores, scorecard
- 07-billing-transport-ddl.sql: lanes, tarifas, recargos, facturas, fuel surcharge
- 08-compliance-schema-ddl.sql: carta porte CFDI 3.1, HOS NOM-087, inspecciones

Features:
- All tables with tenant_id and RLS policies
- ENUMs for all status and type fields
- Proper indexes for common queries
- PostGIS for geospatial data
- Partitioned tables for high-volume GPS data
- CFDI Carta Porte 3.1 compliant structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:26:20 -06:00

442 lines
13 KiB
SQL

-- =============================================================================
-- 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
-- =============================================================================