@fullqueso/mcp-bc-gastos 1.23.0 → 1.23.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [1.23.1] — 2026-06-05
4
+
5
+ ### Fixed
6
+ - **`get_cash_flow` — CapEx y Working Capital alineados al Chart of Accounts real de BC.** El CapEx sumaba el rango `15000–17999`, que en el CoA real son **activos corrientes** (cuentas por cobrar 151xx, impuestos corrientes 152xx, prepagados 16xxx, inversiones corto plazo 17xxx), **no** activos fijos. Esto inflaba CapEx y distorsionaba el FCF.
7
+ - **CapEx ahora = net change en PP&E `11110–12899`** (intangibles 11110–11190 + tangibles 12110–12240), **excluye `12900`** (depreciación acumulada), `17000–17999` (inversiones corto plazo) y `18000–18999` (caja/bancos).
8
+ - **Nueva línea WC `Δ Impuestos corrientes`** (`15210–15280`: IVA crédito fiscal, retenciones IVA/ISLR, anticipos/excedente ISLR) = `−Σ(debit−credit)`.
9
+ - **Nueva línea WC `Δ Prepagados`** (`16110–16900`: anticipos a proveedores, alquileres/otros prepagados) = `−Σ(debit−credit)`.
10
+ - `net_wc_change = ΔAR + ΔAP + ΔInv + ΔImpuestos + ΔPrepagados`. ΔAR (subledger, equivale a 151xx) y ΔInventario (`itemLedgerEntries`, equivale a 14xxx) sin cambios — no se duplican vía G/L.
11
+ - `fcf_bridge` agrega filas `Δ Impuestos` y `Δ Prepagados` (11 ítems). Insight de "CapEx detectado" solo aparece si hay net change real en `11110–12899`; el insight de mayor uso de caja ahora reporta cualquier driver dominante (no solo inventario).
12
+ - HTML: tarjeta de Capital de Trabajo muestra las dos líneas nuevas; KPI CapEx etiquetado `PP&E 11110-12899`.
13
+ - **Verificado contra BC real (FQ88, mayo 2026):** CapEx ANTES `$8,760.92` (cuentas 151xx/152xx) → DESPUÉS `$0` (sin movimientos PP&E). Bridge cuadra: NOPAT `$12,340.71` + ΔWC `$10,794.99` − CapEx `$0` = FCF `$23,135.70`. Cuentas 15110/15135/15210 ya no aparecen en CapEx.
14
+
15
+ ---
16
+
3
17
  ## [1.23.0] — 2026-05-31
4
18
 
5
19
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullqueso/mcp-bc-gastos",
3
- "version": "1.23.0",
3
+ "version": "1.23.1",
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": {
@@ -210,7 +210,7 @@ function renderKpis(s) {
210
210
  ${cell('EBIT', signed(s.ebit.amount), `${s.ebit.pct}%`)}
211
211
  ${cell('NOPAT', signed(s.nopat.amount), `tax ${signed(-s.tax_estimated.amount)}`)}
212
212
  ${cell('CFO', signed(s.cfo.amount), `${s.cfo.pct}%`)}
213
- ${cell('CapEx', signed(-s.capex.amount), 'activos fijos')}
213
+ ${cell('CapEx', signed(-s.capex.amount), 'PP&E 11110-12899')}
214
214
  ${cell('FCF', signed(s.fcf.amount), statusWord(s.fcf.status), fcfCls)}
215
215
  ${cell('FCF Margin', `${s.fcf.pct}%`, '% ingresos', fcfCls)}
216
216
  </div>`;
@@ -238,6 +238,8 @@ function renderWorkingCapital(s) {
238
238
  ${row('Δ Cobranzas (AR)', wc.delta_ar)}
239
239
  ${row('Δ Proveedores (AP)', wc.delta_ap)}
240
240
  ${row('Δ Inventario', wc.delta_inventory)}
241
+ ${row('Δ Impuestos corrientes', wc.delta_taxes || 0)}
242
+ ${row('Δ Prepagados', wc.delta_prepaid || 0)}
241
243
  <div class="wc-row total">
242
244
  <span class="wc-label">Cambio neto WC</span>
243
245
  <span class="wc-arrow ${wc.net_wc_change >= 0 ? 'up' : 'down'}">${wc.net_wc_change >= 0 ? '&#x2191;' : '&#x2193;'}</span>
@@ -6,13 +6,19 @@
6
6
  // CFO, FCF, bridge tipo waterfall e insights. READ-ONLY.
7
7
  //
8
8
  // CONVENCIÓN DE SIGNOS (impacto en caja; + = fuente de caja, − = uso de caja):
9
- // delta_ar = −Σ amountLocalCurrency(customerLedgerEntries) → + = cobró más de lo facturado
9
+ // delta_ar = −Σ amountLocalCurrency(customerLedgerEntries) → + = cobró más de lo facturado (AR 15110–15199)
10
10
  // delta_ap = −Σ amountLocalCurrency(vendorLedgerEntries) → + = debe más (fuente)
11
- // delta_inventory = −Σ costAmountActual(itemLedgerEntries) → + = inventario bajó (fuente)
12
- // net_wc_change = delta_ar + delta_ap + delta_inventory
11
+ // delta_inventory = −Σ costAmountActual(itemLedgerEntries) → + = inventario bajó (fuente) (14000–14999)
12
+ // delta_taxes = −Σ(debit−credit) G/L 15210–15280 → + = impuestos corrientes bajaron (fuente)
13
+ // delta_prepaid = −Σ(debit−credit) G/L 16110–16900 → + = prepagados bajaron (fuente)
14
+ // net_wc_change = delta_ar + delta_ap + delta_inventory + delta_taxes + delta_prepaid
13
15
  // nopat = ebit − tax ; cfo = nopat + net_wc_change ; fcf = cfo − capex
14
16
  // (Resuelve la inconsistencia del spec entre los comentarios del schema y la fórmula:
15
- // se adopta la convención de los comentarios — los 3 deltas ya con signo de caja.)
17
+ // se adopta la convención de los comentarios — los deltas ya con signo de caja.)
18
+ //
19
+ // CAPEX = net change (debit−credit) en PP&E 11110–12899 (intangibles 11110–11190 +
20
+ // tangibles 12110–12240). EXCLUYE 12900 (depreciación acumulada), 17000–17999
21
+ // (inversiones corto plazo) y 18000–18999 (caja y bancos = posición de caja final).
16
22
  //
17
23
  // Montos en USD (LCY = Additional Reporting Currency), igual que get_financial_statements.
18
24
 
@@ -38,8 +44,9 @@ export const cashFlowTool = {
38
44
  description:
39
45
  'Free Cash Flow (FCF) por método indirecto para una o más tiendas Full Queso (FQ01, FQ28, FQ88). ' +
40
46
  'Parte del EBIT (igual que get_financial_statements), resta impuesto estimado (ISLR 34%), ajusta por capital de trabajo ' +
41
- '(Δ cuentas por cobrar, Δ cuentas por pagar, Δ inventario) para obtener el flujo de caja operativo (CFO), y resta CapEx ' +
42
- '(activos fijos 15000-17999) para el FCF. Incluye NOPAT, margen FCF, status (healthy/positive_low/negative_watch/' +
47
+ '(Δ cuentas por cobrar, Δ cuentas por pagar, Δ inventario, Δ impuestos corrientes, Δ prepagados) para obtener el flujo de ' +
48
+ 'caja operativo (CFO), y resta CapEx (PP&E / activos fijos 11110-12899) para el FCF. Incluye NOPAT, margen FCF, status ' +
49
+ '(healthy/positive_low/negative_watch/' +
43
50
  'negative_critical), bridge tipo waterfall e insights automáticos. Multi-tienda calcula consolidado. Con render_html=true ' +
44
51
  'genera un dashboard HTML (lo guarda en ~/Downloads y retorna la ruta). Úsalo cuando el usuario pida flujo de caja, ' +
45
52
  'cash flow, FCF, liquidez, "cuánta caja genera", o capital de trabajo. READ-ONLY.',
@@ -205,6 +212,8 @@ async function buildStoreCashFlow(bcClient, store, start, end) {
205
212
  delta_ar: wc.delta_ar,
206
213
  delta_ap: wc.delta_ap,
207
214
  delta_inventory: wc.delta_inventory,
215
+ delta_taxes: wc.delta_taxes,
216
+ delta_prepaid: wc.delta_prepaid,
208
217
  capex: wc.capex,
209
218
  capex_accounts: wc.capex_accounts,
210
219
  warnings: wc.warnings,
@@ -231,11 +240,20 @@ async function fetchWorkingCapitalAndCapex(bcClient, companyId, start, end) {
231
240
  $select: 'postingDate,entryType,quantity,costAmountActual',
232
241
  });
233
242
 
234
- const [arEntries, apEntries, ileEntries, capexEntries] = await Promise.all([
243
+ // Rangos G/L del Chart of Accounts real de BC (activos 10000–19999):
244
+ // CapEx → PP&E 11110–12899 (excluye 12900 depreciación acumulada)
245
+ // Impuestos corrientes → 15210–15280 (IVA crédito, retenciones, anticipos ISLR)
246
+ // Prepagados → 16110–16900 (anticipos a proveedores, alquileres/otros prepagados)
247
+ // AR (15110–15199) e Inventario (14000–14999) ya se calculan vía subledger
248
+ // (customer/item ledger) — NO consultarlos aquí para no duplicar.
249
+ // Excluidos del FCF: 17000–17999 (inversiones corto plazo) y 18000–18999 (caja/bancos).
250
+ const [arEntries, apEntries, ileEntries, capexEntries, taxEntries, prepaidEntries] = await Promise.all([
235
251
  bcClient.apiCallAllPages(arUrl).catch((e) => { logger.warn(`[cash-flow] AR fetch failed: ${e.message}`); return null; }),
236
252
  bcClient.apiCallAllPages(apUrl).catch((e) => { logger.warn(`[cash-flow] AP fetch failed: ${e.message}`); return null; }),
237
253
  bcClient.apiCallAllPages(ileUrl).catch((e) => { logger.warn(`[cash-flow] ILE fetch failed: ${e.message}`); return null; }),
238
- bcClient.getGLEntries(companyId, start, end, '15000', '17999').catch((e) => { logger.warn(`[cash-flow] CapEx fetch failed: ${e.message}`); return null; }),
254
+ bcClient.getGLEntries(companyId, start, end, '11110', '12899').catch((e) => { logger.warn(`[cash-flow] CapEx fetch failed: ${e.message}`); return null; }),
255
+ bcClient.getGLEntries(companyId, start, end, '15210', '15280').catch((e) => { logger.warn(`[cash-flow] Impuestos fetch failed: ${e.message}`); return null; }),
256
+ bcClient.getGLEntries(companyId, start, end, '16110', '16900').catch((e) => { logger.warn(`[cash-flow] Prepagados fetch failed: ${e.message}`); return null; }),
239
257
  ]);
240
258
 
241
259
  const warnings = [];
@@ -259,11 +277,23 @@ async function fetchWorkingCapitalAndCapex(bcClient, companyId, start, end) {
259
277
  if (ileEntries._truncated) warnings.push('Item ledger truncado (muchas entradas) — Δinventario puede estar incompleto.');
260
278
  }
261
279
 
262
- // CapExdébitos netos en activos fijos 15000-17999 (excluye 18xxx bancos)
280
+ // ΔImpuestos corrientes impacto en caja = −Σ(debit−credit) G/L 15210–15280
281
+ // (activo corriente: aumenta = uso de caja, baja = fuente de caja)
282
+ let delta_taxes = 0;
283
+ if (taxEntries == null) warnings.push('G/L de impuestos corrientes (15210–15280) no disponible — ΔImpuestos asumido en 0.');
284
+ else delta_taxes = round2(-taxEntries.reduce((s, e) => s + ((e.debitAmount || 0) - (e.creditAmount || 0)), 0));
285
+
286
+ // ΔPrepagados — impacto en caja = −Σ(debit−credit) G/L 16110–16900
287
+ let delta_prepaid = 0;
288
+ if (prepaidEntries == null) warnings.push('G/L de prepagados (16110–16900) no disponible — ΔPrepagados asumido en 0.');
289
+ else delta_prepaid = round2(-prepaidEntries.reduce((s, e) => s + ((e.debitAmount || 0) - (e.creditAmount || 0)), 0));
290
+
291
+ // CapEx — net change (debit−credit) en PP&E 11110–12899 (excluye 12900 depreciación,
292
+ // 17xxx inversiones corto plazo y 18xxx bancos)
263
293
  let capex = 0;
264
294
  const capex_accounts = {};
265
295
  if (capexEntries == null) {
266
- warnings.push('G/L de activos fijos no disponible — CapEx asumido en 0.');
296
+ warnings.push('G/L de PP&E (activos fijos 11110–12899) no disponible — CapEx asumido en 0.');
267
297
  } else {
268
298
  for (const e of capexEntries) {
269
299
  const net = (e.debitAmount || 0) - (e.creditAmount || 0);
@@ -275,7 +305,7 @@ async function fetchWorkingCapitalAndCapex(bcClient, companyId, start, end) {
275
305
  for (const k of Object.keys(capex_accounts)) if (capex_accounts[k] === 0) delete capex_accounts[k];
276
306
  }
277
307
 
278
- return { delta_ar, delta_ap, delta_inventory, capex, capex_accounts, warnings };
308
+ return { delta_ar, delta_ap, delta_inventory, delta_taxes, delta_prepaid, capex, capex_accounts, warnings };
279
309
  }
280
310
 
281
311
  // ─────────────────────────────────────────────────────────────────────────────
@@ -294,7 +324,9 @@ function assembleCashFlow(meta, c) {
294
324
  const delta_ar = round2(c.delta_ar);
295
325
  const delta_ap = round2(c.delta_ap);
296
326
  const delta_inventory = round2(c.delta_inventory);
297
- const net_wc_change = round2(delta_ar + delta_ap + delta_inventory);
327
+ const delta_taxes = round2(c.delta_taxes || 0);
328
+ const delta_prepaid = round2(c.delta_prepaid || 0);
329
+ const net_wc_change = round2(delta_ar + delta_ap + delta_inventory + delta_taxes + delta_prepaid);
298
330
 
299
331
  const cfo = round2(nopat + net_wc_change);
300
332
  const cfoPct = income > 0 ? round2((cfo / income) * 100) : 0;
@@ -304,8 +336,8 @@ function assembleCashFlow(meta, c) {
304
336
  const fcfPct = income > 0 ? round2((fcf / income) * 100) : 0;
305
337
 
306
338
  const status = fcfStatus(fcf, fcfPct, ebit);
307
- const bridge = buildBridge({ ebit, tax, nopat, delta_ar, delta_ap, delta_inventory, cfo, capex, fcf });
308
- const insights = buildInsights({ ebit, delta_ar, delta_ap, delta_inventory, capex, fcf, fcfPct, status, warnings: c.warnings || [] });
339
+ const bridge = buildBridge({ ebit, tax, nopat, delta_ar, delta_ap, delta_inventory, delta_taxes, delta_prepaid, cfo, capex, fcf });
340
+ const insights = buildInsights({ ebit, delta_ar, delta_ap, delta_inventory, delta_taxes, delta_prepaid, capex, fcf, fcfPct, status, warnings: c.warnings || [] });
309
341
 
310
342
  return {
311
343
  store_code: meta.store_code,
@@ -316,7 +348,7 @@ function assembleCashFlow(meta, c) {
316
348
  ebit: { amount: ebit, pct: ebitPct },
317
349
  tax_estimated: { amount: tax, rate: TAX_RATE, note: 'Estimado ISLR 34%' },
318
350
  nopat: { amount: nopat },
319
- working_capital: { delta_ar, delta_ap, delta_inventory, net_wc_change },
351
+ working_capital: { delta_ar, delta_ap, delta_inventory, delta_taxes, delta_prepaid, net_wc_change },
320
352
  cfo: { amount: cfo, pct: cfoPct },
321
353
  capex: { amount: capex, accounts: c.capex_accounts || {} },
322
354
  fcf: { amount: fcf, pct: fcfPct, status },
@@ -331,7 +363,7 @@ function fcfStatus(fcf, fcfPct, ebit) {
331
363
  return fcf > ebit * -0.2 ? 'negative_watch' : 'negative_critical';
332
364
  }
333
365
 
334
- function buildBridge({ ebit, tax, nopat, delta_ar, delta_ap, delta_inventory, cfo, capex, fcf }) {
366
+ function buildBridge({ ebit, tax, nopat, delta_ar, delta_ap, delta_inventory, delta_taxes, delta_prepaid, cfo, capex, fcf }) {
335
367
  const flow = (label, amount) => ({ label, amount: round2(amount), type: amount >= 0 ? 'source' : 'use' });
336
368
  return [
337
369
  { label: 'EBIT', amount: ebit, type: 'source' },
@@ -340,13 +372,15 @@ function buildBridge({ ebit, tax, nopat, delta_ar, delta_ap, delta_inventory, cf
340
372
  flow('Δ Cob. (AR)', delta_ar),
341
373
  flow('Δ Prov. (AP)', delta_ap),
342
374
  flow('Δ Inventario', delta_inventory),
375
+ flow('Δ Impuestos', delta_taxes),
376
+ flow('Δ Prepagados', delta_prepaid),
343
377
  { label: 'CFO', amount: cfo, type: 'subtotal' },
344
378
  { label: 'CapEx', amount: round2(-capex), type: 'use' },
345
379
  { label: 'FCF', amount: fcf, type: 'total' },
346
380
  ];
347
381
  }
348
382
 
349
- function buildInsights({ ebit, delta_ar, delta_ap, delta_inventory, capex, fcf, fcfPct, status, warnings }) {
383
+ function buildInsights({ ebit, delta_ar, delta_ap, delta_inventory, delta_taxes, delta_prepaid, capex, fcf, fcfPct, status, warnings }) {
350
384
  const insights = [];
351
385
 
352
386
  // Mayor driver negativo entre los usos de capital de trabajo
@@ -354,9 +388,11 @@ function buildInsights({ ebit, delta_ar, delta_ap, delta_inventory, capex, fcf,
354
388
  { key: 'delta_inventory', label: 'Inventario', val: delta_inventory },
355
389
  { key: 'delta_ar', label: 'Cobranza (AR)', val: delta_ar },
356
390
  { key: 'delta_ap', label: 'Pagos a proveedores (AP)', val: delta_ap },
391
+ { key: 'delta_taxes', label: 'Impuestos corrientes', val: delta_taxes },
392
+ { key: 'delta_prepaid', label: 'Prepagados', val: delta_prepaid },
357
393
  ].filter((u) => u.val < 0).sort((a, b) => a.val - b.val);
358
- if (uses.length && uses[0].key === 'delta_inventory') {
359
- insights.push(`Inventario es el mayor uso de caja del período ($${fmtAbs(delta_inventory)}).`);
394
+ if (uses.length) {
395
+ insights.push(`${uses[0].label} es el mayor uso de caja del período ($${fmtAbs(uses[0].val)}).`);
360
396
  }
361
397
 
362
398
  // AR consume >20% del EBIT (cobranza lenta)
@@ -364,9 +400,11 @@ function buildInsights({ ebit, delta_ar, delta_ap, delta_inventory, capex, fcf,
364
400
  insights.push(`Cobranza lenta presiona caja: ΔAR −$${fmtAbs(delta_ar)} (>20% del EBIT).`);
365
401
  }
366
402
 
367
- // CapEx detectado
403
+ // CapEx — solo si hay net change real en PP&E (11110–12899)
368
404
  if (capex > 0) {
369
- insights.push(`CapEx $${fmtAbs(capex)} en activos fijos detectado.`);
405
+ insights.push(`CapEx $${fmtAbs(capex)} en PP&E (activos fijos 11110–12899) detectado.`);
406
+ } else if (capex < 0) {
407
+ insights.push(`Desinversión neta en PP&E $${fmtAbs(capex)} (fuente de caja).`);
370
408
  }
371
409
 
372
410
  // Margen FCF bajo
@@ -415,6 +453,8 @@ function consolidateCashFlow(storeObjs) {
415
453
  delta_ar: sum((o) => o.working_capital.delta_ar),
416
454
  delta_ap: sum((o) => o.working_capital.delta_ap),
417
455
  delta_inventory: sum((o) => o.working_capital.delta_inventory),
456
+ delta_taxes: sum((o) => o.working_capital.delta_taxes || 0),
457
+ delta_prepaid: sum((o) => o.working_capital.delta_prepaid || 0),
418
458
  capex: sum((o) => o.capex.amount),
419
459
  capex_accounts,
420
460
  warnings: allWarnings,