Files
hackathon-opti-1a67c9077937…/backend/src/data/simulation/route-simulator.ts
2026-05-23 12:36:09 -06:00

106 lines
3.4 KiB
TypeScript

/**
* route-simulator.ts
* Simulador automático: cada N segundos avanza un positionId en cada ruta
* y dispara el ProcessGpsUpdateUseCase. Reemplaza al "pull to refresh" del
* frontend para que el flujo sea más realista en la demo.
*
* Cuando un camión termina su ciclo (positionId=8 → vuelve a 1), se limpia
* el cache de dedup para que las notificaciones se vuelvan a disparar en
* la siguiente vuelta. El inbox NO se limpia para conservar el historial.
*
* Solo simula RUTA-01 por defecto para enfocar la demo.
*/
import { routes } from "../mocks/routes.mock.js";
import type { ProcessGpsUpdateUseCase } from "../../domain/use-cases/notifications/process-gps-update.use-case.js";
import type { NotificationCacheRepository } from "../../domain/repositories/notification-cache.repository.js";
import type { RouteStateRepository } from "../../domain/repositories/route-state.repository.js";
const TICK_INTERVAL_MS = 30_000; // 30 segundos
const SIMULATED_ROUTE_IDS = ["RUTA-01", "RUTA-80"];
export class RouteSimulator {
private timer: NodeJS.Timeout | null = null;
private positionIndexByRoute = new Map<string, number>();
constructor(
private readonly processGpsUpdate: ProcessGpsUpdateUseCase,
private readonly dedupCache: NotificationCacheRepository,
private readonly routeState: RouteStateRepository,
) {}
start() {
if (this.timer) return;
// Inicializa cada ruta en positionId=1 inmediatamente
void this.tick();
this.timer = setInterval(() => {
void this.tick();
}, TICK_INTERVAL_MS);
console.log(
`RouteSimulator started — tick every ${TICK_INTERVAL_MS / 1000}s`,
);
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
/**
* Reinicia el índice de posición de una ruta al inicio (positionId 1).
* Usado por el admin al reanudar una ruta cancelada para que el ciclo
* vuelva a comenzar desde cero.
*/
resetPosition(routeId: string): void {
this.positionIndexByRoute.set(routeId, 0);
}
private async tick() {
for (const routeId of SIMULATED_ROUTE_IDS) {
const route = routes.find((r) => r.routeId === routeId);
if (!route) continue;
// Si el admin canceló esta ruta, no avanzamos
const state = await this.routeState.get(routeId);
if (state?.cancelled) {
continue;
}
const index = this.positionIndexByRoute.get(routeId) ?? 0;
const position = route.positions[index];
if (!position) continue;
try {
await this.processGpsUpdate.execute({
truckId: String(route.truckId),
routeId: route.routeId,
lat: position.lat,
lng: position.lng,
speed: position.speed,
status: route.status,
positionId: position.positionId,
timestamp: position.timestamp,
});
} catch (err) {
console.error(`Simulator tick failed for ${routeId}:`, err);
}
// Avanza al siguiente; al terminar el array vuelve al inicio Y limpia
// el cache de dedup para que el próximo ciclo sí re-emita notificaciones.
const nextIndex = (index + 1) % route.positions.length;
if (nextIndex < index) {
await this.dedupCache.clear();
console.log(
`RouteSimulator: ${routeId} completó ciclo — cache de dedup limpiado`,
);
}
this.positionIndexByRoute.set(routeId, nextIndex);
}
}
}