Fix: Corregir pantalla blanca y mejorar carga masiva

- Fix error .toFixed() con valores DECIMAL de PostgreSQL (string vs number)
- Fix modal de carga masiva que se cerraba sin mostrar resultados
- Validar fechas antes de insertar en BD (evita error con "Installed")
- Agregar mapeos de columnas comunes (device_status, device_name, etc.)
- Normalizar valores de status (Installed -> ACTIVE, New_LoRa -> ACTIVE)
- Actualizar documentación del proyecto

Archivos modificados:
- src/pages/meters/MetersTable.tsx
- src/pages/consumption/ConsumptionPage.tsx
- src/pages/meters/MeterPage.tsx
- water-api/src/services/bulk-upload.service.ts
- ESTADO_ACTUAL.md
- CAMBIOS_SESION.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Exteban08
2026-01-23 23:13:48 +00:00
parent ab97987c6a
commit 6c7d448b2f
6 changed files with 388 additions and 299 deletions

View File

@@ -63,27 +63,54 @@ function normalizeColumnName(name: string): string {
// Map common variations
const mappings: Record<string, string> = {
// Serial number
'serial': 'serial_number',
'numero_de_serie': 'serial_number',
'serial_number': 'serial_number',
'device_s/n': 'serial_number',
'device_sn': 'serial_number',
's/n': 'serial_number',
'sn': 'serial_number',
// Meter ID
'meter_id': 'meter_id',
'meterid': 'meter_id',
'id_medidor': 'meter_id',
// Name
'nombre': 'name',
'name': 'name',
'device_name': 'name',
'meter_name': 'name',
'nombre_medidor': 'name',
// Concentrator
'concentrador': 'concentrator_serial',
'concentrator': 'concentrator_serial',
'concentrator_serial': 'concentrator_serial',
'serial_concentrador': 'concentrator_serial',
'gateway': 'concentrator_serial',
'gateway_serial': 'concentrator_serial',
// Location
'ubicacion': 'location',
'location': 'location',
'direccion': 'location',
'address': 'location',
// Type
'tipo': 'type',
'type': 'type',
'device_type': 'type',
'tipo_dispositivo': 'type',
'protocol': 'type',
'protocolo': 'type',
// Status
'estado': 'status',
'status': 'status',
'device_status': 'status',
'estado_dispositivo': 'status',
// Installation date
'fecha_instalacion': 'installation_date',
'installation_date': 'installation_date',
'fecha_de_instalacion': 'installation_date',
'installed_time': 'installation_date',
'installed_date': 'installation_date',
};
return mappings[normalized] || normalized;
@@ -181,6 +208,19 @@ export async function bulkUploadMeters(buffer: Buffer): Promise<BulkUploadResult
}
// Prepare meter data
// Validate installation_date is actually a valid date
let installationDate: string | undefined = undefined;
if (row.installation_date) {
const dateStr = String(row.installation_date).trim();
// Check if it looks like a date (contains numbers and possibly dashes/slashes)
if (/^\d{4}[-/]\d{1,2}[-/]\d{1,2}/.test(dateStr) || /^\d{1,2}[-/]\d{1,2}[-/]\d{2,4}/.test(dateStr)) {
const parsed = new Date(dateStr);
if (!isNaN(parsed.getTime())) {
installationDate = parsed.toISOString().split('T')[0];
}
}
}
const meterData: MeterRow = {
serial_number: String(row.serial_number).trim(),
meter_id: row.meter_id ? String(row.meter_id).trim() : undefined,
@@ -189,7 +229,7 @@ export async function bulkUploadMeters(buffer: Buffer): Promise<BulkUploadResult
location: row.location ? String(row.location).trim() : undefined,
type: row.type ? String(row.type).trim().toUpperCase() : 'LORA',
status: row.status ? String(row.status).trim().toUpperCase() : 'ACTIVE',
installation_date: row.installation_date ? String(row.installation_date).trim() : undefined,
installation_date: installationDate,
};
// Validate type
@@ -198,11 +238,23 @@ export async function bulkUploadMeters(buffer: Buffer): Promise<BulkUploadResult
meterData.type = 'LORA';
}
// Validate status
const validStatuses = ['ACTIVE', 'INACTIVE', 'MAINTENANCE', 'FAULTY', 'REPLACED'];
if (!validStatuses.includes(meterData.status!)) {
meterData.status = 'ACTIVE';
}
// Validate and normalize status
const statusMappings: Record<string, string> = {
'ACTIVE': 'ACTIVE',
'INACTIVE': 'INACTIVE',
'MAINTENANCE': 'MAINTENANCE',
'FAULTY': 'FAULTY',
'REPLACED': 'REPLACED',
'INSTALLED': 'ACTIVE',
'NEW_LORA': 'ACTIVE',
'NEW': 'ACTIVE',
'ENABLED': 'ACTIVE',
'DISABLED': 'INACTIVE',
'OFFLINE': 'INACTIVE',
'ONLINE': 'ACTIVE',
};
const normalizedStatus = meterData.status?.toUpperCase().replace(/\s+/g, '_') || 'ACTIVE';
meterData.status = statusMappings[normalizedStatus] || 'ACTIVE';
// Insert meter
try {