/** * 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(); 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); } } }