Reflect current project state across all 8 docs: ADMIN/ORGANISMO_OPERADOR/OPERATOR role hierarchy, scope filtering, organismos_operadores table, Histórico de Tomas page, new SQL migrations, and updated API endpoints with auth requirements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
668 lines
25 KiB
Markdown
668 lines
25 KiB
Markdown
# Documentacion Tecnica - GRH (Gestion de Recursos Hidricos)
|
||
|
||
Documentacion tecnica detallada de la arquitectura, componentes, API y patrones del sistema.
|
||
|
||
---
|
||
|
||
## Tabla de Contenidos
|
||
|
||
1. [Arquitectura del Sistema](#arquitectura-del-sistema)
|
||
2. [Frontend - Componentes y Paginas](#frontend---componentes-y-paginas)
|
||
3. [Backend - API REST](#backend---api-rest)
|
||
4. [Base de Datos](#base-de-datos)
|
||
5. [Autenticacion y Autorizacion](#autenticacion-y-autorizacion)
|
||
6. [Capa de API del Frontend](#capa-de-api-del-frontend)
|
||
7. [Hooks Personalizados](#hooks-personalizados)
|
||
8. [Sistema de Temas](#sistema-de-temas)
|
||
9. [Conectores Externos](#conectores-externos)
|
||
10. [Tareas Programadas](#tareas-programadas)
|
||
11. [Guia de Desarrollo](#guia-de-desarrollo)
|
||
|
||
---
|
||
|
||
## Arquitectura del Sistema
|
||
|
||
### Vision General
|
||
|
||
El proyecto es una aplicacion **full-stack** con tres componentes principales:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Capa de Presentacion │
|
||
│ React SPA: Pages, Components, Layout │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Capa de Logica (Frontend) │
|
||
│ Custom Hooks, API Client con JWT, Estado Local │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Capa de API (Backend) │
|
||
│ Express Routes → Controllers → Services → PostgreSQL │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Capa de Datos │
|
||
│ PostgreSQL: 11 tablas, 2 vistas, triggers, indices │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ Integraciones Externas │
|
||
│ The Things Stack (LoRaWAN), SH-Meters, XMeters │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Patrones de Diseno
|
||
|
||
1. **Container/Presentational** - Paginas manejan logica, componentes renderizan UI
|
||
2. **Custom Hooks** - Logica reutilizable en hooks (`useMeters`, `useConcentrators`, `useNotifications`)
|
||
3. **Module Pattern** - Codigo organizado por dominio (`meters/`, `concentrators/`, `analytics/`)
|
||
4. **Service Layer (Backend)** - Routes → Controllers → Services → Database
|
||
5. **Repository Pattern** - Cada servicio encapsula las queries SQL de su dominio
|
||
6. **Middleware Pipeline** - Auth, audit logging, webhook verification
|
||
|
||
---
|
||
|
||
## Frontend - Componentes y Paginas
|
||
|
||
### App.tsx - Componente Raiz
|
||
|
||
Maneja autenticacion global, routing por estado interno y modales globales.
|
||
|
||
```typescript
|
||
export type Page =
|
||
| "home" | "projects" | "meters" | "concentrators"
|
||
| "consumption" | "auditoria" | "users" | "roles"
|
||
| "sh-meters" | "xmeters" | "tts"
|
||
| "analytics-map" | "analytics-reports" | "analytics-server"
|
||
| "organismos" | "historico";
|
||
```
|
||
|
||
La navegacion es **state-based** (sin React Router). El estado `currentPage` determina que pagina se renderiza.
|
||
|
||
### Paginas Principales
|
||
|
||
| Pagina | Archivo | Descripcion |
|
||
|--------|---------|-------------|
|
||
| Dashboard | `Home.tsx` | KPIs, graficos Recharts, selector de organismos, alertas |
|
||
| Login | `LoginPage.tsx` | Formulario de autenticacion |
|
||
| Medidores | `meters/MeterPage.tsx` | CRUD con tabla, sidebar, filtros, carga masiva |
|
||
| Concentradores | `concentrators/ConcentratorsPage.tsx` | CRUD con tabla y sidebar |
|
||
| Proyectos | `projects/ProjectsPage.tsx` | Tabla de proyectos con estados |
|
||
| Consumo | `consumption/ConsumptionPage.tsx` | Lecturas con filtros y estadisticas |
|
||
| Usuarios | `UsersPage.tsx` | Gestion de usuarios (admin) |
|
||
| Roles | `RolesPage.tsx` | Gestion de roles y permisos |
|
||
| Auditoria | `AuditoriaPage.tsx` | Visor de logs de actividad |
|
||
| Mapa | `analytics/AnalyticsMapPage.tsx` | Mapa Leaflet con ubicaciones de medidores |
|
||
| Reportes | `analytics/AnalyticsReportsPage.tsx` | Reportes de consumo |
|
||
| Servidor | `analytics/AnalyticsServerPage.tsx` | Metricas CPU, memoria, requests |
|
||
| SH-Meters | `conectores/SHMetersPage.tsx` | Conector SH-Meters |
|
||
| XMeters | `conectores/XMetersPage.tsx` | Conector XMeters |
|
||
| TTS | `conectores/TTSPage.tsx` | Conector The Things Stack |
|
||
| Organismos | `OrganismosPage.tsx` | Gestion de organismos operadores (ADMIN) |
|
||
| Historico | `historico/HistoricoPage.tsx` | Historico de lecturas por medidor con grafica, estadisticas y tabla |
|
||
|
||
### Componentes de Layout
|
||
|
||
**Sidebar.tsx**
|
||
- Menu lateral colapsable con hover expansion
|
||
- Soporte para pin/unpin
|
||
- Menu jerarquico con submenus (Analytics, Conectores)
|
||
- Visibilidad por rol de 3 niveles:
|
||
- ADMIN: ve todo (incluye Organismos, Conectores, Auditoria)
|
||
- ORGANISMO_OPERADOR: ve Dashboard, Project Management, Users, Analytics
|
||
- OPERATOR: ve Dashboard y Project Management
|
||
|
||
**TopMenu.tsx**
|
||
- Breadcrumb de navegacion
|
||
- Dropdown de notificaciones con conteo de no leidas
|
||
- Menu de usuario: perfil, configuracion, logout
|
||
|
||
**Componentes Comunes**
|
||
- `ProfileModal.tsx` - Edicion de perfil con avatar local
|
||
- `ConfirmModal.tsx` - Confirmacion de acciones destructivas
|
||
- `Watermark.tsx` - Marca de agua GRH
|
||
- `ProjectBadge.tsx` - Badge visual de proyecto
|
||
- `SettingsModals.tsx` - Configuracion de tema y modo compacto
|
||
- `NotificationDropdown.tsx` - Panel de notificaciones
|
||
|
||
---
|
||
|
||
## Backend - API REST
|
||
|
||
### Estructura de Archivos
|
||
|
||
```
|
||
water-api/src/
|
||
├── index.ts # Setup Express: CORS, Helmet, body-parser, rutas
|
||
├── config/
|
||
│ ├── index.ts # Variables de entorno centralizadas
|
||
│ └── database.ts # Pool de conexiones pg
|
||
├── routes/ # Definicion de rutas (18 archivos)
|
||
│ └── organismo-operador.routes.ts # Rutas CRUD organismos
|
||
├── controllers/ # Controladores HTTP
|
||
│ └── organismo-operador.controller.ts
|
||
├── services/ # Logica de negocio (19 modulos)
|
||
│ └── organismo-operador.service.ts
|
||
├── middleware/
|
||
│ ├── auth.middleware.ts # Verificacion JWT + extraccion de rol + requireRole
|
||
│ ├── audit.middleware.ts # Auto-logging de acciones
|
||
│ └── ttsWebhook.middleware.ts # Verificacion de secreto webhook
|
||
├── validators/ # Schemas de validacion Zod
|
||
├── utils/
|
||
│ ├── jwt.ts # sign/verify de tokens (incluye organismoOperadorId)
|
||
│ ├── password.ts # hash/compare con bcrypt
|
||
│ ├── logger.ts # Winston con formato timestamp
|
||
│ └── scope.ts # Filtrado por scope de rol (ADMIN/ORGANISMO/OPERATOR)
|
||
├── jobs/
|
||
│ └── negativeFlowDetection.ts # Cron de deteccion de flujo negativo
|
||
└── types/ # Interfaces TypeScript
|
||
```
|
||
|
||
### Endpoints Detallados
|
||
|
||
#### Autenticacion (`/api/auth`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| POST | `/login` | Autenticar usuario | No |
|
||
| POST | `/refresh` | Renovar access token | No (usa refresh token) |
|
||
| POST | `/logout` | Cerrar sesion | Si |
|
||
| GET | `/me` | Obtener perfil actual | Si |
|
||
| PATCH | `/me` | Actualizar perfil | Si |
|
||
|
||
#### Proyectos (`/api/projects`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar proyectos | Si |
|
||
| GET | `/:id` | Obtener proyecto | Si |
|
||
| GET | `/:id/stats` | Estadisticas del proyecto | Si |
|
||
| POST | `/` | Crear proyecto | Si (ADMIN/OPERATOR) |
|
||
| PUT | `/:id` | Actualizar proyecto | Si (ADMIN/OPERATOR) |
|
||
| DELETE | `/:id` | Eliminar proyecto | Si (ADMIN) |
|
||
|
||
#### Medidores (`/api/meters`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar medidores (filtros: project, status, type) | Si |
|
||
| GET | `/:id` | Obtener medidor | Si |
|
||
| GET | `/:id/readings` | Historial de lecturas del medidor | Si |
|
||
| POST | `/` | Crear medidor | Si (ADMIN/OPERATOR) |
|
||
| PUT | `/:id` | Actualizar medidor | Si (ADMIN/OPERATOR) |
|
||
| DELETE | `/:id` | Eliminar medidor | Si (ADMIN) |
|
||
|
||
#### Concentradores (`/api/concentrators`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar concentradores | Si |
|
||
| GET | `/:id` | Obtener concentrador | Si |
|
||
| POST | `/` | Crear concentrador | Si (ADMIN/OPERATOR) |
|
||
| PUT | `/:id` | Actualizar concentrador | Si (ADMIN/OPERATOR) |
|
||
| DELETE | `/:id` | Eliminar concentrador | Si (ADMIN) |
|
||
|
||
#### Gateways (`/api/gateways`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar gateways | Si |
|
||
| GET | `/:id` | Obtener gateway | Si |
|
||
| GET | `/:id/devices` | Dispositivos del gateway | Si |
|
||
| POST | `/` | Crear gateway | Si (ADMIN/OPERATOR) |
|
||
| PUT | `/:id` | Actualizar gateway | Si (ADMIN/OPERATOR) |
|
||
| DELETE | `/:id` | Eliminar gateway | Si (ADMIN) |
|
||
|
||
#### Dispositivos (`/api/devices`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar dispositivos | Si |
|
||
| GET | `/:id` | Obtener dispositivo | Si |
|
||
| GET | `/dev-eui/:devEui` | Buscar por DevEUI | Si |
|
||
| POST | `/` | Crear dispositivo | Si (ADMIN/OPERATOR) |
|
||
| PUT | `/:id` | Actualizar dispositivo | Si (ADMIN/OPERATOR) |
|
||
| DELETE | `/:id` | Eliminar dispositivo | Si (ADMIN) |
|
||
|
||
#### Organismos Operadores (`/api/organismos-operadores`) - Solo ADMIN
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar organismos operadores | Si (ADMIN) |
|
||
| GET | `/:id` | Obtener organismo operador | Si (ADMIN) |
|
||
| POST | `/` | Crear organismo operador | Si (ADMIN) |
|
||
| PUT | `/:id` | Actualizar organismo operador | Si (ADMIN) |
|
||
| DELETE | `/:id` | Eliminar organismo operador | Si (ADMIN) |
|
||
|
||
#### Usuarios (`/api/users`) - ADMIN y ORGANISMO_OPERADOR
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar usuarios (filtrado por scope) | Si (ADMIN/ORGANISMO_OPERADOR) |
|
||
| GET | `/:id` | Obtener usuario | Si (ADMIN o self) |
|
||
| POST | `/` | Crear usuario | Si (ADMIN/ORGANISMO_OPERADOR) |
|
||
| PUT | `/:id` | Actualizar usuario | Si (ADMIN o self) |
|
||
| DELETE | `/:id` | Desactivar usuario | Si (ADMIN) |
|
||
| PUT | `/:id/password` | Cambiar contrasena | Si (self) |
|
||
|
||
#### Roles (`/api/roles`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar roles | Si |
|
||
| GET | `/:id` | Obtener rol con conteo de usuarios | Si |
|
||
| POST | `/` | Crear rol | Si (ADMIN) |
|
||
| PUT | `/:id` | Actualizar rol | Si (ADMIN) |
|
||
| DELETE | `/:id` | Eliminar rol | Si (ADMIN) |
|
||
|
||
#### Tipos de Medidor (`/api/meter-types`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar tipos | Si |
|
||
| GET | `/:id` | Obtener por ID | Si |
|
||
| GET | `/code/:code` | Obtener por codigo | Si |
|
||
| POST | `/` | Crear tipo | Si (ADMIN) |
|
||
| PUT | `/:id` | Actualizar tipo | Si (ADMIN) |
|
||
| DELETE | `/:id` | Eliminar tipo | Si (ADMIN) |
|
||
|
||
#### Lecturas (`/api/readings`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar lecturas (filtros: proyecto, fecha, medidor) | Si |
|
||
| GET | `/summary` | Resumen de consumo | Si |
|
||
| GET | `/:id` | Obtener lectura | Si |
|
||
| POST | `/` | Crear lectura | Si |
|
||
| DELETE | `/:id` | Eliminar lectura | Si (ADMIN) |
|
||
|
||
#### Notificaciones (`/api/notifications`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar notificaciones del usuario | Si |
|
||
| GET | `/unread-count` | Conteo de no leidas | Si |
|
||
| GET | `/:id` | Obtener notificacion | Si |
|
||
| PATCH | `/:id/read` | Marcar como leida | Si |
|
||
| PATCH | `/read-all` | Marcar todas como leidas | Si |
|
||
| DELETE | `/:id` | Eliminar notificacion | Si |
|
||
|
||
#### Auditoria (`/api/audit-logs`) - Solo ADMIN
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/` | Listar logs | Si (ADMIN) |
|
||
| GET | `/my-activity` | Actividad del usuario actual | Si |
|
||
| GET | `/statistics` | Estadisticas de auditoria | Si (ADMIN) |
|
||
| GET | `/:id` | Detalle de un log | Si (ADMIN) |
|
||
| GET | `/record/:tableName/:recordId` | Logs de un registro especifico | Si (ADMIN) |
|
||
|
||
#### Carga Masiva
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| POST | `/bulk-upload/meters` | Subir Excel de medidores | Si |
|
||
| GET | `/bulk-upload/meters/template` | Descargar plantilla Excel | Si |
|
||
| POST | `/csv-upload/meters` | Subir CSV de medidores (upsert) | No |
|
||
| POST | `/csv-upload/readings` | Subir CSV de lecturas | No |
|
||
| GET | `/csv-upload/meters/template` | Plantilla CSV medidores | No |
|
||
| GET | `/csv-upload/readings/template` | Plantilla CSV lecturas | No |
|
||
|
||
#### TTS Webhooks (`/api/webhooks/tts`)
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/health` | Health check del webhook | No |
|
||
| POST | `/uplink` | Recibir mensajes uplink | Webhook secret |
|
||
| POST | `/join` | Recibir eventos de join | Webhook secret |
|
||
| POST | `/downlink/ack` | Recibir acks de downlink | Webhook secret |
|
||
|
||
#### Sistema (`/api/system`) - Solo ADMIN
|
||
| Metodo | Ruta | Descripcion | Auth |
|
||
|--------|------|-------------|------|
|
||
| GET | `/metrics` | Metricas del servidor (CPU, memoria, requests) | Si (ADMIN) |
|
||
| GET | `/health` | Health check detallado | Si (ADMIN) |
|
||
| GET | `/meters-locations` | Coordenadas de medidores para mapa | Si (ADMIN) |
|
||
| GET | `/report-stats` | Estadisticas para reportes | Si (ADMIN) |
|
||
|
||
---
|
||
|
||
## Base de Datos
|
||
|
||
### Esquema Relacional
|
||
|
||
```
|
||
roles ←─── users ←─── refresh_tokens
|
||
│
|
||
▼
|
||
projects
|
||
╱ │ ╲
|
||
╱ │ ╲
|
||
▼ ▼ ▼
|
||
concentrators gateways meters ──→ meter_readings
|
||
│ │
|
||
▼ │
|
||
devices ─────┘
|
||
│
|
||
▼
|
||
tts_uplink_logs
|
||
```
|
||
|
||
### ENUMs de PostgreSQL
|
||
- `role_name`: ADMIN, ORGANISMO_OPERADOR, OPERATOR
|
||
- `project_status`: ACTIVE, INACTIVE, COMPLETED
|
||
- `device_status`: ACTIVE, INACTIVE, OFFLINE, MAINTENANCE, ERROR
|
||
- `meter_type`: WATER, GAS, ELECTRIC
|
||
- `reading_type`: AUTOMATIC, MANUAL, SCHEDULED
|
||
|
||
### Migraciones SQL
|
||
|
||
Ejecutar en orden despues del schema principal:
|
||
|
||
1. `schema.sql` - Schema principal con tablas base, 2 vistas, seed de roles y admin
|
||
2. `add_audit_logs.sql` - Tabla de logs de auditoria
|
||
3. `add_notifications.sql` - Tabla de notificaciones
|
||
4. `add_meter_extended_fields.sql` - Campos extendidos para medidores
|
||
5. `create_meter_types.sql` - Tabla de tipos de medidor
|
||
6. `add_meter_project_relation.sql` - Relacion meter-proyecto
|
||
7. `add_user_project_relation.sql` - Relacion user-proyecto
|
||
8. `add_organismos_operadores.sql` - Tabla organismos_operadores, FK en projects y users, rol ORGANISMO_OPERADOR
|
||
9. `add_user_meter_fields.sql` - Campos adicionales en users y meters (cespt_account, cadastral_key)
|
||
|
||
---
|
||
|
||
## Autenticacion y Autorizacion
|
||
|
||
### Flujo JWT
|
||
|
||
```
|
||
┌──────────┐ POST /auth/login ┌──────────┐
|
||
│ Cliente │ ───────────────────→ │ Backend │
|
||
│ │ ←─────────────────── │ │
|
||
└──────────┘ {accessToken, └──────────┘
|
||
refreshToken} │
|
||
│ │ bcrypt.compare()
|
||
│ Authorization: Bearer <token> │ jwt.sign()
|
||
│ ───────────────────────────→ │
|
||
│ │ jwt.verify()
|
||
│ │
|
||
│ 401 Unauthorized │
|
||
│ ←─────────────────────────── │
|
||
│ │
|
||
│ POST /auth/refresh │
|
||
│ {refreshToken} │
|
||
│ ───────────────────────────→ │
|
||
│ ←─────────────────────────── │
|
||
│ {newAccessToken} │
|
||
```
|
||
|
||
### Tokens
|
||
- **Access Token** - JWT con userId, roleId, roleName, projectId, organismoOperadorId. Expira en 15 minutos.
|
||
- **Refresh Token** - JWT almacenado hasheado en BD. Expira en 7 dias. Revocable.
|
||
|
||
### Token Refresh Automatico (Frontend)
|
||
|
||
El cliente HTTP (`src/api/client.ts`) intercepta respuestas 401 y automaticamente:
|
||
1. Pone en cola las peticiones pendientes
|
||
2. Llama a `/auth/refresh` con el refresh token
|
||
3. Reintenta las peticiones con el nuevo access token
|
||
|
||
### Almacenamiento en localStorage
|
||
|
||
```
|
||
grh_access_token → JWT access token
|
||
grh_refresh_token → JWT refresh token
|
||
water_project_settings_v1 → {theme, compactMode}
|
||
mock_avatar → Avatar en base64
|
||
```
|
||
|
||
### Control de Acceso por Rol (Jerarquia de 3 niveles)
|
||
|
||
| Recurso | ADMIN | ORGANISMO_OPERADOR | OPERATOR |
|
||
|---------|-------|-------------------|----------|
|
||
| Organismos | CRUD completo | Sin acceso | Sin acceso |
|
||
| Usuarios | CRUD completo | CRUD (su organismo) | Sin acceso |
|
||
| Proyectos | CRUD completo | Leer (su organismo) | Leer (su proyecto) |
|
||
| Medidores | CRUD completo | Leer (su organismo) | Leer (su proyecto) |
|
||
| Lecturas | CRUD + eliminar | Leer (su organismo) | Leer (su proyecto) |
|
||
| Historico | Todos los medidores | Medidores de su organismo | Medidores de su proyecto |
|
||
| Analytics | Completo | Completo | Sin acceso |
|
||
| Conectores | Completo | Sin acceso | Sin acceso |
|
||
| Auditoria | Completa | Sin acceso | Sin acceso |
|
||
|
||
### Scope Filtering (Backend)
|
||
|
||
Todos los servicios aplican filtrado automatico basado en el rol del usuario:
|
||
|
||
```
|
||
ADMIN → Sin filtro (ve todos los datos)
|
||
ORGANISMO_OP. → project_id IN (SELECT id FROM projects WHERE organismo_operador_id = $N)
|
||
OPERATOR → project_id = $N (solo su proyecto asignado)
|
||
```
|
||
|
||
El utility `water-api/src/utils/scope.ts` centraliza esta logica.
|
||
|
||
### Helpers del Frontend
|
||
|
||
```typescript
|
||
getCurrentUserRole() // → "ADMIN" | "ORGANISMO_OPERADOR" | "OPERATOR"
|
||
getCurrentUserId() // → UUID string
|
||
getCurrentUserProjectId() // → UUID string | undefined
|
||
getCurrentUserOrganismoId() // → UUID string | undefined
|
||
isCurrentUserAdmin() // → boolean
|
||
isCurrentUserOrganismo() // → boolean
|
||
isCurrentUserOperator() // → boolean
|
||
```
|
||
|
||
---
|
||
|
||
## Capa de API del Frontend
|
||
|
||
### Cliente HTTP (`src/api/client.ts`)
|
||
|
||
Wrapper de `fetch` con:
|
||
- Inyeccion automatica de `Authorization: Bearer <token>`
|
||
- Refresh automatico de tokens en 401
|
||
- Cola de peticiones durante el refresh (previene race conditions)
|
||
- Buffer de 30 segundos antes de expirar
|
||
- Transformacion de snake_case (backend) a camelCase (frontend)
|
||
|
||
### Modulos de API
|
||
|
||
| Archivo | Descripcion |
|
||
|---------|-------------|
|
||
| `auth.ts` | Login, logout, refresh, getMe, helpers de roles (3 niveles) |
|
||
| `client.ts` | Cliente HTTP base con JWT |
|
||
| `meters.ts` | CRUD medidores + lecturas historicas paginadas |
|
||
| `readings.ts` | Lecturas y resumen de consumo |
|
||
| `projects.ts` | CRUD proyectos + nombres |
|
||
| `concentrators.ts` | CRUD concentradores |
|
||
| `users.ts` | CRUD usuarios |
|
||
| `roles.ts` | CRUD roles |
|
||
| `organismos.ts` | CRUD organismos operadores |
|
||
| `analytics.ts` | Metricas, ubicaciones, reportes |
|
||
| `notifications.ts` | Notificaciones del usuario |
|
||
| `audit.ts` | Logs de auditoria |
|
||
| `me.ts` | Perfil de usuario actual |
|
||
| `meterTypes.ts` | Tipos de medidor |
|
||
| `types.ts` | Interfaces compartidas |
|
||
|
||
---
|
||
|
||
## Hooks Personalizados
|
||
|
||
### useMeters
|
||
|
||
Gestiona el estado completo del modulo de medidores.
|
||
|
||
```typescript
|
||
interface UseMetersReturn {
|
||
// Datos
|
||
meters: Meter[];
|
||
filteredMeters: Meter[];
|
||
projectNames: string[];
|
||
|
||
// Estado
|
||
loading: boolean;
|
||
selectedMeter: Meter | null;
|
||
selectedProject: string | null;
|
||
searchTerm: string;
|
||
|
||
// Acciones
|
||
setSelectedMeter: (meter: Meter | null) => void;
|
||
setSelectedProject: (project: string | null) => void;
|
||
setSearchTerm: (term: string) => void;
|
||
refreshData: () => Promise<void>;
|
||
loadMeters: () => Promise<void>;
|
||
|
||
// CRUD
|
||
handleCreate: (data: Partial<Meter>) => Promise<void>;
|
||
handleUpdate: (id: string, data: Partial<Meter>) => Promise<void>;
|
||
handleDelete: (id: string) => Promise<void>;
|
||
}
|
||
```
|
||
|
||
### useConcentrators
|
||
|
||
Estructura equivalente a `useMeters` para el modulo de concentradores.
|
||
|
||
### useNotifications
|
||
|
||
Gestiona notificaciones del usuario con polling periodico.
|
||
|
||
```typescript
|
||
interface UseNotificationsReturn {
|
||
notifications: Notification[];
|
||
unreadCount: number;
|
||
loading: boolean;
|
||
markAsRead: (id: string) => Promise<void>;
|
||
markAllAsRead: () => Promise<void>;
|
||
deleteNotification: (id: string) => Promise<void>;
|
||
refresh: () => Promise<void>;
|
||
}
|
||
```
|
||
|
||
### Flujo de Datos
|
||
|
||
```
|
||
API Layer (fetch + JWT)
|
||
│
|
||
▼
|
||
Custom Hook (useMeters, etc.)
|
||
│
|
||
▼
|
||
Page Component (MeterPage, etc.)
|
||
│
|
||
├──────────────┬──────────────┐
|
||
▼ ▼ ▼
|
||
Sidebar Table Modal
|
||
```
|
||
|
||
---
|
||
|
||
## Sistema de Temas
|
||
|
||
### Configuracion
|
||
|
||
Tres modos disponibles: `system`, `light`, `dark`.
|
||
|
||
```typescript
|
||
type Theme = "system" | "light" | "dark";
|
||
|
||
// Aplicacion del tema
|
||
document.documentElement.classList.toggle("dark", isDark);
|
||
```
|
||
|
||
### Paleta de Colores (Dark Mode)
|
||
|
||
El dark mode usa la paleta **Zinc** de Tailwind:
|
||
- Fondo principal: `bg-zinc-900`
|
||
- Fondo de tarjetas: `bg-zinc-800`
|
||
- Bordes: `border-zinc-700`
|
||
- Texto primario: `text-zinc-100`
|
||
- Texto secundario: `text-zinc-400`
|
||
|
||
### Persistencia
|
||
|
||
```typescript
|
||
const SETTINGS_KEY = "water_project_settings_v1";
|
||
|
||
interface Settings {
|
||
theme: "system" | "light" | "dark";
|
||
compactMode: boolean;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Conectores Externos
|
||
|
||
### SH-Meters
|
||
Conector para sistema de medidores SH. Muestra estado de conexion, ultima sincronizacion y datos sincronizados.
|
||
|
||
### XMeters
|
||
Conector para sistema XMeters con funcionalidad similar a SH-Meters.
|
||
|
||
### The Things Stack (TTS)
|
||
Integracion con LoRaWAN via webhooks:
|
||
|
||
- **Uplink** (`POST /api/webhooks/tts/uplink`): Recibe lecturas de sensores, decodifica payload, crea readings automaticamente
|
||
- **Join** (`POST /api/webhooks/tts/join`): Registra eventos de conexion de dispositivos
|
||
- **Downlink Ack** (`POST /api/webhooks/tts/downlink/ack`): Confirma envio de comandos a dispositivos
|
||
|
||
Los webhooks usan verificacion por secreto en lugar de JWT.
|
||
|
||
### Sincronizacion
|
||
Los conectores tienen sincronizacion programada a las **9:00 AM**.
|
||
|
||
---
|
||
|
||
## Tareas Programadas
|
||
|
||
### Deteccion de Flujo Negativo
|
||
|
||
Job de `node-cron` que:
|
||
1. Revisa lecturas recientes de medidores
|
||
2. Detecta valores negativos o anomalias
|
||
3. Genera notificaciones automaticas para los usuarios
|
||
|
||
---
|
||
|
||
## Guia de Desarrollo
|
||
|
||
### Agregar una Nueva Pagina
|
||
|
||
1. **Crear el componente** en `src/pages/nueva/NuevaPage.tsx`
|
||
2. **Agregar al tipo Page** en `App.tsx`
|
||
3. **Agregar al Sidebar** en `Sidebar.tsx` (menuItems)
|
||
4. **Agregar al renderizado** en `App.tsx` (switch en `renderPage()`)
|
||
|
||
### Agregar un Nuevo Endpoint en el Backend
|
||
|
||
1. **Crear servicio** en `water-api/src/services/nuevo.service.ts`
|
||
2. **Crear controlador** en `water-api/src/controllers/nuevo.controller.ts`
|
||
3. **Crear validador** (opcional) en `water-api/src/validators/nuevo.validator.ts`
|
||
4. **Crear rutas** en `water-api/src/routes/nuevo.routes.ts`
|
||
5. **Registrar rutas** en `water-api/src/routes/index.ts`
|
||
|
||
### Agregar un Modulo de API en el Frontend
|
||
|
||
1. **Crear archivo** en `src/api/nuevo.ts`
|
||
2. **Usar el cliente HTTP** importando de `./client.ts`
|
||
3. **Crear hook** (opcional) en `src/pages/nueva/useNuevo.ts`
|
||
|
||
### Convenciones de Codigo
|
||
|
||
| Elemento | Convencion | Ejemplo |
|
||
|----------|-----------|---------|
|
||
| Componentes React | PascalCase.tsx | `MeterPage.tsx` |
|
||
| Hooks | camelCase con prefijo `use` | `useMeters.ts` |
|
||
| Servicios backend | camelCase.service.ts | `meter.service.ts` |
|
||
| Rutas backend | camelCase.routes.ts | `meter.routes.ts` |
|
||
| Constantes | UPPER_SNAKE_CASE | `API_BASE_URL` |
|
||
| DB columnas | snake_case | `serial_number` |
|
||
| API response → Frontend | snake_case → camelCase | `serial_number` → `serialNumber` |
|
||
| CSS | Tailwind utility classes | `bg-zinc-800 dark:text-white` |
|
||
|
||
### Seguridad
|
||
|
||
- **Helmet.js** para headers HTTP seguros
|
||
- **CORS** configurado para origenes especificos
|
||
- **Bcrypt** (12 rounds) para hash de contrasenas
|
||
- **JWT** con access + refresh tokens
|
||
- **Queries parametrizadas** para prevenir SQL injection
|
||
- **Zod** para validacion de inputs
|
||
- **Audit logging** automatico de acciones
|
||
|
||
### Testing
|
||
|
||
El proyecto actualmente no tiene suite de tests. Para agregar:
|
||
|
||
```bash
|
||
# Frontend
|
||
npm install -D vitest @testing-library/react @testing-library/jest-dom
|
||
|
||
# Backend
|
||
cd water-api
|
||
npm install -D vitest supertest @types/supertest
|
||
```
|