import { Injectable } from '@angular/core'; import * as XLSX from 'xlsx'; import { saveAs } from 'file-saver'; import { RouteState } from '../../core/route-state/route'; import { sequenceRoutes, summarizeDailyRoutes } from '../utils/route-sequencing.utils'; @Injectable({ providedIn: 'root' }) export class PlanningExportService { constructor(private routeState: RouteState) {} export(): void { const assigned = this.routeState.getAssignedSnapshot(); const unassigned = this.routeState.getNoAssignedSnapshot(); const users = new Map(); for (const route of assigned) { const key = String(route.json.usuario_id); const current = users.get(key) ?? { nombre: route.json.nombre_usuario, rutas: 0, distancia: 0, trabajo: 0, capacidad: Number(route.json.hora_laboral_semanal_usuario) || 0 }; current.rutas++; current.distancia += Number(route.json.distancia) || 0; current.trabajo += Number.parseFloat(String(route.json.horas_trabajo ?? '')) || 0; users.set(key, current); } const total = assigned.length + unassigned.length; const dailyRoutes = summarizeDailyRoutes(assigned); const totalDistance = dailyRoutes.reduce((sum, route) => sum + route.distanceKm, 0); const totalTravel = dailyRoutes.reduce((sum, route) => sum + route.travelHours, 0); const workbook = XLSX.utils.book_new(); const summary = [ { Indicador: 'PDVs totales', Valor: total }, { Indicador: 'PDVs asignados', Valor: assigned.length }, { Indicador: 'PDVs sin asignar', Valor: unassigned.length }, { Indicador: 'Cobertura (%)', Valor: total ? Number(((assigned.length / total) * 100).toFixed(1)) : 0 }, { Indicador: 'Usuarios activos', Valor: users.size }, { Indicador: 'Días planificados', Valor: new Set(assigned.map(route => route.json.dia)).size }, { Indicador: 'Distancia total (km)', Valor: Number(totalDistance.toFixed(2)) }, { Indicador: 'Desplazamiento total (h)', Valor: Number(totalTravel.toFixed(2)) } ]; const assignedRows = sequenceRoutes(assigned).map(segment => ({ 'Usuario ID': segment.route.json.usuario_id, Usuario: segment.route.json.nombre_usuario, 'PDV ID': segment.route.json.pdv_id, PDV: segment.route.json.nombre_pdv, Día: segment.route.json.dia, 'Distancia (km)': segment.route.json.distancia, 'Desplazamiento (h)': segment.route.json.horas_desplazamiento, 'Horas trabajo': segment.route.json.horas_trabajo, 'Horas disponibles': segment.route.json.hora_laboral_semanal_usuario, 'Orden visita': segment.order, 'Origen del tramo': segment.previousPdvName ?? `Casa de ${segment.route.json.nombre_usuario}` })); const unassignedRows = unassigned.map(route => ({ 'PDV ID': route.pdv_id, PDV: route.pdv_nombre, 'Mínimo horas/semana': route.minimo_horas_semana, Latitud: route.latitud, Longitud: route.longitud, Motivo: route.motivo })); const userRows = Array.from(users, ([id, user]) => { const utilization = user.capacidad ? (user.trabajo / user.capacidad) * 100 : 0; return { 'Usuario ID': id, Usuario: user.nombre, Rutas: user.rutas, 'Distancia (km)': Number(user.distancia.toFixed(2)), 'Horas trabajo': Number(user.trabajo.toFixed(2)), 'Capacidad (h)': user.capacidad, 'Utilización (%)': Number(utilization.toFixed(1)), Estado: utilization > 100 ? 'Excede capacidad' : utilization > 90 ? 'Riesgo' : 'OK' }; }); this.appendSheet(workbook, summary, 'Resumen'); this.appendSheet(workbook, assignedRows, 'Rutas asignadas'); this.appendSheet(workbook, unassignedRows, 'PDVs sin asignar'); this.appendSheet(workbook, userRows, 'Carga por usuario'); this.appendSheet(workbook, dailyRoutes.map(route => ({ Usuario: route.userName, Día: route.day, Paradas: route.routes.length, 'Distancia secuencial (km)': Number(route.distanceKm.toFixed(2)), 'Desplazamiento secuencial (h)': Number(route.travelHours.toFixed(2)), 'Carga total (h)': Number(route.totalHours.toFixed(2)), 'Capacidad diaria (h)': Number(route.capacityHours.toFixed(2)), Estado: route.exceedsCapacity ? 'Excede capacidad' : 'OK' })), 'Rutas diarias'); const output = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); saveAs(new Blob([output], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), 'planificacion_rutas.xlsx'); } private appendSheet(workbook: XLSX.WorkBook, rows: object[], name: string): void { const sheet = XLSX.utils.json_to_sheet(rows.length ? rows : [{ Información: 'Sin registros' }]); XLSX.utils.book_append_sheet(workbook, sheet, name); } }