# 🗄️ 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 > **Nota (2026-06-25):** La tabla `contracts` ahora incluye las columnas `id_position` e `id_area` para historizar el puesto y área del empleado en el momento del contrato. Las funciones `getcontracts()` y `reportemployeecontract()` muestran estos valores históricos en lugar de la posición/área actual del empleado. | Función | Parámetros | Retorno | Descripción | |---------|-----------|---------|-------------| | `getcontracts()` | — | setof record | Lista de contratos con posición/área histórica | | `getinfocontract(id)` | `int` | setof record | Info de un contrato | | `neartoend()` | — | setof record | Contratos próximos a vencer | | `newcontract(...)` | varios | `status` | Crea contrato guardando `id_position` e `id_area` | | `updatecontract(...)` | varios | `status` | Actualiza contrato incluyendo `id_position` e `id_area` | | `reportemployeecontract()` | — | setof record | Reporte de empleados-contratos con posición/área histórica | | `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. Incluye `id_position` e `id_area` para historizar puesto y área al momento del contrato | | `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`.