- Updated docs and inventory files - Added new architecture docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
570 lines
14 KiB
Markdown
570 lines
14 KiB
Markdown
# API de Asignacion Inteligente
|
|
|
|
**Modulo:** MAI-005 (Despacho)
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2026-01-27
|
|
|
|
---
|
|
|
|
## Descripcion General
|
|
|
|
API REST para el Centro de Despacho Inteligente. Permite sugerir, asignar y reasignar unidades a viajes usando el algoritmo de scoring ponderado.
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### POST /api/dispatch/suggest
|
|
|
|
Sugiere las mejores unidades disponibles para un viaje basandose en el algoritmo de scoring.
|
|
|
|
**Request:**
|
|
|
|
```typescript
|
|
interface SuggestRequest {
|
|
tripId: string; // ID del viaje a asignar
|
|
maxResults?: number; // Maximo de sugerencias (default: 5)
|
|
maxDistanceKm?: number; // Radio maximo de busqueda (default: 50)
|
|
requiredSkills?: string[]; // Skills requeridos (ej: ['HAZMAT', 'refrigerado'])
|
|
preferredUnitTypes?: string[]; // Tipos de unidad preferidos
|
|
excludeUnitIds?: string[]; // Unidades a excluir
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
interface SuggestResponse {
|
|
tripId: string;
|
|
origin: {
|
|
latitude: number;
|
|
longitude: number;
|
|
address: string;
|
|
};
|
|
suggestions: AssignmentSuggestion[];
|
|
generatedAt: string; // ISO 8601 timestamp
|
|
algorithm: string; // 'weighted_score_v1'
|
|
}
|
|
|
|
interface AssignmentSuggestion {
|
|
rank: number; // 1-based ranking
|
|
unitId: string;
|
|
unitCode: string; // Ej: 'U-005'
|
|
unitType: string; // Ej: 'TRACTOCAMION'
|
|
operatorId: string;
|
|
operatorName: string;
|
|
totalScore: number; // 0-100
|
|
scoreBreakdown: {
|
|
distance: {
|
|
value: number; // Score 0-100
|
|
weight: number; // Peso aplicado (ej: 0.40)
|
|
weighted: number; // value * weight
|
|
distanceKm: number; // Distancia real en km
|
|
};
|
|
capacity: {
|
|
value: number;
|
|
weight: number;
|
|
weighted: number;
|
|
unitCapacity: number; // Capacidad de la unidad
|
|
requiredCapacity: number; // Capacidad requerida
|
|
};
|
|
availability: {
|
|
value: number;
|
|
weight: number;
|
|
weighted: number;
|
|
status: string; // Estado actual
|
|
shiftActive: boolean; // Turno activo
|
|
};
|
|
skills: {
|
|
value: number;
|
|
weight: number;
|
|
weighted: number;
|
|
matched: string[]; // Skills que coinciden
|
|
missing: string[]; // Skills faltantes
|
|
extra: string[]; // Skills adicionales
|
|
};
|
|
};
|
|
currentLocation: {
|
|
latitude: number;
|
|
longitude: number;
|
|
lastUpdate: string;
|
|
};
|
|
estimatedArrival: string; // ISO 8601 timestamp
|
|
estimatedTravelMinutes: number;
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Request:**
|
|
|
|
```json
|
|
{
|
|
"tripId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"maxResults": 3,
|
|
"maxDistanceKm": 30,
|
|
"requiredSkills": ["HAZMAT"]
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Response:**
|
|
|
|
```json
|
|
{
|
|
"tripId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"origin": {
|
|
"latitude": 19.4326,
|
|
"longitude": -99.1332,
|
|
"address": "Av. Insurgentes Sur 1234, CDMX"
|
|
},
|
|
"suggestions": [
|
|
{
|
|
"rank": 1,
|
|
"unitId": "unit-001",
|
|
"unitCode": "U-005",
|
|
"unitType": "TRACTOCAMION",
|
|
"operatorId": "op-001",
|
|
"operatorName": "Juan Perez Garcia",
|
|
"totalScore": 87.5,
|
|
"scoreBreakdown": {
|
|
"distance": {
|
|
"value": 95,
|
|
"weight": 0.40,
|
|
"weighted": 38,
|
|
"distanceKm": 7.2
|
|
},
|
|
"capacity": {
|
|
"value": 100,
|
|
"weight": 0.25,
|
|
"weighted": 25,
|
|
"unitCapacity": 25,
|
|
"requiredCapacity": 20
|
|
},
|
|
"availability": {
|
|
"value": 100,
|
|
"weight": 0.20,
|
|
"weighted": 20,
|
|
"status": "AVAILABLE",
|
|
"shiftActive": true
|
|
},
|
|
"skills": {
|
|
"value": 90,
|
|
"weight": 0.15,
|
|
"weighted": 13.5,
|
|
"matched": ["HAZMAT"],
|
|
"missing": [],
|
|
"extra": ["refrigerado"]
|
|
}
|
|
},
|
|
"currentLocation": {
|
|
"latitude": 19.4126,
|
|
"longitude": -99.1532,
|
|
"lastUpdate": "2026-01-27T10:30:00Z"
|
|
},
|
|
"estimatedArrival": "2026-01-27T10:45:00Z",
|
|
"estimatedTravelMinutes": 15
|
|
}
|
|
],
|
|
"generatedAt": "2026-01-27T10:30:15Z",
|
|
"algorithm": "weighted_score_v1"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /api/dispatch/assign
|
|
|
|
Asigna una unidad a un viaje.
|
|
|
|
**Request:**
|
|
|
|
```typescript
|
|
interface AssignRequest {
|
|
tripId: string; // ID del viaje
|
|
unitId: string; // ID de la unidad a asignar
|
|
operatorId: string; // ID del operador
|
|
notes?: string; // Notas de asignacion
|
|
overrideValidation?: boolean; // Forzar asignacion (requiere permiso especial)
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
interface AssignResponse {
|
|
success: boolean;
|
|
assignmentId: string; // ID del registro de asignacion
|
|
tripId: string;
|
|
unitId: string;
|
|
operatorId: string;
|
|
assignedAt: string; // ISO 8601 timestamp
|
|
assignedBy: string; // Usuario que asigno
|
|
scoreAtAssignment: number | null; // Score si vino de sugerencia
|
|
warnings?: string[]; // Advertencias (ej: turno por terminar)
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Request:**
|
|
|
|
```json
|
|
{
|
|
"tripId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"unitId": "unit-001",
|
|
"operatorId": "op-001",
|
|
"notes": "Asignacion prioritaria por cliente VIP"
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Response:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"assignmentId": "asgn-001",
|
|
"tripId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"unitId": "unit-001",
|
|
"operatorId": "op-001",
|
|
"assignedAt": "2026-01-27T10:35:00Z",
|
|
"assignedBy": "admin@transportes.com",
|
|
"scoreAtAssignment": 87.5,
|
|
"warnings": ["Turno del operador termina en 2 horas"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### POST /api/dispatch/reassign
|
|
|
|
Reasigna un viaje a una unidad diferente, registrando el motivo.
|
|
|
|
**Request:**
|
|
|
|
```typescript
|
|
interface ReassignRequest {
|
|
tripId: string; // ID del viaje
|
|
newUnitId: string; // Nueva unidad
|
|
newOperatorId: string; // Nuevo operador
|
|
reason: ReassignReason; // Motivo de reasignacion
|
|
reasonDetail?: string; // Detalle adicional
|
|
}
|
|
|
|
type ReassignReason =
|
|
| 'UNIT_BREAKDOWN' // Falla mecanica
|
|
| 'OPERATOR_UNAVAILABLE' // Operador no disponible
|
|
| 'CUSTOMER_REQUEST' // Solicitud del cliente
|
|
| 'OPTIMIZATION' // Optimizacion de rutas
|
|
| 'SKILL_MISMATCH' // Skills no coinciden
|
|
| 'CAPACITY_ISSUE' // Problema de capacidad
|
|
| 'OTHER'; // Otro motivo
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
interface ReassignResponse {
|
|
success: boolean;
|
|
reassignmentId: string;
|
|
tripId: string;
|
|
previousUnit: {
|
|
unitId: string;
|
|
unitCode: string;
|
|
operatorId: string;
|
|
operatorName: string;
|
|
};
|
|
newUnit: {
|
|
unitId: string;
|
|
unitCode: string;
|
|
operatorId: string;
|
|
operatorName: string;
|
|
};
|
|
reason: ReassignReason;
|
|
reasonDetail: string | null;
|
|
reassignedAt: string;
|
|
reassignedBy: string;
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Request:**
|
|
|
|
```json
|
|
{
|
|
"tripId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"newUnitId": "unit-002",
|
|
"newOperatorId": "op-002",
|
|
"reason": "UNIT_BREAKDOWN",
|
|
"reasonDetail": "Falla en sistema de frenos detectada en checklist"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### GET /api/dispatch/units/available
|
|
|
|
Obtiene todas las unidades disponibles para asignacion.
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parametro | Tipo | Descripcion |
|
|
|-----------|------|-------------|
|
|
| latitude | number | Latitud del punto de referencia |
|
|
| longitude | number | Longitud del punto de referencia |
|
|
| maxDistanceKm | number | Radio maximo (default: 100) |
|
|
| unitType | string | Filtrar por tipo de unidad |
|
|
| skills | string | Skills requeridos (separados por coma) |
|
|
| minCapacity | number | Capacidad minima |
|
|
| page | number | Pagina (default: 1) |
|
|
| limit | number | Registros por pagina (default: 20) |
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
interface AvailableUnitsResponse {
|
|
units: AvailableUnit[];
|
|
pagination: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
referencePoint: {
|
|
latitude: number;
|
|
longitude: number;
|
|
} | null;
|
|
}
|
|
|
|
interface AvailableUnit {
|
|
unitId: string;
|
|
unitCode: string;
|
|
unitType: string;
|
|
plateNumber: string;
|
|
capacity: number;
|
|
capacityUnit: string; // 'TON', 'M3', etc.
|
|
status: string;
|
|
currentLocation: {
|
|
latitude: number;
|
|
longitude: number;
|
|
lastUpdate: string;
|
|
address: string | null;
|
|
};
|
|
distanceKm: number | null; // Distancia al punto de referencia
|
|
operator: {
|
|
id: string;
|
|
name: string;
|
|
shiftStart: string | null;
|
|
shiftEnd: string | null;
|
|
skills: string[];
|
|
};
|
|
features: string[]; // Caracteristicas (GPS, refrigeracion, etc.)
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Request:**
|
|
|
|
```
|
|
GET /api/dispatch/units/available?latitude=19.4326&longitude=-99.1332&maxDistanceKm=30&skills=HAZMAT
|
|
```
|
|
|
|
---
|
|
|
|
### GET /api/dispatch/logs
|
|
|
|
Obtiene el historial de acciones de despacho para auditoria.
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parametro | Tipo | Descripcion |
|
|
|-----------|------|-------------|
|
|
| tripId | string | Filtrar por viaje |
|
|
| unitId | string | Filtrar por unidad |
|
|
| operatorId | string | Filtrar por operador |
|
|
| action | string | Filtrar por accion (SUGGESTED, ASSIGNED, REASSIGNED, CANCELLED) |
|
|
| performedBy | string | Filtrar por usuario |
|
|
| fromDate | string | Fecha inicio (ISO 8601) |
|
|
| toDate | string | Fecha fin (ISO 8601) |
|
|
| page | number | Pagina (default: 1) |
|
|
| limit | number | Registros por pagina (default: 50) |
|
|
|
|
**Response:**
|
|
|
|
```typescript
|
|
interface DispatchLogsResponse {
|
|
logs: DispatchLogEntry[];
|
|
pagination: {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
}
|
|
|
|
interface DispatchLogEntry {
|
|
id: string;
|
|
tripId: string;
|
|
tripCode: string; // Codigo legible del viaje
|
|
action: 'SUGGESTED' | 'ASSIGNED' | 'REASSIGNED' | 'CANCELLED';
|
|
unitId: string;
|
|
unitCode: string;
|
|
operatorId: string;
|
|
operatorName: string;
|
|
performedBy: string; // Usuario que realizo la accion
|
|
reason: string | null; // Motivo (para REASSIGNED/CANCELLED)
|
|
previousUnitId: string | null; // Unidad anterior (para REASSIGNED)
|
|
previousUnitCode: string | null;
|
|
scoreAtAssignment: number | null; // Score si aplica
|
|
timestamp: string; // ISO 8601
|
|
metadata: Record<string, any>; // Datos adicionales
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Request:**
|
|
|
|
```
|
|
GET /api/dispatch/logs?tripId=550e8400-e29b-41d4-a716-446655440000&fromDate=2026-01-01T00:00:00Z
|
|
```
|
|
|
|
---
|
|
|
|
## DTOs Comunes
|
|
|
|
### TripSummary
|
|
|
|
```typescript
|
|
interface TripSummary {
|
|
id: string;
|
|
code: string;
|
|
customer: {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
origin: {
|
|
latitude: number;
|
|
longitude: number;
|
|
address: string;
|
|
scheduledTime: string;
|
|
};
|
|
destination: {
|
|
latitude: number;
|
|
longitude: number;
|
|
address: string;
|
|
scheduledTime: string;
|
|
};
|
|
requirements: {
|
|
capacity: number;
|
|
capacityUnit: string;
|
|
unitType: string;
|
|
skills: string[];
|
|
};
|
|
priority: 'LOW' | 'NORMAL' | 'HIGH' | 'URGENT';
|
|
status: string;
|
|
}
|
|
```
|
|
|
|
### UnitSummary
|
|
|
|
```typescript
|
|
interface UnitSummary {
|
|
id: string;
|
|
code: string;
|
|
type: string;
|
|
plateNumber: string;
|
|
capacity: number;
|
|
capacityUnit: string;
|
|
status: string;
|
|
operator: {
|
|
id: string;
|
|
name: string;
|
|
} | null;
|
|
location: {
|
|
latitude: number;
|
|
longitude: number;
|
|
lastUpdate: string;
|
|
} | null;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Codigos de Error
|
|
|
|
| Codigo | HTTP Status | Descripcion |
|
|
|--------|-------------|-------------|
|
|
| DISPATCH_001 | 404 | Viaje no encontrado |
|
|
| DISPATCH_002 | 404 | Unidad no encontrada |
|
|
| DISPATCH_003 | 404 | Operador no encontrado |
|
|
| DISPATCH_004 | 400 | Viaje ya tiene unidad asignada |
|
|
| DISPATCH_005 | 400 | Unidad no disponible |
|
|
| DISPATCH_006 | 400 | Operador no disponible |
|
|
| DISPATCH_007 | 400 | Capacidad insuficiente |
|
|
| DISPATCH_008 | 400 | Skills requeridos no cumplidos |
|
|
| DISPATCH_009 | 400 | Unidad fuera de rango maximo |
|
|
| DISPATCH_010 | 400 | Viaje no esta en estado asignable |
|
|
| DISPATCH_011 | 400 | Motivo de reasignacion requerido |
|
|
| DISPATCH_012 | 403 | Sin permisos para forzar asignacion |
|
|
| DISPATCH_013 | 400 | Coordenadas invalidas |
|
|
| DISPATCH_014 | 400 | Rango de fechas invalido |
|
|
| DISPATCH_015 | 500 | Error calculando distancia |
|
|
|
|
**Formato de Error:**
|
|
|
|
```typescript
|
|
interface ErrorResponse {
|
|
error: {
|
|
code: string; // Ej: 'DISPATCH_005'
|
|
message: string; // Mensaje legible
|
|
details?: Record<string, any>; // Detalles adicionales
|
|
timestamp: string;
|
|
};
|
|
}
|
|
```
|
|
|
|
**Ejemplo de Error:**
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "DISPATCH_005",
|
|
"message": "La unidad U-005 no esta disponible para asignacion",
|
|
"details": {
|
|
"unitId": "unit-001",
|
|
"currentStatus": "EN_ROUTE",
|
|
"currentTripId": "trip-999"
|
|
},
|
|
"timestamp": "2026-01-27T10:40:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Autenticacion
|
|
|
|
Todos los endpoints requieren autenticacion via JWT Bearer token:
|
|
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### Permisos Requeridos
|
|
|
|
| Endpoint | Permiso |
|
|
|----------|---------|
|
|
| POST /dispatch/suggest | dispatch:read |
|
|
| POST /dispatch/assign | dispatch:write |
|
|
| POST /dispatch/reassign | dispatch:write |
|
|
| GET /dispatch/units/available | dispatch:read |
|
|
| GET /dispatch/logs | dispatch:audit |
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
| Endpoint | Limite |
|
|
|----------|--------|
|
|
| POST /dispatch/suggest | 30 req/min |
|
|
| POST /dispatch/assign | 60 req/min |
|
|
| POST /dispatch/reassign | 30 req/min |
|
|
| GET /dispatch/units/available | 60 req/min |
|
|
| GET /dispatch/logs | 30 req/min |
|
|
|
|
---
|
|
|
|
*API Asignacion Inteligente - MAI-005 - ERP Transportistas - Sistema SIMCO v4.0.0*
|