@fullqueso/mcp-bc-gastos 1.14.2 → 1.19.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.
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Reporte dinámico: Posted Sales Invoices Pendientes
4
+ * 3 tabs Excel: Sin Draft, Con Draft, Pago Parcial
5
+ *
6
+ * Usage:
7
+ * node scripts/report-pending-sales-invoices.js --store FQ01 --from 2025-12 --to 2026-01
8
+ * node scripts/report-pending-sales-invoices.js --store FQ28 --from 2025-05 --to 2025-07
9
+ * node scripts/report-pending-sales-invoices.js --store all --from 2026-02 --to 2026-02
10
+ */
11
+
12
+ import { join, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { writeFileSync, unlinkSync, existsSync } from 'node:fs';
15
+ import { execSync } from 'node:child_process';
16
+ import { parseArgs } from 'node:util';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+ const PROJECT_ROOT = dirname(__dirname);
21
+
22
+ // ── Parse CLI args ──
23
+ const { values: args } = parseArgs({
24
+ options: {
25
+ store: { type: 'string', short: 's' },
26
+ from: { type: 'string', short: 'f' },
27
+ to: { type: 'string', short: 't' },
28
+ out: { type: 'string', short: 'o' },
29
+ },
30
+ });
31
+
32
+ if (!args.store || !args.from || !args.to) {
33
+ console.error('Usage: node scripts/report-pending-sales-invoices.js --store FQ01 --from 2025-12 --to 2026-01');
34
+ console.error(' --store FQ01 | FQ28 | FQ88 | all');
35
+ console.error(' --from YYYY-MM (mes inicio)');
36
+ console.error(' --to YYYY-MM (mes fin)');
37
+ console.error(' --out (opcional) ruta Excel de salida');
38
+ process.exit(1);
39
+ }
40
+
41
+ // ── Validate month format and build date range ──
42
+ function parseMonth(ym) {
43
+ const m = ym.match(/^(\d{4})-(\d{2})$/);
44
+ if (!m) { console.error(`Formato inválido: "${ym}". Usar YYYY-MM`); process.exit(1); }
45
+ return { year: parseInt(m[1]), month: parseInt(m[2]), raw: ym };
46
+ }
47
+
48
+ function lastDayOfMonth(year, month) {
49
+ return new Date(year, month, 0).getDate();
50
+ }
51
+
52
+ const fromM = parseMonth(args.from);
53
+ const toM = parseMonth(args.to);
54
+ const DATE_START = `${fromM.raw}-01`;
55
+ const DATE_END = `${toM.raw}-${lastDayOfMonth(toM.year, toM.month)}`;
56
+
57
+ // ── Load env & modules ──
58
+ const envPath = join(PROJECT_ROOT, '.env');
59
+ if (existsSync(envPath)) {
60
+ const dotenv = await import('dotenv');
61
+ dotenv.config({ path: envPath });
62
+ }
63
+
64
+ const { BCClient } = await import('../lib/bc-client.js');
65
+ const { resolveStores } = await import('../config/company-config.js');
66
+
67
+ const storeParam = args.store.toUpperCase();
68
+ const stores = resolveStores(storeParam === 'ALL' ? null : [storeParam]);
69
+ const bcClient = new BCClient();
70
+
71
+ // ── Process each store ──
72
+ async function processStore(storeInfo) {
73
+ const companyId = storeInfo.companyId;
74
+ const code = storeInfo.code;
75
+
76
+ // Customer ledger: open invoices in date range
77
+ const ledgerUrl = bcClient.buildBetaApiUrl(companyId, 'customerLedgerEntries', {
78
+ $filter: `open eq true and documentType eq 'Invoice' and postingDate ge ${DATE_START} and postingDate le ${DATE_END}`,
79
+ $select: 'entryNumber,documentNumber,customerNumber,postingDate,amount,amountLocalCurrency,currencyCode,description',
80
+ });
81
+
82
+ // Sales multi-payment headers (non-posted)
83
+ const mpUrl = bcClient.buildMultiPaymentApiUrl(companyId, 'multiPaymentHeaders', {
84
+ $filter: "status ne 'Posted'",
85
+ $select: 'no,invoiceNo,customerNo,customerName,invoiceAmount,remainingAmount,totalPaymentAmount,status,postingDate,currencyCode',
86
+ });
87
+
88
+ const [openInvoices, mpHeaders, exchangeRates] = await Promise.all([
89
+ bcClient.apiCallAllPages(ledgerUrl),
90
+ bcClient.apiCallAllPages(mpUrl),
91
+ bcClient.getExchangeRates(code),
92
+ ]);
93
+
94
+ console.error(`[${code}] ${openInvoices.length} open sales invoices, ${mpHeaders.length} MP headers`);
95
+
96
+ // Build MP lookup by invoice number
97
+ const mpByInvoice = {};
98
+ const customerNames = {};
99
+ for (const mp of mpHeaders) {
100
+ if (!mpByInvoice[mp.invoiceNo]) mpByInvoice[mp.invoiceNo] = [];
101
+ mpByInvoice[mp.invoiceNo].push(mp);
102
+ if (mp.customerNo && mp.customerName) customerNames[mp.customerNo] = mp.customerName;
103
+ }
104
+
105
+ // Fetch MP lines in bulk
106
+ const linesByMp = {};
107
+ if (mpHeaders.length > 0) {
108
+ const mpNos = new Set(mpHeaders.map((mp) => mp.no));
109
+ const linesParams = {
110
+ $select: 'documentNo,lineNo,paymentMethod,amountLCY,currencyCode,bankAccountNo,referenceNo,description',
111
+ };
112
+ if (mpHeaders.length <= 50) {
113
+ linesParams.$filter = [...mpNos].map((no) => `documentNo eq '${no}'`).join(' or ');
114
+ }
115
+ const allLinesUrl = bcClient.buildMultiPaymentApiUrl(companyId, 'multiPaymentLines', linesParams);
116
+ const allLines = await bcClient.apiCallAllPages(allLinesUrl);
117
+ for (const line of allLines) {
118
+ if (mpNos.has(line.documentNo)) {
119
+ if (!linesByMp[line.documentNo]) linesByMp[line.documentNo] = [];
120
+ linesByMp[line.documentNo].push(line);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Compute USD for each MP
126
+ const mpUsdInfo = {};
127
+ for (const mp of mpHeaders) {
128
+ mpUsdInfo[mp.no] = computeMpUsd(bcClient, mp, linesByMp[mp.no] || [], exchangeRates);
129
+ }
130
+
131
+ // Classify invoices
132
+ const invoices = [];
133
+ for (const inv of openInvoices) {
134
+ const docNo = inv.documentNumber;
135
+ // Customer invoices are POSITIVE in amountLocalCurrency (unlike vendor which are negative)
136
+ const amountUsd = round2(Math.abs(inv.amountLocalCurrency || 0));
137
+ const mps = mpByInvoice[docNo] || [];
138
+
139
+ let tier, multiPayment = null;
140
+
141
+ if (mps.length === 0) {
142
+ tier = 'fully_pending';
143
+ } else {
144
+ let totalDraftedUsd = 0;
145
+ for (const mp of mps) totalDraftedUsd += mpUsdInfo[mp.no].usd;
146
+ totalDraftedUsd = round2(totalDraftedUsd);
147
+ const hasTransferred = mps.some((mp) => mp.status === 'Transferred');
148
+ tier = hasTransferred ? 'in_journal' : 'draft_in_progress';
149
+ multiPayment = {
150
+ mp_no: mps[0].no,
151
+ status: mps[0].status,
152
+ total_payment_amount: totalDraftedUsd,
153
+ net_pending: round2(Math.max(0, amountUsd - totalDraftedUsd)),
154
+ };
155
+ if (mps.length > 1) {
156
+ multiPayment.additional_mps = mps.slice(1).map((mp) => ({
157
+ mp_no: mp.no, status: mp.status, total_payment_amount: mpUsdInfo[mp.no].usd,
158
+ }));
159
+ }
160
+ }
161
+
162
+ invoices.push({
163
+ invoice_no: docNo,
164
+ customer_no: inv.customerNumber,
165
+ customer_name: customerNames[inv.customerNumber] || inv.description || inv.customerNumber,
166
+ posting_date: inv.postingDate,
167
+ invoice_amount: amountUsd,
168
+ currency_code: inv.currencyCode || 'USD',
169
+ tier,
170
+ multi_payment: multiPayment,
171
+ });
172
+ }
173
+
174
+ invoices.sort((a, b) => a.posting_date.localeCompare(b.posting_date));
175
+ return { store: code, store_name: storeInfo.name, invoices };
176
+ }
177
+
178
+ // ── Helpers ──
179
+ function round2(n) { return Math.round(n * 100) / 100; }
180
+
181
+ function computeMpUsd(bcClient, mp, lines, exchangeRates) {
182
+ const rateInfo = bcClient.getExchangeRateForDate(exchangeRates, mp.postingDate);
183
+ const rate = rateInfo ? rateInfo.rate : null;
184
+ if (lines.length === 0) {
185
+ if (rate) return { usd: round2((mp.totalPaymentAmount || 0) / rate), rate };
186
+ return { usd: round2(mp.totalPaymentAmount || 0), rate: null };
187
+ }
188
+ if (lines.some((l) => l.currencyCode === 'VES') && !rate) {
189
+ return { usd: round2(mp.totalPaymentAmount || 0), rate: null };
190
+ }
191
+ let usdTotal = 0;
192
+ for (const line of lines) {
193
+ const amount = line.amountLCY || 0;
194
+ usdTotal += line.currencyCode === 'VES' ? amount / rate : amount;
195
+ }
196
+ return { usd: round2(usdTotal), rate };
197
+ }
198
+
199
+ // ── Main ──
200
+ console.error(`[sales-report] Stores: ${stores.map(s => s.code).join(', ')} | Period: ${DATE_START} → ${DATE_END}`);
201
+
202
+ const results = await Promise.all(stores.map(processStore));
203
+
204
+ const allInvoices = results.flatMap(r => r.invoices);
205
+ const storeLabel = results.length === 1 ? results[0].store : 'ALL';
206
+ const storeName = results.length === 1 ? results[0].store_name : 'Todas las tiendas';
207
+
208
+ const counts = { sin_draft: 0, con_draft: 0, pago_parcial: 0 };
209
+ for (const inv of allInvoices) {
210
+ if (inv.tier === 'fully_pending') counts.sin_draft++;
211
+ else if (inv.tier === 'draft_in_progress') counts.con_draft++;
212
+ else if (inv.tier === 'in_journal') counts.pago_parcial++;
213
+ }
214
+
215
+ console.error(`[sales-report] Total: ${allInvoices.length} invoices | Sin Draft=${counts.sin_draft}, Con Draft=${counts.con_draft}, Pago Parcial=${counts.pago_parcial}`);
216
+
217
+ // Write JSON
218
+ const reportDate = new Date().toISOString().substring(0, 10);
219
+ const jsonData = {
220
+ report_type: 'sales',
221
+ store: storeLabel,
222
+ store_name: storeName,
223
+ report_date: reportDate,
224
+ date_range: { start: DATE_START, end: DATE_END },
225
+ invoices: allInvoices,
226
+ };
227
+
228
+ const ts = Date.now();
229
+ const jsonPath = `/tmp/pending_sales_${storeLabel}_${ts}.json`;
230
+ writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2), 'utf-8');
231
+
232
+ // Generate Excel
233
+ const periodSlug = `${args.from.replace('-', '')}-${args.to.replace('-', '')}`;
234
+ const defaultOut = join(process.env.HOME || '/tmp', 'Downloads', `${storeLabel}_Ventas_Pendientes_${periodSlug}_${reportDate.replace(/-/g, '')}.xlsx`);
235
+ const outputPath = args.out ? args.out.replace(/^~/, process.env.HOME || '/tmp') : defaultOut;
236
+
237
+ const scriptPath = join(PROJECT_ROOT, 'scripts', 'generate-pending-sales-invoices-xlsx.py');
238
+ const venvPython = join(process.env.HOME || '/tmp', 'mcp-venv', 'bin', 'python3');
239
+ const pythonCmd = existsSync(venvPython) ? venvPython : 'python3';
240
+
241
+ try {
242
+ const stdout = execSync(`"${pythonCmd}" "${scriptPath}" "${jsonPath}" "${outputPath}"`, {
243
+ timeout: 60000, stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8',
244
+ });
245
+ console.error(`[sales-report] ✓ Excel: ${outputPath}`);
246
+ console.error(stdout);
247
+ } catch (e) {
248
+ console.error(`[sales-report] Error: ${e.message}`);
249
+ if (e.stderr) console.error(e.stderr);
250
+ process.exit(1);
251
+ } finally {
252
+ try { unlinkSync(jsonPath); } catch (_) {}
253
+ }
package/server.js CHANGED
@@ -43,6 +43,9 @@ import { findPotentialMatchesTool, handleFindPotentialMatches } from './tools/au
43
43
  import { suggestJournalEntriesTool, handleSuggestJournalEntries } from './tools/auditoria/suggest-journal-entries.js';
44
44
  import { reconcilePOSSalesTool, handleReconcilePOSSales } from './tools/auditoria/reconcile-pos-sales.js';
45
45
  import { bankReconciliationReportTool, handleBankReconciliationReport } from './tools/auditoria/bank-reconciliation-report.js';
46
+ import { glAccountEntriesTool, handleGLAccountEntries } from './tools/auditoria/gl-account-entries.js';
47
+ import { bankLedgerEntriesTool, handleBankLedgerEntries } from './tools/auditoria/bank-ledger-entries.js';
48
+ import { pmReceiptsTool, handlePmReceipts } from './tools/auditoria/pm-receipts.js';
46
49
 
47
50
  // Cobranzas tools (accounts receivable / payable)
48
51
  import { customerBalancesTool, handleCustomerBalances } from './tools/cobranzas/customer-balances.js';
@@ -51,6 +54,7 @@ import { openReceivablesTool, handleOpenReceivables } from './tools/cobranzas/op
51
54
  import { collectionStatusTool, handleCollectionStatus } from './tools/cobranzas/collection-status.js';
52
55
  import { vendorLedgerTool, handleVendorLedger } from './tools/cobranzas/vendor-ledger.js';
53
56
  import { openPayablesTool, handleOpenPayables } from './tools/cobranzas/open-payables.js';
57
+ import { customerListTool, handleCustomerList } from './tools/cobranzas/customer-list.js';
54
58
 
55
59
  // Multi-Payment draft visibility tools
56
60
  import {
@@ -71,8 +75,19 @@ import {
71
75
  itemCardTool, handleItemCard,
72
76
  itemLedgerEntriesTool, handleItemLedgerEntries,
73
77
  itemValueEntriesTool, handleItemValueEntries,
78
+ itemCostTrendTool, handleItemCostTrend,
79
+ inventoryLevelsTool, handleInventoryLevels,
80
+ inventoryChangeTool, handleInventoryChange,
81
+ inventoryByLocationTool, handleInventoryByLocation,
74
82
  } from './tools/inventario/index.js';
75
83
 
84
+ // Ventas tools (sales analysis)
85
+ import {
86
+ salesAnalysisTool, handleSalesAnalysis,
87
+ productPerformanceTool, handleProductPerformance,
88
+ salesStoreComparisonTool, handleSalesStoreComparison,
89
+ } from './tools/ventas/index.js';
90
+
76
91
  // Reports tools
77
92
  import { managerReportTool, handleGenerateManagerReport } from './tools/reports/manager-report.js';
78
93
  import { cxpReportTool, handleGenerateCxpReport } from './tools/reports/cxp-report.js';
@@ -115,6 +130,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
115
130
  suggestJournalEntriesTool,
116
131
  reconcilePOSSalesTool,
117
132
  bankReconciliationReportTool,
133
+ glAccountEntriesTool,
134
+ bankLedgerEntriesTool,
135
+ pmReceiptsTool,
118
136
  // Cobranzas (accounts receivable / payable)
119
137
  customerBalancesTool,
120
138
  customerLedgerTool,
@@ -122,6 +140,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
122
140
  collectionStatusTool,
123
141
  vendorLedgerTool,
124
142
  openPayablesTool,
143
+ customerListTool,
125
144
  // Multi-Payment draft visibility
126
145
  draftReceivablesTool,
127
146
  draftPayablesTool,
@@ -134,6 +153,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
134
153
  itemCardTool,
135
154
  itemLedgerEntriesTool,
136
155
  itemValueEntriesTool, // get_item_cost_analysis
156
+ itemCostTrendTool,
157
+ inventoryLevelsTool,
158
+ inventoryChangeTool,
159
+ inventoryByLocationTool,
160
+ // Ventas (sales analysis)
161
+ salesAnalysisTool,
162
+ productPerformanceTool,
163
+ salesStoreComparisonTool,
137
164
  // Reports
138
165
  managerReportTool,
139
166
  cxpReportTool,
@@ -204,6 +231,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
204
231
  case 'get_bank_reconciliation_report':
205
232
  result = await handleBankReconciliationReport(bcClient, args);
206
233
  break;
234
+ case 'get_gl_account_entries':
235
+ result = await handleGLAccountEntries(bcClient, args);
236
+ break;
237
+ case 'get_bank_ledger_entries':
238
+ result = await handleBankLedgerEntries(bcClient, args);
239
+ break;
240
+ case 'get_pm_receipts':
241
+ result = await handlePmReceipts(bcClient, args);
242
+ break;
207
243
  // Cobranzas (accounts receivable / payable)
208
244
  case 'get_customer_balances':
209
245
  result = await handleCustomerBalances(bcClient, args);
@@ -223,6 +259,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
223
259
  case 'get_open_payables':
224
260
  result = await handleOpenPayables(bcClient, args);
225
261
  break;
262
+ case 'get_customer_list':
263
+ result = await handleCustomerList(bcClient, args);
264
+ break;
226
265
  // Multi-Payment draft visibility
227
266
  case 'get_draft_receivables':
228
267
  result = await handleDraftReceivables(bcClient, args);
@@ -253,6 +292,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
253
292
  case 'get_item_cost_analysis':
254
293
  result = await handleItemValueEntries(bcClient, args);
255
294
  break;
295
+ case 'get_item_cost_trend':
296
+ result = await handleItemCostTrend(bcClient, args);
297
+ break;
298
+ case 'get_inventory_levels':
299
+ result = await handleInventoryLevels(bcClient, args);
300
+ break;
301
+ case 'get_inventory_change':
302
+ result = await handleInventoryChange(bcClient, args);
303
+ break;
304
+ case 'get_inventory_by_location':
305
+ result = await handleInventoryByLocation(bcClient, args);
306
+ break;
307
+ // Ventas (sales analysis)
308
+ case 'get_sales_analysis':
309
+ result = await handleSalesAnalysis(bcClient, args);
310
+ break;
311
+ case 'get_product_performance':
312
+ result = await handleProductPerformance(bcClient, args);
313
+ break;
314
+ case 'compare_sales_by_store':
315
+ result = await handleSalesStoreComparison(bcClient, args);
316
+ break;
256
317
  // Reports
257
318
  case 'generate_manager_report':
258
319
  result = await handleGenerateManagerReport(bcClient, args);
@@ -0,0 +1,104 @@
1
+ import { resolveStores } from '../../config/company-config.js';
2
+
3
+ export const bankLedgerEntriesTool = {
4
+ name: 'get_bank_ledger_entries',
5
+ description:
6
+ 'Todos los movimientos del libro de banco (abiertos y cerrados) para una cuenta bancaria. A diferencia de get_unmatched_ledger_entries, retorna TODAS las entradas incluyendo las ya conciliadas. Útil para ver el historial completo de pagos y depósitos registrados en BC para una cuenta.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ store: {
11
+ type: 'string',
12
+ enum: ['FQ01', 'FQ28', 'FQ88'],
13
+ description: 'Tienda a consultar.',
14
+ },
15
+ bank_account: {
16
+ type: 'string',
17
+ description: 'Número de cuenta bancaria (ej: "FQ01-MN0008").',
18
+ },
19
+ date_from: {
20
+ type: 'string',
21
+ description: 'Fecha inicio (YYYY-MM-DD).',
22
+ },
23
+ date_to: {
24
+ type: 'string',
25
+ description: 'Fecha fin (YYYY-MM-DD).',
26
+ },
27
+ open_only: {
28
+ type: 'boolean',
29
+ description: 'Si true, solo entradas abiertas (no conciliadas). Default: false (todas).',
30
+ },
31
+ },
32
+ required: ['store', 'bank_account', 'date_from', 'date_to'],
33
+ },
34
+ };
35
+
36
+ export async function handleBankLedgerEntries(bcClient, args) {
37
+ const { store, bank_account, date_from, date_to } = args;
38
+ const openOnly = args.open_only || false;
39
+
40
+ if (!store) throw new Error('Parámetro requerido: store');
41
+ if (!bank_account) throw new Error('Parámetro requerido: bank_account');
42
+ if (!date_from || !date_to) throw new Error('Parámetros requeridos: date_from, date_to');
43
+
44
+ const stores = resolveStores([store]);
45
+ const storeInfo = stores[0];
46
+
47
+ const filterParts = [
48
+ `Bank_Account_No eq '${bank_account}'`,
49
+ `Posting_Date ge ${date_from}`,
50
+ `Posting_Date le ${date_to}`,
51
+ ];
52
+ if (openOnly) {
53
+ filterParts.push('Open eq true');
54
+ }
55
+
56
+ const url = bcClient.buildODataUrl(store, 'BankAccountLedgerEntries', {
57
+ $filter: filterParts.join(' and '),
58
+ $orderby: 'Posting_Date asc',
59
+ });
60
+ const entries = await bcClient.odataCallAllPages(url);
61
+
62
+ const formatted = entries.map((e) => {
63
+ const amount = e.Amount || 0;
64
+ const postingDate = e.Posting_Date || null;
65
+ const isOpen = e.Open === true || e.Open === 'true';
66
+
67
+ return {
68
+ entry_no: e.Entry_No || e.Entry_No_ || 0,
69
+ posting_date: postingDate,
70
+ document_type: e.Document_Type || '',
71
+ document_no: e.Document_No || '',
72
+ external_document_no: e.External_Document_No || '',
73
+ description: e.Description || '',
74
+ amount: Math.round(amount * 100) / 100,
75
+ remaining_amount: Math.round((e.Remaining_Amount || 0) * 100) / 100,
76
+ open: isOpen,
77
+ bal_account_no: e.Bal_Account_No || '',
78
+ };
79
+ });
80
+
81
+ const openEntries = formatted.filter((e) => e.open);
82
+ const closedEntries = formatted.filter((e) => !e.open);
83
+ const totalAmount = Math.round(formatted.reduce((s, e) => s + e.amount, 0) * 100) / 100;
84
+ const deposits = formatted.filter((e) => e.amount > 0);
85
+ const payments = formatted.filter((e) => e.amount < 0);
86
+
87
+ return {
88
+ store,
89
+ store_name: storeInfo.name,
90
+ bank_account,
91
+ period: { from: date_from, to: date_to },
92
+ entries: formatted,
93
+ summary: {
94
+ total_entries: formatted.length,
95
+ open_entries: openEntries.length,
96
+ closed_entries: closedEntries.length,
97
+ total_deposits: deposits.length,
98
+ total_deposit_amount: Math.round(deposits.reduce((s, e) => s + e.amount, 0) * 100) / 100,
99
+ total_payments: payments.length,
100
+ total_payment_amount: Math.round(payments.reduce((s, e) => s + e.amount, 0) * 100) / 100,
101
+ net_amount: totalAmount,
102
+ },
103
+ };
104
+ }
@@ -0,0 +1,75 @@
1
+ import { resolveStores } from '../../config/company-config.js';
2
+
3
+ export const glAccountEntriesTool = {
4
+ name: 'get_gl_account_entries',
5
+ description:
6
+ 'Movimientos del libro mayor (G/L) para una cuenta específica. Retorna todas las entradas contables con débitos y créditos. Útil para ver los registros de ventas diarias por cuenta bancaria (ej: GL 18280 = Pago Móvil FQ01-MN0008).',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ store: {
11
+ type: 'string',
12
+ enum: ['FQ01', 'FQ28', 'FQ88'],
13
+ description: 'Tienda a consultar.',
14
+ },
15
+ gl_account: {
16
+ type: 'string',
17
+ description: 'Número de cuenta G/L (ej: "18280" para BDV Pago Móvil).',
18
+ },
19
+ date_from: {
20
+ type: 'string',
21
+ description: 'Fecha inicio (YYYY-MM-DD).',
22
+ },
23
+ date_to: {
24
+ type: 'string',
25
+ description: 'Fecha fin (YYYY-MM-DD).',
26
+ },
27
+ },
28
+ required: ['store', 'gl_account', 'date_from', 'date_to'],
29
+ },
30
+ };
31
+
32
+ export async function handleGLAccountEntries(bcClient, args) {
33
+ const { store, gl_account, date_from, date_to } = args;
34
+ if (!store) throw new Error('Parámetro requerido: store');
35
+ if (!gl_account) throw new Error('Parámetro requerido: gl_account');
36
+ if (!date_from || !date_to) throw new Error('Parámetros requeridos: date_from, date_to');
37
+
38
+ const stores = resolveStores([store]);
39
+ const storeInfo = stores[0];
40
+
41
+ const url = bcClient.buildApiUrl(storeInfo.companyId, 'generalLedgerEntries', {
42
+ $filter: `postingDate ge ${date_from} and postingDate le ${date_to} and accountNumber eq '${gl_account}'`,
43
+ $select: 'entryNumber,postingDate,documentNumber,accountNumber,description,debitAmount,creditAmount',
44
+ $orderby: 'postingDate asc',
45
+ });
46
+ const entries = await bcClient.apiCallAllPages(url);
47
+
48
+ const formatted = entries.map((e) => ({
49
+ entry_no: e.entryNumber || 0,
50
+ posting_date: e.postingDate || '',
51
+ document_no: e.documentNumber || '',
52
+ gl_account: e.accountNumber || '',
53
+ description: e.description || '',
54
+ debit: Math.round((e.debitAmount || 0) * 100) / 100,
55
+ credit: Math.round((e.creditAmount || 0) * 100) / 100,
56
+ amount: Math.round(((e.creditAmount || 0) - (e.debitAmount || 0)) * 100) / 100,
57
+ }));
58
+
59
+ const totalDebit = Math.round(formatted.reduce((s, e) => s + e.debit, 0) * 100) / 100;
60
+ const totalCredit = Math.round(formatted.reduce((s, e) => s + e.credit, 0) * 100) / 100;
61
+
62
+ return {
63
+ store,
64
+ store_name: storeInfo.name,
65
+ gl_account,
66
+ period: { from: date_from, to: date_to },
67
+ entries: formatted,
68
+ summary: {
69
+ total_entries: formatted.length,
70
+ total_debit: totalDebit,
71
+ total_credit: totalCredit,
72
+ net: Math.round((totalCredit - totalDebit) * 100) / 100,
73
+ },
74
+ };
75
+ }