Add complete project documentation
Documentation includes: - README.md: Project overview and architecture - API.md: Complete API reference with endpoints - MANUAL_USUARIO.md: User manual in Spanish - INSTALACION.md: Installation and deployment guide - ARQUITECTURA.md: Architecture and database schema - UPLOAD_PANEL.md: CSV upload panel documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
496
docs/ARQUITECTURA.md
Normal file
496
docs/ARQUITECTURA.md
Normal file
@@ -0,0 +1,496 @@
|
||||
# Arquitectura y Base de Datos
|
||||
|
||||
## Arquitectura General
|
||||
|
||||
### Diagrama de Componentes
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ CLIENTES │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Navegador Web │ │ Navegador Web │ │ Dispositivos │ │
|
||||
│ │ (App Principal)│ │ (Panel Carga) │ │ LoRaWAN │ │
|
||||
│ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │
|
||||
│ │ │ │ │
|
||||
└────────────┼───────────────────────┼───────────────────────┼────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ CAPA DE PRESENTACION │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ NGINX (Reverse Proxy) │ │
|
||||
│ │ - SSL/TLS Termination │ │
|
||||
│ │ - Load Balancing │ │
|
||||
│ │ - Static File Serving │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────┐ ┌────────────────────┐ │
|
||||
│ │ Frontend React │ │ Upload Panel │ │
|
||||
│ │ (sistema.grh.com) │ │ (panel.grh.com) │ │
|
||||
│ │ Puerto: 5173 │ │ Puerto: 5174 │ │
|
||||
│ └────────────────────┘ └────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ CAPA DE SERVICIOS │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Express.js API Server │ │
|
||||
│ │ (api.grh.com - Puerto 3000) │ │
|
||||
│ ├──────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ Routes │ │ Controllers │ │ Services │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ - auth │ │ - auth │ │ - auth │ │ │
|
||||
│ │ │ - projects │ │ - project │ │ - project │ │ │
|
||||
│ │ │ - meters │ │ - meter │ │ - meter │ │ │
|
||||
│ │ │ - readings │ │ - reading │ │ - reading │ │ │
|
||||
│ │ │ - users │ │ - user │ │ - user │ │ │
|
||||
│ │ │ - csv-upload│ │ - etc... │ │ - csv-upload│ │ │
|
||||
│ │ │ - webhooks │ │ │ │ - tts │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ Middleware │ │ Validators │ │ Jobs │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ - auth │ │ - zod │ │ - cron │ │ │
|
||||
│ │ │ - audit │ │ - schemas │ │ - negative │ │ │
|
||||
│ │ │ - tts verify│ │ │ │ flow │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ CAPA DE DATOS │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ PostgreSQL Database │ │
|
||||
│ │ Puerto: 5432 │ │
|
||||
│ ├──────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ users │ │ projects │ │concentrators│ │ │
|
||||
│ │ │ roles │ │ gateways │ │ meters │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ readings │ │ devices │ │ audit_logs │ │ │
|
||||
│ │ │ (meter_ │ │ tts_uplink │ │notifications│ │ │
|
||||
│ │ │ readings) │ │ _logs │ │ │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ SERVICIOS EXTERNOS │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ The Things Stack (TTS) │ │
|
||||
│ │ Plataforma LoRaWAN │ │
|
||||
│ ├──────────────────────────────────────────────────────────────────┤ │
|
||||
│ │ - Recepcion de uplinks de dispositivos │ │
|
||||
│ │ - Gestion de dispositivos LoRaWAN │ │
|
||||
│ │ - Webhooks hacia la API │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modelo de Datos
|
||||
|
||||
### Diagrama Entidad-Relacion
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ roles │ │ users │ │ projects │
|
||||
├─────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ id (PK) │──┐ │ id (PK) │ ┌──│ id (PK) │
|
||||
│ name │ └───▶│ role_id (FK) │ │ │ name │
|
||||
│ description │ │ project_id (FK) │◀───┤ │ description │
|
||||
│ permissions │ │ email │ │ │ area_name │
|
||||
└─────────────┘ │ password_hash │ │ │ status │
|
||||
│ name │ │ │ created_by (FK) │──▶ users
|
||||
│ is_active │ │ │ meter_type_id │──▶ meter_types
|
||||
└─────────────────┘ │ └─────────────────┘
|
||||
│ │
|
||||
│ │
|
||||
┌─────────────────┐ │ │
|
||||
│ concentrators │◀───┘ │
|
||||
├─────────────────┤ │
|
||||
│ id (PK) │ │
|
||||
│ serial_number │ │
|
||||
│ name │ │
|
||||
│ project_id (FK) │◀───────────────┘
|
||||
│ status │
|
||||
│ ip_address │
|
||||
└─────────────────┘
|
||||
│
|
||||
│
|
||||
┌────────┴────────┐
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ gateways │ │ meters │
|
||||
├─────────────────┤ ├─────────────────┤
|
||||
│ id (PK) │ │ id (PK) │
|
||||
│ gateway_id │ │ serial_number │
|
||||
│ name │ │ name │
|
||||
│ project_id (FK) │ │ project_id (FK) │
|
||||
│ concentrator_id │ │ concentrator_id │
|
||||
│ status │ │ device_id (FK) │──▶ devices
|
||||
└─────────────────┘ │ type │
|
||||
│ │ status │
|
||||
│ │ last_reading │
|
||||
▼ └─────────────────┘
|
||||
┌─────────────────┐ │
|
||||
│ devices │ │
|
||||
├─────────────────┤ ▼
|
||||
│ id (PK) │ ┌─────────────────┐
|
||||
│ dev_eui │ │ meter_readings │
|
||||
│ name │ ├─────────────────┤
|
||||
│ project_id (FK) │ │ id (PK) │
|
||||
│ gateway_id (FK) │ │ meter_id (FK) │
|
||||
│ status │ │ reading_value │
|
||||
└─────────────────┘ │ reading_type │
|
||||
│ │ battery_level │
|
||||
│ │ signal_strength │
|
||||
▼ │ received_at │
|
||||
┌─────────────────┐ └─────────────────┘
|
||||
│ tts_uplink_logs │
|
||||
├─────────────────┤
|
||||
│ id (PK) │
|
||||
│ device_id (FK) │
|
||||
│ raw_payload │
|
||||
│ decoded_payload │
|
||||
│ processed │
|
||||
└─────────────────┘
|
||||
|
||||
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ audit_logs │ │ notifications │ │ meter_types │
|
||||
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||
│ user_id (FK) │ │ user_id (FK) │ │ name │
|
||||
│ action │ │ meter_id (FK) │ │ code │
|
||||
│ table_name │ │ type │ │ description │
|
||||
│ record_id │ │ title │ │ is_active │
|
||||
│ old_values │ │ message │ └─────────────────┘
|
||||
│ new_values │ │ is_read │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Descripcion de Tablas
|
||||
|
||||
### Tablas de Autenticacion y Usuarios
|
||||
|
||||
#### `roles`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| name | ENUM | ADMIN, OPERATOR, VIEWER |
|
||||
| description | TEXT | Descripcion del rol |
|
||||
| permissions | JSONB | Permisos detallados |
|
||||
|
||||
#### `users`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| email | VARCHAR | Email unico (login) |
|
||||
| password_hash | VARCHAR | Hash bcrypt de contraseña |
|
||||
| name | VARCHAR | Nombre completo |
|
||||
| role_id | UUID FK | Rol asignado |
|
||||
| project_id | UUID FK | Proyecto asignado (OPERATOR) |
|
||||
| is_active | BOOLEAN | Estado de la cuenta |
|
||||
| last_login | TIMESTAMP | Ultimo acceso |
|
||||
|
||||
#### `refresh_tokens`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| user_id | UUID FK | Usuario propietario |
|
||||
| token_hash | VARCHAR | Hash del token |
|
||||
| expires_at | TIMESTAMP | Fecha de expiracion |
|
||||
| revoked_at | TIMESTAMP | Fecha de revocacion |
|
||||
|
||||
---
|
||||
|
||||
### Tablas de Estructura
|
||||
|
||||
#### `projects`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| name | VARCHAR | Nombre del proyecto |
|
||||
| description | TEXT | Descripcion |
|
||||
| area_name | VARCHAR | Nombre del area |
|
||||
| location | TEXT | Ubicacion |
|
||||
| status | ENUM | ACTIVE, INACTIVE, COMPLETED |
|
||||
| meter_type_id | UUID FK | Tipo de medidor por defecto |
|
||||
| created_by | UUID FK | Usuario creador |
|
||||
|
||||
#### `concentrators`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| serial_number | VARCHAR | Numero de serie unico |
|
||||
| name | VARCHAR | Nombre descriptivo |
|
||||
| project_id | UUID FK | Proyecto asociado |
|
||||
| location | TEXT | Ubicacion fisica |
|
||||
| status | ENUM | Estado del concentrador |
|
||||
| ip_address | VARCHAR | Direccion IP |
|
||||
| firmware_version | VARCHAR | Version de firmware |
|
||||
| last_communication | TIMESTAMP | Ultima comunicacion |
|
||||
|
||||
#### `gateways`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| gateway_id | VARCHAR | ID unico del gateway |
|
||||
| name | VARCHAR | Nombre descriptivo |
|
||||
| project_id | UUID FK | Proyecto asociado |
|
||||
| concentrator_id | UUID FK | Concentrador asociado |
|
||||
| location | TEXT | Ubicacion |
|
||||
| status | ENUM | Estado |
|
||||
| tts_gateway_id | VARCHAR | ID en The Things Stack |
|
||||
|
||||
---
|
||||
|
||||
### Tablas de Medicion
|
||||
|
||||
#### `meters`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| serial_number | VARCHAR | Numero de serie unico |
|
||||
| name | VARCHAR | Nombre descriptivo |
|
||||
| project_id | UUID FK | Proyecto asociado |
|
||||
| concentrator_id | UUID FK | Concentrador asociado |
|
||||
| device_id | UUID FK | Dispositivo LoRaWAN asociado |
|
||||
| area_name | VARCHAR | Nombre del area |
|
||||
| location | TEXT | Ubicacion especifica |
|
||||
| type | VARCHAR | LORA, LORAWAN, GRANDES CONSUMIDORES |
|
||||
| status | ENUM | ACTIVE, INACTIVE, OFFLINE, MAINTENANCE, ERROR |
|
||||
| last_reading_value | NUMERIC | Ultima lectura registrada |
|
||||
| last_reading_at | TIMESTAMP | Fecha de ultima lectura |
|
||||
| installation_date | DATE | Fecha de instalacion |
|
||||
|
||||
**Campos extendidos:**
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| protocol | VARCHAR | Protocolo de comunicacion |
|
||||
| mac | VARCHAR | Direccion MAC |
|
||||
| voltage | DECIMAL | Voltaje |
|
||||
| signal | INTEGER | Intensidad de senal |
|
||||
| leakage_status | VARCHAR | Estado de fuga |
|
||||
| burst_status | VARCHAR | Estado de ruptura |
|
||||
| current_flow | DECIMAL | Flujo actual |
|
||||
| latitude | DECIMAL | Latitud GPS |
|
||||
| longitude | DECIMAL | Longitud GPS |
|
||||
| data | JSONB | Datos adicionales flexibles |
|
||||
|
||||
#### `meter_readings`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| meter_id | UUID FK | Medidor asociado |
|
||||
| device_id | UUID FK | Dispositivo origen |
|
||||
| reading_value | NUMERIC | Valor de la lectura |
|
||||
| reading_type | ENUM | AUTOMATIC, MANUAL, SCHEDULED |
|
||||
| battery_level | SMALLINT | Nivel de bateria (0-100) |
|
||||
| signal_strength | SMALLINT | Intensidad de senal (dBm) |
|
||||
| raw_payload | TEXT | Payload crudo del dispositivo |
|
||||
| received_at | TIMESTAMP | Fecha/hora de recepcion |
|
||||
|
||||
#### `meter_types`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| name | VARCHAR | Nombre del tipo |
|
||||
| code | VARCHAR | Codigo unico |
|
||||
| description | TEXT | Descripcion |
|
||||
| is_active | BOOLEAN | Estado activo |
|
||||
|
||||
---
|
||||
|
||||
### Tablas de IoT (The Things Stack)
|
||||
|
||||
#### `devices`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| dev_eui | VARCHAR | DevEUI unico del dispositivo |
|
||||
| name | VARCHAR | Nombre descriptivo |
|
||||
| device_type | VARCHAR | Tipo de dispositivo |
|
||||
| project_id | UUID FK | Proyecto asociado |
|
||||
| gateway_id | UUID FK | Gateway asociado |
|
||||
| status | ENUM | Estado del dispositivo |
|
||||
| tts_device_id | VARCHAR | ID en TTS |
|
||||
| tts_status | VARCHAR | Estado en TTS |
|
||||
| app_key | VARCHAR | Application Key |
|
||||
| join_eui | VARCHAR | Join EUI |
|
||||
|
||||
#### `tts_uplink_logs`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| device_id | UUID FK | Dispositivo origen |
|
||||
| dev_eui | VARCHAR | DevEUI |
|
||||
| raw_payload | JSONB | Payload completo |
|
||||
| decoded_payload | JSONB | Payload decodificado |
|
||||
| gateway_ids | TEXT[] | IDs de gateways |
|
||||
| rssi | INTEGER | RSSI |
|
||||
| snr | FLOAT | SNR |
|
||||
| processed | BOOLEAN | Indica si fue procesado |
|
||||
| error_message | TEXT | Mensaje de error si aplica |
|
||||
|
||||
---
|
||||
|
||||
### Tablas de Sistema
|
||||
|
||||
#### `audit_logs`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| user_id | UUID FK | Usuario que realizo la accion |
|
||||
| user_email | VARCHAR | Email del usuario |
|
||||
| user_name | VARCHAR | Nombre del usuario |
|
||||
| action | ENUM | CREATE, UPDATE, DELETE, LOGIN, etc. |
|
||||
| table_name | VARCHAR | Tabla afectada |
|
||||
| record_id | UUID | ID del registro afectado |
|
||||
| old_values | JSONB | Valores anteriores |
|
||||
| new_values | JSONB | Valores nuevos |
|
||||
| description | TEXT | Descripcion de la accion |
|
||||
| ip_address | VARCHAR | IP del cliente |
|
||||
| user_agent | TEXT | User Agent del navegador |
|
||||
| success | BOOLEAN | Resultado de la operacion |
|
||||
| error_message | TEXT | Mensaje de error si fallo |
|
||||
|
||||
#### `notifications`
|
||||
| Campo | Tipo | Descripcion |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | Identificador unico |
|
||||
| user_id | UUID FK | Usuario destinatario |
|
||||
| meter_id | UUID FK | Medidor relacionado (opcional) |
|
||||
| notification_type | ENUM | NEGATIVE_FLOW, SYSTEM_ALERT, MAINTENANCE |
|
||||
| title | VARCHAR | Titulo de la notificacion |
|
||||
| message | TEXT | Mensaje detallado |
|
||||
| meter_serial_number | VARCHAR | Serial del medidor (si aplica) |
|
||||
| flow_value | DECIMAL | Valor de flujo (si aplica) |
|
||||
| is_read | BOOLEAN | Estado de lectura |
|
||||
| read_at | TIMESTAMP | Fecha de lectura |
|
||||
|
||||
---
|
||||
|
||||
## Indices
|
||||
|
||||
### Indices Principales
|
||||
|
||||
```sql
|
||||
-- Meters
|
||||
CREATE INDEX idx_meters_serial_number ON meters(serial_number);
|
||||
CREATE INDEX idx_meters_project_id ON meters(project_id);
|
||||
CREATE INDEX idx_meters_concentrator_id ON meters(concentrator_id);
|
||||
CREATE INDEX idx_meters_status ON meters(status);
|
||||
CREATE INDEX idx_meters_type ON meters(type);
|
||||
|
||||
-- Readings
|
||||
CREATE INDEX idx_meter_readings_meter_id ON meter_readings(meter_id);
|
||||
CREATE INDEX idx_meter_readings_received_at ON meter_readings(received_at);
|
||||
CREATE INDEX idx_meter_readings_meter_id_received_at ON meter_readings(meter_id, received_at);
|
||||
|
||||
-- Devices
|
||||
CREATE INDEX idx_devices_dev_eui ON devices(dev_eui);
|
||||
CREATE INDEX idx_devices_project_id ON devices(project_id);
|
||||
|
||||
-- Audit
|
||||
CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id);
|
||||
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
CREATE INDEX idx_audit_logs_table_name ON audit_logs(table_name);
|
||||
|
||||
-- Notifications
|
||||
CREATE INDEX idx_notifications_user_id ON notifications(user_id);
|
||||
CREATE INDEX idx_notifications_is_read ON notifications(is_read);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujo de Datos
|
||||
|
||||
### Flujo de Lectura Automatica (TTS)
|
||||
|
||||
```
|
||||
1. Dispositivo LoRaWAN envia uplink
|
||||
│
|
||||
▼
|
||||
2. The Things Stack recibe el mensaje
|
||||
│
|
||||
▼
|
||||
3. TTS envia webhook a /api/webhooks/tts/uplink
|
||||
│
|
||||
▼
|
||||
4. API verifica firma del webhook (X-Downlink-Apikey)
|
||||
│
|
||||
▼
|
||||
5. API guarda en tts_uplink_logs
|
||||
│
|
||||
▼
|
||||
6. API busca device por dev_eui
|
||||
│
|
||||
▼
|
||||
7. API busca meter asociado al device
|
||||
│
|
||||
▼
|
||||
8. API crea registro en meter_readings
|
||||
│
|
||||
▼
|
||||
9. API actualiza last_reading en meters
|
||||
│
|
||||
▼
|
||||
10. Job de deteccion de flujo negativo evalua la lectura
|
||||
│
|
||||
▼
|
||||
11. Si detecta anomalia, crea notification
|
||||
```
|
||||
|
||||
### Flujo de Carga CSV
|
||||
|
||||
```
|
||||
1. Usuario sube archivo CSV
|
||||
│
|
||||
▼
|
||||
2. API parsea el CSV
|
||||
│
|
||||
▼
|
||||
3. Por cada fila:
|
||||
│
|
||||
├─▶ Validar campos requeridos
|
||||
│
|
||||
├─▶ Buscar concentrador por serial
|
||||
│
|
||||
├─▶ Si meter existe: UPDATE
|
||||
│ Si no existe: INSERT
|
||||
│
|
||||
└─▶ Registrar resultado (exito/error)
|
||||
│
|
||||
▼
|
||||
4. Retornar resumen de procesamiento
|
||||
```
|
||||
Reference in New Issue
Block a user