# Arquitectura del Modulo Dispatch - ERP Transportistas **Sistema:** SIMCO v4.0.0 **Modulo:** Dispatch (backend/src/modules/dispatch) **Sprint:** S2, S3 **Version:** 1.0.0 **Fecha:** 2026-01-28 --- ## 1. Vision General El modulo Dispatch gestiona la asignacion inteligente de viajes a unidades y operadores, optimizando recursos mediante algoritmos de scoring y reglas de negocio configurables. --- ## 2. Arquitectura de Componentes ``` Dispatch Module ├── Entities │ ├── TableroDespacho # Configuracion del tablero │ ├── EstadoUnidad # Estado actual de unidades │ ├── OperadorCertificacion # Certificaciones de operadores │ ├── TurnoOperador # Turnos de trabajo │ ├── ReglaDespacho # Reglas de asignacion │ ├── ReglaEscalamiento # Reglas de escalamiento │ └── LogDespacho # Auditoria de decisiones ├── Services │ ├── DispatchService # Asignacion principal │ ├── CertificacionService # Validacion certs │ ├── TurnoService # Gestion turnos │ ├── RuleService # Motor de reglas │ └── GpsDispatchIntegrationService # Integracion GPS └── Controllers ├── DispatchController ├── CertificacionController ├── TurnoController └── GpsIntegrationController ``` --- ## 3. Modelo de Datos ### 3.1 EstadoUnidad ```typescript @Entity('estado_unidades', { schema: 'despacho' }) class EstadoUnidad { @PrimaryGeneratedColumn('uuid') id: string; @Column() tenantId: string; @ManyToOne(() => Unidad) unidad: Unidad; @ManyToOne(() => Operador) operador: Operador; @Column({ enum: EstadoUnidadEnum }) estado: EstadoUnidadEnum; // DISPONIBLE | EN_RUTA | EN_SITIO | MANTENIMIENTO @Column({ enum: CapacidadUnidad }) capacidad: CapacidadUnidad; // VACIA | PARCIAL | LLENA @Column('decimal') ubicacionLat: number; @Column('decimal') ubicacionLng: number; @ManyToOne(() => Viaje, { nullable: true }) viajeActual: Viaje | null; @Column() ultimaActualizacion: Date; } ``` ### 3.2 ReglaDespacho ```typescript @Entity('reglas_despacho', { schema: 'despacho' }) class ReglaDespacho { @PrimaryGeneratedColumn('uuid') id: string; @Column() tenantId: string; @Column() nombre: string; @Column() descripcion: string; @Column({ enum: TipoRegla }) tipo: TipoRegla; // RESTRICCION | PREFERENCIA | OBLIGATORIO @Column('jsonb') condiciones: ReglaCodicion[]; // Condiciones a evaluar @Column('jsonb') acciones: ReglaAccion[]; // Acciones si se cumple @Column() prioridad: number; // Orden de evaluacion @Column() activa: boolean; } ``` ### 3.3 LogDespacho ```typescript @Entity('log_despacho', { schema: 'despacho' }) class LogDespacho { @PrimaryGeneratedColumn('uuid') id: string; @Column() tenantId: string; @ManyToOne(() => Viaje) viaje: Viaje; @Column({ enum: TipoAccionDespacho }) accion: TipoAccionDespacho; // ASIGNAR | REASIGNAR | CANCELAR | SUGERIR @ManyToOne(() => Unidad, { nullable: true }) unidadOrigen: Unidad | null; @ManyToOne(() => Unidad, { nullable: true }) unidadDestino: Unidad | null; @Column('jsonb') sugerencias: SugerenciaAsignacion[]; // Sugerencias evaluadas @Column() razon: string; @Column() usuarioId: string; @Column() timestamp: Date; } ``` --- ## 4. Algoritmo de Scoring ### 4.1 Formula General ``` Score = Σ (FactorPeso × FactorValor) Donde: - FactorDistancia: 40% - Cercania al punto de origen - FactorCapacidad: 20% - Capacidad disponible - FactorCertificacion: 20% - Certificaciones requeridas - FactorDisponibilidad: 20% - Tiempo desde ultima asignacion ``` ### 4.2 Implementacion ```typescript // dispatch.service.ts async calculateScore( viaje: Viaje, unidad: EstadoUnidad ): Promise { let score = 0; const razones: string[] = []; // 1. Factor Distancia (40%) const distancia = this.haversineDistance( { lat: unidad.ubicacionLat, lng: unidad.ubicacionLng }, { lat: viaje.origenLat, lng: viaje.origenLng } ); const distanciaScore = Math.max(0, 100 - (distancia / 10)); // 10km = -10 puntos score += distanciaScore * 0.4; razones.push(`Distancia: ${distancia.toFixed(0)} km`); // 2. Factor Capacidad (20%) const capacidadScore = this.getCapacidadScore(unidad.capacidad, viaje.tipoCarga); score += capacidadScore * 0.2; razones.push(`Capacidad: ${unidad.capacidad}`); // 3. Factor Certificacion (20%) const certScore = await this.getCertificacionScore( unidad.operador.id, viaje.restricciones ); score += certScore * 0.2; if (certScore < 100) { razones.push(`Certificaciones: ${certScore}%`); } // 4. Factor Disponibilidad (20%) const dispScore = this.getDisponibilidadScore(unidad.ultimaActualizacion); score += dispScore * 0.2; return { unidadId: unidad.unidad.id, numeroEconomico: unidad.unidad.numeroEconomico, operadorNombre: unidad.operador.nombreCompleto, distanciaKm: distancia, tiempoEstimado: this.estimateTravelTime(distancia), score: Math.round(score), razon: razones.join(' | ') }; } ``` ### 4.3 Haversine Distance ```typescript // Formula para distancia entre dos puntos en la Tierra haversineDistance(p1: Coordenada, p2: Coordenada): number { const R = 6371; // Radio de la Tierra en km const dLat = this.toRad(p2.lat - p1.lat); const dLng = this.toRad(p2.lng - p1.lng); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRad(p1.lat)) * Math.cos(this.toRad(p2.lat)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } ``` --- ## 5. Motor de Reglas ### 5.1 Tipos de Reglas | Tipo | Efecto | Ejemplo | |------|--------|---------| | OBLIGATORIO | Descarta si no cumple | "Operador debe tener licencia tipo E" | | RESTRICCION | Penaliza score | "Evitar unidades con >500k km" | | PREFERENCIA | Bonifica score | "Preferir unidades propias sobre rentadas" | ### 5.2 Evaluacion ```typescript // rule.service.ts async evaluateRules( viaje: Viaje, unidades: EstadoUnidad[] ): Promise { const reglas = await this.getActiveRules(viaje.tenantId); let candidatos = [...unidades]; // 1. Evaluar reglas OBLIGATORIO (filtrar) for (const regla of reglas.filter(r => r.tipo === 'OBLIGATORIO')) { candidatos = candidatos.filter(u => this.evaluateCondition(regla, viaje, u)); } // 2. Evaluar reglas RESTRICCION (penalizar) for (const regla of reglas.filter(r => r.tipo === 'RESTRICCION')) { for (const u of candidatos) { if (!this.evaluateCondition(regla, viaje, u)) { u.scorePenalty = (u.scorePenalty || 0) + regla.penaltyValue; } } } // 3. Evaluar reglas PREFERENCIA (bonificar) for (const regla of reglas.filter(r => r.tipo === 'PREFERENCIA')) { for (const u of candidatos) { if (this.evaluateCondition(regla, viaje, u)) { u.scoreBonus = (u.scoreBonus || 0) + regla.bonusValue; } } } return candidatos; } ``` --- ## 6. Integracion GPS-Dispatch ### 6.1 Actualizacion Automatica de Estados ```typescript // gps-dispatch-integration.service.ts @Injectable() export class GpsDispatchIntegrationService { async onPositionReceived(posicion: PosicionGps): Promise { // 1. Buscar unidad asociada al dispositivo const dispositivo = await this.gpsService.findDispositivo(posicion.dispositivoId); if (!dispositivo.unidadId) return; // 2. Actualizar ubicacion en estado_unidades await this.dispatchService.updateUnitLocation( dispositivo.unidadId, posicion.latitud, posicion.longitud ); // 3. Verificar geocercas y actualizar estado si aplica const eventos = await this.geocercaService.checkGeofences(posicion); for (const evento of eventos) { if (evento.geocerca.tipo === 'CLIENTE' && evento.tipo === 'ENTRADA') { await this.dispatchService.updateUnitStatus( dispositivo.unidadId, 'EN_SITIO' ); } } } } ``` ### 6.2 Scoring Mejorado con GPS ```typescript // Con datos GPS en tiempo real async calculateEnhancedScore(viaje: Viaje, unidad: EstadoUnidad): Promise { let score = await this.calculateScore(viaje, unidad); // Bonus por datos GPS frescos (< 5 min) const minutesSinceUpdate = this.getMinutesSince(unidad.ultimaActualizacion); if (minutesSinceUpdate < 5) { score += 5; // Bonus por ubicacion precisa } // Bonus por velocidad (unidad en movimiento = mas disponible) const ultimaPosicion = await this.gpsService.getLastPosition(unidad.dispositivoId); if (ultimaPosicion?.velocidad > 0) { score += 3; } return score; } ``` --- ## 7. API Endpoints ### 7.1 Despacho Principal | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/v1/despacho/tablero` | Obtener tablero completo | | GET | `/api/v1/despacho/unidades` | Listar estados de unidades | | GET | `/api/v1/despacho/unidades/disponibles` | Unidades disponibles | | PATCH | `/api/v1/despacho/unidades/:id` | Actualizar estado unidad | | POST | `/api/v1/despacho/sugerir` | Obtener sugerencias | | POST | `/api/v1/despacho/asignar` | Asignar viaje | | POST | `/api/v1/despacho/reasignar` | Reasignar viaje | ### 7.2 Transiciones de Estado | Metodo | Endpoint | Transicion | |--------|----------|------------| | POST | `/api/v1/despacho/unidades/:id/en-ruta` | DISPONIBLE → EN_RUTA | | POST | `/api/v1/despacho/unidades/:id/en-sitio` | EN_RUTA → EN_SITIO | | POST | `/api/v1/despacho/unidades/:id/completar` | EN_SITIO → DISPONIBLE | | POST | `/api/v1/despacho/unidades/:id/liberar` | * → DISPONIBLE | ### 7.3 Reglas y Certificaciones | Metodo | Endpoint | Descripcion | |--------|----------|-------------| | GET | `/api/v1/despacho/reglas` | Listar reglas | | POST | `/api/v1/despacho/reglas` | Crear regla | | GET | `/api/v1/despacho/certificaciones` | Listar certificaciones | | POST | `/api/v1/despacho/certificaciones` | Crear certificacion | --- ## 8. Workflow de Asignacion ``` ┌─────────────────────────────────────────────────────────────────┐ │ FLUJO DE ASIGNACION │ └─────────────────────────────────────────────────────────────────┘ 1. Viaje creado (estado: PENDIENTE) │ ▼ 2. Despachador solicita sugerencias POST /despacho/sugerir { viajeId } │ ▼ 3. Backend ejecuta: a) Obtener unidades DISPONIBLES del tenant b) Filtrar por reglas OBLIGATORIO c) Aplicar penalizaciones (RESTRICCION) d) Aplicar bonificaciones (PREFERENCIA) e) Calcular score para cada unidad f) Ordenar por score descendente │ ▼ 4. Retornar top 5-10 sugerencias │ ▼ 5. Despachador selecciona y confirma POST /despacho/asignar { viajeId, unidadId, notas } │ ▼ 6. Backend ejecuta: a) Actualizar Viaje.estado → ASIGNADO b) Actualizar EstadoUnidad.estado → EN_RUTA c) Registrar en LogDespacho d) Enviar notificacion a operador │ ▼ 7. Operador confirma en app movil Viaje.estado → CONFIRMADO ``` --- ## 9. DDL Relacionado - `database/ddl/09-dispatch-schema-ddl.sql` - Tablas de despacho --- ## 10. Metricas y KPIs | KPI | Formula | Meta | |-----|---------|------| | Tiempo asignacion | FechaAsignacion - FechaCreacion | < 15 min | | Tasa utilizacion | Viajes/Unidad/Dia | > 1.5 | | Score promedio | Σ ScoreAsignado / N | > 70 | | Reasignaciones | Reasignaciones / Asignaciones | < 5% | --- *Sprint S2, S3 - TASK-007 | Sistema SIMCO v4.0.0*