API REST en Node.js con Express para gestionar gastos personales, con informes mensuales agregados usando MongoDB, Mongoose y moment.js.
javascript
// Importar dependencias
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const moment = require('moment');
// Crear la aplicación Express
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware para análisis JSON
app.use(bodyParser.json());
// Conexión a MongoDB mediante Mongoose
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/spese_db';
mongoose.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('Conectado a MongoDB');
}).catch(err => {
console.error('Error de conexión a MongoDB:', err);
process.exit(1);
});
// Definición del esquema Mongoose para un gasto
const spesaSchema = new mongoose.Schema({
descrizione: { type: String, required: true, trim: true },
importo: { type: Number, required: true, min: 0 },
categoria: { type: String, required: true, trim: true },
data: { type: Date, required: true }
}, { timestamps: true });
const Spesa = mongoose.model('Spesa', spesaSchema);
// RUTA: Crear nuevo gasto
app.post('/api/spese', async (req, res) => {
try {
const { descrizione, importo, categoria, data } = req.body;
// Validación básica
if (!descrizione || !importo || !categoria || !data) {
return res.status(400).json({ error: 'Todos los campos son obligatorios' });
}
const dataParsed = moment(data, moment.ISO_8601, true);
if (!dataParsed.isValid()) {
return res.status(400).json({ error: 'Fecha no válida, usar formato ISO 8601' });
}
const nuovaSpesa = new Spesa({
descrizione: descrizione.trim(),
importo: parseFloat(importo),
categoria: categoria.trim(),
data: dataParsed.toDate()
});
const saved = await nuovaSpesa.save();
res.status(201).json(saved);
} catch (err) {
console.error('Error POST /api/spese:', err);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
// RUTA: Obtener todos los gastos
app.get('/api/spese', async (req, res) => {
try {
const spese = await Spesa.find().sort({ data: -1 });
res.json(spese);
} catch (err) {
console.error('Error GET /api/spese:', err);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
// RUTA: Obtener gasto por ID
app.get('/api/spese/:id', async (req, res) => {
try {
const spesa = await Spesa.findById(req.params.id);
if (!spesa) {
return res.status(404).json({ error: 'Gasto no encontrado' });
}
res.json(spesa);
} catch (err) {
console.error('Error GET /api/spese/:id:', err);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
// RUTA: Actualizar gasto por ID
app.put('/api/spese/:id', async (req, res) => {
try {
const { descrizione, importo, categoria, data } = req.body;
const updateData = {};
if (descrizione) updateData.descrizione = descrizione.trim();
if (importo !== undefined) updateData.importo = parseFloat(importo);
if (categoria) updateData.categoria = categoria.trim();
if (data) {
const dataParsed = moment(data, moment.ISO_8601, true);
if (!dataParsed.isValid()) {
return res.status(400).json({ error: 'Fecha no válida, usar formato ISO 8601' });
}
updateData.data = dataParsed.toDate();
}
const spesaAggiornata = await Spesa.findByIdAndUpdate(req.params.id, updateData, { new: true, runValidators: true });
if (!spesaAggiornata) {
return res.status(404).json({ error: 'Gasto no encontrado' });
}
res.json(spesaAggiornata);
} catch (err) {
console.error('Error PUT /api/spese/:id:', err);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
// RUTA: Eliminar gasto por ID
app.delete('/api/spese/:id', async (req, res) => {
try {
const spesaEliminata = await Spesa.findByIdAndDelete(req.params.id);
if (!spesaEliminata) {
return res.status(404).json({ error: 'Gasto no encontrado' });
}
res.json({ message: 'Gasto eliminado con éxito' });
} catch (err) {
console.error('Error DELETE /api/spese/:id:', err);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
// RUTA: Informe mensual agregado por categoría
app.get('/api/report/mensile', async (req, res) => {
try {
// Agregación MongoDB para sumar importes agrupados por año, mes y categoría
const report = await Spesa.aggregate([
{
$group: {
_id: {
anno: { $year: '$data' },
mese: { $month: '$data' },
categoria: '$categoria'
},
totale: { $sum: '$importo' }
}
},
{
$sort: {
'_id.anno': -1,
'_id.mese': -1,
'_id.categoria': 1
}
}
]);
// Reformatear datos para respuesta más legible
const risultato = {};
report.forEach(item => {
const anno = item._id.anno;
const mese = item._id.mese;
const categoria = item._id.categoria;
if (!risultato[anno]) risultato[anno] = {};
if (!risultato[anno][mese]) risultato[anno][mese] = {};
risultato[anno][mese][categoria] = item.totale;
});
res.json(risultato);
} catch (err) {
console.error('Error GET /api/report/mensile:', err);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
// Iniciar servidor
app.listen(PORT, () => {
console.log(`Servidor iniciado en el puerto ${PORT}`);
});
// Fin del códigoEntorno de ejecución Node.js
Framework Express.js para el servidor HTTP
MongoDB como base de datos NoSQL para almacenar gastos
Mongoose ODM para modelar datos e interactuar con MongoDB
moment.js para gestión y formateo de fechas
Configuración del entorno: variable MONGODB_URI para la conexión a la base de datos
Dependencias en package.json: express, mongoose, moment, body-parser
Estructura API REST: endpoint /api/spese para CRUD, /api/report/mensile para informe agregado
Middleware Express para análisis JSON y manejo de errores
Ningún hardware Arduino involucrado en este proyecto