next-finance-mcp 0.9.39 → 0.9.40

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/dist/client.d.ts CHANGED
@@ -123,6 +123,7 @@ export interface CriteriosLancamento {
123
123
  data: string;
124
124
  valor?: number;
125
125
  descricao?: string;
126
+ moeda?: string;
126
127
  }
127
128
  /** Edita um lançamento existente. Localiza por critérios e aplica as alterações. */
128
129
  export interface EditarLancamentoOpts extends CriteriosLancamento {
@@ -364,6 +365,7 @@ export declare function buscarLancamentos(opts: FiltroLancamentos): Promise<Resu
364
365
  export interface TituloSlim {
365
366
  vencimento: string;
366
367
  valor: number;
368
+ moeda: string;
367
369
  contraparte: string;
368
370
  conta: string;
369
371
  em_cobranca: boolean;
@@ -378,12 +380,21 @@ export interface FiltroTitulos {
378
380
  dataInicio?: string;
379
381
  dataFim?: string;
380
382
  busca?: string;
383
+ moeda?: string;
381
384
  apenasVencidos?: boolean;
382
385
  incluirCobranca?: boolean;
383
386
  apenasCobranca?: boolean;
384
387
  pagina?: number;
385
388
  limite?: number;
386
389
  }
390
+ export interface TotaisTitulosMoeda {
391
+ moeda: string;
392
+ quantidade: number;
393
+ valor_total: number;
394
+ valor_vencido: number;
395
+ valor_a_vencer: number;
396
+ em_cobranca: number;
397
+ }
387
398
  export interface ResultadoTitulos {
388
399
  titulos: TituloSlim[];
389
400
  paginacao: PaginaMeta;
@@ -391,15 +402,10 @@ export interface ResultadoTitulos {
391
402
  inicio: string;
392
403
  fim: string;
393
404
  };
394
- totais: {
395
- quantidade: number;
396
- valor_total: number;
397
- valor_vencido: number;
398
- valor_a_vencer: number;
399
- em_cobranca: number;
400
- };
405
+ totais_por_moeda: TotaisTitulosMoeda[];
401
406
  por_contraparte?: {
402
407
  contraparte: string;
408
+ moeda: string;
403
409
  valor: number;
404
410
  quantidade: number;
405
411
  vencido: number;
@@ -410,12 +416,14 @@ export declare const buscarContasAReceber: (o?: FiltroTitulos) => Promise<Result
410
416
  export interface ClienteSlim {
411
417
  nome: string;
412
418
  tipo: "PF" | "PJ";
419
+ moeda: string;
413
420
  titulos_a_receber: number;
414
421
  valor_a_receber: number;
415
422
  valor_vencido: number;
416
423
  }
417
424
  export interface FiltroClientes {
418
425
  busca?: string;
426
+ moeda?: string;
419
427
  pagina?: number;
420
428
  limite?: number;
421
429
  }
@@ -423,7 +431,10 @@ export interface FiltroClientes {
423
431
  export declare function listarClientes(opts?: FiltroClientes): Promise<{
424
432
  clientes: ClienteSlim[];
425
433
  paginacao: PaginaMeta;
426
- total_a_receber: number;
434
+ total_a_receber_por_moeda: {
435
+ moeda: string;
436
+ valor: number;
437
+ }[];
427
438
  }>;
428
439
  export interface UltimoPrecoProdutoOpts {
429
440
  busca: string;
package/dist/client.js CHANGED
@@ -1004,38 +1004,58 @@ export async function criarTransferencia(o) {
1004
1004
  };
1005
1005
  }
1006
1006
  /** Localiza UM lançamento por critérios. Retorna ID interno (não exposto).
1007
- * Usado por editar/excluir → exige permissão Editar na conta. */
1007
+ * Usado por editar/excluir → exige permissão Editar na conta.
1008
+ * IMPORTANTE: há contas HOMÔNIMAS (ex.: 5 contas "Conta a Pagar", uma por moeda).
1009
+ * Por isso varremos TODAS as contas cujo nome casa e desambiguamos por
1010
+ * valor/descrição/moeda — assim a edição funciona mesmo com nomes repetidos. */
1008
1011
  async function localizarLancamento(crit) {
1009
- const { id: idConta } = await resolverIdConta(crit.nomeConta, "Editar");
1010
- const body = {
1011
- IntervaloDeDatas: { DataInicio: crit.data, DataFim: crit.data },
1012
- Pagina: 0, RegistrosPorPagina: 200,
1013
- ListarOrdenadoPelaDataDeModificacao: false,
1014
- IdConta: idConta,
1012
+ const { contas } = await fetchContas();
1013
+ const me = await obterUsuarioLogado();
1014
+ const termo = crit.nomeConta.trim().toLowerCase();
1015
+ const candContas = contas
1016
+ .filter(isContaAtiva)
1017
+ .filter(c => permissaoDaConta(c, me) === "Editar")
1018
+ .filter(c => String(c["Nome"] ?? c["NomeConta"] ?? "").toLowerCase().includes(termo));
1019
+ if (candContas.length === 0) {
1020
+ throw new Error(`Conta "${crit.nomeConta}" não encontrada (ou sem permissão de edição).`);
1021
+ }
1022
+ // Resolve moeda de cada conta candidata (para desambiguar e filtrar)
1023
+ const mapaMoedas = await obterMapaMoedas();
1024
+ const moedaDaConta = (c) => {
1025
+ const m = mapaMoedas.get(String(c["IdMoeda"] ?? ""));
1026
+ return (m?.sigla || m?.nome || String(c["NomeMoeda"] ?? "")).trim();
1015
1027
  };
1016
- let lancs;
1017
- try {
1018
- const r = await apiPost("/api/Lancamento/Filtrar", body);
1019
- lancs = r["Lancamentos"] ?? [];
1028
+ // Filtra por moeda, se informada (ex.: "BRL", "Real")
1029
+ let contasBusca = candContas;
1030
+ if (crit.moeda?.trim()) {
1031
+ const tm = normalizar(crit.moeda);
1032
+ contasBusca = candContas.filter(c => normalizar(moedaDaConta(c)).includes(tm));
1033
+ if (contasBusca.length === 0) {
1034
+ const disp = [...new Set(candContas.map(moedaDaConta))].join(", ");
1035
+ throw new Error(`Nenhuma conta "${crit.nomeConta}" na moeda "${crit.moeda}". Moedas disponíveis: ${disp}`);
1036
+ }
1020
1037
  }
1021
- catch {
1022
- // Fallback web
1023
- const params = new URLSearchParams();
1024
- params.set("filtro[IdConta]", idConta);
1025
- params.set("filtro[IntervaloDeDatas][DataInicio]", crit.data);
1026
- params.set("filtro[IntervaloDeDatas][DataFim]", crit.data);
1027
- params.set("filtro[ListarOrdenadoPelaDataDeModificacao]", "false");
1028
- const r = await webPost("/LancamentoAjax/Filtrar", params.toString(), "application/x-www-form-urlencoded");
1029
- if (r.status !== 200)
1030
- throw new Error(`Erro ${r.status} ao buscar lançamentos.`);
1031
- const raw = JSON.parse(r.text);
1032
- lancs = (Array.isArray(raw) ? raw : raw.Lancamentos) ?? [];
1038
+ // Busca lançamentos na data em cada conta candidata
1039
+ const all = [];
1040
+ for (const c of contasBusca) {
1041
+ const idConta = String(c["Id"] ?? "");
1042
+ if (!idConta)
1043
+ continue;
1044
+ try {
1045
+ const lancs = await fetchLancamentosWebDeUmaConta(idConta, crit.data, crit.data);
1046
+ for (const l of lancs) {
1047
+ if (!l["NomeConta"])
1048
+ l["NomeConta"] = c["Nome"];
1049
+ l["__moeda"] = moedaDaConta(c);
1050
+ }
1051
+ all.push(...lancs);
1052
+ }
1053
+ catch { /* ignora conta que falhou; segue nas demais */ }
1033
1054
  }
1034
1055
  // Filtra por valor e descrição
1035
- let cand = lancs;
1036
- if (crit.valor !== undefined) {
1056
+ let cand = all;
1057
+ if (crit.valor !== undefined)
1037
1058
  cand = cand.filter(l => Math.abs(Number(l["Valor"]) - crit.valor) < 0.001);
1038
- }
1039
1059
  if (crit.descricao?.trim()) {
1040
1060
  const t = crit.descricao.trim().toLowerCase();
1041
1061
  cand = cand.filter(l => String(l["Descricao"] ?? "").toLowerCase().includes(t));
@@ -1044,11 +1064,11 @@ async function localizarLancamento(crit) {
1044
1064
  throw new Error(`Nenhum lançamento encontrado em ${crit.data} para "${crit.nomeConta}" com os critérios informados.`);
1045
1065
  }
1046
1066
  if (cand.length > 1) {
1047
- const lista = cand.slice(0, 5).map(l => `R$ ${Number(l["Valor"]).toFixed(2)} — ${String(l["Descricao"] ?? "")}`).join("; ");
1048
- throw new Error(`${cand.length} lançamentos batem com os critérios. Refine valor/descrição. Encontrados: ${lista}`);
1067
+ const lista = cand.slice(0, 6).map(l => `${Number(l["Valor"]).toFixed(2)} ${String(l["__moeda"] ?? "")} — ${String(l["Descricao"] ?? "")} [${String(l["NomeConta"] ?? "")}]`).join("; ");
1068
+ throw new Error(`${cand.length} lançamentos batem com os critérios. Refine valor/descrição/moeda. Encontrados: ${lista}`);
1049
1069
  }
1050
1070
  const m = cand[0];
1051
- return { id: String(m["Id"]), descricao: String(m["Descricao"] ?? ""), valor: Number(m["Valor"]) };
1071
+ return { id: String(m["Id"]), descricao: String(m["Descricao"] ?? ""), valor: Number(m["Valor"]), conta: String(m["NomeConta"] ?? ""), moeda: m["__moeda"] ? String(m["__moeda"]) : undefined };
1052
1072
  }
1053
1073
  export async function editarLancamento(opts) {
1054
1074
  const alvo = await localizarLancamento(opts);
@@ -1896,6 +1916,11 @@ async function buscarTitulos(grupo, opts) {
1896
1916
  const cobrancaTipos = new Set(TIPOS_TITULO.cobranca);
1897
1917
  const { contas } = await fetchContas();
1898
1918
  const me = await obterUsuarioLogado();
1919
+ const mapaMoedas = await obterMapaMoedas();
1920
+ const moedaDaConta = (c) => {
1921
+ const m = mapaMoedas.get(String(c["IdMoeda"] ?? ""));
1922
+ return (m?.sigla || m?.nome || String(c["NomeMoeda"] ?? "") || "?").trim();
1923
+ };
1899
1924
  const alvo = contas
1900
1925
  .filter(isContaAtiva)
1901
1926
  .filter(c => permissaoDaConta(c, me) !== "Negar")
@@ -1903,12 +1928,13 @@ async function buscarTitulos(grupo, opts) {
1903
1928
  const todas = await mapWithConcurrency(alvo, 8, async (c) => {
1904
1929
  const id = String(c["Id"] ?? c["IdConta"] ?? "");
1905
1930
  const nome = String(c["Nome"] ?? c["NomeConta"] ?? "");
1931
+ const moeda = moedaDaConta(c);
1906
1932
  const emCob = cobrancaTipos.has(String(c["NomeTipoConta"] ?? "").toLowerCase().trim());
1907
1933
  if (!id)
1908
1934
  return [];
1909
1935
  try {
1910
1936
  const lancs = await fetchLancamentosWebDeUmaConta(id, dataInicio, dataFim);
1911
- return lancs.map(l => ({ l, nome, emCob }));
1937
+ return lancs.map(l => ({ l, nome, moeda, emCob }));
1912
1938
  }
1913
1939
  catch {
1914
1940
  return [];
@@ -1917,7 +1943,7 @@ async function buscarTitulos(grupo, opts) {
1917
1943
  const hojeStr = isoDate(new Date());
1918
1944
  let titulos = todas.flat()
1919
1945
  .filter(({ l }) => !(l["IdLancamentoTransferencia"] ?? l["Transferencia"]))
1920
- .map(({ l, nome, emCob }) => {
1946
+ .map(({ l, nome, moeda, emCob }) => {
1921
1947
  const s = slimLancamento(l);
1922
1948
  if (!s.conta)
1923
1949
  s.conta = nome;
@@ -1927,6 +1953,7 @@ async function buscarTitulos(grupo, opts) {
1927
1953
  return {
1928
1954
  vencimento: venc,
1929
1955
  valor: s.valor,
1956
+ moeda,
1930
1957
  contraparte: s.descricao,
1931
1958
  conta: s.conta,
1932
1959
  em_cobranca: emCob,
@@ -1938,6 +1965,10 @@ async function buscarTitulos(grupo, opts) {
1938
1965
  observacoes: s.observacoes,
1939
1966
  };
1940
1967
  });
1968
+ if (opts.moeda?.trim()) {
1969
+ const tm = normalizar(opts.moeda);
1970
+ titulos = titulos.filter(t => normalizar(t.moeda).includes(tm));
1971
+ }
1941
1972
  if (opts.apenasCobranca)
1942
1973
  titulos = titulos.filter(t => t.em_cobranca);
1943
1974
  if (opts.busca) {
@@ -1947,19 +1978,23 @@ async function buscarTitulos(grupo, opts) {
1947
1978
  if (opts.apenasVencidos)
1948
1979
  titulos = titulos.filter(x => x.vencido);
1949
1980
  titulos.sort((a, b) => (a.vencimento < b.vencimento ? -1 : a.vencimento > b.vencimento ? 1 : 0));
1950
- let valorTotal = 0, valorVencido = 0, valorAVencer = 0, valorCobranca = 0;
1981
+ // Totais SEMPRE separados por moeda (nunca somar moedas diferentes)
1982
+ const mapMoeda = new Map();
1951
1983
  const mapCp = new Map();
1952
1984
  for (const t of titulos) {
1953
1985
  const v = Math.abs(t.valor);
1954
- valorTotal += v;
1986
+ const tm = mapMoeda.get(t.moeda) ?? { moeda: t.moeda, quantidade: 0, valor_total: 0, valor_vencido: 0, valor_a_vencer: 0, em_cobranca: 0 };
1987
+ tm.quantidade += 1;
1988
+ tm.valor_total += v;
1955
1989
  if (t.vencido)
1956
- valorVencido += v;
1990
+ tm.valor_vencido += v;
1957
1991
  else
1958
- valorAVencer += v;
1992
+ tm.valor_a_vencer += v;
1959
1993
  if (t.em_cobranca)
1960
- valorCobranca += v;
1961
- const key = t.contraparte || "(sem descrição)";
1962
- const g = mapCp.get(key) ?? { contraparte: key, valor: 0, quantidade: 0, vencido: 0 };
1994
+ tm.em_cobranca += v;
1995
+ mapMoeda.set(t.moeda, tm);
1996
+ const key = `${t.contraparte || "(sem descrição)"}|${t.moeda}`;
1997
+ const g = mapCp.get(key) ?? { contraparte: t.contraparte || "(sem descrição)", moeda: t.moeda, valor: 0, quantidade: 0, vencido: 0 };
1963
1998
  g.valor += v;
1964
1999
  g.quantidade += 1;
1965
2000
  if (t.vencido)
@@ -1968,20 +2003,17 @@ async function buscarTitulos(grupo, opts) {
1968
2003
  }
1969
2004
  const inicio = (pagina - 1) * limite;
1970
2005
  const pageItems = titulos.slice(inicio, inicio + limite);
2006
+ const totais_por_moeda = [...mapMoeda.values()]
2007
+ .sort((a, b) => b.valor_total - a.valor_total)
2008
+ .map(t => ({ moeda: t.moeda, quantidade: t.quantidade, valor_total: round2(t.valor_total), valor_vencido: round2(t.valor_vencido), valor_a_vencer: round2(t.valor_a_vencer), em_cobranca: round2(t.em_cobranca) }));
1971
2009
  const por_contraparte = [...mapCp.values()]
1972
2010
  .sort((a, b) => b.valor - a.valor).slice(0, 50)
1973
- .map(g => ({ contraparte: g.contraparte, valor: round2(g.valor), quantidade: g.quantidade, vencido: round2(g.vencido) }));
2011
+ .map(g => ({ contraparte: g.contraparte, moeda: g.moeda, valor: round2(g.valor), quantidade: g.quantidade, vencido: round2(g.vencido) }));
1974
2012
  return {
1975
2013
  titulos: pageItems,
1976
2014
  paginacao: { pagina, limite, total: titulos.length, paginas: Math.ceil(titulos.length / limite) || 1, tem_mais: inicio + limite < titulos.length },
1977
2015
  periodo: { inicio: dataInicio, fim: dataFim },
1978
- totais: {
1979
- quantidade: titulos.length,
1980
- valor_total: round2(valorTotal),
1981
- valor_vencido: round2(valorVencido),
1982
- valor_a_vencer: round2(valorAVencer),
1983
- em_cobranca: round2(valorCobranca),
1984
- },
2016
+ totais_por_moeda,
1985
2017
  por_contraparte,
1986
2018
  };
1987
2019
  }
@@ -1993,13 +2025,11 @@ export async function listarClientes(opts = {}) {
1993
2025
  const pagina = Math.max(1, opts.pagina ?? 1);
1994
2026
  const limite = Math.min(200, Math.max(1, opts.limite ?? 50));
1995
2027
  // Base: títulos a receber (inclui em cobrança) — a contraparte é o cliente.
1996
- const recebiveis = await buscarTitulos("receber", { incluirCobranca: true, limite: 200, pagina: 1 });
1997
- // Reagrupa por contraparte usando TODOS os títulos (não a página).
2028
+ const recebiveis = await buscarTitulos("receber", { incluirCobranca: true, moeda: opts.moeda, limite: 200, pagina: 1 });
2029
+ // Reagrupa por contraparte+moeda (nunca somar moedas diferentes).
1998
2030
  const mapa = new Map();
1999
2031
  for (const g of recebiveis.por_contraparte ?? []) {
2000
- const nome = g.contraparte;
2001
- // tipo: tenta achar a pessoa no cadastro para classificar PF/PJ
2002
- mapa.set(nome, { nome, tipo: "PJ", titulos_a_receber: g.quantidade, valor_a_receber: g.valor, valor_vencido: g.vencido });
2032
+ mapa.set(`${g.contraparte}|${g.moeda}`, { nome: g.contraparte, tipo: "PJ", moeda: g.moeda, titulos_a_receber: g.quantidade, valor_a_receber: g.valor, valor_vencido: g.vencido });
2003
2033
  }
2004
2034
  // Classifica PF/PJ consultando o cadastro de pessoas (lista completa cacheada).
2005
2035
  try {
@@ -2018,11 +2048,14 @@ export async function listarClientes(opts = {}) {
2018
2048
  clientes = clientes.filter(c => normalizar(c.nome).includes(t));
2019
2049
  }
2020
2050
  clientes.sort((a, b) => b.valor_a_receber - a.valor_a_receber);
2051
+ const totMoeda = new Map();
2052
+ for (const c of clientes)
2053
+ totMoeda.set(c.moeda, (totMoeda.get(c.moeda) ?? 0) + c.valor_a_receber);
2021
2054
  const inicio = (pagina - 1) * limite;
2022
2055
  return {
2023
2056
  clientes: clientes.slice(inicio, inicio + limite),
2024
2057
  paginacao: { pagina, limite, total: clientes.length, paginas: Math.ceil(clientes.length / limite) || 1, tem_mais: inicio + limite < clientes.length },
2025
- total_a_receber: round2(clientes.reduce((s, c) => s + c.valor_a_receber, 0)),
2058
+ total_a_receber_por_moeda: [...totMoeda.entries()].map(([moeda, valor]) => ({ moeda, valor: round2(valor) })),
2026
2059
  };
2027
2060
  }
2028
2061
  /**
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { enterDefaultCtx } from "./context.js";
11
11
  import { openLoginUI } from "./login-server.js";
12
12
  // Modo stdio: ativa um contexto único global no startup. Multi-tenancy só no modo HTTP.
13
13
  enterDefaultCtx();
14
- const server = new Server({ name: "next-finance-mcp", version: "0.9.39" }, { capabilities: { tools: {} } });
14
+ const server = new Server({ name: "next-finance-mcp", version: "0.9.40" }, { capabilities: { tools: {} } });
15
15
  // ── Ferramentas ─────────────────────────────────────────────────────────────
16
16
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
17
17
  tools: [
@@ -136,15 +136,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
136
136
  {
137
137
  name: "buscar_contas_a_pagar",
138
138
  description: "Lista os TÍTULOS A PAGAR (contas a pagar) da carteira — agrega TODAS as contas do tipo 'Conta a Pagar'. " +
139
- "Cada título traz: vencimento, valor, fornecedor (campo 'contraparte'), conta, se está vencido e os dias " +
140
- "de atraso/para vencer. Retorna totais (valor_total, valor_vencido, valor_a_vencer) e agregação por fornecedor. " +
141
- "Use apenas_vencidos=true para ver só o que está em atraso. Janela padrão: 1 ano atrás a 1 ano à frente.",
139
+ "Cada título traz: vencimento, valor, moeda, fornecedor (campo 'contraparte'), conta, se está vencido e os dias " +
140
+ "de atraso/para vencer. Retorna totais_por_moeda (nunca soma moedas diferentes) e agregação por fornecedor. " +
141
+ "Use moeda='BRL' para focar no real; apenas_vencidos=true para só o que está em atraso. Janela padrão: 1 ano atrás a 1 ano à frente.",
142
142
  inputSchema: {
143
143
  type: "object",
144
144
  properties: {
145
145
  data_inicio: { type: "string", description: "Data início do vencimento YYYY-MM-DD (padrão: 1 ano atrás)" },
146
146
  data_fim: { type: "string", description: "Data fim do vencimento YYYY-MM-DD (padrão: 1 ano à frente)" },
147
147
  busca: { type: "string", description: "Filtra pelo nome do fornecedor (contraparte)" },
148
+ moeda: { type: "string", description: "Filtra por moeda (ex: 'BRL'). Sem isso, retorna todas — e os totais vêm separados por moeda (nunca somados)." },
148
149
  apenas_vencidos: { type: "boolean", description: "Se true, retorna só títulos vencidos (em atraso)" },
149
150
  pagina: { type: "number", description: "Página (começa em 1, padrão: 1)" },
150
151
  limite: { type: "number", description: "Títulos por página (padrão: 50, máximo: 200)" },
@@ -155,15 +156,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
155
156
  name: "buscar_contas_a_receber",
156
157
  description: "Lista os TÍTULOS A RECEBER da carteira — agrega TODAS as contas do tipo 'Conta a Receber' e, por padrão, " +
157
158
  "também as do tipo 'Contas em Cobrança' (cada título tem o flag em_cobranca). Cada título traz: vencimento, " +
158
- "valor, cliente (campo 'contraparte'), conta, se está vencido e os dias de atraso/para vencer. Retorna totais " +
159
- "(valor_total, valor_vencido, valor_a_vencer, em_cobranca) e agregação por cliente. Use apenas_cobranca=true " +
160
- "para ver SÓ os recebíveis em cobrança, ou apenas_vencidos=true para só os vencidos. Janela padrão: 1 ano atrás a 1 ano à frente.",
159
+ "valor, moeda, cliente (campo 'contraparte'), conta, se está vencido e os dias de atraso/para vencer. Retorna " +
160
+ "totais_por_moeda (nunca soma moedas diferentes) e agregação por cliente. Use moeda='BRL' para focar no real; " +
161
+ "apenas_cobranca=true para SÓ os recebíveis em cobrança; apenas_vencidos=true para só os vencidos. Janela padrão: 1 ano atrás a 1 ano à frente.",
161
162
  inputSchema: {
162
163
  type: "object",
163
164
  properties: {
164
165
  data_inicio: { type: "string", description: "Data início do vencimento YYYY-MM-DD (padrão: 1 ano atrás)" },
165
166
  data_fim: { type: "string", description: "Data fim do vencimento YYYY-MM-DD (padrão: 1 ano à frente)" },
166
167
  busca: { type: "string", description: "Filtra pelo nome do cliente (contraparte)" },
168
+ moeda: { type: "string", description: "Filtra por moeda (ex: 'BRL'). Sem isso, retorna todas — e os totais vêm separados por moeda (nunca somados)." },
167
169
  apenas_vencidos: { type: "boolean", description: "Se true, retorna só títulos vencidos (em atraso)" },
168
170
  apenas_cobranca: { type: "boolean", description: "Se true, retorna SÓ os títulos em cobrança" },
169
171
  incluir_cobranca: { type: "boolean", description: "Inclui as contas 'em Cobrança' na agregação (padrão: true)" },
@@ -183,6 +185,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
183
185
  type: "object",
184
186
  properties: {
185
187
  busca: { type: "string", description: "Filtra pelo nome do cliente" },
188
+ moeda: { type: "string", description: "Filtra por moeda dos recebíveis (ex: 'BRL'). O total a receber vem separado por moeda." },
186
189
  pagina: { type: "number", description: "Página (começa em 1, padrão: 1)" },
187
190
  limite: { type: "number", description: "Clientes por página (padrão: 50, máximo: 200)" },
188
191
  },
@@ -234,8 +237,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
234
237
  },
235
238
  {
236
239
  name: "editar_lancamento",
237
- description: "Edita um lançamento existente. Localiza pelo trio (nome_conta + data + valor/descrição) e aplica " +
238
- "as alterações informadas. Se mais de um lançamento bater, a tool lista as opções para você refinar.",
240
+ description: "Edita um lançamento existente. Localiza pelo nome_conta + data + valor/descrição (varre TODAS as contas " +
241
+ "homônimas, inclusive em moedas diferentes ex: as várias 'Conta a Pagar') e aplica as alterações. " +
242
+ "Se mais de um lançamento bater, a tool lista as opções para você refinar (use 'moeda' para desambiguar).",
239
243
  inputSchema: {
240
244
  type: "object",
241
245
  properties: {
@@ -244,6 +248,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
244
248
  data: { type: "string", description: "Data do lançamento YYYY-MM-DD (para localizar)" },
245
249
  valor: { type: "number", description: "Valor original (para localizar, opcional)" },
246
250
  descricao: { type: "string", description: "Descrição original (substring, para localizar, opcional)" },
251
+ moeda: { type: "string", description: "Moeda da conta (ex: 'BRL') — desambigua contas homônimas em moedas diferentes" },
247
252
  // Campos a alterar (qualquer um, todos opcionais)
248
253
  nova_descricao: { type: "string", description: "Nova descrição" },
249
254
  novo_valor: { type: "number", description: "Novo valor (negativo = despesa, positivo = receita)" },
@@ -271,6 +276,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
271
276
  data: { type: "string", description: "Data do lançamento YYYY-MM-DD" },
272
277
  valor: { type: "number", description: "Valor (opcional, ajuda a desambiguar)" },
273
278
  descricao: { type: "string", description: "Descrição (substring, opcional)" },
279
+ moeda: { type: "string", description: "Moeda da conta (ex: 'BRL') — desambigua contas homônimas em moedas diferentes" },
274
280
  },
275
281
  required: ["nome_conta", "data"],
276
282
  },
@@ -787,6 +793,7 @@ async function handleTool(name, args) {
787
793
  dataInicio: args["data_inicio"],
788
794
  dataFim: args["data_fim"],
789
795
  busca: args["busca"],
796
+ moeda: args["moeda"],
790
797
  apenasVencidos: args["apenas_vencidos"],
791
798
  pagina: args["pagina"],
792
799
  limite: args["limite"],
@@ -803,6 +810,7 @@ async function handleTool(name, args) {
803
810
  dataInicio: args["data_inicio"],
804
811
  dataFim: args["data_fim"],
805
812
  busca: args["busca"],
813
+ moeda: args["moeda"],
806
814
  apenasVencidos: args["apenas_vencidos"],
807
815
  apenasCobranca: args["apenas_cobranca"],
808
816
  incluirCobranca: args["incluir_cobranca"],
@@ -819,6 +827,7 @@ async function handleTool(name, args) {
819
827
  return walErr;
820
828
  return ok(await client.listarClientes({
821
829
  busca: args["busca"],
830
+ moeda: args["moeda"],
822
831
  pagina: args["pagina"],
823
832
  limite: args["limite"],
824
833
  }));
@@ -879,6 +888,7 @@ async function handleTool(name, args) {
879
888
  data: args["data"],
880
889
  valor: args["valor"],
881
890
  descricao: args["descricao"],
891
+ moeda: args["moeda"],
882
892
  nova_descricao: args["nova_descricao"],
883
893
  novo_valor: args["novo_valor"],
884
894
  nova_data: args["nova_data"],
@@ -904,6 +914,7 @@ async function handleTool(name, args) {
904
914
  data: args["data"],
905
915
  valor: args["valor"],
906
916
  descricao: args["descricao"],
917
+ moeda: args["moeda"],
907
918
  }));
908
919
  }
909
920
  case "permissao_conta": {
@@ -1182,6 +1193,6 @@ async function handleTool(name, args) {
1182
1193
  async function main() {
1183
1194
  const transport = new StdioServerTransport();
1184
1195
  await server.connect(transport);
1185
- client.log(`NEXT Finance MCP server v0.9.39 iniciado. Timeout upstream: ${process.env.NEXT_FINANCE_TIMEOUT_MS ?? "15000"}ms`);
1196
+ client.log(`NEXT Finance MCP server v0.9.40 iniciado. Timeout upstream: ${process.env.NEXT_FINANCE_TIMEOUT_MS ?? "15000"}ms`);
1186
1197
  }
1187
1198
  main().catch((err) => { console.error("Erro fatal:", err); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-finance-mcp",
3
- "version": "0.9.39",
3
+ "version": "0.9.40",
4
4
  "mcpName": "io.github.paivapiovesan/next-finance",
5
5
  "description": "MCP Server para o NEXT Finance (finance.net.br) — login via browser, listagem de carteiras, contas e lançamentos",
6
6
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "docx": "^9.7.1",
42
43
  "express": "^5.2.1",
43
44
  "undici": "^7.25.0"
44
45
  },