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:
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`.
|
||||
Reference in New Issue
Block a user