Migrar backend a PostgreSQL + Node.js/Express con nuevas funcionalidades

Backend (water-api/):
- Crear API REST completa con Express + TypeScript
- Implementar autenticación JWT con refresh tokens
- CRUD completo para: projects, concentrators, meters, gateways, devices, users, roles
- Agregar validación con Zod para todas las entidades
- Implementar webhooks para The Things Stack (LoRaWAN)
- Agregar endpoint de lecturas con filtros y resumen de consumo
- Implementar carga masiva de medidores via Excel (.xlsx)

Frontend:
- Crear cliente HTTP con manejo automático de JWT y refresh
- Actualizar todas las APIs para usar nuevo backend
- Agregar sistema de autenticación real (login, logout, me)
- Agregar selector de tipo (LORA, LoRaWAN, Grandes) en concentradores y medidores
- Agregar campo Meter ID en medidores
- Crear modal de carga masiva para medidores
- Agregar página de consumo con gráficas y filtros
- Corregir carga de proyectos independiente de datos existentes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Exteban08
2026-01-23 10:13:26 +00:00
parent 2b5735d78d
commit c81a18987f
92 changed files with 14088 additions and 1866 deletions

View File

@@ -0,0 +1,82 @@
import { Request, Response } from 'express';
import multer from 'multer';
import { bulkUploadMeters, generateMeterTemplate } from '../services/bulk-upload.service';
// Configure multer for memory storage
const storage = multer.memoryStorage();
export const upload = multer({
storage,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB max
},
fileFilter: (_req, file, cb) => {
// Accept Excel files only
const allowedMimes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel', // .xls
];
if (allowedMimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Solo se permiten archivos Excel (.xlsx, .xls)'));
}
},
});
/**
* POST /api/bulk-upload/meters
* Upload Excel file with meters data
*/
export async function uploadMeters(req: Request, res: Response): Promise<void> {
try {
if (!req.file) {
res.status(400).json({
success: false,
error: 'No se proporcionó ningún archivo',
});
return;
}
const result = await bulkUploadMeters(req.file.buffer);
res.status(result.success ? 200 : 207).json({
success: result.success,
data: {
totalRows: result.totalRows,
inserted: result.inserted,
failed: result.errors.length,
errors: result.errors.slice(0, 50), // Limit errors in response
},
});
} catch (err) {
const error = err as Error;
console.error('Error in bulk upload:', error);
res.status(500).json({
success: false,
error: error.message || 'Error procesando la carga masiva',
});
}
}
/**
* GET /api/bulk-upload/meters/template
* Download Excel template for meters
*/
export async function downloadMeterTemplate(_req: Request, res: Response): Promise<void> {
try {
const buffer = generateMeterTemplate();
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename=plantilla_medidores.xlsx');
res.send(buffer);
} catch (err) {
const error = err as Error;
console.error('Error generating template:', error);
res.status(500).json({
success: false,
error: 'Error generando la plantilla',
});
}
}