feat: implement route assignment management and sequencing module with interactive data visualization and editing capabilities

This commit is contained in:
2026-08-31 17:56:33 -04:00
parent cd5c57c5ee
commit 9f42175afc
29 changed files with 458 additions and 218 deletions
@@ -52,6 +52,13 @@ export class ExcelPreviewService {
missingHeaders.push(displayRequired[i]);
}
}
if (type === 'PDV') {
const hasVisitsFormat = normalizedHeaders.includes('visitas_semanales') && normalizedHeaders.includes('duracion_visitahoras');
const hasWeeklyHours = normalizedHeaders.includes('minimo_horas_semana');
if (!hasVisitsFormat && !hasWeeklyHours) {
missingHeaders.push('Mínimo horas semana o Visitas semanales + Duración visita(horas)');
}
}
const errors: FieldError[] = [];
const repeatedHeaders = normalizedHeaders.filter((header, index) => header && normalizedHeaders.indexOf(header) !== index);
@@ -92,6 +99,7 @@ export class ExcelPreviewService {
coerced['longitud'] = this.coerceNum(row['longitud']);
coerced['visitas_semanales'] = this.coerceNum(row['visitas_semanales']);
coerced['duracion_visitahoras'] = this.coerceNum(row['duracion_visitahoras']);
coerced['minimo_horas_semana'] = this.coerceNum(row['minimo_horas_semana']);
coerced['prioridad'] = this.coerceNum(row['prioridad']);
if (typeof coerced['segmentacion'] === 'string') {
coerced['segmentacion'] = String(coerced['segmentacion']).trim().toUpperCase();
@@ -139,6 +147,33 @@ export class ExcelPreviewService {
return Number.isNaN(n) ? value : n;
}
async preparePdvForOptimizer(file: File): Promise<File> {
const buffer = await file.arrayBuffer();
const workbook = XLSX.read(buffer, { type: 'array', cellDates: true });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json(sheet, { defval: null, raw: true })
.map(row => normalizeRow(row as Record<string, unknown>));
const normalized = rows.map(row => {
const weeklyHours = cleanNum(row['minimo_horas_semana']);
const visits = cleanNum(row['visitas_semanales']);
const duration = cleanNum(row['duracion_visitahoras']);
return {
'ID': row['id'],
'Nombre del PDV': row['nombre_del_pdv'],
'Segmentación': row['segmentacion'],
'Latitud': cleanNum(row['latitud']),
'Longitud': cleanNum(row['longitud']),
'Visitas semanales': Number.isFinite(visits) ? visits : 1,
'Duración visita(horas)': Number.isFinite(duration) ? duration : weeklyHours,
'Prioridad': cleanNum(row['prioridad'])
};
});
const output = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(output, XLSX.utils.json_to_sheet(normalized), 'PDV');
const data = XLSX.write(output, { bookType: 'xlsx', type: 'array' });
return new File([data], file.name, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
}
private extractHeaders(sheet: XLSX.WorkSheet): string[] {
const range = XLSX.utils.decode_range(sheet['!ref'] || 'A1');
const headers: string[] = [];