@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 +14 -0
- package/package.json +1 -1
- package/tools/financials/cash-flow-html.js +3 -1
- package/tools/financials/cash-flow.js +61 -21
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.
|
|
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), '
|
|
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 ? '↑' : '↓'}</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
|
-
//
|
|
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
|
|
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
|
|
42
|
-
'(activos fijos
|
|
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
|
-
|
|
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, '
|
|
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
|
-
//
|
|
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
|
|
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
|
|
359
|
-
insights.push(
|
|
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
|
|
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,
|