@fullqueso/mcp-bc-gastos 1.28.0 → 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -17,3 +17,10 @@ BC_COMPANY_FQFR=guid-for-franquicias
17
17
 
18
18
  # Optional
19
19
  LOG_LEVEL=info
20
+
21
+ # Per-request BC fetch timeout (ms). A hung request is aborted + retried instead
22
+ # of blocking the MCP forever (BUG #1 fix). Default 90000. Raise for heavy reports.
23
+ BC_FETCH_TIMEOUT_MS=90000
24
+
25
+ # CRM rate endpoint (get_crm_rate) — opcional, default abajo
26
+ CRM_RATE_BASE=https://crm-rate.fullqueso.com/api/v1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,132 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [1.33.0] — 2026-07-11
4
+
5
+ ### Added — `get_income_statement`: estado de resultados en formato Power BI (Level 1)
6
+ Nuevo tool en `tools/financials/income-statement.js`. READ-ONLY.
7
+
8
+ - **Qué expone:** el reporte "Income Statement by Month" de Power BI (G/L Account **Level 1**)
9
+ para una tienda y un mes, agregando los General Ledger entries por cuenta level-1 con la
10
+ convención de signos de Power BI (`amount = debitAmount − creditAmount` → ingresos negativos,
11
+ costos/gastos positivos, Total negativo = ganancia). Devuelve las 3 secciones (40001/50001/60001),
12
+ cuentas level-1, totales, Total general y un **`accounts_map`** listo para el generador Excel
13
+ del skill-fq-resultados-mensual (`data.json`). Con `level=2` incluye las cuentas de posteo
14
+ hijas con su nombre real de BC.
15
+ - **Por qué:** resuelve las fronteras que `get_financial_statements` (agrega por categoría) no
16
+ separa: nómina **71000** vs impuestos **74000**, y otros **74000** (80xxx) vs **90000** (9xxxx).
17
+ Trabaja desde los entries por cuenta, así que cuadra 1:1 con Power BI.
18
+ - **Estructura level-1** embebida (`COA_PBI`) — fuente: screenshots Power BI FQ28, CoA compartido
19
+ FQ01/FQ28/FQ88. Espejo de `skill-fq-resultados-mensual/references/income_statement_coa.json`.
20
+ - **Nuevo en bc-client:** `getChartOfAccountNames(companyId)` (entidad BC `accounts`, cache por
21
+ companyId) para etiquetar las cuentas hijas en level 2.
22
+ - **Test:** `tests/test-income-statement.js` (agregación pura con GL entries mock, sin BC):
23
+ valida signos, mapeo por rangos, fronteras 71000/74000 y 74000/90000, totales y level 2.
24
+ - La columna **Ajustado** (drafts como posteados) y la reclasificación de dividendos las agrega
25
+ el skill sobre el `posted` — el MCP entrega solo el posted objetivo del GL.
26
+
27
+ ## [1.32.1] — 2026-07-04
28
+
29
+ ### Docs — README "Tools" regenerado desde el código (22 stale → 57 reales)
30
+ - **`scripts/generate-tools-doc.mjs`**: importa dinámicamente todos los `*Tool` de
31
+ `tools/**/*.js` (incluye definiciones en `index.js`, dedup por `name`), agrupa por
32
+ categoría y reescribe la sección `## Tools (N)` del README. Verificado 57/57 contra
33
+ el switch de `server.js`. Uso: `node scripts/generate-tools-doc.mjs --write`
34
+ (sin flag = dry-run a stdout). La sección queda marcada como autogenerada.
35
+
36
+ ## [1.32.0] — 2026-07-04
37
+
38
+ ### Added — `get_unposted_invoices`: facturas sin postear para Cierre Parcial
39
+ Nuevo tool en el cluster de draft visibility (`tools/multi-payment/unposted-invoices.js`).
40
+ Verificado contra BC real (mayo 2026, 4 empresas). READ-ONLY.
41
+
42
+ - **Qué expone:** facturas de compra y venta con `status` `Draft`/`In Review` en
43
+ `api/v2.0` `purchaseInvoices`/`salesInvoices` — documentos que NO están en el GL.
44
+ Complementa a `get_draft_payables`/`receivables`/`summary`, que son Multi-Payments
45
+ (settlement) sobre facturas ya posteadas y por tanto no sirven como ajuste P&L.
46
+ - **Para qué:** ajuste de CIERRE PARCIAL del P&L en `skill-fq-resultados-mensual`
47
+ (compras draft → +costo/gasto; ventas draft no-IC → +ingreso). Cada tienda incluye
48
+ `pl_adjustment_hint` con los montos listos.
49
+ - **Params:** `store` (incl. `all`), `type` (purchases|sales|both), `start_date`/`end_date`
50
+ (filtro por `postingDate`), `include_in_review` (default true), `summary_only`.
51
+ - **Multi-moneda correcto:** totales SIEMPRE por moneda (`by_currency`,
52
+ `non_ic_by_currency`, consolidado por moneda) — nunca se suman VES con USD.
53
+ `LCY` = USD; VES se convierte aguas arriba con `get_exchange_rate`.
54
+ - **Ventas IC:** separa clientes `IC-*` (`intercompany`) del total ajustable.
55
+ - **Smoke test:** `tests/test-unposted-invoices.js` (store y mes por CLI).
56
+ - Hallazgo del estreno (mayo 2026): FQ01 11 compras sin postear (Bs 1,838,737.54),
57
+ FQ28 4 (Bs 1,210,031.04), FQFR 2 ($7,116.50), FQ01 1 venta IC (Bs 165,456.04).
58
+
59
+ ## [1.31.0] — 2026-07-01
60
+
61
+ ### Added — KPIs de nómina operativa: tienda vs eventos, activos vs pagados
62
+ Contratos existentes intactos (solo se agregan campos). Todo USD. Verificado contra BC real (mayo 2026, FQ01/FQ28/FQ88/FQFR).
63
+
64
+ - **`paid_employees` en `get_payroll_documents` (con `include_employee_count=true`).** Además de `summary.unique_employees`, ahora `summary.paid_employees`: lista agregada por empleado en el período con `{ employee_code, employee_name, cedula, cost_usd, lines, payroll_types:[...], classification }`. Permite cruzar activos vs pagados sin leer las líneas doc por doc.
65
+ - **Deduplicación por cédula.** `unique_employees` y `paid_employees` deduplican por `cedula` cuando existe (una persona puede tener un código SEMANAL y otro EVENTO → se consolidan costo y líneas en un registro). `summary.duplicate_codes: [{ cedula, codes:[...] }]` expone los casos multi-código para limpieza en BC. Si `cedula` es null se trata el código como persona única (no se fusiona por nombre). El consolidado (`store="all"`) re-deduplica también entre tiendas (misma cédula en dos tiendas = una persona).
66
+ - Resolución de cédula en 2 pasos: si un código tiene cédula en alguna línea, las líneas con cédula en blanco de ese código heredan la cédula (evita partir un código en dos personas). Verificado mayo 2026: 0 splits, 0 merges; 82 personas = 58 con cédula (1:1) + 24 códigos sin cédula en BC (data-quality, tratados como únicos).
67
+ - **Clasificación evento/operativo por empleado** (relevante FQ01): `classification` = `"evento"` (todos sus pagos EVENTO), `"operativo"` (ningún EVENTO — SEMANAL/VACACIONES/LIQUIDACION), `"mixto"` (tiene EVENTO y no-EVENTO). Evita marcar como "inactivo no declarado" a quien solo trabaja eventos.
68
+ - **Buckets de nómina tienda vs eventos** en `summary` (per-store y consolidado): `store_payroll` = Σ `cost_usd` de docs SEMANAL + VACACIONES + LIQUIDACION; `event_payroll` = Σ EVENTO. `total_cost_usd` se mantiene igual (gerencia queda solo ahí). Constantes `STORE_PAYROLL_TYPES`/`EVENT_PAYROLL_TYPES` en `tools/payroll/cost-model.js`.
69
+
70
+ ### Added — Denominador de ventas correcto para el KPI (get_sales_analysis / compare_sales_by_store)
71
+ - **`revenue_store`, `revenue_events`, `revenue_intercompany`, `revenue_non_sales` + `revenue_total`** en `summary` (y por tienda en `compare_sales_by_store`), además del `total_revenue_usd` actual (sin cambios). Identidad: `revenue_total = revenue_store + revenue_intercompany + revenue_events + revenue_non_sales`.
72
+ - **`revenue_store = revenue_total − intercompañía − eventos − no-venta`.** Se excluye del denominador de tienda la venta intercompañía, la venta de eventos y el ingreso **no-venta** (FX gains + rebajas de compra) — este último por recomendación aceptada por FP.
73
+ - **Cuentas configurables (no inline), por tienda** en `config/income-accounts.js`, verificadas contra el CoA real:
74
+ - `INTERCOMPANY_REVENUE_ACCOUNTS = ['40310']` (tiendas operativas; FQ01/FQ88, FQ28=0).
75
+ - `EVENT_REVENUE_ACCOUNTS = ['40180','40700']` (solo FQ01 con movimiento).
76
+ - `NON_SALES_REVENUE_ACCOUNTS = ['40580','40540','40550']` (dif. cambiario + descuentos/rebajas de compra).
77
+ - **FQFR** usa `FQFR_INTERCOMPANY_REVENUE_ACCOUNTS = ['40420','40430','40660','40930']` (royalties/fees del master de franquicia, NO 40310) vía `getRevenueClassification(storeCode)` → sus buckets salen correctos aunque el KPI no aplique.
78
+ - Lógica en `splitRevenueBuckets(glRevenue, total, storeCode)` (`utils/sales-aggregation.js`); `calculateSummary` ahora recibe `storeCode` (los dos tools de ventas lo pasan por tienda).
79
+ - **KPI** (lo calcula el consumidor cruzando ambos tools): `nomina_tienda_pct = store_payroll / revenue_store`; `nomina_eventos_pct = event_payroll / revenue_events` (**null si `revenue_events = 0`** → solo FQ01). **FQFR excluido por completo** del KPI (`KPI_PAYROLL_STORES = ['FQ01','FQ28','FQ88']`).
80
+ - Verificado mayo 2026 (2 fases, `tests/verify-payroll-sales-kpi.js`):
81
+ - **Estricto** (− interco − eventos), coincide con la validación de FP: FQ01 8.36% tienda / 31.02% eventos (rev_store 86,987.43); FQ28 10.22% (77,407.51); FQ88 8.75% (150,122.32).
82
+ - **Refinado** (además − no-venta): FQ01 8.36% (86,940.37); FQ28 10.25% (77,146.08); FQ88 8.80% (149,322.16).
83
+
84
+ ### Nota operativa
85
+ - **Reiniciar Claude Desktop** tras el publish para que los tools recarguen (G-002).
86
+
87
+ ---
88
+
89
+ ## [1.30.0] — 2026-07-01
90
+
91
+ ### Fixed
92
+ - **BUG #1 — `get_employees`/`get_payroll_documents` con `store="all"` podían colgar el MCP indefinidamente.** Causa raíz: `fetch()` no tenía timeout → una sola petición colgada nunca resolvía, y como el consolidado usaba `Promise.all`, una tienda colgada tumbaba todo el call (aparecía como "MCP no responde").
93
+ - **`lib/bc-client.js` — timeout por petición (AbortController).** Cada intento de `fetchWithRetry` aborta a los `BC_FETCH_TIMEOUT_MS` (default **90000**, env-tunable) y reintenta; al agotar reintentos lanza un error claro de timeout en vez de colgarse. Cubre token + todas las llamadas v2.0/OData/custom (afecta a las 54 tools; el default generoso no toca ninguna llamada legítima — todas las observadas <1s).
94
+ - **Consolidación multi-tienda resiliente.** `get_payroll_documents` y `get_employees` con `store="all"` ahora usan `Promise.allSettled`: una tienda que falle/timeoutee devuelve data parcial + un array `errors[]` por tienda, nunca cuelga ni bota el consolidado.
95
+ - Verificado: per-store discovery <500ms; el timeout aborta+reintenta (3x) y lanza error en ~6s con `BC_FETCH_TIMEOUT_MS=1`.
96
+
97
+ ### Changed / Added — KPIs de nómina operativa
98
+ - **#2/#3 — Costo total en USD por documento (`total_cost_usd`) robusto a drafts sin distribuir.** Nuevo módulo `tools/payroll/cost-model.js` como **fuente única de verdad del costo**. Regla (verificada contra 448 docs reales): `total_cost_usd = totalGrossUSD (>0) → si no, bonos-equivalente USD → si no, neto (MAX, no suma)`. El campo `cost_basis` (`gross`|`bonuses`|`net`) hace auditable de dónde salió el número, y `net_distributed:false` marca los drafts cuyo neto aún no se reparte por método de pago (elimina el parche `max(neto,bonos)`).
99
+ - **Por qué la suma `net_usd + net_ves/tasa` estaba mal:** VACACIONES/LIQUIDACION contabilizan el MISMO monto en `totalNetUSD` **y** `totalNetVES` → esa suma **duplica** el costo. `totalGrossUSD` es el costo real y ya está en el header (antes no se seleccionaba).
100
+ - Expuestos en cada documento: `total_cost_usd`, `cost_basis`, `net_distributed`, `total_gross_usd`, `total_deductions_usd`. Agregado `total_cost_usd` a `summary` y `consolidated`.
101
+ - **#4 — Enums de `payroll_type` corregidos a los valores reales de BC + `exclude_managerial`.** El enum viejo `[SEMANAL|QUINCENAL|MENSUAL]` estaba mal (QUINCENAL/MENSUAL no existen).
102
+ - `get_payroll_documents`: `SEMANAL, GERENCIAL, GERENCIAL_CIERRE, EVENTO, VACACIONES, LIQUIDACION`. Nuevo flag `exclude_managerial` → excluye GERENCIAL + GERENCIAL_CIERRE server-side (nómina operativa en un solo call).
103
+ - `get_employees`: `SEMANAL, GERENCIAL, EVENTO`. Nuevo flag `exclude_managerial` → excluye `payrollType GERENCIAL` + `employeeType Management`.
104
+ - **#5 — `summary.by_type` ahora trae costo, no solo conteo.** De `{ TIPO: n }` a `{ TIPO: { count, lines, cost_usd } }` (per-store y consolidado).
105
+ - **#6 — Headcount único de empleados pagados en el periodo.** Nuevo flag `include_employee_count` en `get_payroll_documents` → `summary.unique_employees` (y `consolidated.unique_employees`). Lee las líneas de los docs filtrados en lotes de OR (no doc-por-doc). Distingue el headcount real de `total_employees_lines` (que duplica al mismo empleado por cada corrida semanal).
106
+ - **#7 — Costo por empleado en USD en `get_payroll_lines`.** Expuesto `total_ves_equivalent` (campo de la API antes sin usar) y `cost_usd` por línea = `totalVESEquivalent / tasa` (la tasa se lee del header en 1 llamada extra). `summary` trae `total_cost_usd`, `total_ves_equivalent`, `payroll_type`, `status`, `exchange_rate`.
107
+ - **Nota (L-001):** la API `fqPayrollLines` **no** expone días/horas trabajados ni FTE por línea → no se pueden calcular KPIs de productividad laboral sin ampliar la extensión de BC. Documentado, no fabricado.
108
+
109
+ ### Nota operativa
110
+ - **Reiniciar Claude Desktop** tras el publish para que los tools recarguen (G-002).
111
+
112
+ ---
113
+
114
+ ## [1.29.0] — 2026-06-21
115
+
116
+ ### Added
117
+ - **`get_crm_rate` — tasa de cambio Bs/USD desde el CRM de Full Queso (tasa de referencia, hora Caracas).** Complementa a `get_exchange_rate` (que trae la tasa BCV contable vigente en BC): este tool consulta el endpoint del CRM `https://crm-rate.fullqueso.com` y expone tanto la tasa **USDT/Binance** (paralelo) como la **BCV** (oficial), histórica o del día.
118
+ - **Dos monedas con sinónimos:** `coin` acepta `USDT`/`BINANCE` (paralelo, mismo valor) y `VES`/`BCV` (oficial). La descripción dispara con "tasa USDT, tasa Binance, dólar Binance, tasa paralelo/paralela, tasa BCV, tasa oficial, tasa del día, tasa histórica".
119
+ - **Histórica vs del día automático:** con `date` (YYYY-MM-DD) → `GET /api/v1/history/price?coin={VES|USDT}&date=...`; sin `date` → `GET /api/v1/coin/{bcv|usdt}` (momento de la consulta).
120
+ - Maneja `400` (coin/date inválidos → error claro), `404` (sin registro → `price: null` + mensaje) y fallo de conexión. Precio redondeado a 2 decimales; respuesta incluye `queried_at_caracas` y `source`.
121
+ - Base URL configurable vía env **`CRM_RATE_BASE`** (default `https://crm-rate.fullqueso.com/api/v1`). El endpoint no requiere auth de BC → tool autónomo (ignora `bcClient`).
122
+ - Nuevo `tools/get-crm-rate.js`; registrado en `server.js` (import + lista de tools + switch).
123
+ - **Verificado contra el endpoint real (21-jun-2026):** USDT del día ≈798.35; BCV del día 612.43; histórica USDT 19-jun 805.40; histórica VES 19-jun **607.39** (coincide con el ejemplo del doc); 404 (1990) y coin inválido (DOGE) manejados. `node --check` OK.
124
+
125
+ ### Nota operativa
126
+ - **Reiniciar Claude Desktop** tras el publish para que el tool recargue (G-002).
127
+
128
+ ---
129
+
3
130
  ## [1.28.0] — 2026-06-12
4
131
 
5
132
  ### Added
package/README.md CHANGED
@@ -52,111 +52,264 @@ cp .env.example .env
52
52
  npm start
53
53
  ```
54
54
 
55
- ## Tools (22)
55
+ ## Tools (58)
56
56
 
57
- ### Expense Analysis (5 tools)
57
+ _Sección generada por `scripts/generate-tools-doc.mjs` — no editar a mano._
58
+ _Regenerar con: `node scripts/generate-tools-doc.mjs --write`_
58
59
 
59
- #### get_expense_analysis
60
- Detailed expense analysis by category with account-level detail and benchmark comparisons.
61
- - Params: `stores`, `period`, `month`, `start_date`, `end_date`
62
-
63
- #### get_efficiency_ratios
64
- Financial ratios: expense-to-income, payroll, rent, utilities, marketing, operating margin.
65
- - Params: `stores`, `period`, `month`, `start_date`, `end_date`
60
+ ### Expense Analysis & Core (11 tools)
66
61
 
67
62
  #### compare_stores
68
- Compare all stores with efficiency rankings, variances, and savings opportunities.
63
+ Compara la eficiencia de gastos entre las tiendas de Full Queso (FQ01 Chacao, FQ28 Marqués, FQ88 Candelaria).
69
64
  - Params: `period`, `month`, `start_date`, `end_date`
70
65
 
71
66
  #### detect_anomalies
72
- Detect expense anomalies with severity levels, root causes, and recommended actions.
73
- - Params: `stores`, `period`, `month`, `start_date`, `end_date`
67
+ Detecta anomalías en los gastos operacionales de Full Queso: gastos por encima de benchmarks, incrementos inusuales vs periodo anterior, concentración excesiva en una cuenta, y alertas de margen.
68
+ - Params: `period`, `month`, `start_date`, `end_date`, `stores`, `sensitivity`
74
69
 
75
- #### get_trends
76
- Historical trend analysis (up to 6 months) with growth rates, seasonality, and ASCII charts.
77
- - Params: `store`, `months`
70
+ #### get_account_transactions
71
+ Listado completo de transacciones para una cuenta contable específica con balance running y nombre de proveedor.
72
+ - Params: `account_number`*, `store`*, `start_date`*, `end_date`*
78
73
 
79
- ### Drill-Down & Vendors (4 tools)
74
+ #### get_crm_rate
75
+ Obtiene la tasa de cambio Bs/USD desde el CRM de Full Queso (hora Caracas).
76
+ - Params: `coin`*, `date`
77
+
78
+ #### get_efficiency_ratios
79
+ Calcula ratios de eficiencia financiera de Full Queso: gastos/ingresos, nómina/ingresos, alquiler/ingresos, servicios/ingresos, marketing/ingresos y margen operativo.
80
+ - Params: `period`, `month`, `start_date`, `end_date`, `stores`
81
+
82
+ #### get_exchange_rate
83
+ Obtiene la tasa de cambio USD → VES desde Business Central.
84
+ - Params: `store`*, `date`, `start_date`, `end_date`
85
+
86
+ #### get_expense_analysis
87
+ Análisis detallado de gastos operacionales de Full Queso por categoría (nómina, alquiler, servicios, marketing, etc.) con números de cuenta específicos.
88
+ - Params: `period`, `month`, `start_date`, `end_date`, `stores`
80
89
 
81
90
  #### get_expense_details
82
- Transaction-level drill-down with vendor lookup, category/account filters, and pagination.
83
- - Params: `store` (req), `period`, `month`, `start_date`, `end_date`, `category`, `account_number`, `min_amount`, `vendor_search`, `limit`, `offset`
91
+ Drill-down de transacciones individuales de gastos operacionales.
92
+ - Params: `store`*, `period`, `month`, `start_date`, `end_date`, `category`, `account_number`, `min_amount`, `vendor_search`, `limit`, `offset`
84
93
 
85
- #### get_account_transactions
86
- Per-account ledger with running balance and vendor information.
87
- - Params: `account_number` (req), `store` (req), `start_date` (req), `end_date` (req)
94
+ #### get_trends
95
+ Análisis de tendencias históricas de gastos e ingresos de Full Queso.
96
+ - Params: `months`, `store`
88
97
 
89
98
  #### get_vendor_transactions
90
- All transactions for a vendor (partial name search) with account breakdown.
91
- - Params: `store` (req), `vendor_search` (req), `start_date` (req), `end_date` (req)
99
+ Todas las transacciones de gastos operacionales de un proveedor específico.
100
+ - Params: `store`*, `vendor_search`*, `start_date`*, `end_date`*
92
101
 
93
102
  #### list_vendors
94
- Directory of active vendors ordered by total amount paid.
95
- - Params: `store` (req), `start_date`, `end_date`
103
+ Lista todos los proveedores activos de una tienda en un periodo, con monto total pagado y número de transacciones.
104
+ - Params: `store`*, `start_date`, `end_date`
105
+
106
+ ### Auditoría / POS Reconciliation (11 tools)
96
107
 
97
- ### Bank Reconciliation (7 tools)
108
+ #### find_potential_matches
109
+ Para una línea no conciliada del banco, busca posibles correspondencias en las entradas contables de BC por monto, fecha y descripción.
110
+ - Params: `store`*, `bank_account`*, `statement_amount`*, `transaction_date`*, `description`, `date_tolerance_days`, `amount_tolerance_pct`
98
111
 
99
- > These tools use OData V4 Web Services. Amounts in VES. Read-only.
112
+ #### get_bank_ledger_entries
113
+ Todos los movimientos del libro de banco (abiertos y cerrados) para una cuenta bancaria.
114
+ - Params: `store`*, `bank_account`*, `date_from`*, `date_to`*, `open_only`
100
115
 
101
- #### list_bank_accounts
102
- List active bank accounts for a store.
103
- - Params: `store` (req)
116
+ #### get_bank_reconciliation_report
117
+ Reporte consolidado de reconciliación bancaria: progreso de todos los statements abiertos, líneas no conciliadas (débitos y créditos por separado), y sugerencias de asientos contables para débitos.
118
+ - Params: `store`*, `bank_account`*, `statement_no`, `month`, `min_amount`, `include_suggestions`, `save_to_file`, `excel_output`
119
+
120
+ #### get_gl_account_entries
121
+ Movimientos del libro mayor (G/L) para una cuenta específica.
122
+ - Params: `store`*, `gl_account`*, `date_from`*, `date_to`*
123
+
124
+ #### get_pm_receipts
125
+ Recibos de Pago Móvil (PM) registrados en BC para una cuenta bancaria.
126
+ - Params: `store`*, `bank_account`*, `date_from`*, `date_to`*
104
127
 
105
128
  #### get_reconciliation_status
106
- Open reconciliation summary: matched vs unmatched lines per bank account.
107
- - Params: `store` (req), `bank_account`
129
+ Resumen del estado de reconciliaciones bancarias abiertas: total líneas, conciliadas, pendientes y diferencia.
130
+ - Params: `store`*, `bank_account`
131
+
132
+ #### get_unmatched_ledger_entries
133
+ Entradas contables del banco en BC sin correspondencia en el estado de cuenta.
134
+ - Params: `store`*, `bank_account`*, `date_from`, `date_to`
108
135
 
109
136
  #### get_unmatched_statement_lines
110
- Bank statement lines not reconciled with BC, categorized by type (payments/deposits).
111
- - Params: `store` (req), `bank_account` (req), `statement_no`, `min_amount`, `type_filter`
137
+ Líneas del estado de cuenta bancario NO conciliadas con entradas en BC.
138
+ - Params: `store`*, `bank_account`*, `statement_no`, `min_amount`, `type_filter`
112
139
 
113
- #### get_unmatched_ledger_entries
114
- BC ledger entries not cleared at the bank, with stale entry detection (>30 days).
115
- - Params: `store` (req), `bank_account` (req), `date_from`, `date_to`
140
+ #### list_bank_accounts
141
+ Lista las cuentas bancarias de una tienda Full Queso.
142
+ - Params: `store`*
116
143
 
117
- #### find_potential_matches
118
- Scoring-based match finder (0-100) for unmatched bank lines against BC entries.
119
- - Params: `store` (req), `bank_account` (req), `statement_amount` (req), `transaction_date` (req), `description`, `date_tolerance_days`, `amount_tolerance_pct`
144
+ #### reconcile_pos_sales
145
+ Conciliación automática de ventas POS: cruza montos bancarios de BC (BankAccountLedgerEntries) con liquidaciones bancarias por número de lote, agrupa por liquidación bancaria (settlement batches), calcula comisiones reales.
146
+ - Params: `store`*, `start_date`*, `end_date`*, `bank_account`
120
147
 
121
148
  #### suggest_journal_entries
122
- Suggest GL journal entries for unreconciled bank payments using keyword matching and historical patterns.
123
- - Params: `store` (req), `bank_account` (req), `statement_no`, `auto_categorize`
149
+ Para pagos del banco sin correspondencia en BC, sugiere asientos contables basándose en la descripción y patrones históricos.
150
+ - Params: `store`*, `bank_account`*, `statement_no`, `auto_categorize`
124
151
 
125
- #### reconcile_pos_sales
126
- Multi-bank POS sales reconciliation. Matches bank deposits against BC lot records with commission tracking.
127
- - **Banesco**: lot-based matching, commission netted in deposit
128
- - **Bancrecer**: lot-based with separate commission/ISLR lines
129
- - **BDV**: aggregate matching by period totals (no lot numbers)
130
- - **UBII**: cross-account matching (BC virtual account → Bancrecer/BDV deposits)
131
- - Params: `store` (req), `start_date` (req), `end_date` (req), `bank_account`
152
+ ### Cierre Mensual Bancario (6 tools)
153
+
154
+ #### generate_closing_journal
155
+ Genera el EXCEL del CIERRE MENSUAL bancario (mes completo, multi-banco).
156
+ - Params: `store`*, `month`*, `output_dir`, `allow_partial`, `strict`
132
157
 
133
- ### Accounts Receivable & Payable (6 tools)
158
+ #### get_closing_match_results
159
+ CIERRE MENSUAL bancario: vista paginada de las 4 listas con auto-clasificación.
160
+ - Params: `store`*, `month`*, `list`, `bank_account`, `category`, `source`, `confidence`, `min_abs_amount_ves`, `sort`, `limit`, `offset`
134
161
 
135
- > These tools use the Finance Reports Beta API. Amounts in VES and USD. Read-only.
162
+ #### get_closing_questionnaire
163
+ CIERRE MENSUAL bancario: vista paginada/filtrada de las entradas del cuestionario del MES.
164
+ - Params: `store`*, `month`*, `bucket`, `counterparty_no`, `bank_account`, `status_filter`, `min_abs_amount_ves`, `min_abs_amount_usd`, `limit`, `offset`, `sort`, `pair_by_amount`, `pair_tolerance_pct`, `pair_max_days`, `description_regex`
165
+
166
+ #### reconcile_closing_with_bc
167
+ CIERRE MENSUAL bancario: refresca el estado contra BC para detectar drift desde la última corrida.
168
+ - Params: `store`*, `month`*
169
+
170
+ #### start_month_closing
171
+ CIERRE MENSUAL bancario (mes completo, multi-banco) en Business Central.
172
+ - Params: `store`*, `month`, `force_refresh`
173
+
174
+ #### submit_closing_answers
175
+ CIERRE MENSUAL bancario: registra respuestas/aprobaciones al cuestionario del MES.
176
+ - Params: `store`*, `month`*, `user`, `answers`*
177
+
178
+ ### Cobranzas (AR / AP) (7 tools)
179
+
180
+ #### get_collection_status
181
+ Verificación rápida del estado de cobranza de un período específico.
182
+ - Params: `store`*, `start_date`*, `end_date`*
136
183
 
137
184
  #### get_customer_balances
138
- Customers with outstanding balances.
139
- - Params: `store` (req), `only_with_balance`, `customer_number`
185
+ Lista clientes con saldos pendientes, montos vencidos y estado de cobranza.
186
+ - Params: `store`*, `only_with_balance`, `customer_number`
140
187
 
141
188
  #### get_customer_ledger
142
- Customer transaction history: invoices, payments, credit memos with collection rate.
143
- - Params: `store` (req), `start_date` (req), `end_date` (req), `customer_number`, `document_type`, `open_only`
189
+ Movimientos del libro mayor de clientes: facturas emitidas, pagos recibidos, notas de crédito.
190
+ - Params: `store`*, `start_date`*, `end_date`*, `customer_number`, `document_type`, `open_only`
144
191
 
145
- #### get_open_receivables
146
- Open accounts receivable by customer with aging buckets (0-30, 31-60, 61-90, 90+).
147
- - Params: `store` (req), `as_of_date`, `customer_number`, `min_amount`
192
+ #### get_customer_list
193
+ Lista de clientes con número, nombre y RIF (taxRegistrationNumber).
194
+ - Params: `store`*, `customer_number`
148
195
 
149
- #### get_collection_status
150
- Period collection completeness: "Were all December invoices collected?"
151
- - Params: `store` (req), `start_date` (req), `end_date` (req)
196
+ #### get_open_payables
197
+ Resumen de todas las cuentas por pagar abiertas.
198
+ - Params: `store`*, `as_of_date`, `vendor_number`, `min_amount`
199
+
200
+ #### get_open_receivables
201
+ Resumen rápido de todas las cuentas por cobrar abiertas.
202
+ - Params: `store`*, `as_of_date`, `customer_number`, `min_amount`
152
203
 
153
204
  #### get_vendor_ledger
154
- Vendor transaction history: bills received, payments made.
155
- - Params: `store` (req), `start_date` (req), `end_date` (req), `vendor_number`, `document_type`, `open_only`
205
+ Movimientos del libro mayor de proveedores: facturas recibidas, pagos realizados, notas de crédito.
206
+ - Params: `store`*, `start_date`*, `end_date`*, `vendor_number`, `document_type`, `open_only`
156
207
 
157
- #### get_open_payables
158
- Open accounts payable by vendor with aging buckets (0-30, 31-60, 61-90, 90+).
159
- - Params: `store` (req), `as_of_date`, `vendor_number`, `min_amount`
208
+ ### Financial Statements & Cash Flow (3 tools)
209
+
210
+ #### get_cash_flow
211
+ Free Cash Flow (FCF) por método indirecto para una o más tiendas Full Queso (FQ01, FQ28, FQ88).
212
+ - Params: `stores`, `period`, `month`, `start_date`, `end_date`, `compare_previous`, `include_cash_position`, `render_html`, `output_path`, `open_browser`, `inline_html`
213
+
214
+ #### get_financial_statements
215
+ Estado financiero completo (P&L / Profit & Loss) para una o más tiendas Full Queso (FQ01, FQ28, FQ88, FQFR) en un período.
216
+ - Params: `stores`, `period`, `month`, `start_date`, `end_date`, `render_html`, `compare_previous`, `output_path`, `open_browser`, `inline_html`
217
+
218
+ #### get_income_statement
219
+ Estado de resultados (Income Statement) en el formato EXACTO del reporte Power BI "Income Statement by Month" (G/L Account Level 1) para una tienda Full Queso (FQ01, FQ28, FQ88) y un mes.
220
+ - Params: `store`, `stores`, `period`, `month`, `start_date`, `end_date`, `level`
221
+
222
+ ### Inventario (7 tools)
223
+
224
+ #### get_inventory_by_location
225
+ Inventario desglosado por ubicación (Location Code) dentro de una tienda.
226
+ - Params: `store`*, `location_code`, `item_category`, `classification`, `as_of_date`
227
+
228
+ #### get_inventory_change
229
+ Cambio de inventario WoW (semana) o MoM (mes).
230
+ - Params: `store`*, `period`*, `periods_back`, `item_category`, `classification`
231
+
232
+ #### get_inventory_levels
233
+ Niveles de inventario agrupados por itemCategoryCode y/o inventoryPostingGroupCode (congelados, importado, local).
234
+ - Params: `store`*, `item_category`, `classification`, `as_of_date`
235
+
236
+ #### get_item_card
237
+ Datos maestros de ítems de inventario: unitCost (BC), calculated_unit_cost (real desde últimas entradas), inventory qty, unitPrice, categoría.
238
+ - Params: `store`*, `item_number`, `item_search`, `item_category`
239
+
240
+ #### get_item_cost_analysis
241
+ Análisis de costo de un ítem — calcula costo promedio ponderado de entradas recientes (compras, ensamblaje, ajustes positivos), compara con el unitCost actual de BC, y muestra el historial de costos por mes.
242
+ - Params: `store`*, `item_number`*, `months`
243
+
244
+ #### get_item_cost_trend
245
+ Tendencia de costo de ítems: compara weighted avg inbound cost de últimas 2 semanas vs últimas 4 semanas.
246
+ - Params: `store`*, `item_number`, `item_category`, `period_days`
247
+
248
+ #### get_item_ledger_entries
249
+ Entradas del libro de artículos (Item Ledger Entries) — historial de movimientos de inventario con costo real por entrada.
250
+ - Params: `store`*, `item_number`*, `entry_type`, `start_date`, `end_date`, `top`
251
+
252
+ ### Draft Visibility (Multi-Payments + facturas sin postear) (4 tools)
253
+
254
+ #### get_draft_payables
255
+ Muestra facturas de compra abiertas clasificadas en tres niveles: totalmente pendientes (sin documento de pago), con Purch.
256
+ - Params: `store`*, `status_filter`, `vendor_number`, `include_lines`, `report`, `summary_only`, `save_to_file`, `excel_output`
257
+
258
+ #### get_draft_receivables
259
+ Muestra facturas de venta abiertas clasificadas en tres niveles: totalmente pendientes (sin documento de cobro), con Multi-Payment en borrador (Open o Transferred), y el monto neto realmente sin cubrir.
260
+ - Params: `store`*, `status_filter`, `customer_number`, `include_lines`, `report`, `summary_only`, `save_to_file`, `excel_output`
261
+
262
+ #### get_draft_summary
263
+ Resumen ejecutivo consolidado de todos los Multi-Payments en borrador (Open y Transferred) para una o todas las tiendas.
264
+ - Params: `store`*
265
+
266
+ #### get_unposted_invoices
267
+ Facturas de compra y/o venta SIN POSTEAR en Business Central (status Draft o In Review).
268
+ - Params: `store`*, `type`, `start_date`, `end_date`, `include_in_review`, `summary_only`
269
+
270
+ ### Nómina (3 tools)
271
+
272
+ #### get_employees
273
+ Lista empleados de una tienda con datos de nomina: tipo, status, salarios base, bonos predeterminados, fechas.
274
+ - Params: `store`*, `status`, `payroll_type`, `exclude_managerial`, `employee_search`, `employee_code`
275
+
276
+ #### get_payroll_documents
277
+ Lista documentos de nomina (headers) con filtros por periodo, tipo, status.
278
+ - Params: `store`*, `period_code`, `payroll_type`, `exclude_managerial`, `status`, `start_date`, `end_date`, `include_employee_count`, `summary_only`
279
+
280
+ #### get_payroll_lines
281
+ Detalle de nomina por empleado para un documento.
282
+ - Params: `store`*, `document_no`*, `employee_code`, `employee_search`
283
+
284
+ ### Reports (2 tools)
285
+
286
+ #### generate_cxp_report
287
+ Genera reporte Excel de Cuentas por Pagar con 3 hojas: Sin Draft, Draft No Posteado, Pago Parcial + hoja Resumen con desglose por proveedor.
288
+ - Params: `store`*, `output_path`
289
+
290
+ #### generate_manager_report
291
+ Genera el Reporte Gerente HTML completo para una tienda FQ.
292
+ - Params: `store`*, `date`, `output_path`, `open_browser`, `payroll_days`
293
+
294
+ ### Ventas (4 tools)
295
+
296
+ #### compare_sales_by_store
297
+ Comparación de rendimiento de VENTAS entre las tiendas de Full Queso (FQ01 Chacao, FQ28 Marqués, FQ88 Candelaria).
298
+ - Params: `period`, `month`, `start_date`, `end_date`, `metrics`
299
+
300
+ #### get_item_sales_detail
301
+ Detalle de ventas por ítem específico (1–50 SKUs) con granularidad día/semana/mes/total y desglose por tienda.
302
+ - Params: `items`*, `start_date`*, `end_date`*, `stores`, `granularity`, `include_zero_days`
303
+
304
+ #### get_product_performance
305
+ Análisis detallado de rendimiento de productos de Full Queso.
306
+ - Params: `period`, `month`, `start_date`, `end_date`, `stores`, `sort_by`, `top_n`
307
+
308
+ #### get_sales_analysis
309
+ Análisis multidimensional de ventas de Full Queso.
310
+ - Params: `period`, `month`, `start_date`, `end_date`, `stores`, `dimensions`, `metrics`
311
+
312
+ _Los params marcados con `*` son requeridos. Detalle completo en `docs/tool_*.md`._
160
313
 
161
314
  ## API Integrations
162
315
 
@@ -49,6 +49,7 @@
49
49
  "MN0004": { "gl": "18240", "name": "BDV 7191 Bs. *", "currency": "VES", "category": "bancos_nacionales" },
50
50
  "MN0005": { "gl": "18250", "name": "Bancrecer 2558 Bs.", "currency": "VES", "category": "bancos_nacionales" },
51
51
  "MN0007": { "gl": "18270", "name": "BDV Pago Movil 5145", "currency": "VES", "category": "bancos_nacionales" },
52
+ "MN0008": { "gl": "18275", "name": "Banesco 9547 Bs.", "currency": "VES", "category": "bancos_nacionales", "_nota": "Banco nuevo agregado 2026-07-10 (Puntos 20 y 21, BANESCO_9547_BS., banco 0134). gl 18275 removido de adjustment_accounts_ves el 2026-07-10 (FP): ahora es banco real, entra normal en revaluación/reconciliación." },
52
53
  "MN0028": { "gl": "18290", "name": "Ubii Bank", "currency": "VES", "category": "bancos_nacionales" },
53
54
  "MN0030": { "gl": "18130", "name": "Caja tienda ventas Bs.", "currency": "VES", "category": "caja" },
54
55
  "ME0030": { "gl": "18140", "name": "Caja tienda ventas $", "currency": "USD", "category": "caja" },
@@ -74,6 +75,7 @@
74
75
  "18250": "Banco quinto Bs.",
75
76
  "18260": "Banco sexto Bs.",
76
77
  "18270": "Banco séptimo Bs.",
78
+ "18275": "Banesco 9547 Bs. (FQ88)",
77
79
  "18280": "Banco octavo Bs.",
78
80
  "18290": "UBII principal",
79
81
  "18291": "UBII secundario",
@@ -90,6 +92,6 @@
90
92
  "18998": "Total Bancos",
91
93
  "18999": "Total Caja y Bancos"
92
94
  },
93
- "_adjustment_accounts_nota": "Cuentas VES de ajuste/tránsito (NO son bancos reales, no tienen statement). Se tratan como informativas en la revaluación cambiaria (fuera del total de pérdida y del asiento sugerido). FP 2026-06-05.",
94
- "adjustment_accounts_ves": ["18275", "18297"]
95
+ "_adjustment_accounts_nota": "Cuentas VES de ajuste/tránsito (NO son bancos reales, no tienen statement). Se tratan como informativas en la revaluación cambiaria (fuera del total de pérdida y del asiento sugerido). FP 2026-06-05. 18275 removido 2026-07-10: BC lo reasignó al banco real Banesco 9547 (FQ88-MN0008).",
96
+ "adjustment_accounts_ves": ["18297"]
95
97
  }
@@ -25,6 +25,35 @@ export const COGS_ACCOUNTS = {
25
25
  57020: { name: 'Costos Diversos', nameEn: 'Diverse Costs' },
26
26
  };
27
27
 
28
+ // ── Revenue classification for the store-payroll KPI denominator ─────────────
29
+ // Verified against the real CoA (tests/discover-revenue-accounts.js, May 2026):
30
+ // 40310 "Ventas Materia Prima otras tiendas" = intercompany de tienda (FQ01/FQ88,
31
+ // FQ28=0); su costo par es 50110 (see LESSONS L-011).
32
+ // 40180 "Ventas Eventos - NEN" + 40700 "Ventas Eventos" = event sales (solo FQ01).
33
+ // 40580 "Ganancia por dif. cambiario" + 40540/40550 "Descuentos/Rebajas en Compras"
34
+ // = ingreso NO-venta → fuera del denominador de tienda (confirmado por FP).
35
+ // revenue_store = revenue_total − intercompany − events − non_sales.
36
+ export const INTERCOMPANY_REVENUE_ACCOUNTS = ['40310'];
37
+ export const EVENT_REVENUE_ACCOUNTS = ['40180', '40700'];
38
+ export const NON_SALES_REVENUE_ACCOUNTS = ['40580', '40540', '40550'];
39
+
40
+ // FQFR (master de franquicia): su intercompañía usa cuentas propias (royalties/fees),
41
+ // NO 40310, y no tiene ventas de evento de tienda. El KPI de nómina/ventas NO aplica a
42
+ // FQFR (ver KPI_PAYROLL_STORES) — esta clasificación es solo para que sus buckets de
43
+ // venta salgan correctos si alguien consulta ventas de FQFR.
44
+ export const FQFR_INTERCOMPANY_REVENUE_ACCOUNTS = ['40420', '40430', '40660', '40930'];
45
+
46
+ // Tiendas a las que aplica el KPI nómina/ventas (FQFR excluido por completo).
47
+ export const KPI_PAYROLL_STORES = ['FQ01', 'FQ28', 'FQ88'];
48
+
49
+ // Cuentas de clasificación de ingreso por tienda (interco/eventos/no-venta).
50
+ export function getRevenueClassification(storeCode) {
51
+ if (storeCode === 'FQFR') {
52
+ return { intercompany: FQFR_INTERCOMPANY_REVENUE_ACCOUNTS, events: [], nonSales: NON_SALES_REVENUE_ACCOUNTS };
53
+ }
54
+ return { intercompany: INTERCOMPANY_REVENUE_ACCOUNTS, events: EVENT_REVENUE_ACCOUNTS, nonSales: NON_SALES_REVENUE_ACCOUNTS };
55
+ }
56
+
28
57
  // Income group labels for breakdown (by 100-prefix: 40110 → 40100)
29
58
  export const INCOME_GROUP_LABELS = {
30
59
  40100: '40100_ingreso_ventas',
package/lib/bc-client.js CHANGED
@@ -18,6 +18,12 @@ export class BCClient {
18
18
  this._inflightRequests = new Map();
19
19
  this.STORE_DATA_TTL = 5 * 60 * 1000; // 5 minutes
20
20
  this.EXCHANGE_RATE_TTL = 15 * 60 * 1000; // 15 minutes
21
+
22
+ // Per-request hard timeout (ms). Without this a single hung fetch() never
23
+ // rejects → the whole tool call (and, via Promise.all consolidations, the
24
+ // MCP process) hangs forever. Generous default (all observed BC calls are
25
+ // <1s); tunable via env for unusually heavy OData reports. See L-015.
26
+ this.FETCH_TIMEOUT_MS = Number(process.env.BC_FETCH_TIMEOUT_MS) || 90000;
21
27
  }
22
28
 
23
29
  /**
@@ -135,8 +141,12 @@ export class BCClient {
135
141
 
136
142
  async fetchWithRetry(url, options, retries = 3) {
137
143
  for (let attempt = 1; attempt <= retries; attempt++) {
144
+ // Abort a hung request so it fails fast (and retries) instead of blocking
145
+ // forever. Each attempt gets its own controller/timer. See L-015.
146
+ const controller = new AbortController();
147
+ const timer = setTimeout(() => controller.abort(), this.FETCH_TIMEOUT_MS);
138
148
  try {
139
- const response = await fetch(url, options);
149
+ const response = await fetch(url, { ...options, signal: controller.signal });
140
150
  if (response.status === 429 && attempt < retries) {
141
151
  const retryAfter = response.headers.get('Retry-After');
142
152
  const delay = retryAfter ? parseInt(retryAfter) * 1000 : attempt * 3000;
@@ -146,10 +156,20 @@ export class BCClient {
146
156
  }
147
157
  return response;
148
158
  } catch (err) {
149
- if (attempt === retries) throw err;
159
+ const timedOut = err.name === 'AbortError';
160
+ if (attempt === retries) {
161
+ throw timedOut
162
+ ? new Error(`BC request timed out after ${this.FETCH_TIMEOUT_MS}ms (${retries} attempts): ${url}`)
163
+ : err;
164
+ }
150
165
  const delay = attempt * 2000;
151
- logger.warn(`Fetch failed (attempt ${attempt}/${retries}), retrying in ${delay}ms...`);
166
+ logger.warn(
167
+ `Fetch ${timedOut ? `timed out (>${this.FETCH_TIMEOUT_MS}ms)` : 'failed'} ` +
168
+ `(attempt ${attempt}/${retries}), retrying in ${delay}ms...`
169
+ );
152
170
  await new Promise((r) => setTimeout(r, delay));
171
+ } finally {
172
+ clearTimeout(timer);
153
173
  }
154
174
  }
155
175
  }
@@ -316,6 +336,20 @@ export class BCClient {
316
336
  return this.apiCallAllPages(url);
317
337
  }
318
338
 
339
+ // Nombres del Chart of Accounts (entidad `accounts` de BC api/v2.0) → { number: displayName }.
340
+ // Cache por companyId (el CoA cambia rara vez). Usado por get_income_statement (level 2)
341
+ // para etiquetar las cuentas de posteo hijas con su nombre real de BC.
342
+ async getChartOfAccountNames(companyId) {
343
+ if (!this._coaNameCache) this._coaNameCache = {};
344
+ if (this._coaNameCache[companyId]) return this._coaNameCache[companyId];
345
+ const url = this.buildApiUrl(companyId, 'accounts', { $select: 'number,displayName' });
346
+ const rows = await this.apiCallAllPages(url);
347
+ const map = {};
348
+ for (const a of rows) if (a && a.number) map[String(a.number)] = a.displayName || '';
349
+ this._coaNameCache[companyId] = map;
350
+ return map;
351
+ }
352
+
319
353
  // Saldos acumulados (a fecha) de cuentas con AMBAS monedas: debit/creditAmount = USD (LCY)
320
354
  // y additionalCurrency*Amount = VES (moneda adicional). Para revaluación de caja VES.
321
355
  // postingDate le asOfDate (acumulado desde el inicio = saldo a la fecha).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullqueso/mcp-bc-gastos",
3
- "version": "1.28.0",
3
+ "version": "1.33.0",
4
4
  "description": "MCP server for Business Central operational expense analysis, bank reconciliation, POS reconciliation, accounts receivable/payable, multi-payment draft visibility, payroll, inventory cost analysis, and manager reports - Full Queso franchise stores",
5
5
  "main": "server.js",
6
6
  "bin": {