4 Commits

2 changed files with 23 additions and 24 deletions
@@ -86,7 +86,13 @@ export class PlanningKpisComponent implements OnInit, OnDestroy {
private refresh(): void { private refresh(): void {
const assigned = this.routeState.getAssignedSnapshot(); const assigned = this.routeState.getAssignedSnapshot();
const unassigned = this.routeState.getNoAssignedSnapshot(); const unassigned = this.routeState.getNoAssignedSnapshot();
const totalPdvs = assigned.length + unassigned.length; const assignedPdvIds = new Set(assigned.map(route => String(route.json.pdv_id)));
// A PDV with at least one scheduled visit is assigned; repeated weekly visits and
// partial-assignment alerts must not inflate the planning coverage counters.
const unassignedPdvIds = new Set(unassigned
.map(route => String(route.pdv_id))
.filter(pdvId => !assignedPdvIds.has(pdvId)));
const totalPdvs = new Set([...assignedPdvIds, ...unassignedPdvIds]).size;
const dailyRoutes = summarizeDailyRoutes(assigned); const dailyRoutes = summarizeDailyRoutes(assigned);
const totalDistance = dailyRoutes.reduce((sum, day) => sum + day.distanceKm, 0); const totalDistance = dailyRoutes.reduce((sum, day) => sum + day.distanceKm, 0);
const totalTravelHours = dailyRoutes.reduce((sum, day) => sum + day.travelHours, 0); const totalTravelHours = dailyRoutes.reduce((sum, day) => sum + day.travelHours, 0);
@@ -134,9 +140,9 @@ export class PlanningKpisComponent implements OnInit, OnDestroy {
this.summary = { this.summary = {
totalPdvs, totalPdvs,
assignedPdvs: assigned.length, assignedPdvs: assignedPdvIds.size,
unassignedPdvs: unassigned.length, unassignedPdvs: unassignedPdvIds.size,
coverage: totalPdvs ? (assigned.length / totalPdvs) * 100 : 0, coverage: totalPdvs ? (assignedPdvIds.size / totalPdvs) * 100 : 0,
users, users,
days, days,
totalDistance, totalDistance,
@@ -149,9 +155,15 @@ export class PlanningKpisComponent implements OnInit, OnDestroy {
assignmentBalance assignmentBalance
}; };
const reasonCounts = new Map<string, number>(); const reasonByPdv = new Map<string, string>();
for (const route of unassigned) { for (const route of unassigned) {
const pdvId = String(route.pdv_id);
if (assignedPdvIds.has(pdvId) || reasonByPdv.has(pdvId)) continue;
const reason = route.motivo?.trim() || 'Sin motivo especificado'; const reason = route.motivo?.trim() || 'Sin motivo especificado';
reasonByPdv.set(pdvId, reason);
}
const reasonCounts = new Map<string, number>();
for (const reason of reasonByPdv.values()) {
reasonCounts.set(reason, (reasonCounts.get(reason) ?? 0) + 1); reasonCounts.set(reason, (reasonCounts.get(reason) ?? 0) + 1);
} }
this.reasons = Array.from(reasonCounts.entries()) this.reasons = Array.from(reasonCounts.entries())
@@ -51,7 +51,7 @@ export class RouteOverviewMapComponent implements OnInit, OnDestroy, AfterViewIn
usuarios: { id: string; nombre: string }[] = []; usuarios: { id: string; nombre: string }[] = [];
dias: string[] = []; dias: string[] = [];
selectedUsuarioId: string | null = null; selectedUsuarioId: string = 'Todos';
selectedDia: string = 'Todos'; selectedDia: string = 'Todos';
selectedRouteDetail: RouteAssignedResponse | null = null; selectedRouteDetail: RouteAssignedResponse | null = null;
@@ -112,11 +112,12 @@ export class RouteOverviewMapComponent implements OnInit, OnDestroy, AfterViewIn
this.allRoutes = [...routes]; this.allRoutes = [...routes];
this.usuarios = this.routeState.getUsuariosUnicos(); this.usuarios = this.routeState.getUsuariosUnicos();
this.dias = this.routeState.getDiasUnicos(); this.dias = this.routeState.getDiasUnicos();
// Init from queryParams default (Todos) // Start each map load with the complete planning selected by default.
const qp = this.activatedRoute.snapshot.queryParams; const qp = this.activatedRoute.snapshot.queryParams;
if (qp['usuario_id']) this.selectedUsuarioId = String(qp['usuario_id']); if (qp['pdv_id']) {
else if (!this.selectedUsuarioId && this.usuarios.length) this.selectedUsuarioId = 'Todos'; this.selectedUsuarioId = 'Todos';
if (qp['dia']) this.selectedDia = String(qp['dia']); this.selectedDia = 'Todos';
}
// If focus pdv provided, auto-select detail after map loads // If focus pdv provided, auto-select detail after map loads
this.applyFilter(); this.applyFilter();
this.cdr.detectChanges(); this.cdr.detectChanges();
@@ -135,20 +136,6 @@ export class RouteOverviewMapComponent implements OnInit, OnDestroy, AfterViewIn
// React to queryParams changes // React to queryParams changes
this.subs.push( this.subs.push(
this.activatedRoute.queryParams.subscribe(qp => { this.activatedRoute.queryParams.subscribe(qp => {
let changed = false;
if (qp['usuario_id'] && String(qp['usuario_id']) !== this.selectedUsuarioId) {
this.selectedUsuarioId = String(qp['usuario_id']);
changed = true;
}
if (qp['dia'] && qp['dia'] !== this.selectedDia) {
this.selectedDia = String(qp['dia']);
changed = true;
}
if (changed) {
this.applyFilter();
if (this.map) this.renderFiltered();
this.cdr.detectChanges();
}
// Focus a specific route if pdv_id passed // Focus a specific route if pdv_id passed
if (qp['pdv_id'] && this.filteredRoutes.length) { if (qp['pdv_id'] && this.filteredRoutes.length) {
const found = this.filteredRoutes.find(r => String(r.json.pdv_id) === String(qp['pdv_id'])); const found = this.filteredRoutes.find(r => String(r.json.pdv_id) === String(qp['pdv_id']));