- README.md con guía completa de instalación y arquitectura - docs/BACKEND.md — guía detallada del API - docs/FRONTEND.md — guía del SPA React - docs/DATABASE.md — funciones SQL y patrones de datos - docs/API.md — referencia completa de endpoints REST - docs/DEPLOYMENT.md — guía de despliegue en producción - docs/TROUBLESHOOTING.md — solución de problemas comunes - CHANGELOG.md — historial de cambios - CONTRIBUTING.md — guía de contribución
10 KiB
10 KiB
📦 Documentación del Backend — API Hotel Hacienda
Índice
- Introducción
- Estructura de Carpetas
- Flujo de una Petición
- Configuración de Express (
app.js) - Conexión a Base de Datos
- Controllers
- Rutas
- Middlewares
- Servicios
- Variables de Entorno
- Patrones y Convenciones
- Guía para Agregar un Nuevo Endpoint
Introducción
El backend es una API REST construida con Node.js y Express.js. No utiliza ORM; la lógica de negocio reside en funciones SQL de PostgreSQL que son invocadas desde los controllers.
Entry point: index.js → src/server.js → src/app.js
Estructura de Carpetas
hotel_hacienda/
├── index.js # Solo requiere src/server.js
├── src/
│ ├── server.js # Levanta el servidor
│ ├── app.js # Configura Express, CORS, rutas
│ ├── db/
│ │ └── connection.js # Pool de PostgreSQL
│ ├── middlewares/
│ │ ├── handleValidation.js # Manejo de errores de express-validator
│ │ └── validators.js # Validadores (login)
│ ├── routes/ # 16 archivos de rutas
│ │ ├── auth.routes.js
│ │ ├── employee.routes.js
│ │ ├── contract.routes.js
│ │ ├── product.routes.js
│ │ ├── expense.routes.js
│ │ ├── status.routes.js
│ │ ├── payment.routes.js
│ │ ├── settings.routes.js
│ │ ├── mail.routes.js
│ │ ├── incomes.routes.js
│ │ ├── purchase.routes.js
│ │ ├── exchange.routes.js
│ │ ├── hotelpl.routes.js
│ │ ├── restaurantpl.routes.js
│ │ ├── incomehrx.routes.js
│ │ └── reportcontract.routes.js
│ ├── controllers/ # 18 archivos de controllers
│ │ ├── auth.controller.js
│ │ ├── employee.controller.js
│ │ ├── contract.controller.js
│ │ ├── product.controller.js
│ │ ├── expense.controller.js
│ │ ├── status.controller.js
│ │ ├── payment.controller.js
│ │ ├── settings.controller.js
│ │ ├── mail.controller.js
│ │ ├── incomes.controller.js
│ │ ├── purchase.controller.js
│ │ ├── exchange.controller.js
│ │ ├── hotelp.controller.js
│ │ ├── restaurantpl.controller.js
│ │ ├── incomehrx.controller.js
│ │ ├── reportcontract.controller.js
│ │ └── reporter.controller.js
│ └── services/
│ └── mailService.js # Transporte de Nodemailer
Flujo de una Petición
HTTP Request
│
▼
┌─────────────┐
│ Express │ ← CORS, JSON parser
│ (app.js) │
└──────┬──────┘
│
▼
┌─────────────┐
│ Routes │ ← Prefijo /api/xxx
│ (.routes) │
└──────┬──────┘
│
▼
┌─────────────┐
│ Controllers │ ← Extrae req.params/body
│ (.controller)│
└──────┬──────┘
│
▼
┌─────────────┐
│ Pool │ ← pg (node-postgres)
│ (connection)│
└──────┬──────┘
│
▼
┌─────────────┐
│ PostgreSQL │ ← Funciones SQL
│ (funciones)│
└─────────────┘
Configuración de Express (app.js)
const express = require('express');
const app = express();
const cors = require('cors');
require("dotenv").config();
const corsOptions = {
origin: process.env.URL_CORS,
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"]
};
app.use(cors(corsOptions));
app.use(express.json());
Observaciones:
URL_CORSdebe definirse sin slash al final.- No hay middleware de autenticación global.
- Todas las rutas usan prefijo
/api/.
Conexión a Base de Datos
// src/db/connection.js
const { Pool } = require('pg');
require('dotenv').config();
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
pool.connect()
.then(() => console.log('Conectado a PostgreSQL'))
.catch(err => console.error('Error de conexión:', err));
module.exports = pool;
Nota: Se usa un Pool reutilizable. Cada controller lo importa directamente.
Controllers
Patrón General
const pool = require('../db/connection');
const nombreFuncion = async (req, res) => {
try {
const { param1, param2 } = req.body; // o req.params / req.query
const result = await pool.query(
'SELECT nombrefuncionsql($1, $2) AS status',
[param1, param2]
);
const status = result.rows[0].status;
res.json({ message: 'Éxito', status });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error interno' });
}
};
module.exports = { nombreFuncion };
Paginación Manual
Algunos endpoints implementan paginación manual:
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 500;
const offset = (page - 1) * limit;
const result = await pool.query(
'SELECT * FROM getemployees() LIMIT $1 OFFSET $2',
[limit, offset]
);
const totalResult = await pool.query('SELECT COUNT(*) FROM employees');
const total = parseInt(totalResult.rows[0].count);
const totalPages = Math.ceil(total / limit);
res.json({ page, limit, total, totalPages, data: result.rows });
Manejo de JSONB
Para pasar arrays/objetos a PostgreSQL:
const categoriesJson = categories ? JSON.stringify(categories) : '[]';
await pool.query(
'SELECT newincome($1,$2,$3,$4,$5,$6::jsonb) AS status',
[param1, param2, param3, param4, param5, categoriesJson]
);
Rutas
Convención de Nombres
- Archivos:
nombreplural.routes.js - Prefijo en
app.js:/api/nombreplural
Ejemplo
// src/routes/expense.routes.js
const express = require('express');
const router = express.Router();
const expenseController = require('../controllers/expense.controller');
router.get('/pendingapproval', expenseController.getPendingAppExpenses);
router.post('/newexpense', expenseController.newExpense);
router.put('/updateexpense/:id', expenseController.updateExpense);
module.exports = router;
Registro en app.js
const expenseRoutes = require('./routes/expense.routes');
app.use('/api/expenses', expenseRoutes);
Middlewares
Validación de Login
// src/middlewares/validators.js
const { body } = require('express-validator');
const loginValidator = [
body('name_mail_user').notEmpty().withMessage('Usuario requerido'),
body('user_pass').notEmpty().withMessage('Contraseña requerida')
];
module.exports = { loginValidator };
Manejo de Errores de Validación
// src/middlewares/handleValidation.js
const { validationResult } = require('express-validator');
const handleValidation = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
};
module.exports = handleValidation;
Nota: Actualmente solo el login tiene validación. Los demás endpoints no validan inputs.
Servicios
Mail Service (src/services/mailService.js)
Configura un transporte SMTP reutilizable con Nodemailer:
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_PORT,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
module.exports = transporter;
Uso: Importado en controllers que envían correos (auth.controller.js, mail.controller.js).
Variables de Entorno
| Variable | Descripción | Requerida |
|---|---|---|
PORT |
Puerto del servidor | Sí (default 3000) |
URL_CORS |
Origen permitido para CORS | Sí |
DB_HOST |
Host de PostgreSQL | Sí |
DB_PORT |
Puerto de PostgreSQL | Sí |
DB_USER |
Usuario de PostgreSQL | Sí |
DB_PASSWORD |
Contraseña de PostgreSQL | Sí |
DB_NAME |
Nombre de la base de datos | Sí |
EMAIL_HOST |
Servidor SMTP | Sí |
EMAIL_PORT |
Puerto SMTP | Sí |
EMAIL_USER |
Usuario SMTP | Sí |
EMAIL_PASS |
Contraseña SMTP | Sí |
STRIPE_SECRET_KEY |
Clave secreta de Stripe | Para integración Stripe |
FACTURAS_API_URL |
URL API de facturas | Para integración facturas |
FACTURAS_ISSUER_RFC |
RFC del emisor | Para integración facturas |
FACTURAS_TYPE |
Tipo de factura (I/E) | Para integración facturas |
FACTURAS_API_TOKEN |
Token de API facturas | Para integración facturas |
BANXICO_TOKEN |
Token de Banxico | Para tipo de cambio |
Patrones y Convenciones
- Un controller por dominio: Todos los endpoints relacionados con un recurso viven en el mismo controller.
- Parámetros posicionales: Las funciones SQL usan
$1, $2, $3...en orden. - Respuesta consistente: Siempre retornar JSON con al menos
{ message, ...data }. - Errores 500: Cualquier excepción se captura y responde con status 500.
- Sin ORM: Las consultas son SQL crudo o llamadas a funciones almacenadas.
Guía para Agregar un Nuevo Endpoint
- Crear/actualizar la función SQL en PostgreSQL.
- Crear el método en el controller correspondiente.
- Agregar la ruta en el archivo
.routes.jsapropiado. - Registrar la ruta en
src/app.jscon un prefijo/api/. - Probar la función SQL directamente en PostgreSQL antes de probar el endpoint.
- Actualizar
docs/API.mdcon el nuevo endpoint.