Base URL: /api
Formato: JSON
Auth: Bearer Token (header Authorization: Bearer <token>)
- Endpoints protegidos usan el middleware
protect(JWT). - Respuestas de auth usan
{ ok: boolean, ... }.
{
"ok": false,
"statusCode": 400,
"error": "BAD_REQUEST",
"message": "El campo 'email' es requerido."
}- Header:
Authorization: Bearer <token> - Errores comunes:
- 401 si no hay token, token inválido o usuario no existe.
Propósito: Verificar que el API está vivo y respondiendo.
🔐 Permisos: Público.
📥 Parámetros de Entrada
- Path Parameters: No aplica
- Query Parameters: No aplica
- Request Body: No aplica
📤 Respuesta Exitosa (200 OK)
{
"ok": true
}- 500 si falla el servidor.
Propósito: Estado de salud del backend (DB, servicios, uptime, etc.).
🔐 Permisos: Público.
📥 Parámetros de Entrada
- Path Parameters: No aplica
- Query Parameters: No aplica
- Request Body: No aplica
📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"info": {
"name": "wallets-backend",
"version": "1.0.0",
"env": "development",
"uptimeSeconds": 12345,
"timestamp": "2026-02-03T12:00:00.000Z"
},
"components": {
"db": { "ok": true, "latencyMs": 8, "error": null },
"exchangeRateApi": { "ok": true, "latencyMs": 120, "error": null }
},
"totalLatencyMs": 130
}- 500 si falla la verificación interna.
Propósito: Registra un usuario nuevo y lo autentica inmediatamente.
🔐 Permisos: Público.
📥 Request Body
{
"username": "demo",
"name": "Demo User",
"email": "demo@correo.com",
"password": "secret123"
}Validaciones:
username: mínimo 3 caracteres.email: formato válido y único.password: mínimo 6 caracteres.
📤 Respuesta Exitosa (201 Created)
{
"ok": true,
"token": "eyJhbGciOi...",
"user": {
"id": 1,
"username": "demo",
"email": "demo@correo.com",
"name": "Demo User"
}
}- 400 si faltan campos o no cumplen validación.
- 409 si el usuario o email ya existe.
💡 Nota de Lógica: al registrarse se crea automáticamente una cuenta inicial Efectivo (USD) con balance 0.
Propósito: Inicia sesión y devuelve un token JWT.
🔐 Permisos: Público.
📥 Request Body
{
"username": "demo",
"password": "secret"
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"token": "eyJhbGciOi...",
"user": {
"id": 1,
"username": "demo",
"email": "demo@correo.com",
"name": "Demo User"
}
}- 400 si falta usuario o contraseña.
- 401 si las credenciales son inválidas.
Propósito: Valida el token de Google, crea el usuario si no existe (onboarding) y devuelve un JWT propio.
🔐 Permisos: Público.
📥 Request Body
{
"token": "eyJhbGciOi..."
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"token": "eyJhbGciOi...",
"user": {
"id": 1,
"username": "demo",
"email": "demo@correo.com",
"name": "Demo User"
}
}- 400 si el body no contiene
token. - 401 si el token de Google es inválido o expiró.
💡 Nota de Lógica: si el usuario no existe, se crea automáticamente una cuenta inicial Efectivo (USD, balance 0) y categorías base (Comida, Transporte, Servicios, Salario).
Propósito: Obtiene el usuario autenticado actual.
🔐 Permisos: Requiere token.
📥 Parámetros de Entrada
- Path Parameters: No aplica
- Query Parameters: No aplica
- Request Body: No aplica
📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"user": {
"id": 1,
"username": "demo",
"email": "demo@correo.com",
"name": "Demo User"
}
}- 401 si el token es inválido o falta.
Propósito: Cierra sesión simbólicamente (invalida en cliente).
🔐 Permisos: Público.
📥 Parámetros de Entrada
- Path Parameters: No aplica
- Query Parameters: No aplica
- Request Body: No aplica
📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"message": "Logout exitoso. Elimine el token en el cliente."
}Propósito: Vincula un chat de Telegram con el usuario autenticado y guarda el JWT actual.
🔐 Permisos: Requiere token.
📥 Request Body
{
"chatId": 123456789,
"username": "mi_usuario"
}📤 Respuesta Exitosa (201 Created)
{
"ok": true,
"session": {
"chatId": 123456789,
"userId": 1,
"username": "mi_usuario",
"jwtToken": "eyJhbGciOi...",
"createdAt": "2026-02-09T12:00:00.000Z",
"updatedAt": "2026-02-09T12:00:00.000Z"
}
}- 400 si
chatIdno es válido o falta el token enAuthorization. - 401 si el token es inválido o falta.
💡 Nota de Lógica: si el usuario reinicia el bot, el vínculo se actualiza sin duplicar registros.
Propósito: Verifica si existe una sesión de Telegram por chatId y username.
🔐 Permisos: Público.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| chatId | int | ✅ | ID del chat de Telegram |
| username | string | ✅ | Username de Telegram |
📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"exists": true
}- 400 si
chatIdousernameno son válidos.
Propósito: Obtiene la sesión de Telegram por chatId.
🔐 Permisos: Público.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| chatId | int | ✅ | ID del chat de Telegram |
📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"session": {
"chatId": 123456789,
"userId": 1,
"username": "mi_usuario",
"jwtToken": "eyJhbGciOi...",
"createdAt": "2026-02-09T12:00:00.000Z",
"updatedAt": "2026-02-09T12:00:00.000Z"
}
}- 400 si
chatIdno es válido. - 404 si la sesión no existe.
Propósito: Elimina la sesión de Telegram por chatId.
🔐 Permisos: Público.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| chatId | int | ✅ | ID del chat de Telegram |
📤 Respuesta Exitosa (200 OK)
{
"ok": true
}- 400 si
chatIdno es válido. - 404 si la sesión no existe.
Propósito: Lista todas las cuentas del usuario.
🔐 Permisos: Requiere token.
📥 Parámetros de Entrada
- Path Parameters: No aplica
- Query Parameters: No aplica
- Request Body: No aplica
📤 Respuesta Exitosa (200 OK)
[
{
"id": 10,
"name": "Banco",
"type": "efectivo",
"currency": "USD",
"balance": "123.45",
"userId": 1
}
]- 401 si falta token.
Propósito: Crea una nueva cuenta.
🔐 Permisos: Requiere token.
📥 Request Body
{
"name": "Caja chica",
"type": "efectivo",
"currency": "USD",
"balance": 0
}📤 Respuesta Exitosa (201 Created)
{
"id": 15
}- 400 si el body no cumple el esquema (validator).
Propósito: Actualiza una cuenta por id.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| id | int | ✅ | — | ID de la cuenta |
📥 Request Body
{
"name": "Banco B",
"currency": "VES",
"balance": 1000
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"id": 10
}- 400 si
idno es válido. - 404 si la cuenta no existe.
💡 Nota de Lógica: si se envía balance, se crea automáticamente una transacción de Ajuste de Balance para mantener consistencia contable.
Propósito: Elimina una cuenta por id.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| id | int | ✅ | — | ID de la cuenta |
📤 Respuesta Exitosa (200 OK)
{
"ok": true
}- 400 si
idno es válido. - 404 si la cuenta no existe.
Propósito: Lista categorías del usuario.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción/Valores Permitidos |
|---|---|---|---|---|
| includeInStats | boolean | ❌ | null | true/false / 1/0 / yes/no |
| type | string | ❌ | null | income, expense, ingreso, gasto |
📤 Respuesta Exitosa (200 OK)
[
{
"id": 5,
"name": "Comida",
"type": "gasto",
"includeInStats": true,
"icon": "Pizza",
"color": "#F59E0B",
"colorName": "Amber",
"userId": 1
}
]- 400 si
includeInStatsotypeno son válidos.
Propósito: Crea una categoría.
🔐 Permisos: Requiere token.
📥 Request Body
{
"name": "Servicios",
"type": "expense",
"icon": "Plug",
"color": "#3B82F6",
"colorName": "Blue",
"includeInStats": true
}📤 Respuesta Exitosa (201 Created)
{
"id": 22
}- 400 por validación de esquema.
Propósito: Actualiza una categoría.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| id | int | ✅ | — | ID de categoría |
📥 Request Body
{
"name": "Servicios hogar",
"type": "gasto",
"includeInStats": false
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"id": 22
}- 400 si
idno es válido. - 404 si la categoría no existe.
Propósito: Elimina una categoría.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| id | int | ✅ | — | ID de categoría |
📤 Respuesta Exitosa (200 OK)
{
"ok": true
}- 400 si
idno es válido. - 404 si la categoría no existe.
Propósito: Activa includeInStats=true en lote.
🔐 Permisos: Requiere token.
📥 Request Body
{
"ids": [1, 2, 3]
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"rowCount": 3
}- 400 si el body no cumple el esquema.
Propósito: Desactiva includeInStats=false en lote.
🔐 Permisos: Requiere token.
📥 Request Body
{
"ids": [1, 2, 3]
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"rowCount": 3
}- 400 si el body no cumple el esquema.
Propósito: Lista transacciones (simple o agrupadas por fecha).
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción/Valores Permitidos |
|---|---|---|---|---|
| grouped | string | ❌ | null | 1 para resultados agrupados |
| pageSize | int | ❌ | 20 | Solo cuando grouped=1 |
| cursorDate | string | ❌ | null | Paginación por fecha (YYYY-MM-DD) |
| q | string | ❌ | null | Búsqueda por descripción o nombre de categoría |
| type | string | ❌ | null | income, expense, ingreso, gasto |
| categoryId | string | ❌ | null | ID o lista separada por coma |
| accountId | string | ❌ | null | ID o lista separada por coma |
| date | string | ❌ | null | Fecha exacta YYYY-MM-DD |
| dateFrom | string | ❌ | null | Inicio de rango YYYY-MM-DD |
| dateTo | string | ❌ | null | Fin de rango YYYY-MM-DD |
| month | string | ❌ | null | YYYY-MM |
| includeInStats | boolean | ❌ | null | true/false/1/0 |
📤 Respuesta Exitosa (200 OK) - modo simple
[
{
"id": 100,
"description": "Pago luz",
"amount": "15.00",
"currency": "USD",
"amountUsd": "15.00",
"exchangeRateUsed": "35.500000",
"date": "2026-02-01",
"categoryId": 2,
"accountId": 1,
"type": "gasto"
}
]📤 Respuesta Exitosa (200 OK) - modo agrupado
{
"items": [
{
"id": 100,
"description": "Pago luz",
"amount": "15.00",
"currency": "USD",
"amountUsd": "15.00",
"exchangeRateUsed": "35.500000",
"date": "2026-02-01",
"categoryId": 2,
"accountId": 1,
"type": "gasto"
}
],
"hasMore": true,
"nextCursorDate": "2026-01-15"
}- 401 si falta token.
💡 Nota de Lógica: grouped=1 devuelve un bloque de días completo hasta pageSize transacciones y genera nextCursorDate si hay más.
Propósito: Crea una transacción de ingreso o gasto.
🔐 Permisos: Requiere token.
📥 Request Body
{
"description": "Pago luz",
"amount": 15.00,
"currency": "USD",
"date": "2026-02-01",
"categoryId": 2,
"accountId": 1,
"commission": 0
}📤 Respuesta Exitosa (201 Created)
{
"ok": true,
"newId": 101,
"tx": {
"id": 101,
"description": "Pago luz",
"amount": "15.00",
"currency": "USD",
"amountUsd": "15.00",
"exchangeRateUsed": null,
"date": "2026-02-01",
"categoryId": 2,
"accountId": 1,
"type": "expense"
},
"commissionTx": null
}- 400 por validación de esquema.
- 400/500 si la categoría o cuenta no pertenece al usuario.
💡 Nota de Lógica: si commission > 0, se crea una transacción adicional de comisión.
Propósito: Crea una transferencia entre cuentas (genera 2 o 3 transacciones).
🔐 Permisos: Requiere token.
📥 Request Body
{
"fromAccountId": 1,
"toAccountId": 2,
"amount": 100,
"commission": 1.5,
"date": "2026-02-01",
"concept": "Ahorro"
}📤 Respuesta Exitosa (201 Created)
{
"ok": true,
"transfer": {
"outTx": { "id": 200, "type": "expense" },
"inTx": { "id": 201, "type": "income" },
"commissionTx": { "id": 202, "type": "expense" }
}
}- 400 si
fromAccountIdytoAccountIdson iguales. - 400 si el monto es inválido o la fecha falta.
- 400 si monedas entre cuentas son distintas (no soportado).
💡 Nota de Lógica: el sistema crea categorías “Transferencia” y “comision” si no existen y evita duplicados recientes (ventana de 2 minutos).
Propósito: Actualiza una transacción.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| id | int | ✅ | — | ID de transacción |
📥 Request Body
{
"description": "Pago luz - Feb",
"amount": 20,
"currency": "USD",
"date": "2026-02-02",
"categoryId": 2,
"accountId": 1
}📤 Respuesta Exitosa (200 OK)
{
"ok": true,
"tx": {
"id": 101,
"description": "Pago luz - Feb",
"amount": "20.00",
"currency": "USD",
"amountUsd": "20.00",
"exchangeRateUsed": null,
"date": "2026-02-02",
"categoryId": 2,
"accountId": 1,
"type": "expense"
},
"message": "Transacción actualizada"
}- 400 si
ides inválido. - 404 si la transacción no existe o no pertenece al usuario.
💡 Nota de Lógica: se recalculan balances y tasas si cambia monto/moneda/fecha.
Propósito: Elimina una transacción y revierte su impacto en balance.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| id | int | ✅ | — | ID de transacción |
📤 Respuesta Exitosa (200 OK)
{
"ok": true
}- 404 si la transacción no existe o no pertenece al usuario.
Propósito: Exporta transferencias (XLSX/PDF) con filtros simples.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| format | string | ❌ | xlsx | pdf o xlsx |
| from_date | string | ❌ | null | YYYY-MM-DD |
| to_date | string | ❌ | null | YYYY-MM-DD |
| account_id | int | ❌ | null | ID cuenta |
| include_commission | boolean | ❌ | false | Incluye comisiones |
📤 Respuesta Exitosa (200 OK)
- Archivo descargable
transfers_YYYY-MM-DD.pdf|xlsx.
- 400 si el formato o fechas no son válidas.
Propósito: Exportación avanzada de transferencias o lista de transacciones (según body).
🔐 Permisos: Requiere token.
📥 Request Body (modo exportar transferencias)
{
"format": "xlsx",
"from_date": "2026-01-01",
"to_date": "2026-02-01",
"account_id": 1,
"include_commission": false
}📥 Request Body (modo exportar lista de transacciones)
{
"format": "pdf",
"title": "Mis Transacciones",
"createdBy": "demo@correo.com",
"items": [
{
"id": 100,
"description": "Pago luz",
"amount": 15,
"currency": "USD",
"amountUsd": 15,
"exchangeRateUsed": 35.5,
"date": "2026-02-01",
"categoryId": 2,
"accountId": 1,
"type": "expense"
}
],
"accounts": [
{ "id": 1, "name": "Banco", "currency": "USD" }
],
"categories": [
{ "id": 2, "name": "Servicios", "type": "gasto" }
]
}📤 Respuesta Exitosa (200 OK)
- Archivo descargable
transfers_YYYY-MM-DD.pdf|xlsxotransactions_YYYY-MM-DD.pdf|xlsx.
- 400 si el formato o fechas no son válidas.
💡 Nota de Lógica: si el body incluye items[], se usa un renderizado más visual (React-PDF) o se fallback a PDFKit.
Propósito: Exporta todas las transacciones con filtros (PDF/XLSX) desde base de datos.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| format | string | ❌ | pdf o xlsx |
|
| q | string | ❌ | null | Búsqueda por descripción o categoría |
| type | string | ❌ | null | income, expense, ingreso, gasto |
| categoryId | string | ❌ | null | ID o lista separada por coma |
| accountId | string | ❌ | null | ID o lista separada por coma |
| date | string | ❌ | null | YYYY-MM-DD |
| dateFrom | string | ❌ | null | YYYY-MM-DD |
| dateTo | string | ❌ | null | YYYY-MM-DD |
| month | string | ❌ | null | YYYY-MM |
| includeInStats | boolean | ❌ | null | true/false/1/0 |
📤 Respuesta Exitosa (200 OK)
- Archivo descargable
transactions_YYYY-MM-DD.pdf|xlsx.
- 400 si
formates inválido.
Propósito: Retorna balance consolidado o de una cuenta específica (USD).
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| accountId | string | ❌ | null | ID o lista separada por coma |
| q | string | ❌ | null | Filtro por descripción/categoría |
| categoryId | string | ❌ | null | ID o lista |
| date | string | ❌ | null | YYYY-MM-DD |
| dateFrom | string | ❌ | null | YYYY-MM-DD |
| dateTo | string | ❌ | null | YYYY-MM-DD |
| month | string | ❌ | null | YYYY-MM |
| includeInStats | boolean | ❌ | null | true/false/1/0 |
📤 Respuesta Exitosa (200 OK) - múltiples cuentas
{
"ok": true,
"balance": {
"accounts_total_usd": 300.50,
"income_total_usd": 1000,
"expense_total_usd": 699.5,
"net_total_usd": 300.5
}
}📤 Respuesta Exitosa (200 OK) - una sola cuenta
{
"ok": true,
"balance": 120.25
}Propósito: Resumen de ingresos (total o mensual).
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| from_month | string | ❌ | null | YYYY-MM para resumen mensual |
| to_month | string | ❌ | null | YYYY-MM |
| includeInStats | boolean | ❌ | null | true/false/1/0 |
| q | string | ❌ | null | búsqueda |
| categoryId | string | ❌ | null | ID o lista |
| accountId | string | ❌ | null | ID o lista |
| date | string | ❌ | null | YYYY-MM-DD |
| dateFrom | string | ❌ | null | YYYY-MM-DD |
| dateTo | string | ❌ | null | YYYY-MM-DD |
| month | string | ❌ | null | YYYY-MM |
📤 Respuesta Exitosa (200 OK) - total simple
{
"ok": true,
"income_total": 1000.5
}📤 Respuesta Exitosa (200 OK) - resumen mensual
{
"ok": true,
"income_total": 1500,
"income": [
{
"income_2026-01": 500,
"income_2026-02": 1000
}
]
}Propósito: Resumen de gastos (total o mensual).
🔐 Permisos: Requiere token.
📥 Query Parameters (mismos que /summary/income)
📤 Respuesta Exitosa (200 OK) - total simple
{
"ok": true,
"expense_total": 700.25
}📤 Respuesta Exitosa (200 OK) - resumen mensual
{
"ok": true,
"expense_total": 900,
"expense": [
{
"expense_2026-01": 400,
"expense_2026-02": 500
}
]
}Propósito: Flujo neto de caja y tasa de ahorro por periodo.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| from_date | string | ✅ | — | YYYY-MM-DD |
| to_date | string | ✅ | — | YYYY-MM-DD |
| time_unit | string | ❌ | month | month o week |
| accountId | string | ❌ | null | ID o lista |
📤 Respuesta Exitosa (200 OK)
{
"summary": {
"total_income": 1500,
"total_expenses": 900,
"net_cash_flow": 600,
"avg_savings_rate": 0.4
},
"time_series": [
{
"period": "2026-01",
"income": 700,
"expenses": 300,
"net_flow": 400,
"savings_rate": 0.57
}
]
}- 400 si faltan fechas o formato inválido.
Propósito: Heatmap de gastos por categoría y día de semana.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| from_date | string | ✅ | — | YYYY-MM-DD |
| to_date | string | ✅ | — | YYYY-MM-DD |
| accountId | string | ❌ | null | ID o lista |
📤 Respuesta Exitosa (200 OK)
{
"categories": ["Comida", "Servicios"],
"weekdays": ["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],
"data_points": [
{ "category_idx": 0, "day_idx": 2, "amount": 40 }
],
"summary": {
"peak_category": "Comida",
"peak_day": "Martes"
}
}Propósito: Estadísticas de volatilidad (boxplot) por categoría de gastos.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| from_date | string | ✅ | — | YYYY-MM-DD |
| to_date | string | ✅ | — | YYYY-MM-DD |
| top_n_categories | int | ❌ | 5 | Top N categorías |
📤 Respuesta Exitosa (200 OK)
{
"categories_data": [
{ "category": "Comida", "count": 10, "q1": 5, "median": 10, "q3": 20, "min": 2, "max": 40, "outliers": [60] }
]
}Propósito: Comparación Month-over-Month (gastos).
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| date | string | ❌ | hoy | Fecha base YYYY-MM-DD |
📤 Respuesta Exitosa (200 OK)
{
"summary": {
"current_period_name": "Febrero MTD (1-3)",
"previous_period_name": "Enero MTD (1-3)",
"current_total": 300,
"previous_total": 200,
"total_delta_usd": 100,
"total_delta_percent": 0.5
},
"categories_comparison": [
{ "category": "Comida", "current_amount": 120, "previous_amount": 80, "delta_percent": 0.5 }
]
}Propósito: Proyección de gasto mensual y desviación de presupuesto.
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| date | string | ❌ | hoy | Fecha base YYYY-MM-DD |
| accountId | string | ❌ | null | ID o lista |
| budget_total | number | ❌ | null | Presupuesto mensual |
📤 Respuesta Exitosa (200 OK)
{
"current_date": "2026-02-03",
"days_in_month": 28,
"days_elapsed": 3,
"current_spending_mtd": 40,
"avg_daily_spending": 13.33,
"projected_total_spending": 373.24,
"budget_total": 300,
"projected_over_under": 73.24
}Propósito: Heatmap de ingresos por categoría y día.
🔐 Permisos: Requiere token.
📥 Query Parameters (mismos que /stats/spending-heatmap)
📤 Respuesta Exitosa (200 OK)
{
"categories": ["Salario"],
"weekdays": ["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],
"data_points": [
{ "category_idx": 0, "day_idx": 1, "amount": 1000 }
],
"summary": {
"peak_category": "Salario",
"peak_day": "Lunes"
}
}Propósito: Volatilidad de ingresos por categoría.
🔐 Permisos: Requiere token.
📥 Query Parameters (mismos que /stats/expense-volatility)
📤 Respuesta Exitosa (200 OK)
{
"categories_data": [
{ "category": "Salario", "count": 3, "q1": 900, "median": 1000, "q3": 1100, "min": 850, "max": 1200, "outliers": [] }
]
}Propósito: Comparación Month-over-Month (ingresos).
🔐 Permisos: Requiere token.
📥 Query Parameters
| Parámetro | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| date | string | ❌ | hoy | Fecha base YYYY-MM-DD |
📤 Respuesta Exitosa (200 OK)
{
"summary": {
"current_period_name": "Febrero MTD (1-3)",
"previous_period_name": "Enero MTD (1-3)",
"current_total": 1500,
"previous_total": 1300,
"total_delta_usd": 200,
"total_delta_percent": 0.15
},
"categories_comparison": [
{ "category": "Salario", "current_amount": 1500, "previous_amount": 1300, "delta_percent": 0.15 }
]
}- 400 Validación (Joi) o parámetros inválidos.
- 401 No autorizado (token faltante/ inválido).
- 404 Recurso inexistente.
- 500 Error interno.
✅ Cobertura total: Todos los endpoints definidos en routes/ están documentados por módulo funcional.