import { Component, OnDestroy, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Subscription } from 'rxjs'; import { RouteState } from '../../../core/route-state/route'; import { PlanningExportService } from '../../../shared/services/planning-export.service'; import { RouteAssignedResponse } from '../../../core/models/route.model'; interface KpiSummary { totalPdvs: number; assignedPdvs: number; unassignedPdvs: number; coverage: number; users: number; days: number; totalDistance: number; totalTravelHours: number; averageDistance: number; averageTravelHours: number; totalWorkHours: number; averageRoutesPerUser: number; averageUtilization: number; assignmentBalance: number; } interface ReasonSummary { reason: string; count: number; } interface UserLoad { name: string; routes: number; distance: number; workHours: number; capacityHours: number; utilization: number; } @Component({ standalone: true, selector: 'app-planning-kpis', imports: [CommonModule], templateUrl: './planning-kpis.component.html', styleUrl: './planning-kpis.component.css' }) export class PlanningKpisComponent implements OnInit, OnDestroy { summary: KpiSummary = this.emptySummary(); reasons: ReasonSummary[] = []; userLoads: UserLoad[] = []; hasData = false; private readonly subs: Subscription[] = []; constructor( private routeState: RouteState, private exportService: PlanningExportService ) {} ngOnInit(): void { this.subs.push( this.routeState.assignedRoutes$.subscribe(() => this.refresh()), this.routeState.noAssignedRoutes$.subscribe(() => this.refresh()) ); } ngOnDestroy(): void { this.subs.forEach(subscription => subscription.unsubscribe()); } barWidth(value: number): number { return Math.min(Math.max(value, 0), 100); } exportPlanning(): void { this.exportService.export(); } private refresh(): void { const assigned = this.routeState.getAssignedSnapshot(); const unassigned = this.routeState.getNoAssignedSnapshot(); const totalPdvs = assigned.length + unassigned.length; const totalDistance = assigned.reduce((sum, route) => sum + this.numberValue(route.json.distancia), 0); const totalTravelHours = assigned.reduce((sum, route) => sum + this.numberValue(route.json.horas_desplazamiento), 0); const totalWorkHours = assigned.reduce((sum, route) => sum + this.parseHours(route.json.horas_trabajo), 0); const users = new Set(assigned.map(route => String(route.json.usuario_id))).size; const days = new Set(assigned.map(route => route.json.dia)).size; const loads = new Map(); for (const route of assigned) { const key = String(route.json.usuario_id); const current = loads.get(key) ?? { name: route.json.nombre_usuario, routes: 0, distance: 0, workHours: 0, capacityHours: this.numberValue(route.json.hora_laboral_semanal_usuario), utilization: 0 }; current.routes++; current.distance += this.numberValue(route.json.distancia); current.workHours += this.parseHours(route.json.horas_trabajo); loads.set(key, current); } this.userLoads = Array.from(loads.values()) .map(load => ({ ...load, utilization: load.capacityHours ? (load.workHours / load.capacityHours) * 100 : 0 })) .sort((a, b) => b.routes - a.routes || b.workHours - a.workHours); const routeCounts = this.userLoads.map(load => load.routes); const assignmentBalance = routeCounts.length ? Math.max(...routeCounts) - Math.min(...routeCounts) : 0; const averageUtilization = this.userLoads.length ? this.userLoads.reduce((sum, load) => sum + load.utilization, 0) / this.userLoads.length : 0; this.summary = { totalPdvs, assignedPdvs: assigned.length, unassignedPdvs: unassigned.length, coverage: totalPdvs ? (assigned.length / totalPdvs) * 100 : 0, users, days, totalDistance, totalTravelHours, averageDistance: assigned.length ? totalDistance / assigned.length : 0, averageTravelHours: assigned.length ? totalTravelHours / assigned.length : 0, totalWorkHours, averageRoutesPerUser: users ? assigned.length / users : 0, averageUtilization, assignmentBalance }; const reasonCounts = new Map(); for (const route of unassigned) { const reason = route.motivo?.trim() || 'Sin motivo especificado'; reasonCounts.set(reason, (reasonCounts.get(reason) ?? 0) + 1); } this.reasons = Array.from(reasonCounts.entries()) .map(([reason, count]) => ({ reason, count })) .sort((a, b) => b.count - a.count); this.hasData = totalPdvs > 0; } private numberValue(value: number | string | null | undefined): number { const result = Number(value); return Number.isFinite(result) ? result : 0; } private parseHours(value: number | string | null | undefined): number { const result = Number.parseFloat(String(value ?? '')); return Number.isFinite(result) ? result : 0; } private emptySummary(): KpiSummary { return { totalPdvs: 0, assignedPdvs: 0, unassignedPdvs: 0, coverage: 0, users: 0, days: 0, totalDistance: 0, totalTravelHours: 0, averageDistance: 0, averageTravelHours: 0, totalWorkHours: 0, averageRoutesPerUser: 0, averageUtilization: 0, assignmentBalance: 0 }; } }