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
This commit is contained in:
103
CHANGELOG.md
Normal file
103
CHANGELOG.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# 📜 Changelog — Sistema de Gestión Hotelera Hacienda San Angel
|
||||
|
||||
> Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/).
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2026-06-09
|
||||
|
||||
### Agregado
|
||||
- Documentación técnica completa del sistema (`DOCUMENTACION_TECNICA.md`).
|
||||
- README principal con guía de instalación y arquitectura.
|
||||
- Documentación extendida en carpeta `docs/`:
|
||||
- `BACKEND.md` — Guía detallada de la API Node.js/Express.
|
||||
- `FRONTEND.md` — Guía del SPA React + Vite.
|
||||
- `DATABASE.md` — Documentación de funciones SQL de PostgreSQL.
|
||||
- `API.md` — Referencia completa de endpoints REST.
|
||||
- `DEPLOYMENT.md` — Guía de despliegue en producción.
|
||||
- `TROUBLESHOOTING.md` — Solución de problemas comunes.
|
||||
- Commit de consolidación con todo el código base del proyecto.
|
||||
- Repositorio migrado a Gitea (`https://git.consultoria-as.com`).
|
||||
|
||||
### Notas Técnicas
|
||||
- El proyecto utiliza arquitectura "Database-First" donde la lógica de negocio reside en funciones SQL de PostgreSQL.
|
||||
- Backend: Node.js + Express 5 + pg (sin ORM).
|
||||
- Frontend: React 19 + Vite + React Router DOM 7.
|
||||
- Integraciones: Stripe, Banxico, API de Facturas (México), Nodemailer.
|
||||
|
||||
---
|
||||
|
||||
## [Pre-1.0] — Historial de Desarrollo
|
||||
|
||||
### Funcionalidades Implementadas
|
||||
|
||||
#### Autenticación
|
||||
- Login con validación mediante función SQL `validarusuario()`.
|
||||
- Recuperación de contraseña con generación aleatoria y envío por correo.
|
||||
- Creación de usuarios con roles numéricos.
|
||||
|
||||
#### Nómina (Payroll)
|
||||
- CRUD de empleados con RFC, NSS, CURP, contactos de emergencia.
|
||||
- Gestión de contratos laborales con fechas de vencimiento.
|
||||
- Catálogos: puestos, áreas, grados de estudio, parentescos.
|
||||
- Reporte de asistencia.
|
||||
|
||||
#### Gastos (Expenses)
|
||||
- Creación de gastos con productos asociados (JSONB).
|
||||
- Flujo de aprobación: Pendiente → Aprobado → Rechazado.
|
||||
- Flujo de pago con estados separados de aprobación.
|
||||
- Gastos mensuales recurrentes.
|
||||
- Reportes de gastos y pagos.
|
||||
|
||||
#### Inventario
|
||||
- Catálogo de productos con categorías, tipos, proveedores.
|
||||
- Entradas de compra (`purchaseentry`) con recepción física.
|
||||
- Ajustes de stock masivos via JSONB.
|
||||
- Consumos de inventario por empleado.
|
||||
- Descarte de productos con motivo.
|
||||
- Reporte de movimientos de inventario.
|
||||
|
||||
#### Ingresos
|
||||
- Carga masiva desde Little Hotelier (CSV/XLSX).
|
||||
- Sincronización de facturas electrónicas via API externa.
|
||||
- Integración con Stripe para transfers.
|
||||
- Ingresos manuales (Horux) con categorías.
|
||||
- Reportes P&L para Hotel y Restaurante.
|
||||
|
||||
#### Configuraciones
|
||||
- Gestión de habitaciones y propiedades.
|
||||
- Catálogos maestros: monedas, unidades, categorías de gasto, recurrencias.
|
||||
|
||||
#### Emails Programáticos
|
||||
- Envío controlado de emails (1x/mes) para:
|
||||
- Contratos por vencer
|
||||
- Pagos atrasados
|
||||
- Gastos próximos a fecha límite
|
||||
- Cumpleaños de empleados
|
||||
- Contratos vencidos
|
||||
|
||||
---
|
||||
|
||||
## Problemas Conocidos (Deuda Técnica)
|
||||
|
||||
### Críticos
|
||||
- Sin autenticación JWT ni middleware de autorización en el backend.
|
||||
- Ruta malformada: `updatesupplier/:id` falta `/` inicial.
|
||||
- `useAuth` no exportado desde `AuthContext.jsx`.
|
||||
- URLs hardcodeadas a `localhost` en varios servicios del frontend.
|
||||
|
||||
### Medios
|
||||
- Sin `ProtectedRoute` activo en `App.jsx`.
|
||||
- Mezcla inconsistente de `fetch` y `axios`.
|
||||
- Código comentado masivo en varios archivos.
|
||||
- `multer` incluido en dependencias del frontend (es un middleware de Node.js).
|
||||
- Sin tests unitarios ni e2e.
|
||||
|
||||
### Leves
|
||||
- Tailwind CSS instalado pero usado esporádicamente junto con Bootstrap y CSS puro.
|
||||
- Sin manejo de estado de servidor (TanStack Query/SWR).
|
||||
- Sin TypeScript.
|
||||
|
||||
---
|
||||
|
||||
> Para ver el historial completo de commits, ejecutar: `git log --oneline --all --graph`
|
||||
62
CONTRIBUTING.md
Normal file
62
CONTRIBUTING.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 🤝 Guía de Contribución
|
||||
|
||||
## Cómo Trabajar en Este Proyecto
|
||||
|
||||
### 1. Flujo de Trabajo con Git
|
||||
|
||||
```bash
|
||||
# Clonar el repositorio
|
||||
git clone https://git.consultoria-as.com/consultoria-as/hotel-hacienda.git
|
||||
|
||||
# Crear una rama para tu cambio
|
||||
git checkout -b feature/nombre-de-la-funcionalidad
|
||||
|
||||
# Hacer commit con mensajes descriptivos
|
||||
git add .
|
||||
git commit -m "feat: descripción clara del cambio"
|
||||
|
||||
# Subir y crear Pull Request
|
||||
git push origin feature/nombre-de-la-funcionalidad
|
||||
```
|
||||
|
||||
### 2. Convención de Commits
|
||||
|
||||
| Prefijo | Uso |
|
||||
|---------|-----|
|
||||
| `feat:` | Nueva funcionalidad |
|
||||
| `fix:` | Corrección de bug |
|
||||
| `docs:` | Cambios en documentación |
|
||||
| `style:` | Cambios de formato (espacios, comas) |
|
||||
| `refactor:` | Refactorización de código |
|
||||
| `test:` | Agregar o corregir tests |
|
||||
| `chore:` | Tareas de mantenimiento |
|
||||
|
||||
### 3. Antes de Modificar Cualquier Cosa
|
||||
|
||||
1. **Lee `DOCUMENTACION_TECNICA.md`** — entiende la arquitectura.
|
||||
2. **Si modificas lógica de negocio:** revisa la función SQL correspondiente en PostgreSQL.
|
||||
3. **Si agregas un endpoint:** sigue el patrón `route → controller → función SQL`.
|
||||
4. **Si agregas una página:** actualiza `menuconfig.js`, `App.jsx` y `Layout2.jsx`.
|
||||
|
||||
### 4. Reglas de Oro
|
||||
|
||||
- No hardcodees `localhost` en el frontend. Usa `import.meta.env.VITE_API_BASE_URL`.
|
||||
- Usa queries parametrizadas (`$1`, `$2`) en PostgreSQL para evitar SQL Injection.
|
||||
- Si cambias una función SQL, busca TODOS los controllers que la usen.
|
||||
- Mantén la consistencia en la respuesta JSON: `{ message, data, status }`.
|
||||
- Agrega traducciones EN/ES para todo texto visible.
|
||||
|
||||
### 5. Testing
|
||||
|
||||
Actualmente no hay tests. Si agregas funcionalidad crítica, considera:
|
||||
- Probar la función SQL directamente en PostgreSQL.
|
||||
- Probar el endpoint con `curl` o Postman.
|
||||
- Probar el flujo completo en el navegador.
|
||||
|
||||
### 6. Documentación
|
||||
|
||||
Si tu cambio afecta:
|
||||
- **Endpoints:** actualiza `docs/API.md`
|
||||
- **Base de datos:** actualiza `docs/DATABASE.md`
|
||||
- **Despliegue:** actualiza `docs/DEPLOYMENT.md`
|
||||
- **Arquitectura:** actualiza `DOCUMENTACION_TECNICA.md`
|
||||
527
README.md
527
README.md
@@ -1,362 +1,301 @@
|
||||
# Hacienda San Angel - Sistema de Gestion Hotelera
|
||||
# 🏨 Hacienda San Angel — Sistema de Gestión Hotelera
|
||||
|
||||
Sistema integral de gestion para el hotel Hacienda San Angel. Incluye modulos de administracion de empleados, nomina, inventario, gastos, ingresos, y reporteria.
|
||||
> **Versión:** 1.0.0
|
||||
> **Entorno:** Node.js + PostgreSQL + React 19
|
||||
> **Repositorio:** `https://git.consultoria-as.com/consultoria-as/hotel-hacienda`
|
||||
|
||||
## Documentacion
|
||||
---
|
||||
|
||||
- [Arquitectura y Diagramas de Base de Datos](docs/ARQUITECTURA.md) - Diagramas ERD, arquitectura del sistema y flujo de datos
|
||||
## 📋 Índice
|
||||
|
||||
## Tecnologias
|
||||
1. [Descripción del Proyecto](#descripción-del-proyecto)
|
||||
2. [Arquitectura General](#arquitectura-general)
|
||||
3. [Estructura del Repositorio](#estructura-del-repositorio)
|
||||
4. [Tecnologías](#tecnologías)
|
||||
5. [Instalación y Configuración](#instalación-y-configuración)
|
||||
6. [Scripts Disponibles](#scripts-disponibles)
|
||||
7. [Documentación Adicional](#documentación-adicional)
|
||||
8. [Integraciones Externas](#integraciones-externas)
|
||||
9. [Seguridad y Autenticación](#seguridad-y-autenticación)
|
||||
10. [Créditos](#créditos)
|
||||
|
||||
---
|
||||
|
||||
## Descripción del Proyecto
|
||||
|
||||
Sistema administrativo integral para la gestión financiera, operativa y de recursos humanos del hotel **Hacienda San Angel**. El sistema gestiona:
|
||||
|
||||
- **Ingresos:** Little Hotelier, Stripe, Facturas electrónicas (Horux), análisis P&L
|
||||
- **Gastos:** Aprobaciones, pagos, reportes, pagos mensuales recurrentes
|
||||
- **Nómina:** Empleados, contratos, asistencia, puestos, áreas
|
||||
- **Inventario:** Productos, proveedores, ajustes de stock, consumos, descartes
|
||||
- **Configuraciones:** Habitaciones, propiedades, catálogos maestros
|
||||
- **Reportes:** Dashboards, análisis de habitaciones, análisis de restaurante
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura General
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CLIENTE (Browser) │
|
||||
│ React 19 + Vite + React Router │
|
||||
│ Puerto: 5172 (dev) │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│ HTTP / JSON
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ SERVIDOR API (Node.js) │
|
||||
│ Express 5 + CORS + Nodemailer │
|
||||
│ Puerto: 3000-4000 │
|
||||
│ │
|
||||
│ Endpoints: /api/auth, /api/employees, /api/expenses, │
|
||||
│ /api/products, /api/incomes, /api/incomeshrx, etc. │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│ SQL (funciones PostgreSQL)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ BASE DE DATOS (PostgreSQL) │
|
||||
│ │
|
||||
│ Lógica de negocio implementada en funciones SQL: │
|
||||
│ newexpensev2(), purchaseentry(), setpaymentstatusv2(), │
|
||||
│ getincomehorux(), addstripedatav2(), etc. │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Patrón arquitectónico:** MVC simplificado donde los **Controllers** solo orquestan llamadas a funciones PostgreSQL. La lógica de negocio reside principalmente en la base de datos.
|
||||
|
||||
---
|
||||
|
||||
## Estructura del Repositorio
|
||||
|
||||
```
|
||||
hotel-hacienda/
|
||||
├── backend/
|
||||
│ └── hotel_hacienda/ # API REST Node.js
|
||||
│ ├── src/
|
||||
│ │ ├── server.js # Entry point
|
||||
│ │ ├── app.js # Config Express + rutas
|
||||
│ │ ├── db/connection.js # Pool PostgreSQL
|
||||
│ │ ├── controllers/ # 18 controllers
|
||||
│ │ ├── routes/ # 16 archivos de rutas
|
||||
│ │ ├── middlewares/ # Validaciones
|
||||
│ │ └── services/ # Nodemailer
|
||||
│ ├── .env # Variables de entorno (NO subir)
|
||||
│ └── package.json
|
||||
│
|
||||
├── frontend/
|
||||
│ └── Frontend-Hotel/ # SPA React
|
||||
│ ├── src/
|
||||
│ │ ├── main.jsx # Entry point React
|
||||
│ │ ├── App.jsx # Enrutamiento
|
||||
│ │ ├── components/ # Layout, Tablas, Modales, etc.
|
||||
│ │ ├── pages/ # ~50+ páginas por dominio
|
||||
│ │ ├── context/ # AuthContext, LangContext
|
||||
│ │ ├── services/ # API clients
|
||||
│ │ ├── constants/ # Configuración de menú
|
||||
│ │ └── styles/ # CSS por componente
|
||||
│ ├── public/ # Assets estáticos
|
||||
│ ├── .env # VITE_API_BASE_URL
|
||||
│ └── package.json
|
||||
│
|
||||
├── backupcondatos22122025.sql # Backup de base de datos
|
||||
├── DOCUMENTACION_TECNICA.md # Documentación técnica completa
|
||||
├── README.md # Este archivo
|
||||
└── docs/ # Documentación extendida
|
||||
├── BACKEND.md
|
||||
├── FRONTEND.md
|
||||
├── DATABASE.md
|
||||
├── API.md
|
||||
├── DEPLOYMENT.md
|
||||
└── TROUBLESHOOTING.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tecnologías
|
||||
|
||||
### Backend
|
||||
- **Node.js** con Express 5
|
||||
- **PostgreSQL 15** como base de datos
|
||||
- **Nodemailer** para envio de correos (Brevo/Sendinblue)
|
||||
- **Stripe** para procesamiento de pagos
|
||||
- **Axios** para consultas externas (tipo de cambio Banxico)
|
||||
| Tecnología | Versión | Propósito |
|
||||
|-----------|---------|-----------|
|
||||
| Node.js | — | Runtime |
|
||||
| Express | ^5.1.0 | Framework web |
|
||||
| PostgreSQL | — | Base de datos |
|
||||
| pg (node-postgres) | ^8.16.3 | Driver PostgreSQL |
|
||||
| Axios | ^1.13.2 | HTTP client para APIs externas |
|
||||
| Nodemailer | ^7.0.12 | Envío de correos |
|
||||
| Stripe | ^20.1.0 | Integración de pagos |
|
||||
| xlsx / csv-parser | ^0.18.5 / ^3.2.0 | Procesamiento de archivos |
|
||||
| express-validator | ^7.2.1 | Validación de inputs |
|
||||
| dotenv | ^17.2.2 | Variables de entorno |
|
||||
|
||||
### Frontend
|
||||
- **React 19** con Vite 7
|
||||
- **React Router DOM 7** para navegacion
|
||||
- **React Hook Form** + **Yup** para validacion de formularios
|
||||
- **Bootstrap 5** + **Tailwind CSS** para estilos
|
||||
- **Axios** para peticiones HTTP
|
||||
- **XLSX** para exportacion de datos a Excel
|
||||
|
||||
### Infraestructura
|
||||
- **Docker** y **Docker Compose** para contenedorizacion
|
||||
- **PostgreSQL 15** en contenedor
|
||||
| Tecnología | Versión | Propósito |
|
||||
|-----------|---------|-----------|
|
||||
| React | ^19.1.1 | UI Library |
|
||||
| React DOM | ^19.1.1 | Renderizado |
|
||||
| React Router DOM | ^7.8.2 | Enrutamiento SPA |
|
||||
| Vite | ^7.1.2 | Build tool y dev server |
|
||||
| Axios | ^1.11.0 | HTTP client |
|
||||
| Tailwind CSS | ^4.1.13 | Framework CSS (uso parcial) |
|
||||
| Bootstrap | ^5.3.8 | Framework CSS (uso parcial) |
|
||||
| react-hook-form | ^7.66.1 | Manejo de formularios |
|
||||
| Yup | ^1.7.1 | Validación de esquemas |
|
||||
| xlsx | ^0.18.5 | Exportación a Excel |
|
||||
| react-icons | ^5.5.0 | Iconografía |
|
||||
|
||||
---
|
||||
|
||||
## Estructura del Proyecto
|
||||
|
||||
```
|
||||
Hotel/
|
||||
├── backend/
|
||||
│ ├── Dockerfile
|
||||
│ └── hotel_hacienda/
|
||||
│ ├── src/
|
||||
│ │ ├── app.js # Configuracion Express
|
||||
│ │ ├── server.js # Punto de entrada
|
||||
│ │ ├── controllers/ # Logica de negocio
|
||||
│ │ ├── routes/ # Definicion de rutas API
|
||||
│ │ ├── middlewares/ # Validadores
|
||||
│ │ ├── db/ # Conexion a PostgreSQL
|
||||
│ │ └── services/ # Servicios (email)
|
||||
│ ├── scripts bd/ # Scripts SQL para la BD
|
||||
│ └── package.json
|
||||
├── frontend/
|
||||
│ ├── Dockerfile
|
||||
│ └── Frontend-Hotel/
|
||||
│ ├── src/
|
||||
│ │ ├── App.jsx # Rutas principales
|
||||
│ │ ├── main.jsx # Punto de entrada
|
||||
│ │ ├── components/ # Componentes reutilizables
|
||||
│ │ ├── pages/ # Paginas de la aplicacion
|
||||
│ │ ├── services/ # Servicios API
|
||||
│ │ ├── context/ # Context API
|
||||
│ │ └── styles/ # Estilos globales
|
||||
│ └── package.json
|
||||
├── docker-compose.yml
|
||||
├── .env.example
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modulos del Sistema
|
||||
|
||||
### 1. Dashboard
|
||||
- **Income**: Vista general de ingresos
|
||||
- **Hotel P&L**: Estado de perdidas y ganancias del hotel
|
||||
- **Restaurant P&L**: Estado de perdidas y ganancias del restaurante
|
||||
- **Budget**: Presupuesto
|
||||
- **Cost Per Room**: Costo por habitacion
|
||||
- **Room Analysis**: Analisis de ocupacion de habitaciones
|
||||
- **Expenses**: Resumen de gastos
|
||||
|
||||
### 2. Nomina y Empleados (Payroll)
|
||||
- Gestion de empleados (alta, baja, modificacion)
|
||||
- Contratos laborales
|
||||
- Control de asistencia
|
||||
- Uniformes
|
||||
- Pago diario
|
||||
|
||||
### 3. Gastos (Expenses)
|
||||
- Registro de gastos
|
||||
- Pagos mensuales recurrentes
|
||||
- Aprobacion de gastos (flujo de aprobacion)
|
||||
- Proveedores
|
||||
- Reportes de gastos
|
||||
- Entradas de compras
|
||||
|
||||
### 4. Inventario (Inventory)
|
||||
- Catalogo de productos
|
||||
- Ajustes de inventario
|
||||
- Salidas de inventario
|
||||
- Salidas de ama de llaves (Housekeeper)
|
||||
- Reportes de inventario
|
||||
- Descarte de productos
|
||||
|
||||
### 5. Ingresos (Income)
|
||||
- Registro de ingresos
|
||||
- Reportes de ingresos
|
||||
- Integracion con sistema Horux
|
||||
|
||||
### 6. Hotel
|
||||
- Gestion de propiedades
|
||||
- Gestion de habitaciones
|
||||
|
||||
### 7. Configuracion (Settings)
|
||||
- Gestion de habitaciones
|
||||
- Configuracion del sistema
|
||||
- Gestion de usuarios
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Autenticacion
|
||||
- `POST /api/auth/login` - Iniciar sesion
|
||||
- `POST /api/auth/create` - Crear usuario
|
||||
- `POST /api/auth/recover` - Recuperar contrasena
|
||||
|
||||
### Empleados
|
||||
- `GET /api/employees` - Listar empleados
|
||||
- `POST /api/employees/new` - Crear empleado
|
||||
- `PUT /api/employees/update` - Actualizar empleado
|
||||
- `GET /api/employees/attendance` - Obtener asistencia
|
||||
|
||||
### Contratos
|
||||
- `GET /api/contracts` - Listar contratos
|
||||
- `POST /api/contracts` - Crear contrato
|
||||
- `PUT /api/contracts` - Actualizar contrato
|
||||
|
||||
### Productos/Inventario
|
||||
- `GET /api/products` - Listar productos
|
||||
- `POST /api/products` - Crear producto
|
||||
- `PUT /api/products` - Actualizar producto
|
||||
|
||||
### Gastos
|
||||
- `GET /api/expenses` - Listar gastos
|
||||
- `POST /api/expenses` - Crear gasto
|
||||
- `PUT /api/expenses` - Actualizar gasto
|
||||
- `GET /api/expenses/pending` - Gastos pendientes de aprobacion
|
||||
|
||||
### Ingresos
|
||||
- `GET /api/incomes` - Listar ingresos
|
||||
- `POST /api/incomes` - Registrar ingreso
|
||||
- `GET /api/incomeshrx` - Ingresos Horux
|
||||
|
||||
### Pagos
|
||||
- `POST /api/payment` - Procesar pago con Stripe
|
||||
|
||||
### Tipo de Cambio
|
||||
- `GET /api/exchange` - Obtener tipo de cambio (Banxico)
|
||||
|
||||
### Configuracion
|
||||
- `GET /api/settings` - Obtener configuracion
|
||||
- `PUT /api/settings` - Actualizar configuracion
|
||||
|
||||
---
|
||||
|
||||
## Guia de Instalacion
|
||||
## Instalación y Configuración
|
||||
|
||||
### Requisitos Previos
|
||||
- Docker y Docker Compose instalados
|
||||
- Git
|
||||
- Node.js 18+
|
||||
- PostgreSQL 13+
|
||||
- npm o pnpm
|
||||
|
||||
### Opcion 1: Instalacion con Docker (Recomendado)
|
||||
### 1. Clonar el Repositorio
|
||||
|
||||
1. **Clonar el repositorio**
|
||||
```bash
|
||||
git clone https://git.consultoria-as.com/usuario/Hacienda-San-Angel.git
|
||||
cd Hacienda-San-Angel
|
||||
git clone https://git.consultoria-as.com/consultoria-as/hotel-hacienda.git
|
||||
cd hotel-hacienda
|
||||
```
|
||||
|
||||
2. **Configurar variables de entorno**
|
||||
### 2. Configurar Base de Datos
|
||||
|
||||
Restaurar el backup de PostgreSQL:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
psql -U postgres -c "CREATE DATABASE hotel_hacienda;"
|
||||
psql -U postgres -d hotel_hacienda -f backupcondatos22122025.sql
|
||||
```
|
||||
|
||||
Editar el archivo `.env` con los valores correspondientes:
|
||||
```env
|
||||
POSTGRES_PASSWORD=tu_password_seguro
|
||||
EMAIL_USER=tu_email@ejemplo.com
|
||||
EMAIL_PASS=tu_api_key_brevo
|
||||
BANXICO_TOKEN=tu_token_banxico
|
||||
```
|
||||
> **Nota:** El backup contiene las funciones SQL, tablas, catálogos y datos maestros del sistema.
|
||||
|
||||
3. **Construir e iniciar los contenedores**
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
### 3. Configurar Backend
|
||||
|
||||
4. **Verificar que los servicios esten corriendo**
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
5. **Importar la base de datos**
|
||||
```bash
|
||||
# Copiar el archivo SQL al contenedor
|
||||
docker cp backupcondatos22122025.sql postgres_db:/tmp/
|
||||
|
||||
# Ejecutar el script SQL
|
||||
docker exec -it postgres_db psql -U oposgres -d haciendasanangel -f /tmp/backupcondatos22122025.sql
|
||||
```
|
||||
|
||||
6. **Acceder a la aplicacion**
|
||||
- Frontend: http://localhost:5172
|
||||
- Backend API: http://localhost:4000/api
|
||||
|
||||
### Opcion 2: Instalacion Manual (Desarrollo)
|
||||
|
||||
#### Backend
|
||||
|
||||
1. **Navegar al directorio del backend**
|
||||
```bash
|
||||
cd backend/hotel_hacienda
|
||||
```
|
||||
|
||||
2. **Instalar dependencias**
|
||||
```bash
|
||||
cp .env.example .env # Si no existe, crear manualmente
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Configurar variables de entorno**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Editar .env con los valores correspondientes
|
||||
Archivo `.env` del backend:
|
||||
```env
|
||||
PORT=3000
|
||||
URL_CORS=https://hotel.consultoria-as.com
|
||||
|
||||
# PostgreSQL
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=TU_PASSWORD
|
||||
DB_NAME=hotel_hacienda
|
||||
|
||||
# Email
|
||||
EMAIL_HOST=smtp.tudominio.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=soporte@horuxfin.com
|
||||
EMAIL_PASS=TU_PASSWORD
|
||||
|
||||
# Stripe
|
||||
STRIPE_SECRET_KEY=sk_test_...
|
||||
|
||||
# API Facturas (México)
|
||||
FACTURAS_API_URL=https://api.facturas.com/...
|
||||
FACTURAS_ISSUER_RFC=RFC_EMISOR
|
||||
FACTURAS_TYPE=I
|
||||
FACTURAS_API_TOKEN=token_aqui
|
||||
|
||||
# Banxico
|
||||
BANXICO_TOKEN=token_banxico
|
||||
```
|
||||
|
||||
4. **Iniciar el servidor**
|
||||
### 4. Iniciar Backend
|
||||
|
||||
```bash
|
||||
npm run dev # Desarrollo con nodemon
|
||||
npm start # Produccion
|
||||
# o
|
||||
npm start # Producción
|
||||
```
|
||||
|
||||
#### Frontend
|
||||
### 5. Configurar Frontend
|
||||
|
||||
1. **Navegar al directorio del frontend**
|
||||
```bash
|
||||
cd frontend/Frontend-Hotel
|
||||
```
|
||||
|
||||
2. **Instalar dependencias**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Configurar variables de entorno**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Editar .env con la URL del API
|
||||
Archivo `.env` del frontend:
|
||||
```env
|
||||
VITE_API_BASE_URL=http://localhost:3000/api
|
||||
```
|
||||
|
||||
4. **Iniciar la aplicacion**
|
||||
### 6. Iniciar Frontend
|
||||
|
||||
```bash
|
||||
npm run dev # Desarrollo
|
||||
npm run build # Construir para produccion
|
||||
npm run dev # Puerto 5172
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Base de Datos
|
||||
## Scripts Disponibles
|
||||
|
||||
### Configuracion
|
||||
- **Motor**: PostgreSQL 15
|
||||
- **Base de datos**: haciendasanangel
|
||||
- **Usuario**: oposgres
|
||||
### Backend (`backend/hotel_hacienda/`)
|
||||
| Script | Comando | Descripción |
|
||||
|--------|---------|-------------|
|
||||
| `start` | `node src/server.js` | Producción |
|
||||
| `dev` | `nodemon src/server.js` | Desarrollo con hot reload |
|
||||
|
||||
### Scripts SQL
|
||||
Los scripts de la base de datos se encuentran en `backend/hotel_hacienda/scripts bd/funcionesparaproduccion/`. Incluyen:
|
||||
|
||||
- Funciones para empleados y contratos
|
||||
- Funciones para gastos e ingresos
|
||||
- Funciones para inventario
|
||||
- Funciones para reporteria
|
||||
- Funciones de autenticacion
|
||||
### Frontend (`frontend/Frontend-Hotel/`)
|
||||
| Script | Comando | Descripción |
|
||||
|--------|---------|-------------|
|
||||
| `dev` | `vite` | Servidor de desarrollo |
|
||||
| `build` | `vite build` | Build de producción |
|
||||
| `preview` | `vite preview` | Previsualizar build |
|
||||
| `lint` | `eslint .` | Linting del código |
|
||||
|
||||
---
|
||||
|
||||
## Variables de Entorno
|
||||
## Documentación Adicional
|
||||
|
||||
### Backend (.env)
|
||||
| Variable | Descripcion |
|
||||
|----------|-------------|
|
||||
| PORT | Puerto del servidor (default: 4000) |
|
||||
| DB_HOST | Host de PostgreSQL |
|
||||
| DB_PORT | Puerto de PostgreSQL (default: 5432) |
|
||||
| DB_USER | Usuario de PostgreSQL |
|
||||
| DB_PASSWORD | Contrasena de PostgreSQL |
|
||||
| DB_NAME | Nombre de la base de datos |
|
||||
| EMAIL_HOST | Servidor SMTP |
|
||||
| EMAIL_PORT | Puerto SMTP |
|
||||
| EMAIL_USER | Usuario SMTP |
|
||||
| EMAIL_PASS | Contrasena/API Key SMTP |
|
||||
| URL_CORS | URL permitida para CORS |
|
||||
| BANXICO_TOKEN | Token API de Banxico |
|
||||
| STRIPE_SECRET_KEY | Llave secreta de Stripe |
|
||||
|
||||
### Frontend (.env)
|
||||
| Variable | Descripcion |
|
||||
|----------|-------------|
|
||||
| VITE_API_BASE_URL | URL base del API backend |
|
||||
- [`DOCUMENTACION_TECNICA.md`](./DOCUMENTACION_TECNICA.md) — Análisis técnico completo, endpoints, roles, integraciones y reglas de oro para no romper integridad.
|
||||
- [`docs/BACKEND.md`](./docs/BACKEND.md) — Guía detallada del backend: estructura de carpetas, controllers, servicios y configuración.
|
||||
- [`docs/FRONTEND.md`](./docs/FRONTEND.md) — Guía del frontend: componentes, rutas, manejo de estado, permisos y estilos.
|
||||
- [`docs/DATABASE.md`](./docs/DATABASE.md) — Documentación de la base de datos: funciones SQL conocidas, tablas principales y relaciones.
|
||||
- [`docs/API.md`](./docs/API.md) — Referencia completa de todos los endpoints REST.
|
||||
- [`docs/DEPLOYMENT.md`](./docs/DEPLOYMENT.md) — Guía de despliegue en producción.
|
||||
- [`docs/TROUBLESHOOTING.md`](./docs/TROUBLESHOOTING.md) — Problemas comunes y soluciones.
|
||||
|
||||
---
|
||||
|
||||
## Despliegue en Produccion
|
||||
## Integraciones Externas
|
||||
|
||||
### Con Docker Compose
|
||||
|
||||
1. Asegurarse de tener configurado el archivo `.env` con valores de produccion
|
||||
|
||||
2. Modificar `docker-compose.yml` si es necesario:
|
||||
- Cambiar `URL_CORS` a tu dominio de produccion
|
||||
- Configurar volumenes persistentes para la base de datos
|
||||
|
||||
3. Iniciar los servicios:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Notas de Seguridad
|
||||
- Cambiar las contrasenas por defecto
|
||||
- Usar HTTPS en produccion (configurar con nginx o similar)
|
||||
- Respaldar la base de datos regularmente
|
||||
- No exponer el puerto de PostgreSQL al exterior
|
||||
| Servicio | Uso | Archivo Relacionado |
|
||||
|----------|-----|---------------------|
|
||||
| **Banxico API** | Tipo de cambio USD/MXN | `exchange.controller.js` |
|
||||
| **Stripe API** | Transfers y balance transactions | `incomehrx.controller.js` |
|
||||
| **API Facturas** | Descarga de facturas electrónicas (México) | `incomehrx.controller.js` |
|
||||
| **Nodemailer/SMTP** | Correos transaccionales | `mail.controller.js`, `auth.controller.js` |
|
||||
|
||||
---
|
||||
|
||||
## Mantenimiento
|
||||
## Seguridad y Autenticación
|
||||
|
||||
### Respaldo de Base de Datos
|
||||
```bash
|
||||
docker exec postgres_db pg_dump -U oposgres haciendasanangel > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### Ver logs
|
||||
```bash
|
||||
docker-compose logs -f backend
|
||||
docker-compose logs -f frontend
|
||||
docker-compose logs -f postgres
|
||||
```
|
||||
|
||||
### Reiniciar servicios
|
||||
```bash
|
||||
docker-compose restart
|
||||
```
|
||||
> ⚠️ **Advertencia de seguridad:** Este sistema utiliza autenticación básica basada en función SQL (`validarusuario`) sin JWT ni middleware de autorización en los endpoints del backend. El control de acceso se realiza principalmente en el frontend mediante un número de rol guardado en `localStorage`.
|
||||
>
|
||||
> **Recomendación:** Para producción, considerar implementar:
|
||||
> - JWT o API Keys en el backend
|
||||
> - Middleware de autorización por rol
|
||||
> - HTTPS obligatorio
|
||||
> - Sanitización adicional de inputs
|
||||
|
||||
---
|
||||
|
||||
## Licencia
|
||||
## Créditos
|
||||
|
||||
Proyecto privado - Hacienda San Angel
|
||||
Desarrollado para **Consultoría AS** — Hacienda San Angel.
|
||||
|
||||
---
|
||||
|
||||
## Contacto
|
||||
|
||||
Para soporte tecnico, contactar al equipo de desarrollo.
|
||||
> **Mantenimiento:** Si realizas cambios significativos, actualiza tanto el código como la documentación correspondiente para mantener la integridad del sistema.
|
||||
|
||||
699
docs/API.md
Normal file
699
docs/API.md
Normal file
@@ -0,0 +1,699 @@
|
||||
# 🔌 Referencia Completa de la API REST
|
||||
|
||||
**Base URL:** `https://<dominio>/api`
|
||||
|
||||
---
|
||||
|
||||
## Índice
|
||||
1. [Convenciones](#convenciones)
|
||||
2. [Autenticación](#autenticación)
|
||||
3. [Empleados](#empleados)
|
||||
4. [Contratos](#contratos)
|
||||
5. [Productos](#productos)
|
||||
6. [Gastos](#gastos)
|
||||
7. [Aprobaciones y Pagos](#aprobaciones-y-pagos)
|
||||
8. [Pagos Mensuales](#pagos-mensuales)
|
||||
9. [Configuraciones](#configuraciones)
|
||||
10. [Emails](#emails)
|
||||
11. [Ingresos (Little Hotelier)](#ingresos-little-hotelier)
|
||||
12. [Compras](#compras)
|
||||
13. [Tipo de Cambio](#tipo-de-cambio)
|
||||
14. [Hotel P&L](#hotel-pl)
|
||||
15. [Restaurant P&L](#restaurant-pl)
|
||||
16. [Ingresos Horux](#ingresos-horux)
|
||||
|
||||
---
|
||||
|
||||
## Convenciones
|
||||
|
||||
- **Formato:** JSON en request body y response.
|
||||
- **Paginación:** `?page=N&limit=M` (default limit: 500 para empleados, 10 para productos).
|
||||
- **Errores:** Status `500` con `{ message: "Error descripción" }`.
|
||||
- **CORS:** Origen controlado por `URL_CORS` en el backend.
|
||||
|
||||
---
|
||||
|
||||
## Autenticación
|
||||
|
||||
> ⚠️ **Nota:** No hay JWT. El login devuelve un rol numérico que el frontend guarda en `localStorage`.
|
||||
|
||||
### POST `/api/auth/login`
|
||||
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"name_mail_user": "usuario_o_email",
|
||||
"user_pass": "contraseña"
|
||||
}
|
||||
|
||||
// Response 200
|
||||
{
|
||||
"rol": 1,
|
||||
"user_id": 5,
|
||||
"user_name": "Admin",
|
||||
"message": "Usuario autenticado correctamente"
|
||||
}
|
||||
```
|
||||
|
||||
**Status codes de la función SQL:**
|
||||
- `status: 1` = Autenticado
|
||||
- `status: 2` = Credenciales incorrectas
|
||||
|
||||
### POST `/api/auth/createuser`
|
||||
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"name_user": "Nuevo Usuario",
|
||||
"id_rol": 2,
|
||||
"email": "user@email.com",
|
||||
"user_pass": "password123"
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"message": "Se agrego el usuario correctamente",
|
||||
"status": 1
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/auth/recoverpass`
|
||||
|
||||
Genera una contraseña aleatoria y la envía por correo.
|
||||
|
||||
```json
|
||||
// Request
|
||||
{ "user_mail": "user@email.com" }
|
||||
|
||||
// Response
|
||||
{
|
||||
"message": "Correo enviado con nueva contraseña.",
|
||||
"status": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Empleados
|
||||
|
||||
Base: `/api/employees`
|
||||
|
||||
### GET `/api/employees/`
|
||||
|
||||
Lista paginada de empleados.
|
||||
|
||||
**Query params:** `?page=1&limit=500`
|
||||
|
||||
```json
|
||||
// Response
|
||||
{
|
||||
"page": 1,
|
||||
"limit": 500,
|
||||
"total": 45,
|
||||
"totalPages": 1,
|
||||
"data": [
|
||||
{
|
||||
"employee_rfc": "RFC123456",
|
||||
"name_employee": "Juan Pérez",
|
||||
"nss_employe": "12345678901",
|
||||
"position_employee": "Recepcionista",
|
||||
"area_employee": "Front Desk",
|
||||
"phone_employee": "5551234567",
|
||||
"end_contract": "2025-12-31",
|
||||
"daily_pay": 500.00,
|
||||
"uniforms": 2,
|
||||
"status": "Activo",
|
||||
"birthday": "1990-05-15",
|
||||
"curp": "CURP123456HDF"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/employees/activeEmployees`
|
||||
|
||||
```json
|
||||
{ "message": "Total de empleados activos", "data": 42 }
|
||||
```
|
||||
|
||||
### GET `/api/employees/getattendance`
|
||||
|
||||
```json
|
||||
{ "message": "attendance", "data": [...] }
|
||||
```
|
||||
|
||||
### GET `/api/employees/gradeofstudy`
|
||||
|
||||
Catálogo de grados de estudio.
|
||||
|
||||
### GET `/api/employees/relationship`
|
||||
|
||||
Catálogo de parentescos.
|
||||
|
||||
### POST `/api/employees/employee`
|
||||
|
||||
Obtiene un empleado por RFC.
|
||||
|
||||
```json
|
||||
// Request
|
||||
{ "rfcEmployee": "RFC123456" }
|
||||
```
|
||||
|
||||
### POST `/api/employees/newemployee`
|
||||
|
||||
```json
|
||||
// Request
|
||||
{
|
||||
"name_emp": "Juan Pérez",
|
||||
"rfc_emp": "RFC123456",
|
||||
"nss_emp": "12345678901",
|
||||
"addres_emp": "Calle 123",
|
||||
"phone_emp": "5551234567",
|
||||
"email_emp": "juan@email.com",
|
||||
"birthday_emp": "1990-05-15",
|
||||
"curp_emp": "CURP123456HDF",
|
||||
"study_emp": 1,
|
||||
"emergency_name": "María Pérez",
|
||||
"emergency_tel": "5559876543",
|
||||
"relationship_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/employees/updateemployee`
|
||||
|
||||
Misma firma que `newemployee`. Actualiza por RFC.
|
||||
|
||||
---
|
||||
|
||||
## Contratos
|
||||
|
||||
Base: `/api/contracts`
|
||||
|
||||
### GET `/api/contracts/`
|
||||
|
||||
### GET `/api/contracts/getinfocontract/:id`
|
||||
|
||||
### GET `/api/contracts/neartoend`
|
||||
|
||||
### GET `/api/contracts/positions`
|
||||
|
||||
### GET `/api/contracts/areas`
|
||||
|
||||
### GET `/api/contracts/bosses`
|
||||
|
||||
### GET `/api/contracts/reportempcontract`
|
||||
|
||||
### GET `/api/contracts/disabledcontract`
|
||||
|
||||
### POST `/api/contracts/newcontract`
|
||||
|
||||
### PUT `/api/contracts/updatecontract/:id`
|
||||
|
||||
---
|
||||
|
||||
## Productos
|
||||
|
||||
Base: `/api/products`
|
||||
|
||||
### GET `/api/products/`
|
||||
|
||||
Paginado (default limit: 10).
|
||||
|
||||
### GET `/api/products/productcategory`
|
||||
|
||||
### GET `/api/products/producttype`
|
||||
|
||||
### GET `/api/products/suppliers`
|
||||
|
||||
### GET `/api/products/gdiscardproducts`
|
||||
|
||||
### GET `/api/products/reportinventory`
|
||||
|
||||
### GET `/api/products/stockadjusment`
|
||||
|
||||
### GET `/api/products/gethousekeeper`
|
||||
|
||||
### GET `/api/products/getproducts`
|
||||
|
||||
### GET `/api/products/getconsumptionstockreport`
|
||||
|
||||
### POST `/api/products/newsupplier`
|
||||
|
||||
```json
|
||||
{
|
||||
"new_name_supp": "Proveedor SA",
|
||||
"new_rfc_supp": "RFC123456",
|
||||
"new_mail_supp": "prov@email.com",
|
||||
"new_phone_supp": "5551234567"
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/products/newproduct`
|
||||
|
||||
```json
|
||||
{
|
||||
"new_name_product": "Toallas",
|
||||
"new_sku_product": "TOW-001",
|
||||
"product_type": [{"id": 1, "name": "Textil"}],
|
||||
"new_category": 1,
|
||||
"suppliers_id": 1,
|
||||
"unit": 1,
|
||||
"new_stock": 100,
|
||||
"newprice_product": 150.00,
|
||||
"new_tax": 1,
|
||||
"currency": 1,
|
||||
"image_product": "url_o_base64"
|
||||
}
|
||||
```
|
||||
|
||||
> Nota: `product_type` se serializa a JSONB.
|
||||
|
||||
### POST `/api/products/stockadjusmentset`
|
||||
|
||||
```json
|
||||
{
|
||||
"stockproductadjusment": [
|
||||
{ "id_product": 1, "new_stock": 50 },
|
||||
{ "id_product": 2, "new_stock": 30 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/products/newconsumptionstock`
|
||||
|
||||
```json
|
||||
{
|
||||
"product_id": 1,
|
||||
"quantity_consumption": 5,
|
||||
"date_consumption": "2025-01-15",
|
||||
"rfc_emp": "RFC123456"
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/products/disableSupplier`
|
||||
|
||||
```json
|
||||
{ "supplier_id": 1 }
|
||||
```
|
||||
|
||||
### PUT `/api/products/update_product/:id`
|
||||
|
||||
### PUT `/api/products/discardproduct/:id`
|
||||
|
||||
```json
|
||||
{
|
||||
"quantity": 5,
|
||||
"reasons": "Dañado"
|
||||
}
|
||||
```
|
||||
|
||||
### PUT `/api/products/product/:id`
|
||||
|
||||
### PUT `/api/products/updatesupplier/:id`
|
||||
|
||||
⚠️ **Bug conocido:** Falta `/` inicial en la ruta del backend.
|
||||
|
||||
---
|
||||
|
||||
## Gastos
|
||||
|
||||
Base: `/api/expenses`
|
||||
|
||||
### GET `/api/expenses/pendingapproval`
|
||||
|
||||
### GET `/api/expenses/approvedexpenses`
|
||||
|
||||
### GET `/api/expenses/rejectedexpenses`
|
||||
|
||||
### GET `/api/expenses/mainsupplier`
|
||||
|
||||
### PUT `/api/expenses/getexpense/:id`
|
||||
|
||||
⚠️ **Nota:** Es `PUT` pero debería ser `GET`. No requiere body.
|
||||
|
||||
### GET `/api/expenses/getinfo`
|
||||
|
||||
Devuelve catálogos combinados (suppliers, categories, areas, users, currencies, uniforms, taxes).
|
||||
|
||||
### GET `/api/expenses/reportexpenses`
|
||||
|
||||
### POST `/api/expenses/countpending`
|
||||
|
||||
```json
|
||||
{ "option": 1 } // 1 = aprobaciones, 2 = mensuales
|
||||
```
|
||||
|
||||
### GET `/api/expenses/reportpayments`
|
||||
|
||||
### GET `/api/expenses/monthlypayments`
|
||||
|
||||
### POST `/api/expenses/newexpense`
|
||||
|
||||
```json
|
||||
{
|
||||
"new_description": "Compra de amenities",
|
||||
"suppliers_id": 1,
|
||||
"new_request_date": "2025-01-01",
|
||||
"new_payment_deadline": "2025-01-31",
|
||||
"request_by": 1,
|
||||
"area": 1,
|
||||
"expense_cat": 1,
|
||||
"currency_id": 1,
|
||||
"products": [
|
||||
{ "id_product": 1, "quantity": 10 },
|
||||
{ "id_product": 2, "quantity": 5 }
|
||||
],
|
||||
"new_iva": 16.00,
|
||||
"new_ieps": 0.00,
|
||||
"new_subtotal": 1000.00,
|
||||
"new_total": 1160.00,
|
||||
"needtoapprove": true
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/expenses/totalapproved`
|
||||
|
||||
```json
|
||||
{ "option": 1 } // 1 = aprobado, 2 = rechazado
|
||||
```
|
||||
|
||||
### PUT `/api/expenses/updateexpense/:id`
|
||||
|
||||
Misma estructura que `newexpense`, con campos prefijados con `up_`.
|
||||
|
||||
### GET `/api/expenses/gettaxes`
|
||||
|
||||
---
|
||||
|
||||
## Aprobaciones y Pagos
|
||||
|
||||
Base: `/api/status`
|
||||
|
||||
### PUT `/api/status/approveupdate/:id`
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 2,
|
||||
"approved_by": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Status:**
|
||||
- `2` = Aprobado
|
||||
- `3` = Rechazado
|
||||
|
||||
### PUT `/api/status/paymentupdate/:id`
|
||||
|
||||
```json
|
||||
{ "status": 2 }
|
||||
```
|
||||
|
||||
### GET `/api/status/penapppayments`
|
||||
|
||||
### GET `/api/status/countdelaypay`
|
||||
|
||||
### GET `/api/status/totalspent`
|
||||
|
||||
---
|
||||
|
||||
## Pagos Mensuales
|
||||
|
||||
Base: `/api/payment`
|
||||
|
||||
### POST `/api/payment/newexpmonthly`
|
||||
|
||||
```json
|
||||
{
|
||||
"descriptionex": "Renta",
|
||||
"recurrence_id": 1,
|
||||
"payment_type": 1,
|
||||
"currency_id": 1,
|
||||
"suppliers_id": 1,
|
||||
"area": 1,
|
||||
"expense_category": 1,
|
||||
"day_expense": 15,
|
||||
"tax_id": 1,
|
||||
"new_subtotal": 5000.00
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/payment/refreshmonthly`
|
||||
|
||||
### PUT `/api/payment/paymentstatusmonthly/:id`
|
||||
|
||||
```json
|
||||
{
|
||||
"status_payment": 2,
|
||||
"tax_id": 1,
|
||||
"subtotal": 5000.00
|
||||
}
|
||||
```
|
||||
|
||||
Si `subtotal` es null, solo actualiza el estado sin recalcular total.
|
||||
|
||||
### PUT `/api/payment/updateexpmonthly/:id`
|
||||
|
||||
### GET `/api/payment/onemothlyexpense/:id`
|
||||
|
||||
### PUT `/api/payment/needtorefresh/:id`
|
||||
|
||||
```json
|
||||
{ "notrefresh": true }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuraciones
|
||||
|
||||
Base: `/api/settings`
|
||||
|
||||
### POST `/api/settings/newroom`
|
||||
|
||||
### POST `/api/settings/newproperty`
|
||||
|
||||
### GET `/api/settings/reportrooms`
|
||||
|
||||
### GET `/api/settings/reportproperties`
|
||||
|
||||
### GET `/api/settings/approveby`
|
||||
|
||||
### GET `/api/settings/requestby`
|
||||
|
||||
### GET `/api/settings/categoryexpense`
|
||||
|
||||
### GET `/api/settings/currency`
|
||||
|
||||
### GET `/api/settings/units`
|
||||
|
||||
### GET `/api/settings/recurrence`
|
||||
|
||||
---
|
||||
|
||||
## Emails
|
||||
|
||||
Base: `/api/emails`
|
||||
|
||||
> ⚠️ **Nota:** Todos estos endpoints tienen validación de frecuencia (1 ejecución por mes) mediante la función `validateendpoint()`.
|
||||
|
||||
### POST `/api/emails/nearexpiring`
|
||||
|
||||
### POST `/api/emails/paymentdelay`
|
||||
|
||||
### POST `/api/emails/expensesneartodeadline`
|
||||
|
||||
### POST `/api/emails/expensesspecial`
|
||||
|
||||
### POST `/api/emails/birthdays`
|
||||
|
||||
### POST `/api/emails/contractexpired`
|
||||
|
||||
### POST `/api/emails/expiredcontractsmonth`
|
||||
|
||||
---
|
||||
|
||||
## Ingresos (Little Hotelier)
|
||||
|
||||
Base: `/api/incomes`
|
||||
|
||||
> **Nota:** La mayoría de estos endpoints reciben filtros de fecha en el body.
|
||||
|
||||
### POST `/api/incomes/getincomes`
|
||||
|
||||
### POST `/api/incomes/totalincomes`
|
||||
|
||||
### POST `/api/incomes/channelscards`
|
||||
|
||||
### POST `/api/incomes/loadincomes`
|
||||
|
||||
Carga masiva desde CSV/XLSX previamente colocado en `src/resources/littleHotelier/`.
|
||||
|
||||
### POST `/api/incomes/loadproductsales`
|
||||
|
||||
### POST `/api/incomes/loadchequesdetalle`
|
||||
|
||||
### POST `/api/incomes/reportincomes`
|
||||
|
||||
### POST `/api/incomes/countticket`
|
||||
|
||||
### POST `/api/incomes/efectivo`
|
||||
|
||||
### POST `/api/incomes/otros`
|
||||
|
||||
### POST `/api/incomes/propinas`
|
||||
|
||||
### POST `/api/incomes/tarjeta`
|
||||
|
||||
### POST `/api/incomes/vales`
|
||||
|
||||
### POST `/api/incomes/sumatotal`
|
||||
|
||||
### POST `/api/incomes/ticketpromedio`
|
||||
|
||||
### GET `/api/incomes/getproductsales`
|
||||
|
||||
### GET `/api/incomes/getdetallecheque`
|
||||
|
||||
---
|
||||
|
||||
## Compras
|
||||
|
||||
Base: `/api/purchases`
|
||||
|
||||
### GET `/api/purchases/getpurchases`
|
||||
|
||||
Devuelve líneas de compra pendientes de recepción.
|
||||
|
||||
```json
|
||||
// Response
|
||||
{
|
||||
"message": "Se obtuvieron todos los Purchases details",
|
||||
"data": [
|
||||
{
|
||||
"id_purchase_dt": 1,
|
||||
"id_expense": 5,
|
||||
"id_product": 2,
|
||||
"product_name": "Toallas",
|
||||
"quantity": 10,
|
||||
"delivered": 0,
|
||||
"id_tax": 1,
|
||||
"total": 1500.00
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### PUT `/api/purchases/entry/:id`
|
||||
|
||||
Registra la recepción física de una compra.
|
||||
|
||||
```json
|
||||
// Request
|
||||
{ "checking": 10 }
|
||||
|
||||
// Response
|
||||
{
|
||||
"message": "Se obtuvo el Purchases details",
|
||||
"newentry": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tipo de Cambio
|
||||
|
||||
Base: `/api/exchange`
|
||||
|
||||
### POST `/api/exchange/consultexchange`
|
||||
|
||||
Consulta tipo de cambio actual desde API de Banxico.
|
||||
|
||||
### GET `/api/exchange/getexchanges`
|
||||
|
||||
Devuelve historial de tipos de cambio guardados.
|
||||
|
||||
---
|
||||
|
||||
## Hotel P&L
|
||||
|
||||
Base: `/api/hotelpl`
|
||||
|
||||
### POST `/api/hotelpl/cogs`
|
||||
|
||||
### POST `/api/hotelpl/ebitda`
|
||||
|
||||
### POST `/api/hotelpl/employeeshare`
|
||||
|
||||
### POST `/api/hotelpl/grossprofit`
|
||||
|
||||
### POST `/api/hotelpl/tips`
|
||||
|
||||
### POST `/api/hotelpl/totalrevenue`
|
||||
|
||||
### POST `/api/hotelpl/weightedCategoriesCost`
|
||||
|
||||
---
|
||||
|
||||
## Restaurant P&L
|
||||
|
||||
Base: `/api/restaurantpl`
|
||||
|
||||
### POST `/api/restaurantpl/cogs`
|
||||
|
||||
### POST `/api/restaurantpl/ebitda`
|
||||
|
||||
### POST `/api/restaurantpl/grossprofit`
|
||||
|
||||
### POST `/api/restaurantpl/totalrevenue`
|
||||
|
||||
### POST `/api/restaurantpl/weightedCategoriesCost`
|
||||
|
||||
---
|
||||
|
||||
## Ingresos Horux
|
||||
|
||||
Base: `/api/incomeshrx`
|
||||
|
||||
### GET `/api/incomeshrx/accountincome`
|
||||
|
||||
### GET `/api/incomeshrx/categoryincome`
|
||||
|
||||
### GET `/api/incomeshrx/invoiceIncome`
|
||||
|
||||
### GET `/api/incomeshrx/totalIncome`
|
||||
|
||||
### GET `/api/incomeshrx/incomehorux`
|
||||
|
||||
### GET `/api/incomeshrx/oneincomehorux/:id`
|
||||
|
||||
### GET `/api/incomeshrx/stripedata/`
|
||||
|
||||
Obtiene transfers de Stripe y las inserta en PostgreSQL.
|
||||
|
||||
### POST `/api/incomeshrx/stripedatademo/`
|
||||
|
||||
Crea un transfer de demo en Stripe.
|
||||
|
||||
### POST `/api/incomeshrx/insertinvoice`
|
||||
|
||||
Descarga facturas de API externa (año actual) y las inserta.
|
||||
|
||||
### POST `/api/incomeshrx/newincome`
|
||||
|
||||
```json
|
||||
{
|
||||
"account_id": 1,
|
||||
"amount": 5000.00,
|
||||
"new_date": "2025-01-15",
|
||||
"newinvoice": "FAC-001",
|
||||
"area_id": 1,
|
||||
"categories": [{"id": 1, "amount": 5000}]
|
||||
}
|
||||
```
|
||||
|
||||
### PUT `/api/incomeshrx/updateincome/:id`
|
||||
|
||||
Misma estructura que `newincome`.
|
||||
|
||||
---
|
||||
|
||||
> **Fin de la referencia API.** Para ver la lógica interna de cada endpoint, revisa los controllers en `backend/hotel_hacienda/src/controllers/`.
|
||||
365
docs/BACKEND.md
Normal file
365
docs/BACKEND.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# 📦 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.
|
||||
355
docs/DATABASE.md
Normal file
355
docs/DATABASE.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# 🗄️ Documentación de Base de Datos — PostgreSQL
|
||||
|
||||
## Índice
|
||||
1. [Filosofía de Diseño](#filosofía-de-diseño)
|
||||
2. [Conexión](#conexión)
|
||||
3. [Funciones SQL por Dominio](#funciones-sql-por-dominio)
|
||||
4. [Tablas Principales (Inferidas)](#tablas-principales-inferidas)
|
||||
5. [Patrones de Datos](#patrones-de-datos)
|
||||
6. [JSONB como Patrón de Intercambio](#jsonb-como-patrón-de-intercambio)
|
||||
7. [Integraciones que Escriben en JSONB](#integraciones-que-escriben-en-jsonb)
|
||||
8. [Guía para Modificar Funciones SQL](#guía-para-modificar-funciones-sql)
|
||||
|
||||
---
|
||||
|
||||
## Filosofía de Diseño
|
||||
|
||||
Este sistema sigue un patrón **"Database-First"** donde la lógica de negocio reside principalmente en **funciones SQL almacenadas** de PostgreSQL. Los controllers de Node.js actúan como una capa de presentación HTTP liviana que solo orquesta llamadas a estas funciones.
|
||||
|
||||
**Implicaciones:**
|
||||
- Para modificar lógica de negocio, debes modificar las funciones SQL, no solo el JavaScript.
|
||||
- Los controllers pasan parámetros posicionales (`$1`, `$2`) y reciben resultados directamente.
|
||||
- No hay ORM ni capa de abstracción de base de datos.
|
||||
|
||||
---
|
||||
|
||||
## Conexión
|
||||
|
||||
El backend usa `pg` (node-postgres) con un `Pool`:
|
||||
|
||||
```javascript
|
||||
const { Pool } = require('pg');
|
||||
|
||||
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
|
||||
});
|
||||
```
|
||||
|
||||
**Variables de entorno requeridas:** `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`.
|
||||
|
||||
---
|
||||
|
||||
## Funciones SQL por Dominio
|
||||
|
||||
### 🔐 Autenticación
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `validarusuario(name_mail_user, user_pass)` | `text`, `text` | `{status, rol, user_id, user_name}` | Valida credenciales |
|
||||
| `createuser(name_user, id_rol, email, user_pass)` | `text`, `int`, `text`, `text` | `status` | Crea usuario |
|
||||
| `reppassuser(user_mail, new_pass)` | `text`, `text` | `status` | Reemplaza contraseña |
|
||||
|
||||
### 👥 Empleados
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `getemployees()` | — | setof record | Lista paginada de empleados |
|
||||
| `activeemployeesnumber()` | — | `int` | Total de empleados activos |
|
||||
| `getoneemployee(rfcEmployee)` | `text` | setof record | Un empleado por RFC |
|
||||
| `newemployee(...)` | 12 parámetros | `status` | Inserta empleado |
|
||||
| `updateemployee(...)` | 12 parámetros | `status` | Actualiza empleado |
|
||||
| `getattendance()` | — | setof record | Registros de asistencia |
|
||||
|
||||
### 📄 Contratos
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `getcontracts()` | — | setof record | Lista de contratos |
|
||||
| `getinfocontract(id)` | `int` | setof record | Info de un contrato |
|
||||
| `neartoend()` | — | setof record | Contratos próximos a vencer |
|
||||
| `newcontract(...)` | varios | `status` | Crea contrato |
|
||||
| `updatecontract(...)` | varios | `status` | Actualiza contrato |
|
||||
| `positions()` | — | setof record | Catálogo de puestos |
|
||||
| `areas()` | — | setof record | Catálogo de áreas |
|
||||
| `bosses()` | — | setof record | Catálogo de jefes |
|
||||
|
||||
### 📦 Productos / Inventario
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `getproducts()` | — | setof record | Lista de productos |
|
||||
| `getoneproduct(id)` | `int` | setof record | Un producto |
|
||||
| `newproduct(..., jsonb, ...)` | 11 parámetros | `status` | Crea producto |
|
||||
| `updateproduct(..., jsonb, ...)` | 12 parámetros | `status` | Actualiza producto |
|
||||
| `discardproductsstock(id, quantity, reasons)` | `int`, `numeric`, `text` | `status` | Descarta stock |
|
||||
| `getdiscardproduct()` | — | setof record | Productos descartados |
|
||||
| `report_inventoryv2()` | — | setof record | Reporte de inventario |
|
||||
| `stockadjusment()` | — | setof record | Ajustes pendientes |
|
||||
| `setstockadjusmentsv2(jsonb)` | `jsonb` | `stockadjus` | Aplica ajustes |
|
||||
| `consumptionstock(product_id, quantity, date, rfc)` | varios | `status` | Registra consumo |
|
||||
| `getconsumptionreport()` | — | setof record | Reporte de consumos |
|
||||
| `newsupplier(name, rfc, mail, phone)` | 4 parámetros | `status` | Crea proveedor |
|
||||
| `updatesupplier(id, name, rfc, mail, phone)` | 5 parámetros | `status` | Actualiza proveedor |
|
||||
| `disableSuppliers(id)` | `int` | `status` | Deshabilita proveedor |
|
||||
| `newsuplier(...)` | varios | `status` | (posible duplicado ortográfico) |
|
||||
|
||||
### 💰 Gastos (Expenses)
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `newexpensev2(..., jsonb, ...)` | 14 parámetros | `status` | Crea gasto |
|
||||
| `updateexpensev4(..., jsonb)` | 15 parámetros | `status` | Actualiza gasto |
|
||||
| `getexpense(id)` | `int` | setof record | Obtiene un gasto |
|
||||
| `getpendingappexpenses()` | — | setof record | Gastos pendientes de aprobación |
|
||||
| `getapprovedappexpenses()` | — | setof record | Gastos aprobados |
|
||||
| `getrejectedappexpenses()` | — | setof record | Gastos rechazados |
|
||||
| `setapprovalstatus(id, status, approved_by)` | 3 parámetros | `status` | Actualiza estado de aprobación |
|
||||
| `setpaymentstatusv2(id, status)` | 2 parámetros | `status` | Actualiza estado de pago |
|
||||
| `pendingapppayments()` | — | setof record | Pagos pendientes |
|
||||
| `countdelaypayments()` | — | `int` | Pagos con retraso |
|
||||
| `totalspent()` | — | `numeric` | Total gastado |
|
||||
| `totalapproved()` | — | `numeric` | Total aprobado |
|
||||
| `totalrejected()` | — | `numeric` | Total rechazado |
|
||||
| `report_expenses()` | — | setof record | Reporte de gastos |
|
||||
| `paymentsreport()` | — | setof record | Reporte de pagos |
|
||||
| `mainsupplier()` | — | setof record | Proveedor principal |
|
||||
| `gettaxes()` | — | setof record | Catálogo de impuestos |
|
||||
|
||||
### 🛒 Compras / Purchase Entries
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `getpurchases()` | — | setof record | Detalles de compras pendientes |
|
||||
| `purchaseentry(id, checking)` | `int`, `int` | `status` | Registra recepción física |
|
||||
|
||||
### 💳 Pagos Mensuales
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `newmonthlyexpensev2(...)` | 10 parámetros | `status` | Crea gasto mensual |
|
||||
| `updatemonthlyexpensev2(...)` | 11 parámetros | `status` | Actualiza gasto mensual |
|
||||
| `setpaymentstatusmonthlyv2(id, status)` | 2 parámetros | `status` | Actualiza estado de pago |
|
||||
| `setpaymentstatusmonthlyv2(id, status, tax_id, total)` | 4 parámetros | `status` | Actualiza estado y monto |
|
||||
| `refresh_monthly_expenses()` | — | `status` | Refresca gastos mensuales |
|
||||
| `getmonthlypayments()` | — | setof record | Lista de pagos mensuales |
|
||||
| `getONEmonthlypayment(id)` | `int` | setof record | Un pago mensual |
|
||||
| `needToRefresh(id, boolean)` | 2 parámetros | rows | Controla actualización automática |
|
||||
|
||||
### 📧 Emails Programáticos
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `validateendpoint(endpoint_name)` | `text` | `boolean` | Verifica si se puede ejecutar (1x/mes) |
|
||||
| `nearexpiring()` | — | setof record | Contratos por vencer |
|
||||
| `paymentdelay()` | — | setof record | Pagos atrasados |
|
||||
| `expensesneartodeadline()` | — | setof record | Gastos próximos a fecha límite |
|
||||
| `expensesspecial()` | — | setof record | Gastos especiales |
|
||||
| `birthdays()` | — | setof record | Cumpleaños |
|
||||
| `contractexpired()` | — | setof record | Contratos vencidos |
|
||||
| `expiredcontractsmonth()` | — | setof record | Contratos vencidos del mes |
|
||||
|
||||
### 📊 Ingresos (Little Hotelier)
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `getincomes(...)` | varios (filtros) | setof record | Lista de ingresos |
|
||||
| `totalincomes(...)` | varios | `numeric` | Total de ingresos |
|
||||
| `channelscards(...)` | varios | setof record | Canales de ingreso |
|
||||
| `loadincomes(jsonb)` | `jsonb` | `status` | Carga masiva de ingresos |
|
||||
| `loadproductsales(jsonb)` | `jsonb` | `status` | Carga masiva de ventas de productos |
|
||||
| `loadchequesdetalle(jsonb)` | `jsonb` | `status` | Carga masiva de cheques |
|
||||
| `reportincomes(...)` | varios | setof record | Reporte de ingresos |
|
||||
| `countticket(...)` | varios | `int` | Conteo de tickets |
|
||||
| `efectivo(...)`, `otros(...)`, `propinas(...)`, `tarjeta(...)`, `vales(...)` | varios | `numeric` | Desglose por tipo de pago |
|
||||
| `sumatotal(...)` | varios | `numeric` | Suma total |
|
||||
| `ticketpromedio(...)` | varios | `numeric` | Ticket promedio |
|
||||
|
||||
### 🏨 Horux / Facturas / Stripe
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `addhoruxdata(jsonb)` | `jsonb` | `int` (total insertados) | Inserta facturas masivamente |
|
||||
| `getcategoryincome()` | — | setof record | Categorías de ingreso |
|
||||
| `getinvoiceincome()` | — | setof record | Facturas de ingreso |
|
||||
| `getaccountincome()` | — | setof record | Cuentas de ingreso |
|
||||
| `getincomehorux()` | — | setof record | Ingresos Horux |
|
||||
| `gettotalincome()` | — | `numeric` | Total de ingresos Horux |
|
||||
| `getoneincome(id)` | `int` | setof record | Un ingreso Horux |
|
||||
| `newincome(account_id, amount, date, invoice, area_id, categories::jsonb)` | 6 parámetros | `status` | Crea ingreso Horux |
|
||||
| `updateincome(id, account_id, amount, date, invoice, area_id, categories::jsonb)` | 7 parámetros | `status` | Actualiza ingreso Horux |
|
||||
| `addstripedatav2(jsonb)` | `jsonb` | `int` (inserted) | Inserta datos de Stripe |
|
||||
|
||||
### 📈 Hotel P&L / Restaurant P&L
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `cogs(...)` | varios (fechas) | setof record | Cost of Goods Sold |
|
||||
| `ebitda(...)` | varios | setof record | EBITDA |
|
||||
| `employeeshare(...)` | varios | setof record | Participación de empleados |
|
||||
| `grossprofit(...)` | varios | setof record | Ganancia bruta |
|
||||
| `tips(...)` | varios | setof record | Propinas |
|
||||
| `totalrevenue(...)` | varios | setof record | Ingresos totales |
|
||||
| `weightedCategoriesCost(...)` | varios | setof record | Costos ponderados por categoría |
|
||||
|
||||
### 💱 Tipo de Cambio
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `consultexchange(...)` | varios | setof record | Consulta tipo de cambio |
|
||||
| `getexchanges()` | — | setof record | Historial de tipos de cambio |
|
||||
|
||||
### ⚙️ Configuraciones
|
||||
|
||||
| Función | Parámetros | Retorno | Descripción |
|
||||
|---------|-----------|---------|-------------|
|
||||
| `newroom(...)` | varios | `status` | Crea habitación |
|
||||
| `newproperty(...)` | varios | `status` | Crea propiedad |
|
||||
| `reportrooms()` | — | setof record | Reporte de habitaciones |
|
||||
| `reportproperties()` | — | setof record | Reporte de propiedades |
|
||||
| `approveby()` | — | setof record | Usuarios que pueden aprobar |
|
||||
| `requestby()` | — | setof record | Usuarios que pueden solicitar |
|
||||
| `categoryexpense()` | — | setof record | Categorías de gasto |
|
||||
| `currency()` | — | setof record | Monedas |
|
||||
| `units()` | — | setof record | Unidades de medida |
|
||||
| `recurrence()` | — | setof record | Recurrencias (mensual, etc.) |
|
||||
|
||||
---
|
||||
|
||||
## Tablas Principales (Inferidas)
|
||||
|
||||
Basado en las funciones SQL y los controllers, las tablas principales son:
|
||||
|
||||
| Tabla | Propósito |
|
||||
|-------|-----------|
|
||||
| `employees` | Datos de empleados |
|
||||
| `contracts` | Contratos laborales |
|
||||
| `products` | Catálogo de productos |
|
||||
| `suppliers` | Proveedores |
|
||||
| `expenses` | Gastos/egresos |
|
||||
| `purchase_details` | Líneas de compra asociadas a gastos |
|
||||
| `inventory_entries` / `stock_movements` | Movimientos de inventario |
|
||||
| `incomes` | Ingresos de Little Hotelier |
|
||||
| `income_hrx` | Ingresos de Horux |
|
||||
| `invoice_income` | Facturas descargadas |
|
||||
| `stripe_data` | Datos de transfers de Stripe |
|
||||
| `users` | Usuarios del sistema |
|
||||
| `roles` | Roles de usuario |
|
||||
| `areas` | Áreas del hotel |
|
||||
| `positions` | Puestos de trabajo |
|
||||
| `endpoint_logs` | Control de ejecución de emails (1x/mes) |
|
||||
| `settings_rooms` | Habitaciones |
|
||||
| `settings_properties` | Propiedades |
|
||||
|
||||
---
|
||||
|
||||
## Patrones de Datos
|
||||
|
||||
### Paginación Manual
|
||||
|
||||
```sql
|
||||
-- En los controllers
|
||||
SELECT * FROM getemployees() LIMIT $1 OFFSET $2;
|
||||
SELECT COUNT(*) FROM employees;
|
||||
```
|
||||
|
||||
### Estados Numéricos
|
||||
|
||||
Muchas funciones retornan un `status` numérico:
|
||||
- `1` = Éxito
|
||||
- `0` = Error/No permitido
|
||||
- `-3` = Condición no cumplida (ej. "gasto no aprobado")
|
||||
- Otros negativos = Errores específicos
|
||||
|
||||
---
|
||||
|
||||
## JSONB como Patrón de Intercambio
|
||||
|
||||
El sistema usa extensivamente `jsonb` para pasar datos complejos:
|
||||
|
||||
### Ejemplo: Productos en un Gasto
|
||||
|
||||
```javascript
|
||||
// Controller
|
||||
const products = [
|
||||
{ id_product: 1, quantity: 10 },
|
||||
{ id_product: 2, quantity: 5 }
|
||||
];
|
||||
const productJson = JSON.stringify(products);
|
||||
|
||||
await pool.query(
|
||||
'SELECT newexpensev2($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11,$12,$13,$14)',
|
||||
[..., productJson, ...]
|
||||
);
|
||||
```
|
||||
|
||||
### Ejemplo: Ajustes de Stock
|
||||
|
||||
```javascript
|
||||
const stockproductadjusment = [
|
||||
{ id_product: 1, new_stock: 50 },
|
||||
{ id_product: 2, new_stock: 30 }
|
||||
];
|
||||
|
||||
await pool.query(
|
||||
'SELECT * FROM setstockadjusmentsv2($1::jsonb)',
|
||||
[JSON.stringify(stockproductadjusment)]
|
||||
);
|
||||
```
|
||||
|
||||
### Ejemplo: Facturas Masivas
|
||||
|
||||
```javascript
|
||||
const facturas = await getFacturas(desde, hasta);
|
||||
await pool.query(
|
||||
'SELECT addhoruxdata($1::jsonb)',
|
||||
[JSON.stringify(facturas)]
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integraciones que Escriben en JSONB
|
||||
|
||||
| Integración | Función SQL | Tabla Destino |
|
||||
|-------------|-------------|---------------|
|
||||
| Little Hotelier (CSV/XLSX) | `loadincomes(jsonb)` | `incomes` |
|
||||
| Facturas API (México) | `addhoruxdata(jsonb)` | `income_hrx` / `invoice_income` |
|
||||
| Stripe | `addstripedatav2(jsonb)` | `stripe_data` |
|
||||
|
||||
---
|
||||
|
||||
## Guía para Modificar Funciones SQL
|
||||
|
||||
### Paso 1: Verificar Dependencias
|
||||
|
||||
Busca TODOS los controllers que llaman a la función:
|
||||
|
||||
```bash
|
||||
grep -r "nombrefuncion(" backend/hotel_hacienda/src/
|
||||
```
|
||||
|
||||
### Paso 2: Probar en PostgreSQL Directamente
|
||||
|
||||
```sql
|
||||
SELECT * FROM nombrefuncion('param1', 'param2');
|
||||
-- o
|
||||
SELECT nombrefuncion('param1') AS status;
|
||||
```
|
||||
|
||||
### Paso 3: Considerar Idempotencia
|
||||
|
||||
Si la función modifica datos (INSERT/UPDATE), considera:
|
||||
- ¿Qué pasa si se llama dos veces con los mismos parámetros?
|
||||
- ¿Debería usar `ON CONFLICT` o verificar `IF EXISTS`?
|
||||
|
||||
### Paso 4: Actualizar Controllers si Cambia la Firma
|
||||
|
||||
Si agregas/quitas parámetros, actualiza TODOS los `pool.query(...)` que la llaman.
|
||||
|
||||
### Paso 5: Documentar el Cambio
|
||||
|
||||
Actualiza este archivo (`docs/DATABASE.md`) y `DOCUMENTACION_TECNICA.md`.
|
||||
262
docs/DEPLOYMENT.md
Normal file
262
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# 🚀 Guía de Despliegue
|
||||
|
||||
## Índice
|
||||
1. [Requisitos del Servidor](#requisitos-del-servidor)
|
||||
2. [Despliegue de Base de Datos](#despliegue-de-base-de-datos)
|
||||
3. [Despliegue del Backend](#despliegue-del-backend)
|
||||
4. [Despliegue del Frontend](#despliegue-del-frontend)
|
||||
5. [Configuración de Proxy Inverso (Nginx)](#configuración-de-proxy-inverso-nginx)
|
||||
6. [Configuración de PM2](#configuración-de-pm2)
|
||||
7. [SSL con Certbot](#ssl-con-certbot)
|
||||
8. [Verificación Post-Despliegue](#verificación-post-despliegue)
|
||||
9. [Rollback](#rollback)
|
||||
|
||||
---
|
||||
|
||||
## Requisitos del Servidor
|
||||
|
||||
- Ubuntu 22.04 LTS (recomendado)
|
||||
- 2GB RAM mínimo
|
||||
- Node.js 18+
|
||||
- PostgreSQL 13+
|
||||
- Nginx
|
||||
- PM2 (opcional pero recomendado)
|
||||
- Git
|
||||
|
||||
---
|
||||
|
||||
## Despliegue de Base de Datos
|
||||
|
||||
### 1. Instalar PostgreSQL
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install postgresql postgresql-contrib
|
||||
sudo systemctl enable postgresql
|
||||
```
|
||||
|
||||
### 2. Crear Base de Datos y Usuario
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
```sql
|
||||
CREATE DATABASE hotel_hacienda;
|
||||
CREATE USER hoteluser WITH ENCRYPTED PASSWORD 'password_seguro';
|
||||
GRANT ALL PRIVILEGES ON DATABASE hotel_hacienda TO hoteluser;
|
||||
\q
|
||||
```
|
||||
|
||||
### 3. Restaurar Backup
|
||||
|
||||
```bash
|
||||
scp backupcondatos22122025.sql usuario@servidor:/tmp/
|
||||
ssh usuario@servidor
|
||||
sudo -u postgres psql -d hotel_hacienda -f /tmp/backupcondatos22122025.sql
|
||||
```
|
||||
|
||||
### 4. Verificar Funciones
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql -d hotel_hacienda -c "\df"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Despliegue del Backend
|
||||
|
||||
### 1. Clonar Repositorio
|
||||
|
||||
```bash
|
||||
cd /var/www
|
||||
git clone https://git.consultoria-as.com/consultoria-as/hotel-hacienda.git
|
||||
cd hotel-hacienda/backend/hotel_hacienda
|
||||
```
|
||||
|
||||
### 2. Instalar Dependencias
|
||||
|
||||
```bash
|
||||
npm install --production
|
||||
```
|
||||
|
||||
### 3. Configurar Variables de Entorno
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
```env
|
||||
PORT=3000
|
||||
URL_CORS=https://hotel.consultoria-as.com
|
||||
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=hoteluser
|
||||
DB_PASSWORD=password_seguro
|
||||
DB_NAME=hotel_hacienda
|
||||
|
||||
EMAIL_HOST=smtp.gmail.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=soporte@horuxfin.com
|
||||
EMAIL_PASS=app_password
|
||||
|
||||
STRIPE_SECRET_KEY=sk_live_...
|
||||
|
||||
FACTURAS_API_URL=https://api.facturas.com/v1/
|
||||
FACTURAS_ISSUER_RFC=RFC_DEL_HOTEL
|
||||
FACTURAS_TYPE=I
|
||||
FACTURAS_API_TOKEN=token_aqui
|
||||
|
||||
BANXICO_TOKEN=token_banxico
|
||||
```
|
||||
|
||||
### 4. Iniciar con PM2
|
||||
|
||||
```bash
|
||||
npm install -g pm2
|
||||
pm2 start src/server.js --name hotel-api
|
||||
pm2 save
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Despliegue del Frontend
|
||||
|
||||
### 1. Compilar para Producción
|
||||
|
||||
```bash
|
||||
cd /var/www/hotel-hacienda/frontend/Frontend-Hotel
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. Configurar Environment de Producción
|
||||
|
||||
```bash
|
||||
echo "VITE_API_BASE_URL=https://hotel.consultoria-as.com/api" > .env
|
||||
```
|
||||
|
||||
### 3. Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Esto genera la carpeta `dist/` con los archivos estáticos.
|
||||
|
||||
---
|
||||
|
||||
## Configuración de Proxy Inverso (Nginx)
|
||||
|
||||
### 1. Instalar Nginx
|
||||
|
||||
```bash
|
||||
sudo apt install nginx
|
||||
```
|
||||
|
||||
### 2. Configuración del Sitio
|
||||
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/hotel-hacienda
|
||||
```
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name hotel.consultoria-as.com;
|
||||
|
||||
# Frontend estático
|
||||
location / {
|
||||
root /var/www/hotel-hacienda/frontend/Frontend-Hotel/dist;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3000/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/hotel-access.log;
|
||||
error_log /var/log/nginx/hotel-error.log;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Activar Sitio
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/hotel-hacienda /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSL con Certbot
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d hotel.consultoria-as.com
|
||||
sudo systemctl enable certbot.timer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verificación Post-Despliegue
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] Backend responde: `curl https://hotel.consultoria-as.com/api/employees`
|
||||
- [ ] Frontend carga sin errores en navegador
|
||||
- [ ] Login funciona correctamente
|
||||
- [ ] Base de datos tiene datos correctos
|
||||
- [ ] Emails se envían (probar recuperación de contraseña)
|
||||
- [ ] CORS permite el dominio correcto
|
||||
- [ ] PM2 muestra el proceso activo: `pm2 list`
|
||||
- [ ] Nginx no muestra errores: `sudo tail -f /var/log/nginx/hotel-error.log`
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
### Backend
|
||||
|
||||
```bash
|
||||
pm2 stop hotel-api
|
||||
# Restaurar código anterior con git
|
||||
git checkout <commit-anterior>
|
||||
npm install
|
||||
pm2 restart hotel-api
|
||||
```
|
||||
|
||||
### Base de Datos
|
||||
|
||||
```bash
|
||||
# Restaurar backup previo
|
||||
sudo -u postgres pg_dump hotel_hacienda > backup_antes_del_cambio.sql
|
||||
sudo -u postgres psql -d hotel_hacienda -f backup_seguro.sql
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
```bash
|
||||
# Recompilar versión anterior
|
||||
git checkout <commit-anterior>
|
||||
cd frontend/Frontend-Hotel
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **Nota:** Mantener siempre un backup reciente de la base de datos antes de cualquier despliegue que modifique funciones SQL.
|
||||
431
docs/FRONTEND.md
Normal file
431
docs/FRONTEND.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# 🎨 Documentación del Frontend — Frontend-Hotel
|
||||
|
||||
## Índice
|
||||
1. [Introducción](#introducción)
|
||||
2. [Estructura de Carpetas](#estructura-de-carpetas)
|
||||
3. [Entry Points](#entry-points)
|
||||
4. [Enrutamiento](#enrutamiento)
|
||||
5. [Manejo de Estado Global](#manejo-de-estado-global)
|
||||
6. [Sistema de Permisos (Roles)](#sistema-de-permisos-roles)
|
||||
7. [Comunicación con el Backend](#comunicación-con-el-backend)
|
||||
8. [Componentes Reutilizables](#componentes-reutilizables)
|
||||
9. [Estilos](#estilos)
|
||||
10. [Páginas por Dominio](#páginas-por-dominio)
|
||||
11. [Guía para Agregar una Nueva Página](#guía-para-agregar-una-nueva-página)
|
||||
12. [Bugs y Limitaciones Conocidas](#bugs-y-limitaciones-conocidas)
|
||||
|
||||
---
|
||||
|
||||
## Introducción
|
||||
|
||||
El frontend es una **Single Page Application (SPA)** construida con **React 19**, **Vite** y **React Router DOM**. Es una aplicación cliente que consume la API REST del backend.
|
||||
|
||||
**Tecnologías clave:**
|
||||
- React 19.1.1 (JSX, JavaScript puro, sin TypeScript)
|
||||
- Vite 7.1.2 con SWC para compilación rápida
|
||||
- React Router DOM 7.8.2 (BrowserRouter)
|
||||
- Estado global: React Context API
|
||||
- HTTP: mezcla de `fetch` nativo y `axios`
|
||||
- Estilos: Tailwind CSS + Bootstrap 5 + CSS puro
|
||||
|
||||
---
|
||||
|
||||
## Estructura de Carpetas
|
||||
|
||||
```
|
||||
Frontend-Hotel/
|
||||
├── index.html # Entry HTML (título: "Hacienda San Angel")
|
||||
├── vite.config.js # Config Vite (puerto 5172)
|
||||
├── tailwind.config.cjs # Config Tailwind
|
||||
├── postcss-config.js # PostCSS con Tailwind + Autoprefixer
|
||||
├── eslint.config.js # ESLint 9 flat config
|
||||
├── public/
|
||||
│ ├── logoHotel.png
|
||||
│ ├── IconoHotel.svg
|
||||
│ └── ...
|
||||
├── src/
|
||||
│ ├── main.jsx # Entry point React
|
||||
│ ├── App.jsx # Definición central de rutas
|
||||
│ ├── index.css # Tailwind directives + estilos base
|
||||
│ ├── App.css
|
||||
│ ├── constants/
|
||||
│ │ └── menuconfig.js # Configuración del menú de navegación
|
||||
│ ├── context/
|
||||
│ │ ├── AuthContext.jsx # Estado de autenticación
|
||||
│ │ └── LenguageContext.jsx # Estado de idioma (EN/ES)
|
||||
│ ├── routes/
|
||||
│ │ └── ProtectedRoute.jsx # Ruta protegida (NO usada en App.jsx)
|
||||
│ ├── services/
|
||||
│ │ ├── api.js # Instancia axios (baseURL desde env)
|
||||
│ │ ├── employeeService.js
|
||||
│ │ ├── incomeService.js
|
||||
│ │ ├── userService.js
|
||||
│ │ └── contractService.js
|
||||
│ ├── styles/
|
||||
│ │ ├── global.css
|
||||
│ │ ├── Dashboard.css
|
||||
│ │ ├── Login.css
|
||||
│ │ ├── Layout.css
|
||||
│ │ └── Sidebar.css
|
||||
│ ├── assets/
|
||||
│ │ └── pages/login.jsx
|
||||
│ ├── components/
|
||||
│ │ ├── Layout2.jsx # Layout activo (Sidebar + Topbar + Outlet)
|
||||
│ │ ├── Layout.jsx # Layout antiguo (comentado)
|
||||
│ │ ├── Sidebar.jsx
|
||||
│ │ ├── topbar/Topbar.jsx
|
||||
│ │ ├── Navbar/Navbar.jsx
|
||||
│ │ ├── Table/HotelTable.jsx
|
||||
│ │ ├── ExcelExportButton.jsx
|
||||
│ │ ├── SummaryCard.jsx
|
||||
│ │ ├── FormInput.jsx
|
||||
│ │ ├── FormSelect.jsx
|
||||
│ │ ├── Switch.jsx
|
||||
│ │ ├── Modals/
|
||||
│ │ ├── Buttons/
|
||||
│ │ ├── Filters/
|
||||
│ │ └── Inputs/
|
||||
│ └── pages/ # ~50+ páginas organizadas por dominio
|
||||
│ ├── Login.jsx
|
||||
│ ├── Dashboard.jsx
|
||||
│ ├── Dashboard/
|
||||
│ ├── Expenses/
|
||||
│ ├── ExpensesToBeApproval/
|
||||
│ ├── Inventory/
|
||||
│ ├── Hotel/
|
||||
│ ├── Income/
|
||||
│ ├── Payroll/
|
||||
│ ├── Settings/
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entry Points
|
||||
|
||||
### `index.html`
|
||||
|
||||
Carga la fuente Roboto desde Google Fonts y el script `src/main.jsx`.
|
||||
|
||||
### `src/main.jsx`
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { AuthProvider } from "./context/AuthContext";
|
||||
import { LangProvider } from "./context/LenguageContext";
|
||||
import App from "./App.jsx";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<AuthProvider>
|
||||
<LangProvider>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</LangProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
```
|
||||
|
||||
**Jerarquía de providers:**
|
||||
1. `AuthProvider` — disponible en toda la app
|
||||
2. `LangProvider` — depende de `AuthContext` (lee el rol para forzar español a housekeepers)
|
||||
3. `BrowserRouter` — enrutamiento
|
||||
|
||||
---
|
||||
|
||||
## Enrutamiento
|
||||
|
||||
### `src/App.jsx`
|
||||
|
||||
Define todas las rutas de la aplicación. No usa `ProtectedRoute`. Todas las rutas internas están bajo `/app` y renderizan `<Layout />` (que es `Layout2.jsx`).
|
||||
|
||||
```jsx
|
||||
<Route path="/" element={<Login />} />
|
||||
<Route path="/app" element={<Layout />}>
|
||||
<Route path="income" element={<Income/>} />
|
||||
<Route path="employees" element={<Employees />} />
|
||||
<Route path="contracts" element={<Contracts />} />
|
||||
<Route path="hotelpl" element={<HotelPL />} />
|
||||
<Route path="restaurantpl" element={<RestaurantPL />} />
|
||||
<Route path="expenses" element={<Expenses />} />
|
||||
<Route path="new-expense" element={<NewExpense />} />
|
||||
<Route path="products" element={<Products />} />
|
||||
<Route path="payroll" element={<Payroll />} />
|
||||
{/* ... ~40 rutas más */}
|
||||
</Route>
|
||||
```
|
||||
|
||||
**Convenciones de rutas:**
|
||||
- Páginas de listado: plural simple (`/products`, `/payroll`)
|
||||
- Páginas de creación: prefijo `new-` (`/new-product`, `/new-expense`)
|
||||
- Páginas de edición: `:id` como parámetro (`/expenses/edit/:id`, `/payroll/employee/:id`)
|
||||
- Detalles: segmento fijo + `:id` (`/expenses/:id`, `/properties/:id`)
|
||||
|
||||
---
|
||||
|
||||
## Manejo de Estado Global
|
||||
|
||||
### AuthContext (`src/context/AuthContext.jsx`)
|
||||
|
||||
```jsx
|
||||
export const AuthContext = createContext();
|
||||
|
||||
function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null); // rol numérico
|
||||
const [userData, setUserData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const savedUser = localStorage.getItem("rol");
|
||||
if (savedUser) setUser(JSON.parse(savedUser));
|
||||
}, []);
|
||||
|
||||
const login = (userData, data) => {
|
||||
setUserData(data);
|
||||
setUser(userData);
|
||||
localStorage.setItem("rol", JSON.stringify(userData));
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setUser(null);
|
||||
localStorage.removeItem("rol");
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, login, logout, userData }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Observaciones:**
|
||||
- Guarda el rol como número en `localStorage` bajo la clave `"rol"`.
|
||||
- **NO exporta `useAuth`**, lo cual es un bug conocido.
|
||||
- No usa JWT ni tokens.
|
||||
|
||||
### LangContext (`src/context/LenguageContext.jsx`)
|
||||
|
||||
```jsx
|
||||
export const langContext = createContext();
|
||||
|
||||
export const LangProvider = ({ children }) => {
|
||||
const authContext = useContext(AuthContext);
|
||||
const user = authContext?.user || null;
|
||||
|
||||
const [lang, setLang] = useState(user === 6 ? "es" : "en");
|
||||
|
||||
const toggleLang = (event) => setLang(event.target.value);
|
||||
|
||||
return (
|
||||
<langContext.Provider value={{ lang, toggleLang }}>
|
||||
{children}
|
||||
</langContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useLang = () => useContext(langContext);
|
||||
```
|
||||
|
||||
**Observaciones:**
|
||||
- Forza español si `user === 6` (Housekeeper).
|
||||
- No persiste en localStorage.
|
||||
- Se accede con `useContext(langContext)` o `useLang()`.
|
||||
|
||||
---
|
||||
|
||||
## Sistema de Permisos (Roles)
|
||||
|
||||
Los permisos son **numéricos y hardcodeados** en `Layout2.jsx`. No hay backend de ACL.
|
||||
|
||||
### Roles Definidos
|
||||
|
||||
| Rol | Acceso |
|
||||
|-----|--------|
|
||||
| 1 | Admin — Todo |
|
||||
| 2 | Supervisor limitado — Dashboards, Expenses (Report/Monthly Report), Payroll (Report/Attendance/Employees/Contracts), Expenses to be approved |
|
||||
| 3-4 | Rangos intermedios (según lógica de rangos) |
|
||||
| 5 | Compras — Solo: New Expense, Purchase Entries, New Suppliers |
|
||||
| 6 | Housekeeper — Solo Outcomes, forzado a español |
|
||||
|
||||
### Lógica de Permisos en `Layout2.jsx`
|
||||
|
||||
```javascript
|
||||
const menuConfigWithPermissions = Object.values(menuConfig).map(section => ({
|
||||
...section,
|
||||
hidden:
|
||||
section.label === "Dashboards" ? (user >= 1 && user <= 2 ? false : true) :
|
||||
section.label === "Expenses to be approved" ? (user === 1 || user === 2 ? false : true) :
|
||||
section.label === "Expenses" ? (user >= 1 && user <= 5 ? false : true) :
|
||||
section.label === "Inventory" ? (user >= 1 && user <= 5 ? false : true) :
|
||||
section.label === "Payroll" ? (user >= 1 && user <= 4 ? false : true) :
|
||||
section.label === "Hotel" ? (user === 1 ? false : true) :
|
||||
section.label === "Income" ? (user >= 1 && user <= 4 ? false : true) :
|
||||
section.label === "Housekeeper" ? (user === 6 ? false : true) :
|
||||
false,
|
||||
// ... lógica de submenús
|
||||
}));
|
||||
```
|
||||
|
||||
**Regla:** Si `hidden === true`, la sección no aparece en el sidebar.
|
||||
|
||||
---
|
||||
|
||||
## Comunicación con el Backend
|
||||
|
||||
### Variable de Entorno
|
||||
|
||||
```env
|
||||
VITE_API_BASE_URL=http://localhost:4000/api
|
||||
```
|
||||
|
||||
Se accede en el frontend como:
|
||||
```javascript
|
||||
import.meta.env.VITE_API_BASE_URL
|
||||
```
|
||||
|
||||
### Instancia Axios (`src/services/api.js`)
|
||||
|
||||
```javascript
|
||||
import axios from "axios";
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
export default api;
|
||||
```
|
||||
|
||||
**Problema:** Esta instancia está infrautilizada. Muchas páginas usan `fetch` directo o axios sin la instancia base.
|
||||
|
||||
### Patrón Fetch (más común)
|
||||
|
||||
```javascript
|
||||
fetch(import.meta.env.VITE_API_BASE_URL + '/purchases/getpurchases')
|
||||
.then(res => res.json())
|
||||
.then(data => setState(data))
|
||||
.catch(err => console.error(err));
|
||||
```
|
||||
|
||||
### Patrón Axios
|
||||
|
||||
```javascript
|
||||
axios.put(`${import.meta.env.VITE_API_BASE_URL}/purchases/entry/${id}`, {
|
||||
checking: parseInt(checking)
|
||||
})
|
||||
.then(res => { ... })
|
||||
.catch(err => { ... });
|
||||
```
|
||||
|
||||
**Problemas conocidos:**
|
||||
- Varios archivos en `services/` tienen URLs hardcodeadas a `localhost:3000` o `localhost:4000`.
|
||||
- No hay interceptores para manejo centralizado de errores.
|
||||
- No se envían headers de autorización.
|
||||
|
||||
---
|
||||
|
||||
## Componentes Reutilizables
|
||||
|
||||
### `HotelTable.jsx` (`src/components/Table/`)
|
||||
|
||||
Tabla genérica que recibe:
|
||||
- `columns`: array de `{ header, key, render?, headerStyle? }`
|
||||
- `data`: array de objetos
|
||||
|
||||
```jsx
|
||||
<Table
|
||||
columns={[
|
||||
{ header: "Nombre", key: "name" },
|
||||
{ header: "Acciones", key: "id", render: (id) => <button>Editar</button> }
|
||||
]}
|
||||
data={products}
|
||||
/>
|
||||
```
|
||||
|
||||
### `ExcelExportButton.jsx`
|
||||
|
||||
Exporta datos a `.xlsx` usando la librería `xlsx`.
|
||||
|
||||
### `SummaryCard.jsx`
|
||||
|
||||
Card de resumen con indicador de loading.
|
||||
|
||||
### `FormInput.jsx` / `FormSelect.jsx`
|
||||
|
||||
Wrappers básicos de inputs HTML con estilos consistentes.
|
||||
|
||||
### Modales (`src/components/Modals/`)
|
||||
|
||||
- `ConfirmationModal.jsx` — Confirmación genérica
|
||||
- `DiscardConfirmModal.jsx` — Confirmación de descarte
|
||||
- `ConfirmationMontlyPay.jsx` — Confirmación de pago mensual
|
||||
- `ConfirmationOutcome.jsx` — Confirmación de salida
|
||||
|
||||
---
|
||||
|
||||
## Estilos
|
||||
|
||||
### Mezcla de Tecnologías
|
||||
|
||||
El proyecto usa tres sistemas de estilos simultáneamente:
|
||||
|
||||
1. **Tailwind CSS** — Configurado pero usado esporádicamente.
|
||||
2. **Bootstrap 5** — Clases como `btn btn-primary`, `btn btn-secondary`.
|
||||
3. **CSS Puro** — Un archivo `.css` por página/componente.
|
||||
|
||||
### Convención Actual
|
||||
|
||||
Cada página tiene su propio archivo CSS:
|
||||
```
|
||||
src/pages/Expenses/NewExpense.css
|
||||
src/pages/Inventory/Products.css
|
||||
src/components/Table/Table.css
|
||||
```
|
||||
|
||||
### Fuente
|
||||
|
||||
Roboto cargada desde Google Fonts en `index.html`.
|
||||
|
||||
---
|
||||
|
||||
## Páginas por Dominio
|
||||
|
||||
| Dominio | Páginas | Ruta Base |
|
||||
|---------|---------|-----------|
|
||||
| **Dashboards** | Income, HotelPL, RestaurantPL, RoomAnalysis, RestaurantAnalysis, Budget, CostPerRoom, Expenses | `/app/income`, `/app/hotelpl`... |
|
||||
| **Income** | NewIncome, IncomeReport | `/app/new-income-form`, `/app/new-income-report` |
|
||||
| **Expenses to Approve** | PendingApproval, Approved, Rejected | `/app/pending-approval`... |
|
||||
| **Expenses** | NewExpense, EditExpense, ExpenseDetail, ReportExpense, Payments, MonthlyPayments, MonthlyReport, NewMonthlyPayment, NewSuppliers, PurchaseEntries | `/app/new-expense`... |
|
||||
| **Inventory** | Products, NewProduct, AlterProduct, InventoryReport, Adjustments, Outcomes, HousekeeperOutcomes, DiscardProduct | `/app/products`... |
|
||||
| **Hotel** | Properties, PropertiesId | `/app/properties`... |
|
||||
| **Payroll** | Payroll, Plantillapayroll, EditPayroll, PayrollEmployees, NewEmployee, PayrollAttendance, PayrollContract, ContractsDetail | `/app/payroll`... |
|
||||
| **Settings** | Settings, SettingsId, RoomsManagement | `/app/settings`... |
|
||||
|
||||
---
|
||||
|
||||
## Guía para Agregar una Nueva Página
|
||||
|
||||
1. **Crear el componente** en `src/pages/[Dominio]/NombrePagina.jsx`.
|
||||
2. **Crear los estilos** opcionales en `src/pages/[Dominio]/NombrePagina.css`.
|
||||
3. **Agregar la ruta** en `src/App.jsx` dentro de `<Route path="/app" element={<Layout />}>`.
|
||||
4. **Agregar al menú** (si aplica):
|
||||
- Editar `src/constants/menuconfig.js`
|
||||
- Agregar la ruta a la sección correspondiente
|
||||
5. **Actualizar permisos** en `src/components/Layout2.jsx`:
|
||||
- Agregar lógica de `hidden` para la sección/submenú
|
||||
- Agregar detección de ruta en `activeSection` si la ruta no sigue el patrón estándar
|
||||
6. **Agregar traducciones** EN/ES si es texto visible.
|
||||
7. **Consumir la API** usando `import.meta.env.VITE_API_BASE_URL`.
|
||||
|
||||
---
|
||||
|
||||
## Bugs y Limitaciones Conocidas
|
||||
|
||||
1. **`useAuth` no existe** — `AuthContext.jsx` no exporta `useAuth`, pero `ProtectedRoute.jsx` y `Navbar.jsx` lo intentan importar.
|
||||
2. **No hay rutas protegidas** — `App.jsx` no usa `ProtectedRoute`. Cualquiera puede acceder a `/app/*`.
|
||||
3. **URLs hardcodeadas** — Varios `*Service.js` apuntan a `localhost` en vez de usar `VITE_API_BASE_URL`.
|
||||
4. **Código comentado masivo** — Especialmente en `Layout.jsx` y páginas grandes.
|
||||
5. **`multer` en frontend** — Es un middleware de Node.js, inapropiado para React.
|
||||
6. **Sin tests** — No hay Jest, Vitest, ni Playwright configurados.
|
||||
217
docs/TROUBLESHOOTING.md
Normal file
217
docs/TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# 🔧 Solución de Problemas Comunes
|
||||
|
||||
## Índice
|
||||
1. [Backend](#backend)
|
||||
2. [Frontend](#frontend)
|
||||
3. [Base de Datos](#base-de-datos)
|
||||
4. [Integraciones Externas](#integraciones-externas)
|
||||
5. [Producción](#producción)
|
||||
|
||||
---
|
||||
|
||||
## Backend
|
||||
|
||||
### Error: "Conectado a PostgreSQL" no aparece
|
||||
|
||||
**Causa:** Variables de entorno de DB incorrectas o PostgreSQL no está corriendo.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
# Verificar PostgreSQL
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Verificar variables de entorno
|
||||
cat backend/hotel_hacienda/.env | grep DB_
|
||||
|
||||
# Probar conexión manual
|
||||
node -e "require('./backend/hotel_hacienda/src/db/connection')"
|
||||
```
|
||||
|
||||
### Error: CORS bloquea peticiones del frontend
|
||||
|
||||
**Causa:** `URL_CORS` no coincide con el dominio del frontend.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
# En backend/.env
|
||||
URL_CORS=https://hotel.consultoria-as.com
|
||||
# Sin slash al final
|
||||
```
|
||||
|
||||
### Error: "Cannot find module 'pg'"
|
||||
|
||||
**Causa:** Dependencias no instaladas.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
cd backend/hotel_hacienda
|
||||
npm install
|
||||
```
|
||||
|
||||
### Emails no se envían
|
||||
|
||||
**Causa:** Credenciales SMTP incorrectas o servidor bloqueando.
|
||||
|
||||
**Solución:**
|
||||
1. Verificar variables `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USER`, `EMAIL_PASS`
|
||||
2. Para Gmail, usar "App Password", no la contraseña normal
|
||||
3. Revisar logs: `pm2 logs hotel-api`
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
### Error: "Failed to fetch" o "Network Error"
|
||||
|
||||
**Causa:** `VITE_API_BASE_URL` apunta a localhost en producción.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
# Verificar .env del frontend
|
||||
cat frontend/Frontend-Hotel/.env
|
||||
|
||||
# Debe ser:
|
||||
VITE_API_BASE_URL=https://hotel.consultoria-as.com/api
|
||||
```
|
||||
|
||||
### Página en blanco después del build
|
||||
|
||||
**Causa:** Rutas de React Router no configuradas en Nginx.
|
||||
|
||||
**Solución:** Asegurar que Nginx tenga:
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
```
|
||||
|
||||
### Error: `useAuth` is not exported
|
||||
|
||||
**Causa:** Bug conocido. `AuthContext.jsx` no exporta `useAuth`.
|
||||
|
||||
**Solución temporal:**
|
||||
```jsx
|
||||
// En vez de:
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
|
||||
// Usar:
|
||||
import { AuthContext } from "../context/AuthContext";
|
||||
const { user, login, logout } = useContext(AuthContext);
|
||||
```
|
||||
|
||||
### Tailwind no aplica estilos
|
||||
|
||||
**Causa:** Tailwind v4 usa configuración diferente o `index.css` no tiene las directivas.
|
||||
|
||||
**Solución:** Verificar que `src/index.css` contenga:
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Base de Datos
|
||||
|
||||
### Error: "function newexpensev2 does not exist"
|
||||
|
||||
**Causa:** Backup no restaurado completamente o función no migrada.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
# Restaurar backup completo
|
||||
sudo -u postgres psql -d hotel_hacienda -f backupcondatos22122025.sql
|
||||
|
||||
# Verificar funciones existentes
|
||||
sudo -u postgres psql -d hotel_hacienda -c "\df public.*"
|
||||
```
|
||||
|
||||
### Error: "duplicate key value violates unique constraint"
|
||||
|
||||
**Causa:** Algunas funciones SQL no son idempotentes y se llaman dos veces.
|
||||
|
||||
**Solución:** Revisar la función SQL correspondiente. Agregar `IF EXISTS` o `ON CONFLICT`.
|
||||
|
||||
### Stock duplicado en inventario
|
||||
|
||||
**Causa:** Dos procesos tocan el mismo stock (ver análisis técnico en `DOCUMENTACION_TECNICA.md`, sección "Problema de duplicación en inventario").
|
||||
|
||||
**Solución:** Decidir si el stock se actualiza en `purchaseentry()` o en `setpaymentstatusv2()`, no en ambos.
|
||||
|
||||
---
|
||||
|
||||
## Integraciones Externas
|
||||
|
||||
### Stripe no devuelve datos
|
||||
|
||||
**Causa:** `STRIPE_SECRET_KEY` incorrecta o modo live/test mezclado.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
# Verificar que la key comience con sk_test_ o sk_live_
|
||||
cat backend/.env | grep STRIPE
|
||||
```
|
||||
|
||||
### API de Facturas devuelve error
|
||||
|
||||
**Causa:** Token expirado, RFC incorrecto, o fechas fuera de rango.
|
||||
|
||||
**Solución:**
|
||||
1. Verificar `FACTURAS_API_TOKEN` y `FACTURAS_ISSUER_RFC`
|
||||
2. Verificar que el rango de fechas no exceda el límite de la API
|
||||
3. Revisar logs del backend
|
||||
|
||||
### Banxico retorna error
|
||||
|
||||
**Causa:** Token inválido o serie incorrecta.
|
||||
|
||||
**Solución:**
|
||||
1. Verificar `BANXICO_TOKEN`
|
||||
2. La serie usada es `SF43718` (Fix de USD/MXN)
|
||||
3. Verificar que el token no haya expirado
|
||||
|
||||
---
|
||||
|
||||
## Producción
|
||||
|
||||
### PM2 no inicia al reiniciar servidor
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
pm2 startup
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### Nginx error 502 Bad Gateway
|
||||
|
||||
**Causa:** Backend no está corriendo o escucha en puerto diferente.
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
# Verificar proceso
|
||||
pm2 list
|
||||
|
||||
# Verificar puerto
|
||||
sudo ss -tlnp | grep 3000
|
||||
|
||||
# Reiniciar
|
||||
pm2 restart hotel-api
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### Certificado SSL expirado
|
||||
|
||||
**Solución:**
|
||||
```bash
|
||||
sudo certbot renew --dry-run
|
||||
sudo certbot renew
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> Para problemas no listados aquí, revisar los logs detallados:
|
||||
> - Backend: `pm2 logs hotel-api`
|
||||
> - Nginx: `sudo tail -f /var/log/nginx/hotel-error.log`
|
||||
> - PostgreSQL: `sudo tail -f /var/log/postgresql/postgresql-*.log`
|
||||
Reference in New Issue
Block a user