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

10 KiB

📦 Documentación del Backend — API Hotel Hacienda

Índice

  1. Introducción
  2. Estructura de Carpetas
  3. Flujo de una Petición
  4. Configuración de Express (app.js)
  5. Conexión a Base de Datos
  6. Controllers
  7. Rutas
  8. Middlewares
  9. Servicios
  10. Variables de Entorno
  11. Patrones y Convenciones
  12. 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.jssrc/server.jssrc/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_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

// 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
DB_HOST Host de PostgreSQL
DB_PORT Puerto de PostgreSQL
DB_USER Usuario de PostgreSQL
DB_PASSWORD Contraseña de PostgreSQL
DB_NAME Nombre de la base de datos
EMAIL_HOST Servidor SMTP
EMAIL_PORT Puerto SMTP
EMAIL_USER Usuario SMTP
EMAIL_PASS Contraseña SMTP
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.