# 🗄️ 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`.
