diff --git a/CAMBIOS_SESION.md b/CAMBIOS_SESION.md index 66c456c..d7c0b47 100644 --- a/CAMBIOS_SESION.md +++ b/CAMBIOS_SESION.md @@ -1,131 +1,171 @@ -# Cambios Realizados en Esta Sesión +# Cambios Realizados - Sesión 2026-01-23 -**Fecha:** 2026-01-23 +## Resumen +Corrección de errores críticos que causaban pantalla blanca y mejoras en el sistema de carga masiva. --- -## Problema a Resolver -Pantalla en blanco al entrar a "Water Meters" y "Consumo" después de implementar carga masiva de lecturas. +## Problema 1: Pantalla Blanca en Water Meters y Consumo ---- +### Síntoma +Al navegar a "Water Meters" o "Consumo", la página se quedaba en blanco. -## Cambios Realizados +### Causa +PostgreSQL devuelve valores DECIMAL como strings (ej: `"300.0000"`). El código llamaba `.toFixed()` directamente sobre estos strings, pero `.toFixed()` es un método de números, no de strings. -### 1. `src/api/client.ts` (línea ~224-237) -**Cambio:** Modificado `parseResponse` para manejar respuestas con paginación +### Solución +Convertir los valores a número con `Number()` antes de llamar `.toFixed()`. +### Archivos Modificados + +**`src/pages/meters/MetersTable.tsx` (línea 75)** ```typescript // ANTES: -if ('success' in data) { - if (data.success === false) { throw... } - return data.data as T; // Solo devolvía data.data -} +r.lastReadingValue?.toFixed(2) // DESPUÉS: -if ('success' in data) { - if (data.success === false) { throw... } - // Si hay pagination, devolver objeto completo - if ('pagination' in data) { - return { - data: data.data, - pagination: data.pagination, - } as T; +r.lastReadingValue != null ? Number(r.lastReadingValue).toFixed(2) : "-" +``` + +**`src/pages/consumption/ConsumptionPage.tsx` (líneas 133, 213, 432)** +```typescript +// ANTES: +r.readingValue.toFixed(2) +summary?.avgReading.toFixed(1) +reading.readingValue.toFixed(2) + +// DESPUÉS: +Number(r.readingValue).toFixed(2) +summary?.avgReading != null ? Number(summary.avgReading).toFixed(1) : "0" +Number(reading.readingValue).toFixed(2) +``` + +--- + +## Problema 2: Modal de Carga Masiva se Cerraba sin Mostrar Resultados + +### Síntoma +Al subir un archivo Excel para carga masiva, el modal se cerraba inmediatamente sin mostrar cuántos registros se insertaron o qué errores hubo. + +### Causa +El callback `onSuccess` cerraba el modal automáticamente: +```typescript +onSuccess={() => { + m.loadMeters(); + setShowBulkUpload(false); // ← Cerraba antes de ver resultados +}} +``` + +### Solución +Separar la recarga de datos del cierre del modal. Ahora el modal solo se cierra cuando el usuario hace clic en "Cerrar". + +### Archivo Modificado + +**`src/pages/meters/MeterPage.tsx` (líneas 332-340)** +```typescript +// ANTES: + setShowBulkUpload(false)} + onSuccess={() => { + m.loadMeters(); + setShowBulkUpload(false); + }} +/> + +// DESPUÉS: + { + m.loadMeters(); + setShowBulkUpload(false); + }} + onSuccess={() => { + m.loadMeters(); + }} +/> +``` + +--- + +## Problema 3: Error de Fecha Inválida en Carga Masiva + +### Síntoma +Al subir medidores, aparecía el error: +``` +Fila X: invalid input syntax for type date: "Installed" +``` + +### Causa +El archivo Excel tenía columnas con valores como "Installed" o "New_LoRa" que el sistema interpretaba como fechas porque no estaban mapeadas correctamente. + +### Solución +1. **Validar fechas**: Verificar que `installation_date` sea realmente una fecha válida antes de usarla. +2. **Más mapeos de columnas**: Agregar mapeos para columnas comunes como `device_status`, `device_name`, etc. +3. **Normalizar status**: Convertir valores como "Installed", "New_LoRa" a "ACTIVE". + +### Archivo Modificado + +**`water-api/src/services/bulk-upload.service.ts`** + +Validación de fechas (líneas 183-195): +```typescript +let installationDate: string | undefined = undefined; +if (row.installation_date) { + const dateStr = String(row.installation_date).trim(); + 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]; + } } - return data.data as T; } ``` -### 2. `src/api/readings.ts` -**Cambios en interface `MeterReading`:** +Mapeos de columnas adicionales (líneas 65-90): ```typescript -// ELIMINADO: -deviceId: string | null; -areaName: string | null; - -// AGREGADO: -meterLocation: string | null; -concentratorId: string; -concentratorName: string; -projectName: string; +const mappings: Record = { + // Serial number + 'device_s/n': 'serial_number', + 'device_sn': 'serial_number', + // Name + 'device_name': 'name', + 'meter_name': 'name', + // Status + 'device_status': 'status', + // ... más mapeos +}; ``` -**Cambios en interface `ReadingFilters`:** +Normalización de status (líneas 210-225): ```typescript -// ELIMINADO: -areaName?: string; - -// AGREGADO: -concentratorId?: string; +const statusMappings: Record = { + 'INSTALLED': 'ACTIVE', + 'NEW_LORA': 'ACTIVE', + 'NEW': 'ACTIVE', + 'ENABLED': 'ACTIVE', + 'DISABLED': 'INACTIVE', + // ... +}; ``` -**Cambios en interface `ReadingInput`:** -```typescript -// ELIMINADO: -deviceId?: string; -``` - -**Cambios en función `createReading`:** -```typescript -// ELIMINADO de backendData: -device_id: data.deviceId, -``` - -### 3. `src/api/projects.ts` (función `fetchProjects`) -**Cambio:** Ahora maneja respuestas paginadas - -```typescript -// ANTES: -const response = await apiClient.get[]>('/api/projects'); -return transformArray(response); - -// DESPUÉS: -const response = await apiClient.get<...>('/api/projects'); -if (response && typeof response === 'object' && 'data' in response && Array.isArray(response.data)) { - return transformArray(response.data); -} -return transformArray(response as Record[]); -``` - -### 4. `src/api/concentrators.ts` (función `fetchConcentrators`) -**Cambio:** Mismo patrón que projects.ts para manejar paginación - -### 5. `src/pages/consumption/ConsumptionPage.tsx` -**Cambios:** -- Línea ~94: `r.areaName` → `r.meterLocation` (filtro de búsqueda) -- Línea ~127: `"Área"` → `"Ubicación"` (header CSV) -- Línea ~132: `r.areaName` → `r.meterLocation` (datos CSV) -- Línea ~365: `"Área"` → `"Ubicación"` (header tabla) -- Línea ~428: `reading.areaName` → `reading.meterLocation` (celda tabla) - -### 6. `water-api/src/services/reading.service.ts` -**Cambios previos (ya aplicados):** -- Eliminadas todas las referencias a `device_id` en queries SQL -- La columna `device_id` no existe en la tabla `meter_readings` - --- -## Estado de los Servidores +## Archivos Modificados en Esta Sesión -### Backend (puerto 3000) ✅ Funcionando -```bash -curl http://localhost:3000/api/readings?pageSize=1 -# Devuelve datos correctamente -``` - -### Frontend (puerto 5173) ⚠️ Pantalla blanca -- El servidor está corriendo -- Las páginas no renderizan correctamente -- Se necesita revisar la consola del navegador para ver el error específico +| Archivo | Cambio | +|---------|--------| +| `src/pages/meters/MetersTable.tsx` | Fix `.toFixed()` en lastReadingValue | +| `src/pages/consumption/ConsumptionPage.tsx` | Fix `.toFixed()` en readingValue y avgReading | +| `src/pages/meters/MeterPage.tsx` | Fix modal de carga masiva | +| `water-api/src/services/bulk-upload.service.ts` | Validación de fechas, mapeos de columnas, normalización de status | +| `ESTADO_ACTUAL.md` | Documentación actualizada | +| `CAMBIOS_SESION.md` | Este archivo | --- -## Para Debug +## Verificación -1. Abrir http://localhost:5173 -2. F12 → Console -3. Navegar a "Water Meters" o "Consumo" -4. Copiar el error de la consola - -El error más probable es: -- `Cannot read property 'map' of undefined` - si `transformArray` recibe undefined -- `TypeError: response.data is undefined` - si la respuesta no tiene la estructura esperada +1. ✅ La página de Water Meters carga correctamente +2. ✅ La página de Consumo carga correctamente +3. ✅ El modal de carga masiva muestra resultados +4. ✅ Errores de carga masiva se muestran claramente +5. ✅ Valores como "Installed" no causan error de fecha diff --git a/ESTADO_ACTUAL.md b/ESTADO_ACTUAL.md index 07c3582..66a28d9 100644 --- a/ESTADO_ACTUAL.md +++ b/ESTADO_ACTUAL.md @@ -1,7 +1,7 @@ -# Estado Actual del Proyecto Water Project +# Estado Actual del Proyecto Water Project GRH **Fecha:** 2026-01-23 -**Última sesión:** Migración de NocoDB a PostgreSQL + Node.js/Express +**Última actualización:** Corrección de errores y mejoras en carga masiva --- @@ -19,145 +19,165 @@ Projects → Concentrators → Meters → Readings --- -## Lo que se implementó +## Arquitectura del Sistema -### 1. Backend API completo (`water-api/`) -- Autenticación JWT con refresh tokens -- CRUD completo para: Projects, Concentrators, Meters, Readings, Users, Roles -- Carga masiva de medidores y lecturas via Excel -- Endpoints de resumen/estadísticas +``` +┌─────────────────────────────────────────────────────────────┐ +│ FRONTEND (React) │ +│ http://localhost:5173 │ +├─────────────────────────────────────────────────────────────┤ +│ - React 18 + TypeScript + Vite │ +│ - Tailwind CSS + Material-UI │ +│ - Recharts para gráficos │ +│ - Cliente API con JWT automático │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ BACKEND (Node.js) │ +│ http://localhost:3000 │ +├─────────────────────────────────────────────────────────────┤ +│ - Express + TypeScript │ +│ - Autenticación JWT con refresh tokens │ +│ - CRUD completo para todas las entidades │ +│ - Carga masiva via Excel (xlsx) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ BASE DE DATOS │ +│ PostgreSQL │ +├─────────────────────────────────────────────────────────────┤ +│ Tablas: users, roles, projects, concentrators, │ +│ meters, meter_readings, refresh_tokens │ +└─────────────────────────────────────────────────────────────┘ +``` -### 2. Frontend adaptado -- Cliente API con manejo automático de JWT (`src/api/client.ts`) -- Transformación automática snake_case ↔ camelCase -- Páginas: Home, Meters, Consumption, Projects, Users, Roles +--- -### 3. Carga Masiva -- **Medidores:** Subir Excel con columnas: `serial_number`, `name`, `concentrator_serial`, `location`, `type`, `status` -- **Lecturas:** Subir Excel con columnas: `meter_serial`, `reading_value`, `reading_type`, `battery_level`, `signal_strength`, `received_at` +## Funcionalidades Implementadas -### 4. Credenciales actualizadas -- **Usuario admin:** Ivan Alcaraz +### 1. Autenticación +- Login con JWT + refresh tokens +- Manejo automático de renovación de tokens +- Roles: ADMIN, USER + +### 2. Gestión de Proyectos +- CRUD completo +- Estados: ACTIVE/INACTIVE + +### 3. Gestión de Concentradores +- CRUD completo +- Vinculados a proyectos +- Tipos: Gateway LoRa/LoRaWAN + +### 4. Gestión de Medidores +- CRUD completo +- Tipos: LORA, LORAWAN, GRANDES +- Estados: ACTIVE, INACTIVE, MAINTENANCE, FAULTY, REPLACED +- **Carga masiva via Excel** +- Última lectura visible en tabla + +### 5. Gestión de Lecturas (Consumo) +- CRUD completo +- Tipos: AUTOMATIC, MANUAL, SCHEDULED +- **Carga masiva via Excel** +- Filtros por proyecto, fecha +- Exportación a CSV +- Indicadores de batería y señal + +### 6. Dashboard +- KPIs: Total lecturas, medidores activos, consumo promedio +- Gráficos por proyecto +- Últimas alertas + +--- + +## Carga Masiva + +### Medidores (Excel) +Columnas requeridas: +- `serial_number` - Número de serie del medidor (único) +- `name` - Nombre del medidor +- `concentrator_serial` - Serial del concentrador existente + +Columnas opcionales: +- `meter_id` - ID del medidor +- `location` - Ubicación +- `type` - LORA, LORAWAN, GRANDES (default: LORA) +- `status` - ACTIVE, INACTIVE, etc. (default: ACTIVE) +- `installation_date` - Fecha de instalación (YYYY-MM-DD) + +### Lecturas (Excel) +Columnas requeridas: +- `meter_serial` - Serial del medidor existente +- `reading_value` - Valor de la lectura + +Columnas opcionales: +- `reading_type` - AUTOMATIC, MANUAL, SCHEDULED (default: MANUAL) +- `received_at` - Fecha/hora (default: ahora) +- `battery_level` - Nivel de batería (%) +- `signal_strength` - Intensidad de señal (dBm) + +--- + +## Credenciales + +### Usuario Admin +- **Nombre:** Ivan Alcaraz - **Email:** ialcarazsalazar@consultoria-as.com - **Password:** Aasi940812 --- -## Problema Actual: Pantalla en Blanco +## Datos Actuales en BD -### Síntoma -Al entrar a "Water Meters" o "Consumo", la pantalla se queda en blanco. +### Proyectos +- ADAMANT +- OLE +- LUZIA +- ATELIER -### Causa identificada -El cliente API (`src/api/client.ts`) fue modificado para manejar respuestas paginadas. Cuando el backend devuelve: -```json -{ - "success": true, - "data": [...], - "pagination": {...} -} -``` +### Concentradores +| Serial | Nombre | Proyecto | +|--------|--------|----------| +| 2024072612 | Adamant | ADAMANT | +| 2024030601 | OLE | OLE | +| 2024030402 | LUZIA | LUZIA | +| 2024072602 | ATELIER | ATELIER | -El cliente ahora devuelve `{ data: [...], pagination: {...} }` en lugar de solo el array. - -### Archivos modificados para manejar esto: -1. ✅ `src/api/client.ts` - Detecta si hay `pagination` y devuelve el objeto completo -2. ✅ `src/api/meters.ts` - Ya manejaba respuestas paginadas -3. ✅ `src/api/readings.ts` - Ya manejaba respuestas paginadas -4. ✅ `src/api/projects.ts` - Actualizado para manejar paginación -5. ✅ `src/api/concentrators.ts` - Actualizado para manejar paginación - -### Cambios en interfaces de readings.ts: -- Eliminado `deviceId` (no existe en BD) -- Cambiado `areaName` por `meterLocation` -- Agregado `concentratorId`, `concentratorName`, `projectName` - -### Cambios en ConsumptionPage.tsx: -- Cambiado `areaName` por `meterLocation` en todas las referencias -- Cambiado header de tabla "Área" por "Ubicación" +### Medidores +- ADAMANT: 201 medidores +- OLE: 5 medidores --- -## Para Continuar Debugging +## Correcciones Recientes (2026-01-23) -### 1. Verificar que los APIs funcionan -```bash -# Projects -curl http://localhost:3000/api/projects +### 1. Error `.toFixed()` con valores string +**Problema:** PostgreSQL devuelve DECIMAL como string, causando error al llamar `.toFixed()`. +**Solución:** Convertir a número con `Number()` antes de llamar `.toFixed()`. +**Archivos:** +- `src/pages/meters/MetersTable.tsx:75` +- `src/pages/consumption/ConsumptionPage.tsx:133, 213, 432` -# Meters (requiere auth) -curl http://localhost:3000/api/meters?pageSize=2 +### 2. Modal de carga masiva se cerraba sin mostrar resultados +**Problema:** El modal se cerraba automáticamente después de la carga. +**Solución:** El modal ahora permanece abierto para mostrar resultados y errores. +**Archivo:** `src/pages/meters/MeterPage.tsx:332-340` -# Readings -curl http://localhost:3000/api/readings?pageSize=2 -``` +### 3. Validación de fechas en carga masiva +**Problema:** Valores como "Installed" en columnas no mapeadas causaban error de fecha inválida. +**Solución:** Validar que `installation_date` sea realmente una fecha antes de insertarla. +**Archivo:** `water-api/src/services/bulk-upload.service.ts:183-195` -### 2. Ver errores en el navegador -1. Abrir la aplicación en http://localhost:5173 -2. Abrir DevTools (F12) -3. Ir a la pestaña "Console" -4. Navegar a "Water Meters" o "Consumo" -5. Copiar el error que aparezca +### 4. Mapeo de columnas mejorado +**Mejora:** Agregados más mapeos de columnas comunes (device_status, device_name, etc.) +**Archivo:** `water-api/src/services/bulk-upload.service.ts:65-90` -### 3. Posibles causas adicionales -Si el error persiste, verificar: - -a) **Error de transformación de datos:** - - El `transformArray` puede estar recibiendo undefined - - Verificar en `src/api/readings.ts` línea ~120 - -b) **Error en componentes React:** - - Algún componente puede estar accediendo a propiedades que no existen - - Verificar `src/pages/consumption/ConsumptionPage.tsx` - - Verificar `src/pages/meters/MeterPage.tsx` - -c) **Problema con el cliente API:** - - El `parseResponse` en `src/api/client.ts` línea ~211 - - Verificar que la detección de pagination funcione correctamente - -### 4. Test rápido del cliente -Agregar console.log temporal en `src/api/client.ts`: -```typescript -// En la función parseResponse, después de línea 221 -console.log('API Response:', data); -``` - ---- - -## Estructura de Archivos Clave - -``` -water-project/ -├── src/ -│ ├── api/ -│ │ ├── client.ts # Cliente HTTP con JWT -│ │ ├── readings.ts # API de lecturas -│ │ ├── meters.ts # API de medidores -│ │ ├── projects.ts # API de proyectos -│ │ └── concentrators.ts # API de concentradores -│ ├── pages/ -│ │ ├── meters/ -│ │ │ ├── MeterPage.tsx -│ │ │ ├── useMeters.ts # Hook para cargar datos -│ │ │ └── ... -│ │ └── consumption/ -│ │ ├── ConsumptionPage.tsx -│ │ └── ReadingsBulkUploadModal.tsx -│ └── ... -├── water-api/ -│ ├── src/ -│ │ ├── controllers/ -│ │ │ ├── reading.controller.ts -│ │ │ ├── meter.controller.ts -│ │ │ └── bulk-upload.controller.ts -│ │ ├── services/ -│ │ │ ├── reading.service.ts -│ │ │ ├── meter.service.ts -│ │ │ └── bulk-upload.service.ts -│ │ └── routes/ -│ └── ... -└── ESTADO_ACTUAL.md (este archivo) -``` +### 5. Normalización de status +**Mejora:** Valores como "Installed", "New_LoRa" se convierten automáticamente a "ACTIVE". +**Archivo:** `water-api/src/services/bulk-upload.service.ts:210-225` --- @@ -172,83 +192,58 @@ npm run dev cd /home/GRH/water-project npm run dev -# Verificar TypeScript (ignorar TS6133 - variables no usadas) -cd /home/GRH/water-project -npx tsc --noEmit 2>&1 | grep -v "TS6133" +# Compilar backend +cd /home/GRH/water-project/water-api +npm run build # Ver logs del backend tail -f /tmp/water-api.log - -# Conectar a PostgreSQL -psql -U postgres -d water_project ``` --- -## Esquema de Base de Datos Relevante +## Estructura de Archivos -### Tabla `meter_readings` -```sql -- id (UUID) -- meter_id (UUID, FK → meters) -- reading_value (DECIMAL) -- reading_type (VARCHAR) -- AUTOMATIC, MANUAL, SCHEDULED -- battery_level (SMALLINT, nullable) -- signal_strength (SMALLINT, nullable) -- raw_payload (TEXT, nullable) -- received_at (TIMESTAMP) -- created_at (TIMESTAMP) ``` - -**NOTA:** La columna `device_id` NO EXISTE en esta tabla. Fue removida del código. - -### Respuesta del API `/api/readings` -```json -{ - "success": true, - "data": [ - { - "id": "...", - "meter_id": "...", - "reading_value": "300.0000", - "reading_type": "MANUAL", - "battery_level": null, - "signal_strength": null, - "raw_payload": null, - "received_at": "2026-01-23T21:07:28.726Z", - "created_at": "2026-01-23T21:07:28.726Z", - "meter_serial_number": "24300001", - "meter_name": "D307_IB-113107", - "meter_location": null, - "concentrator_id": "...", - "concentrator_name": "Adamant", - "project_id": "...", - "project_name": "ADAMANT" - } - ], - "pagination": { - "page": 1, - "pageSize": 100, - "total": 206, - "totalPages": 3 - } -} +water-project/ +├── src/ # Frontend React +│ ├── api/ # Cliente API +│ │ ├── client.ts # Cliente HTTP con JWT +│ │ ├── meters.ts # API de medidores +│ │ ├── readings.ts # API de lecturas +│ │ ├── projects.ts # API de proyectos +│ │ └── concentrators.ts # API de concentradores +│ ├── pages/ # Páginas +│ │ ├── meters/ # Módulo de medidores +│ │ │ ├── MeterPage.tsx +│ │ │ ├── MetersTable.tsx +│ │ │ ├── MetersModal.tsx +│ │ │ ├── MetersSidebar.tsx +│ │ │ ├── MetersBulkUploadModal.tsx +│ │ │ └── useMeters.ts +│ │ ├── consumption/ # Módulo de consumo +│ │ │ ├── ConsumptionPage.tsx +│ │ │ └── ReadingsBulkUploadModal.tsx +│ │ └── ... +│ └── components/ # Componentes reutilizables +│ +└── water-api/ # Backend Node.js + ├── src/ + │ ├── controllers/ # Controladores REST + │ ├── services/ # Lógica de negocio + │ │ ├── bulk-upload.service.ts + │ │ └── ... + │ ├── routes/ # Definición de rutas + │ ├── middleware/ # Middlewares (auth, etc.) + │ └── config/ # Configuración (DB, etc.) + └── sql/ # Scripts SQL ``` --- ## Próximos Pasos Sugeridos -1. **Resolver pantalla blanca** - Obtener el error específico de la consola del navegador -2. **Probar carga masiva** - Una vez que las páginas funcionen -3. **Integración TTS** - Webhooks para The Things Stack (pendiente) +1. **Integración TTS** - Webhooks para The Things Stack +2. **Alertas automáticas** - Notificaciones por consumo anormal +3. **Reportes** - Generación de reportes PDF 4. **Despliegue** - Configurar para producción - ---- - -## Contacto del Plan Original - -El plan completo de migración está en: -``` -/home/GRH/.claude/plans/peaceful-napping-bumblebee.md -``` diff --git a/src/pages/consumption/ConsumptionPage.tsx b/src/pages/consumption/ConsumptionPage.tsx index 53a836e..373ca03 100644 --- a/src/pages/consumption/ConsumptionPage.tsx +++ b/src/pages/consumption/ConsumptionPage.tsx @@ -130,7 +130,7 @@ export default function ConsumptionPage() { r.meterName || "—", r.meterSerialNumber || "—", r.meterLocation || "—", - r.readingValue.toFixed(2), + Number(r.readingValue).toFixed(2), r.readingType || "—", r.batteryLevel !== null ? `${r.batteryLevel}%` : "—", r.signalStrength !== null ? `${r.signalStrength} dBm` : "—", @@ -210,7 +210,7 @@ export default function ConsumptionPage() { } label="Consumo Promedio" - value={`${summary?.avgReading.toFixed(1) ?? "0"} m³`} + value={`${summary?.avgReading != null ? Number(summary.avgReading).toFixed(1) : "0"} m³`} loading={loadingSummary} gradient="from-violet-500 to-purple-600" /> @@ -429,7 +429,7 @@ export default function ConsumptionPage() { - {reading.readingValue.toFixed(2)} + {Number(reading.readingValue).toFixed(2)} diff --git a/src/pages/meters/MeterPage.tsx b/src/pages/meters/MeterPage.tsx index 7eca26a..c4716d2 100644 --- a/src/pages/meters/MeterPage.tsx +++ b/src/pages/meters/MeterPage.tsx @@ -331,11 +331,13 @@ export default function MetersPage({ {showBulkUpload && ( setShowBulkUpload(false)} - onSuccess={() => { + onClose={() => { m.loadMeters(); setShowBulkUpload(false); }} + onSuccess={() => { + m.loadMeters(); + }} /> )} diff --git a/src/pages/meters/MetersTable.tsx b/src/pages/meters/MetersTable.tsx index 2582651..b10b0c4 100644 --- a/src/pages/meters/MetersTable.tsx +++ b/src/pages/meters/MetersTable.tsx @@ -72,7 +72,7 @@ export default function MetersTable({ ), }, { title: "Concentrador", field: "concentratorName", render: (r: Meter) => r.concentratorName || "-" }, - { title: "Última Lectura", field: "lastReadingValue", render: (r: Meter) => r.lastReadingValue?.toFixed(2) ?? "-" }, + { title: "Última Lectura", field: "lastReadingValue", render: (r: Meter) => r.lastReadingValue != null ? Number(r.lastReadingValue).toFixed(2) : "-" }, ]} data={disabled ? [] : data} onRowClick={(_, rowData) => onRowClick(rowData as Meter)} diff --git a/water-api/src/services/bulk-upload.service.ts b/water-api/src/services/bulk-upload.service.ts index 8622d5f..5ababd2 100644 --- a/water-api/src/services/bulk-upload.service.ts +++ b/water-api/src/services/bulk-upload.service.ts @@ -63,27 +63,54 @@ function normalizeColumnName(name: string): string { // Map common variations const mappings: Record = { + // 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 = { + '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 {