Files
hotel-hacienda/docs/BACKEND.md
Consultoria AS 411d517ba4 docs: documentación extensiva del proyecto
- 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
2026-06-09 16:26:09 -07:00

366 lines
10 KiB
Markdown

# 📦 Documentación del Backend — API Hotel Hacienda
## Índice
1. [Introducción](#introducción)
2. [Estructura de Carpetas](#estructura-de-carpetas)
3. [Flujo de una Petición](#flujo-de-una-petición)
4. [Configuración de Express (`app.js`)](#configuración-de-express-appjs)
5. [Conexión a Base de Datos](#conexión-a-base-de-datos)
6. [Controllers](#controllers)
7. [Rutas](#rutas)
8. [Middlewares](#middlewares)
9. [Servicios](#servicios)
10. [Variables de Entorno](#variables-de-entorno)
11. [Patrones y Convenciones](#patrones-y-convenciones)
12. [Guía para Agregar un Nuevo Endpoint](#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`)
```javascript
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_CORS` debe definirse sin slash al final.
- No hay middleware de autenticación global.
- Todas las rutas usan prefijo `/api/`.
---
## Conexión a Base de Datos
```javascript
// 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
```javascript
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:
```javascript
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:
```javascript
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
```javascript
// 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`
```javascript
const expenseRoutes = require('./routes/expense.routes');
app.use('/api/expenses', expenseRoutes);
```
---
## Middlewares
### Validación de Login
```javascript
// 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
```javascript
// 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:
```javascript
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
1. **Un controller por dominio:** Todos los endpoints relacionados con un recurso viven en el mismo controller.
2. **Parámetros posicionales:** Las funciones SQL usan `$1, $2, $3...` en orden.
3. **Respuesta consistente:** Siempre retornar JSON con al menos `{ message, ...data }`.
4. **Errores 500:** Cualquier excepción se captura y responde con status 500.
5. **Sin ORM:** Las consultas son SQL crudo o llamadas a funciones almacenadas.
---
## Guía para Agregar un Nuevo Endpoint
1. **Crear/actualizar la función SQL** en PostgreSQL.
2. **Crear el método** en el controller correspondiente.
3. **Agregar la ruta** en el archivo `.routes.js` apropiado.
4. **Registrar la ruta** en `src/app.js` con un prefijo `/api/`.
5. **Probar** la función SQL directamente en PostgreSQL antes de probar el endpoint.
6. **Actualizar** `docs/API.md` con el nuevo endpoint.