-- ============================================================================= -- ERP TRANSPORTISTAS - Schema Despacho DDL -- ============================================================================= -- Archivo: 09-dispatch-schema-ddl.sql -- Version: 1.0.0 -- Fecha: 2026-01-28 -- Descripcion: Tablas para modulo de despacho (dispatch center) -- Basado en: erp-mecanicas-diesel MMD-011 Dispatch Center -- ============================================================================= -- ============================================================================= -- SCHEMA DESPACHO -- ============================================================================= CREATE SCHEMA IF NOT EXISTS despacho; -- ============================================================================= -- TIPOS ENUMERADOS -- ============================================================================= DO $$ BEGIN CREATE TYPE despacho.estado_unidad AS ENUM ( 'available', 'assigned', 'en_route', 'on_site', 'returning', 'offline', 'maintenance' ); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE despacho.capacidad_unidad AS ENUM ( 'light', 'medium', 'heavy' ); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE despacho.accion_despacho AS ENUM ( 'created', 'assigned', 'reassigned', 'rejected', 'escalated', 'cancelled', 'acknowledged', 'completed' ); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE TYPE despacho.canal_notificacion AS ENUM ( 'email', 'sms', 'whatsapp', 'push', 'call' ); EXCEPTION WHEN duplicate_object THEN null; END $$; -- ============================================================================= -- TABLA: tableros_despacho -- ============================================================================= CREATE TABLE IF NOT EXISTS despacho.tableros_despacho ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), nombre VARCHAR(100) NOT NULL, descripcion TEXT, -- Map defaults default_zoom INTEGER DEFAULT 12, centro_lat DECIMAL(10, 7) DEFAULT 19.4326, centro_lng DECIMAL(10, 7) DEFAULT -99.1332, -- Behavior intervalo_refresco_segundos INTEGER DEFAULT 30, mostrar_unidades_offline BOOLEAN DEFAULT TRUE, auto_asignar_habilitado BOOLEAN DEFAULT FALSE, max_sugerencias INTEGER DEFAULT 5, -- Filters filtros_default JSONB DEFAULT '{}', activo BOOLEAN DEFAULT TRUE, -- Audit created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), created_by UUID ); CREATE INDEX IF NOT EXISTS idx_tableros_despacho_tenant ON despacho.tableros_despacho(tenant_id); -- ============================================================================= -- TABLA: estado_unidades -- ============================================================================= CREATE TABLE IF NOT EXISTS despacho.estado_unidades ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Unit reference (FK a fleet.unidades) unidad_id UUID NOT NULL, codigo_unidad VARCHAR(50), nombre_unidad VARCHAR(100), -- Status estado despacho.estado_unidad DEFAULT 'offline', -- Current assignment (FK a transport.viajes) viaje_actual_id UUID, operador_ids UUID[] DEFAULT '{}', -- Location (cached from GPS) ultima_posicion_id UUID, ultima_posicion_lat DECIMAL(10, 7), ultima_posicion_lng DECIMAL(10, 7), ultima_actualizacion_ubicacion TIMESTAMPTZ, -- Timing ultimo_cambio_estado TIMESTAMPTZ DEFAULT NOW(), disponible_estimado_en TIMESTAMPTZ, -- Capacity and capabilities capacidad_unidad despacho.capacidad_unidad DEFAULT 'light', puede_remolcar BOOLEAN DEFAULT FALSE, peso_max_remolque_kg INTEGER, -- Transport-specific es_refrigerada BOOLEAN DEFAULT FALSE, capacidad_peso_kg DECIMAL(10, 2), notas TEXT, metadata JSONB DEFAULT '{}', updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT uq_estado_unidad UNIQUE (tenant_id, unidad_id) ); CREATE INDEX IF NOT EXISTS idx_estado_unidades_tenant ON despacho.estado_unidades(tenant_id); CREATE INDEX IF NOT EXISTS idx_estado_unidades_estado ON despacho.estado_unidades(tenant_id, estado); CREATE INDEX IF NOT EXISTS idx_estado_unidades_viaje ON despacho.estado_unidades(viaje_actual_id) WHERE viaje_actual_id IS NOT NULL; -- ============================================================================= -- TABLA: reglas_despacho -- ============================================================================= CREATE TABLE IF NOT EXISTS despacho.reglas_despacho ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), nombre VARCHAR(100) NOT NULL, descripcion TEXT, prioridad INTEGER DEFAULT 0, -- Applicability tipo_viaje VARCHAR(50), categoria_viaje VARCHAR(50), -- Conditions (JSONB with transport-specific rules) condiciones JSONB NOT NULL DEFAULT '{}', -- Action auto_asignar BOOLEAN DEFAULT FALSE, peso_asignacion INTEGER DEFAULT 100, activo BOOLEAN DEFAULT TRUE, -- Audit created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), created_by UUID ); CREATE INDEX IF NOT EXISTS idx_reglas_despacho_tenant ON despacho.reglas_despacho(tenant_id); CREATE INDEX IF NOT EXISTS idx_reglas_despacho_prioridad ON despacho.reglas_despacho(tenant_id, prioridad DESC); -- ============================================================================= -- TABLA: reglas_escalamiento -- ============================================================================= CREATE TABLE IF NOT EXISTS despacho.reglas_escalamiento ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), nombre VARCHAR(100) NOT NULL, descripcion TEXT, -- Trigger conditions disparar_despues_minutos INTEGER NOT NULL, disparar_estado VARCHAR(50), disparar_prioridad VARCHAR(20), -- Escalation target escalar_a_rol VARCHAR(50) NOT NULL, escalar_a_usuarios UUID[], -- Notification (default whatsapp for transport) canal_notificacion despacho.canal_notificacion DEFAULT 'whatsapp', plantilla_notificacion TEXT, datos_notificacion JSONB DEFAULT '{}', -- Repeat intervalo_repeticion_minutos INTEGER, max_escalamientos INTEGER DEFAULT 3, activo BOOLEAN DEFAULT TRUE, -- Audit created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), created_by UUID ); CREATE INDEX IF NOT EXISTS idx_reglas_escalamiento_tenant ON despacho.reglas_escalamiento(tenant_id); CREATE INDEX IF NOT EXISTS idx_reglas_escalamiento_trigger ON despacho.reglas_escalamiento(tenant_id, disparar_despues_minutos); -- ============================================================================= -- TABLA: log_despacho -- ============================================================================= CREATE TABLE IF NOT EXISTS despacho.log_despacho ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Viaje reference (FK a transport.viajes) viaje_id UUID NOT NULL, -- Action accion despacho.accion_despacho NOT NULL, -- Unit/Operador changes desde_unidad_id UUID, hacia_unidad_id UUID, desde_operador_id UUID, hacia_operador_id UUID, -- Context razon TEXT, automatizado BOOLEAN DEFAULT FALSE, regla_id UUID, escalamiento_id UUID, -- Response times tiempo_respuesta_segundos INTEGER, -- Actor ejecutado_por UUID, ejecutado_en TIMESTAMPTZ DEFAULT NOW(), -- Extra data metadata JSONB DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_log_despacho_tenant ON despacho.log_despacho(tenant_id); CREATE INDEX IF NOT EXISTS idx_log_despacho_viaje ON despacho.log_despacho(tenant_id, viaje_id); CREATE INDEX IF NOT EXISTS idx_log_despacho_fecha ON despacho.log_despacho(tenant_id, ejecutado_en DESC); CREATE INDEX IF NOT EXISTS idx_log_despacho_accion ON despacho.log_despacho(tenant_id, accion); -- ============================================================================= -- TABLAS ADICIONALES EN FLEET (certificaciones y turnos) -- ============================================================================= -- TABLA: certificaciones_operador CREATE TABLE IF NOT EXISTS fleet.certificaciones_operador ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Operador reference (FK a fleet.operadores) operador_id UUID NOT NULL, -- Certification definition codigo_certificacion VARCHAR(50) NOT NULL, nombre_certificacion VARCHAR(100) NOT NULL, descripcion TEXT, -- Level nivel VARCHAR(20) DEFAULT 'basico', -- Certification details numero_certificado VARCHAR(100), fecha_certificacion DATE, vigencia_hasta DATE, documento_url TEXT, -- Status activa BOOLEAN DEFAULT TRUE, verificado_por UUID, verificado_en TIMESTAMPTZ, -- Audit created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT uq_operador_certificacion UNIQUE(tenant_id, operador_id, codigo_certificacion) ); CREATE INDEX IF NOT EXISTS idx_certificaciones_operador ON fleet.certificaciones_operador(operador_id); CREATE INDEX IF NOT EXISTS idx_certificaciones_tenant ON fleet.certificaciones_operador(tenant_id); CREATE INDEX IF NOT EXISTS idx_certificaciones_vencimiento ON fleet.certificaciones_operador(vigencia_hasta) WHERE activa = TRUE; -- TABLA: turnos_operador CREATE TABLE IF NOT EXISTS fleet.turnos_operador ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES public.tenants(id), -- Operador reference (FK a fleet.operadores) operador_id UUID NOT NULL, -- Schedule fecha_turno DATE NOT NULL, tipo_turno VARCHAR(20) NOT NULL, hora_inicio TIME NOT NULL, hora_fin TIME NOT NULL, -- On-call specifics en_guardia BOOLEAN DEFAULT FALSE, prioridad_guardia INTEGER DEFAULT 0, -- Assignment unidad_asignada_id UUID, -- Status hora_inicio_real TIMESTAMPTZ, hora_fin_real TIMESTAMPTZ, ausente BOOLEAN DEFAULT FALSE, motivo_ausencia TEXT, notas TEXT, -- Audit created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), created_by UUID ); CREATE INDEX IF NOT EXISTS idx_turnos_operador ON fleet.turnos_operador(operador_id, fecha_turno); CREATE INDEX IF NOT EXISTS idx_turnos_tenant ON fleet.turnos_operador(tenant_id); CREATE INDEX IF NOT EXISTS idx_turnos_fecha ON fleet.turnos_operador(tenant_id, fecha_turno); -- ============================================================================= -- RLS POLICIES -- ============================================================================= ALTER TABLE despacho.tableros_despacho ENABLE ROW LEVEL SECURITY; ALTER TABLE despacho.estado_unidades ENABLE ROW LEVEL SECURITY; ALTER TABLE despacho.reglas_despacho ENABLE ROW LEVEL SECURITY; ALTER TABLE despacho.reglas_escalamiento ENABLE ROW LEVEL SECURITY; ALTER TABLE despacho.log_despacho ENABLE ROW LEVEL SECURITY; ALTER TABLE fleet.certificaciones_operador ENABLE ROW LEVEL SECURITY; ALTER TABLE fleet.turnos_operador ENABLE ROW LEVEL SECURITY; DO $$ BEGIN CREATE POLICY tenant_isolation_tableros ON despacho.tableros_despacho USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE POLICY tenant_isolation_estado ON despacho.estado_unidades USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE POLICY tenant_isolation_reglas ON despacho.reglas_despacho USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE POLICY tenant_isolation_escalamiento ON despacho.reglas_escalamiento USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE POLICY tenant_isolation_log ON despacho.log_despacho USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE POLICY tenant_isolation_certificaciones ON fleet.certificaciones_operador USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; DO $$ BEGIN CREATE POLICY tenant_isolation_turnos ON fleet.turnos_operador USING (tenant_id = current_setting('app.tenant_id')::uuid); EXCEPTION WHEN duplicate_object THEN null; END $$; -- ============================================================================= -- COMENTARIOS -- ============================================================================= COMMENT ON SCHEMA despacho IS 'Modulo de despacho para asignacion de viajes a unidades/operadores'; COMMENT ON TABLE despacho.tableros_despacho IS 'Configuracion de tableros de despacho con mapa'; COMMENT ON TABLE despacho.estado_unidades IS 'Estado en tiempo real de unidades para despacho'; COMMENT ON TABLE despacho.reglas_despacho IS 'Reglas para asignacion automatica de viajes'; COMMENT ON TABLE despacho.reglas_escalamiento IS 'Reglas para escalar viajes sin respuesta'; COMMENT ON TABLE despacho.log_despacho IS 'Auditoria de acciones de despacho'; COMMENT ON TABLE fleet.certificaciones_operador IS 'Certificaciones y licencias de operadores'; COMMENT ON TABLE fleet.turnos_operador IS 'Programacion de turnos de operadores'; COMMENT ON COLUMN despacho.estado_unidades.viaje_actual_id IS 'FK a transport.viajes - viaje actualmente asignado'; COMMENT ON COLUMN despacho.estado_unidades.operador_ids IS 'IDs de operadores asignados a la unidad'; COMMENT ON COLUMN despacho.log_despacho.viaje_id IS 'FK a transport.viajes - viaje al que aplica la accion'; -- ============================================================================= -- FIN DDL DESPACHO -- =============================================================================