# 📦 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.