@frihet/mcp-server 1.17.0 → 2.0.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.
Files changed (87) hide show
  1. package/README.md +84 -59
  2. package/dist/agent-onboarding.d.ts +1 -1
  3. package/dist/agent-onboarding.js +3 -3
  4. package/dist/canary/mcp-harness.d.ts +1 -1
  5. package/dist/canary/mcp-harness.js +4 -1
  6. package/dist/capability-truth.js +20 -12
  7. package/dist/claude-candidate-contract.d.ts +482 -0
  8. package/dist/claude-candidate-contract.js +87 -0
  9. package/dist/claude-output-privacy.d.ts +17 -0
  10. package/dist/claude-output-privacy.js +240 -0
  11. package/dist/claude-profile.d.ts +18 -0
  12. package/dist/claude-profile.js +260 -0
  13. package/dist/client-interface.d.ts +26 -20
  14. package/dist/client.d.ts +65 -29
  15. package/dist/client.js +278 -167
  16. package/dist/demo-client.d.ts +27 -21
  17. package/dist/demo-client.js +31 -14
  18. package/dist/deposit-contract.d.ts +29 -0
  19. package/dist/deposit-contract.js +76 -0
  20. package/dist/fetch-no-redirect.d.ts +3 -0
  21. package/dist/fetch-no-redirect.js +16 -0
  22. package/dist/fiscal-aliases.d.ts +2 -2
  23. package/dist/fiscal-aliases.js +2 -2
  24. package/dist/fiscal-period.d.ts +26 -0
  25. package/dist/fiscal-period.js +31 -0
  26. package/dist/google-profile.d.ts +19 -0
  27. package/dist/google-profile.js +122 -0
  28. package/dist/index.js +31 -7
  29. package/dist/logger.d.ts +1 -1
  30. package/dist/logger.js +2 -2
  31. package/dist/muse-profile.d.ts +19 -0
  32. package/dist/muse-profile.js +122 -0
  33. package/dist/observability.js +3 -2
  34. package/dist/openai-profile.d.ts +2 -2
  35. package/dist/openai-profile.js +37 -11
  36. package/dist/openai-review-contract.d.ts +1 -1
  37. package/dist/openai-review-contract.js +4 -5
  38. package/dist/openai-review-oauth.d.ts +107 -7
  39. package/dist/openai-review-oauth.js +161 -17
  40. package/dist/prompts/register-all.js +32 -78
  41. package/dist/redaction.d.ts +5 -0
  42. package/dist/redaction.js +9 -0
  43. package/dist/resources/register-all.js +150 -54
  44. package/dist/retry-after.d.ts +19 -0
  45. package/dist/retry-after.js +84 -0
  46. package/dist/server-composition.d.ts +5 -2
  47. package/dist/server-composition.js +35 -5
  48. package/dist/tool-exposure.d.ts +6 -5
  49. package/dist/tool-exposure.js +15 -13
  50. package/dist/tools/audit_gl.js +8 -8
  51. package/dist/tools/backend-availability.d.ts +8 -29
  52. package/dist/tools/backend-availability.js +31 -30
  53. package/dist/tools/bank_rules.js +66 -15
  54. package/dist/tools/banking.js +5 -13
  55. package/dist/tools/clients.js +252 -6
  56. package/dist/tools/deposits.js +107 -97
  57. package/dist/tools/einvoice.d.ts +17 -3
  58. package/dist/tools/einvoice.js +59 -12
  59. package/dist/tools/fiscal.d.ts +3 -4
  60. package/dist/tools/fiscal.js +93 -60
  61. package/dist/tools/gestoria.js +15 -7
  62. package/dist/tools/hr.js +41 -11
  63. package/dist/tools/igic.d.ts +9 -8
  64. package/dist/tools/igic.js +34 -64
  65. package/dist/tools/impuesto_sociedades.d.ts +4 -5
  66. package/dist/tools/impuesto_sociedades.js +16 -37
  67. package/dist/tools/intelligence.js +164 -10
  68. package/dist/tools/invoices.js +109 -42
  69. package/dist/tools/kitchen.js +19 -9
  70. package/dist/tools/quotes.js +86 -25
  71. package/dist/tools/recurring.js +26 -25
  72. package/dist/tools/register-all.d.ts +2 -2
  73. package/dist/tools/register-all.js +9 -6
  74. package/dist/tools/search.d.ts +19 -0
  75. package/dist/tools/search.js +84 -0
  76. package/dist/tools/shared.d.ts +17 -37
  77. package/dist/tools/shared.js +96 -30
  78. package/dist/tools/team.js +8 -16
  79. package/dist/tools/time.js +4 -12
  80. package/dist/tools/vendors.js +240 -6
  81. package/dist/types.d.ts +3 -1
  82. package/dist/workflows/daily-operational-brief.d.ts +163 -0
  83. package/dist/workflows/daily-operational-brief.js +881 -0
  84. package/docs/agent-onboarding.json +15 -5
  85. package/package.json +26 -12
  86. package/dist/tools/onboarding.d.ts +0 -19
  87. package/dist/tools/onboarding.js +0 -64
@@ -2,93 +2,69 @@
2
2
  * IGIC (Canary Islands indirect tax) tools for the Frihet MCP server — Day 1 Megasprint (4 tools).
3
3
  *
4
4
  * Tools:
5
- * 1. frihet_modelo_415_summary — M415 operations >€3,005 annual (Canarias)
6
- * 2. frihet_modelo_425_summary — M425 resumen anual IGIC (Canarias)
7
- * 3. frihet_modelo_418_summary — M418 mensual grandes empresas IGIC (Canarias)
8
- * 4. frihet_aiem_calculate — Arbitrio sobre Importaciones y Entrega de Mercancías calculation
5
+ * 1. frihet_modelo_415_summary — M415 operations >€3,005 annual (Canarias) — NOT DEPLOYED
6
+ * 2. frihet_modelo_425_summary — M425 resumen anual IGIC (Canarias) — NOT DEPLOYED
7
+ * 3. frihet_modelo_418_summary — M418 autoliquidacion mensual individual, regimen especial
8
+ * del grupo de entidades (ATC) — NOT DEPLOYED
9
+ * 4. frihet_aiem_calculate — AIEM (Arbitrio sobre Importaciones y Entrega de Mercancías) — NOT DEPLOYED
9
10
  *
10
11
  * NOTE: ATC SOAP integration skipped — internal infrastructure, not exposed via MCP.
11
12
  *
12
- * REST surface: /v1/igic/* — service-layer reads of workspace fiscal data.
13
- * Backend callables: #390 IGIC services (Frihet-ERP PR #390).
14
- * Tools surface 404 until REST shell ships.
13
+ * REST surface: Frihet-ERP functions/src/publicApi.ts has NO /v1/igic/* route
14
+ * (neither modelo summaries nor AIEM), so all four tools return NOT_DEPLOYED
15
+ * without calling (capability truth: `unavailable`, src/capability-truth.ts).
15
16
  */
16
17
  import { z } from "zod/v4";
17
- import { withToolLogging, formatRecord, getContent, openObjectOutput, READ_ONLY_ANNOTATIONS, } from "./shared.js";
18
- import { withBackendGuard } from "./backend-availability.js";
19
- export function registerIgicTools(server, client) {
18
+ import { withToolLogging, openObjectOutput, READ_ONLY_ANNOTATIONS, } from "./shared.js";
19
+ import { notDeployedError } from "./backend-availability.js";
20
+ export function registerIgicTools(server, _client) {
20
21
  // -- frihet_modelo_415_summary -------------------------------------------
21
22
  server.registerTool("frihet_modelo_415_summary", {
22
23
  title: "Get Modelo 415 Summary (IGIC Annual Operations >€3,005)",
23
- description: "Get IGIC Modelo 415 summary — annual declaration of operations with third parties exceeding €3,005. " +
24
- "Canary Islands equivalent of Modelo 347 (peninsular Spain). " +
25
- "Returns counterparty list, operation totals, and filing deadline. " +
26
- "Example: year='2025'. " +
27
- "/ Resumen del Modelo 415 IGIC — declaracion anual de operaciones con terceros > €3.005. " +
28
- "Equivalente canario del Modelo 347. Devuelve listado de contrapartes, totales y plazo.",
24
+ description: "NOT DEPLOYED: IGIC Modelo 415 (annual declaration of operations with third parties exceeding €3,005, Canary Islands) " +
25
+ "has no Frihet backend yet; calling this tool returns a NOT_DEPLOYED error and never data. " +
26
+ "/ NO DESPLEGADO: el Modelo 415 IGIC (operaciones con terceros > €3.005) aun no tiene backend en Frihet; " +
27
+ "devuelve un error NOT_DEPLOYED y nunca datos.",
29
28
  annotations: READ_ONLY_ANNOTATIONS,
30
29
  inputSchema: {
31
30
  year: z.string().optional().describe("Tax year (e.g. '2025', defaults to previous year) / Ejercicio fiscal (ej. '2025', por defecto ejercicio anterior)"),
32
31
  },
33
32
  outputSchema: openObjectOutput("Modelo 415 summary: counterparty operations, totals and filing deadline / Resumen Modelo 415: operaciones, totales y plazo"),
34
- }, async ({ year }) => withToolLogging("frihet_modelo_415_summary", () => withBackendGuard("frihet_modelo_415_summary", "/v1/igic/415", async () => {
35
- const result = await client.getIgicModeloSummary("415", { year });
36
- return {
37
- content: [getContent(formatRecord("Modelo 415 Summary (IGIC)", result))],
38
- structuredContent: result,
39
- };
40
- })));
33
+ }, async () => withToolLogging("frihet_modelo_415_summary", async () => notDeployedError("frihet_modelo_415_summary", "/v1/igic/modelo/415")));
41
34
  // -- frihet_modelo_425_summary -------------------------------------------
42
35
  server.registerTool("frihet_modelo_425_summary", {
43
36
  title: "Get Modelo 425 Summary (IGIC Annual Recap)",
44
- description: "Get IGIC Modelo 425 summary — annual IGIC recap for Canary Islands businesses. " +
45
- "Returns aggregated IGIC collected, deductible IGIC, net payable, and filing status. " +
46
- "Example: year='2025'. " +
47
- "/ Resumen del Modelo 425 IGIC — resumen anual IGIC para empresas canarias. " +
48
- "Devuelve IGIC repercutido, IGIC soportado, cuota neta y estado de presentacion.",
37
+ description: "NOT DEPLOYED: IGIC Modelo 425 (annual IGIC recap, Canary Islands) has no Frihet backend yet; " +
38
+ "calling this tool returns a NOT_DEPLOYED error and never data. " +
39
+ "/ NO DESPLEGADO: el Modelo 425 IGIC (resumen anual) aun no tiene backend en Frihet; " +
40
+ "devuelve un error NOT_DEPLOYED y nunca datos.",
49
41
  annotations: READ_ONLY_ANNOTATIONS,
50
42
  inputSchema: {
51
43
  year: z.string().optional().describe("Tax year (e.g. '2025', defaults to previous year) / Ejercicio fiscal (ej. '2025', por defecto ejercicio anterior)"),
52
44
  },
53
45
  outputSchema: openObjectOutput("Modelo 425 annual IGIC recap: IGIC collected, deductible, net payable / Resumen anual IGIC: repercutido, soportado, cuota"),
54
- }, async ({ year }) => withToolLogging("frihet_modelo_425_summary", () => withBackendGuard("frihet_modelo_425_summary", "/v1/igic/425", async () => {
55
- const result = await client.getIgicModeloSummary("425", { year });
56
- return {
57
- content: [getContent(formatRecord("Modelo 425 Summary (IGIC)", result))],
58
- structuredContent: result,
59
- };
60
- })));
46
+ }, async () => withToolLogging("frihet_modelo_425_summary", async () => notDeployedError("frihet_modelo_425_summary", "/v1/igic/modelo/425")));
61
47
  // -- frihet_modelo_418_summary -------------------------------------------
62
48
  server.registerTool("frihet_modelo_418_summary", {
63
- title: "Get Modelo 418 Summary (IGIC Monthly Large Enterprises)",
64
- description: "Get IGIC Modelo 418 summary — monthly IGIC return for large enterprises (grandes empresas) in the Canary Islands. " +
65
- "Returns monthly IGIC collected, deductible, net due, and any carryover. " +
66
- "Applicable when annual turnover exceeds the grandes empresas threshold. " +
67
- "Example: period='2026-04'. " +
68
- "/ Resumen del Modelo 418 IGIC — declaracion mensual para grandes empresas en Canarias. " +
69
- "Devuelve IGIC repercutido, soportado, cuota a ingresar y saldo a compensar.",
49
+ title: "Get Modelo 418 Summary (IGIC Monthly, Group of Entities)",
50
+ description: "NOT DEPLOYED: IGIC Modelo 418 is the monthly individual self-assessment filed by each entity (dominant and dependent) " +
51
+ "under the special regime for groups of entities (regimen especial del grupo de entidades, Agencia Tributaria Canaria). " +
52
+ "It has no Frihet backend yet; calling this tool returns a NOT_DEPLOYED error and never data. " +
53
+ "/ NO DESPLEGADO: el Modelo 418 IGIC es la autoliquidacion mensual individual de cada entidad del regimen especial " +
54
+ "del grupo de entidades (ATC). Aun no tiene backend en Frihet; devuelve un error NOT_DEPLOYED y nunca datos.",
70
55
  annotations: READ_ONLY_ANNOTATIONS,
71
56
  inputSchema: {
72
57
  period: z.string().optional().describe("Period in YYYY-MM format (defaults to last month) / Periodo YYYY-MM (por defecto mes anterior)"),
73
58
  },
74
- outputSchema: openObjectOutput("Modelo 418 monthly IGIC summary: collected, deductible, net due, carryover / Resumen mensual IGIC: repercutido, soportado, cuota, saldo"),
75
- }, async ({ period }) => withToolLogging("frihet_modelo_418_summary", () => withBackendGuard("frihet_modelo_418_summary", "/v1/igic/418", async () => {
76
- const result = await client.getIgicModeloSummary("418", { period });
77
- return {
78
- content: [getContent(formatRecord("Modelo 418 Summary (IGIC Monthly)", result))],
79
- structuredContent: result,
80
- };
81
- })));
59
+ outputSchema: openObjectOutput("Modelo 418 monthly IGIC summary (group of entities) / Resumen mensual IGIC del grupo de entidades"),
60
+ }, async () => withToolLogging("frihet_modelo_418_summary", async () => notDeployedError("frihet_modelo_418_summary", "/v1/igic/modelo/418")));
82
61
  // -- frihet_aiem_calculate ------------------------------------------------
83
62
  server.registerTool("frihet_aiem_calculate", {
84
63
  title: "Calculate AIEM (Arbitrio Importación Canarias)",
85
- description: "Calculate the AIEM (Arbitrio sobre Importaciones y Entrega de Mercancias) for goods imported " +
86
- "to or produced in the Canary Islands. " +
87
- "Returns applicable AIEM rate, tax base, and amount due for the given product. " +
88
- "AIEM is a Canarian surcharge on top of IGIC for protected local industries. " +
89
- "Example: ncCode='8471', amount=1000, description='Ordenadores portatiles'. " +
90
- "/ Calcula el AIEM para mercancias importadas o producidas en Canarias. " +
91
- "Devuelve tipo aplicable, base imponible y cuota. El AIEM protege la industria local canaria.",
64
+ description: "NOT DEPLOYED: AIEM (Arbitrio sobre Importaciones y Entrega de Mercancias, Canary Islands) calculation " +
65
+ "has no Frihet backend yet; calling this tool returns a NOT_DEPLOYED error and never a rate or amount. " +
66
+ "/ NO DESPLEGADO: el calculo del AIEM (Canarias) aun no tiene backend en Frihet; " +
67
+ "devuelve un error NOT_DEPLOYED y nunca un tipo ni una cuota.",
92
68
  annotations: READ_ONLY_ANNOTATIONS,
93
69
  inputSchema: {
94
70
  ncCode: z.string().describe("Nomenclatura Combinada (NC) tariff code / Codigo NC (nomenclatura combinada)"),
@@ -96,12 +72,6 @@ export function registerIgicTools(server, client) {
96
72
  description: z.string().optional().describe("Product description for audit reference / Descripcion del producto (referencia auditoria)"),
97
73
  },
98
74
  outputSchema: openObjectOutput("AIEM calculation result: applicable rate, tax base and amount due / Resultado AIEM: tipo aplicable, base imponible y cuota"),
99
- }, async ({ ncCode, amount, description }) => withToolLogging("frihet_aiem_calculate", () => withBackendGuard("frihet_aiem_calculate", "/v1/igic/aiem", async () => {
100
- const result = await client.calculateAiem({ ncCode, amount, description });
101
- return {
102
- content: [getContent(formatRecord(`AIEM Calculation (NC: ${ncCode})`, result))],
103
- structuredContent: result,
104
- };
105
- })));
75
+ }, async () => withToolLogging("frihet_aiem_calculate", async () => notDeployedError("frihet_aiem_calculate", "/v1/igic/aiem/calculate")));
106
76
  }
107
77
  //# sourceMappingURL=igic.js.map
@@ -5,14 +5,13 @@
5
5
  * 1. frihet_modelo_200_summary — Modelo 200 IS anual (annual corporate tax return)
6
6
  * 2. frihet_modelo_202_summary — Modelo 202 IS pagos fraccionados (3 installment payments)
7
7
  *
8
- * REST surface: /v1/is/* — service-layer reads of workspace IS data.
9
- * Backend callables: #392 Modelo 200/202 IS (Frihet-ERP PR #392).
10
- * Tools surface 404 until REST shell ships.
8
+ * REST surface: Frihet-ERP functions/src/publicApi.ts has NO /v1/is/* route,
9
+ * so both tools return NOT_DEPLOYED without calling (capability truth:
10
+ * `unavailable`, src/capability-truth.ts).
11
11
  *
12
12
  * Scope: Spanish SLs (Sociedad Limitada) and SAs filing corporate tax with AEAT.
13
- * Canary Islands variant: same models apply; ATC collects IS with territorial adjustments.
14
13
  */
15
14
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
16
15
  import type { IFrihetClient } from "../client-interface.js";
17
- export declare function registerImpuestoSociedadesTools(server: McpServer, client: IFrihetClient): void;
16
+ export declare function registerImpuestoSociedadesTools(server: McpServer, _client: IFrihetClient): void;
18
17
  //# sourceMappingURL=impuesto_sociedades.d.ts.map
@@ -5,63 +5,42 @@
5
5
  * 1. frihet_modelo_200_summary — Modelo 200 IS anual (annual corporate tax return)
6
6
  * 2. frihet_modelo_202_summary — Modelo 202 IS pagos fraccionados (3 installment payments)
7
7
  *
8
- * REST surface: /v1/is/* — service-layer reads of workspace IS data.
9
- * Backend callables: #392 Modelo 200/202 IS (Frihet-ERP PR #392).
10
- * Tools surface 404 until REST shell ships.
8
+ * REST surface: Frihet-ERP functions/src/publicApi.ts has NO /v1/is/* route,
9
+ * so both tools return NOT_DEPLOYED without calling (capability truth:
10
+ * `unavailable`, src/capability-truth.ts).
11
11
  *
12
12
  * Scope: Spanish SLs (Sociedad Limitada) and SAs filing corporate tax with AEAT.
13
- * Canary Islands variant: same models apply; ATC collects IS with territorial adjustments.
14
13
  */
15
14
  import { z } from "zod/v4";
16
- import { withToolLogging, formatRecord, getContent, openObjectOutput, READ_ONLY_ANNOTATIONS, } from "./shared.js";
17
- import { withBackendGuard } from "./backend-availability.js";
18
- export function registerImpuestoSociedadesTools(server, client) {
15
+ import { withToolLogging, openObjectOutput, READ_ONLY_ANNOTATIONS, } from "./shared.js";
16
+ import { notDeployedError } from "./backend-availability.js";
17
+ export function registerImpuestoSociedadesTools(server, _client) {
19
18
  // -- frihet_modelo_200_summary -------------------------------------------
20
19
  server.registerTool("frihet_modelo_200_summary", {
21
20
  title: "Get Modelo 200 Summary (Corporate Tax — Annual IS)",
22
- description: "Get Modelo 200 summary — annual corporate income tax return (Impuesto sobre Sociedades) " +
23
- "for Spanish SLs and SAs. Returns taxable base, deductions, tax rate, withholdings, " +
24
- "installment payments made (M202), and net payable/refundable. " +
25
- "Filing deadline: 25 days after 6 months from fiscal year end (typically 25 July for Dec FY). " +
26
- "Example: year='2025'. " +
27
- "/ Resumen Modelo 200 — declaracion anual del Impuesto sobre Sociedades para SL/SA. " +
28
- "Devuelve base imponible, deducciones, tipo, retenciones, pagos fraccionados y cuota.",
21
+ description: "NOT DEPLOYED: Modelo 200 (annual corporate income tax return, Impuesto sobre Sociedades) has no Frihet backend yet; " +
22
+ "calling this tool returns a NOT_DEPLOYED error and never data. " +
23
+ "/ NO DESPLEGADO: el Modelo 200 (declaracion anual del Impuesto sobre Sociedades) aun no tiene backend en Frihet; " +
24
+ "devuelve un error NOT_DEPLOYED y nunca datos.",
29
25
  annotations: READ_ONLY_ANNOTATIONS,
30
26
  inputSchema: {
31
27
  year: z.string().optional().describe("Fiscal year (e.g. '2025', defaults to last closed year) / Ejercicio fiscal (ej. '2025', por defecto ultimo ejercicio cerrado)"),
32
28
  },
33
29
  outputSchema: openObjectOutput("Modelo 200 annual corporate tax summary: taxable base, deductions, rate, net payable / Resumen anual IS: base, deducciones, tipo, cuota"),
34
- }, async ({ year }) => withToolLogging("frihet_modelo_200_summary", () => withBackendGuard("frihet_modelo_200_summary", "/v1/is/200", async () => {
35
- const result = await client.getISSummary("200", { year });
36
- return {
37
- content: [getContent(formatRecord("Modelo 200 Summary (IS Anual)", result))],
38
- structuredContent: result,
39
- };
40
- })));
30
+ }, async () => withToolLogging("frihet_modelo_200_summary", async () => notDeployedError("frihet_modelo_200_summary", "/v1/is/modelo/200")));
41
31
  // -- frihet_modelo_202_summary -------------------------------------------
42
32
  server.registerTool("frihet_modelo_202_summary", {
43
33
  title: "Get Modelo 202 Summary (Corporate Tax — Installment Payments)",
44
- description: "Get Modelo 202 summary — installment payments (pagos fraccionados) for Impuesto sobre Sociedades. " +
45
- "Three annual payments: April (1P), October (2P), December (3P). " +
46
- "Returns each installment amount, due date, payment status, and cumulative total vs M200 projection. " +
47
- "Example: year='2026', installment='1P'. " +
48
- "/ Resumen Modelo 202 — pagos fraccionados del Impuesto sobre Sociedades. " +
49
- "Tres plazos: abril (1P), octubre (2P), diciembre (3P). Devuelve importes, plazos y estado de pago.",
34
+ description: "NOT DEPLOYED: Modelo 202 (Impuesto sobre Sociedades installment payments, 1P/2P/3P) has no Frihet backend yet; " +
35
+ "calling this tool returns a NOT_DEPLOYED error and never data. " +
36
+ "/ NO DESPLEGADO: el Modelo 202 (pagos fraccionados del Impuesto sobre Sociedades) aun no tiene backend en Frihet; " +
37
+ "devuelve un error NOT_DEPLOYED y nunca datos.",
50
38
  annotations: READ_ONLY_ANNOTATIONS,
51
39
  inputSchema: {
52
40
  year: z.string().optional().describe("Fiscal year of the installments (e.g. '2026') / Ejercicio de los pagos fraccionados (ej. '2026')"),
53
41
  installment: z.enum(["1P", "2P", "3P"]).optional().describe("Specific installment (1P=April, 2P=October, 3P=December) or omit for all three / Plazo especifico (1P=abril, 2P=octubre, 3P=diciembre) u omitir para los tres"),
54
42
  },
55
43
  outputSchema: openObjectOutput("Modelo 202 installment payments: amounts, due dates, payment status / Pagos fraccionados Modelo 202: importes, plazos, estado"),
56
- }, async ({ year, installment }) => withToolLogging("frihet_modelo_202_summary", () => withBackendGuard("frihet_modelo_202_summary", "/v1/is/202", async () => {
57
- const result = await client.getISSummary("202", { year, installment });
58
- const label = installment
59
- ? `Modelo 202 Summary (${installment} ${year ?? ""})`
60
- : `Modelo 202 Summary (all installments ${year ?? ""})`;
61
- return {
62
- content: [getContent(formatRecord(label.trim(), result))],
63
- structuredContent: result,
64
- };
65
- })));
44
+ }, async () => withToolLogging("frihet_modelo_202_summary", async () => notDeployedError("frihet_modelo_202_summary", "/v1/is/modelo/202")));
66
45
  }
67
46
  //# sourceMappingURL=impuesto_sociedades.js.map
@@ -194,14 +194,40 @@ export function registerIntelligenceTools(server, client) {
194
194
  },
195
195
  outputSchema: invoiceItemOutput.describe("The newly created draft invoice, cloned from the original / La nueva factura borrador, clonada de la original"),
196
196
  }, async ({ id, newIssueDate, newDueDate }) => withToolLogging("duplicate_invoice", async () => {
197
- // 1. Fetch the original invoice (returns the FULL raw stored document —
198
- // payments, verifactu, eInvoice, operationType, createdBy, etc.)
197
+ // 1. Validate caller-supplied newIssueDate before touching the API.
198
+ if (newIssueDate !== undefined) {
199
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(newIssueDate);
200
+ const validCalendar = (() => {
201
+ if (!match)
202
+ return false;
203
+ const y = Number(match[1]);
204
+ const m = Number(match[2]);
205
+ const d = Number(match[3]);
206
+ if (m < 1 || m > 12 || d < 1 || d > 31)
207
+ return false;
208
+ const dt = new Date(Date.UTC(y, m - 1, d));
209
+ return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;
210
+ })();
211
+ if (!validCalendar) {
212
+ return {
213
+ content: [
214
+ {
215
+ type: "text",
216
+ text: `Error: invalid newIssueDate "${newIssueDate}". Use a real calendar date in YYYY-MM-DD format.`,
217
+ },
218
+ ],
219
+ isError: true,
220
+ };
221
+ }
222
+ }
223
+ // 2. Fetch the original invoice (returns the FULL raw stored document —
224
+ // payments, verifactu, eInvoice, createdBy, etc.)
199
225
  const original = await client.getInvoice(id);
200
- // 2. Allowlist-PICK only the fields the create endpoint actually accepts.
226
+ // 3. Allowlist-PICK only the fields the create endpoint actually accepts.
201
227
  // The create POST validates against a Zod `.strict()` schema that
202
228
  // REJECTS unknown keys (HTTP 400). Spreading the raw GET doc and only
203
229
  // blacklisting a handful of fields left stored-only fields (payments,
204
- // amountPaid, verifactu, eInvoice, operationType, poNumber, createdBy,
230
+ // amountPaid, verifactu, eInvoice, createdBy,
205
231
  // sentTo/sentAt, cancelled*, attachments, …) in the body, so any paid /
206
232
  // sent / cancelled / e-invoiced invoice failed to duplicate. Picking
207
233
  // the writable subset mirrors the create schema and never 400s.
@@ -220,23 +246,151 @@ export function registerIntelligenceTools(server, client) {
220
246
  "irpfRate",
221
247
  "equivalenceSurchargeRate",
222
248
  "clientLocation",
249
+ "operationType",
250
+ "fiscalTreatment",
223
251
  "prepayment",
224
252
  "seriesId",
225
253
  ];
226
- // 3. Build the duplicate from the writable subset + new values.
254
+ const ALLOWED_ADDRESS_KEYS = [
255
+ "street",
256
+ "city",
257
+ "zip",
258
+ "province",
259
+ "country",
260
+ "countryCode",
261
+ "state",
262
+ "postalCode",
263
+ ];
264
+ // 4. Build the duplicate from the writable subset + new values.
265
+ // Note: Firestore documents frequently store `null` for unset optional
266
+ // fields (e.g. `notes: null`, `seriesId: null`, `clientTaxId: null`), and
267
+ // stored `items` often carry computed/UI keys (`total`, `taxRate`,
268
+ // `discount`, `productId`) that `invoiceItemSchema.strict()` rejects.
269
+ // Economic integrity invariant: NEVER fabricate `0` when `quantity` or
270
+ // `unitPrice` is missing, null, non-finite, or invalid. Preserve `0` only
271
+ // when it is an explicit finite number (or explicit numeric string).
272
+ const parseExplicitFiniteNumber = (raw) => {
273
+ if (typeof raw === "number") {
274
+ return Number.isFinite(raw) ? raw : undefined;
275
+ }
276
+ if (typeof raw === "string") {
277
+ const trimmed = raw.trim();
278
+ if (trimmed === "")
279
+ return undefined;
280
+ const parsed = Number(trimmed);
281
+ return Number.isFinite(parsed) ? parsed : undefined;
282
+ }
283
+ return undefined;
284
+ };
285
+ if (typeof original.clientName !== "string" || !original.clientName.trim()) {
286
+ return {
287
+ content: [{
288
+ type: "text",
289
+ text: "Error: cannot duplicate invoice because it is missing a valid clientName. / Error: no se puede duplicar la factura porque falta el nombre del cliente.",
290
+ }],
291
+ isError: true,
292
+ };
293
+ }
294
+ if (!Array.isArray(original.items) || original.items.length === 0) {
295
+ return {
296
+ content: [{
297
+ type: "text",
298
+ text: "Error: cannot duplicate invoice because it has no valid line items. / Error: no se puede duplicar la factura porque no tiene lineas validas.",
299
+ }],
300
+ isError: true,
301
+ };
302
+ }
303
+ const sanitizedItems = [];
304
+ for (let i = 0; i < original.items.length; i++) {
305
+ const rawItem = original.items[i];
306
+ if (!rawItem || typeof rawItem !== "object" || Array.isArray(rawItem)) {
307
+ return {
308
+ content: [{
309
+ type: "text",
310
+ text: `Error: cannot duplicate invoice: line item ${i + 1} is not a valid object. / Error: no se puede duplicar la factura: la linea ${i + 1} es invalida.`,
311
+ }],
312
+ isError: true,
313
+ };
314
+ }
315
+ const itemObj = rawItem;
316
+ const description = typeof itemObj.description === "string" ? itemObj.description.trim() : "";
317
+ if (!description) {
318
+ return {
319
+ content: [{
320
+ type: "text",
321
+ text: `Error: cannot duplicate invoice: line item ${i + 1} is missing a description. / Error: no se puede duplicar la factura: falta la descripcion en la linea ${i + 1}.`,
322
+ }],
323
+ isError: true,
324
+ };
325
+ }
326
+ const quantity = parseExplicitFiniteNumber(itemObj.quantity);
327
+ if (quantity === undefined) {
328
+ return {
329
+ content: [{
330
+ type: "text",
331
+ text: `Error: cannot duplicate invoice: line item ${i + 1} has a missing or invalid quantity. Refusing to fabricate economic values. / Error: no se puede duplicar la factura: la linea ${i + 1} carece de cantidad valida.`,
332
+ }],
333
+ isError: true,
334
+ };
335
+ }
336
+ const unitPrice = parseExplicitFiniteNumber(itemObj.unitPrice);
337
+ if (unitPrice === undefined) {
338
+ return {
339
+ content: [{
340
+ type: "text",
341
+ text: `Error: cannot duplicate invoice: line item ${i + 1} has a missing or invalid unitPrice. Refusing to fabricate economic values. / Error: no se puede duplicar la factura: la linea ${i + 1} carece de precio unitario valido.`,
342
+ }],
343
+ isError: true,
344
+ };
345
+ }
346
+ sanitizedItems.push({
347
+ ...(typeof itemObj.id === "string" && itemObj.id.trim() ? { id: itemObj.id.trim() } : {}),
348
+ description: itemObj.description,
349
+ quantity,
350
+ unitPrice,
351
+ });
352
+ }
227
353
  const today = new Date().toISOString().split("T")[0];
228
- const invoiceData = {};
354
+ const invoiceData = {
355
+ clientName: original.clientName,
356
+ items: sanitizedItems,
357
+ };
229
358
  for (const field of COPYABLE_INVOICE_FIELDS) {
230
- if (original[field] !== undefined)
231
- invoiceData[field] = original[field];
359
+ if (field === "items")
360
+ continue;
361
+ const val = original[field];
362
+ if (val === undefined || val === null || val === "")
363
+ continue;
364
+ if (field === "clientAddress" && typeof val === "object" && !Array.isArray(val)) {
365
+ const addrObj = val;
366
+ const cleanAddr = {};
367
+ for (const k of ALLOWED_ADDRESS_KEYS) {
368
+ if (typeof addrObj[k] === "string" && addrObj[k].trim()) {
369
+ cleanAddr[k] = addrObj[k].trim();
370
+ }
371
+ }
372
+ if (Object.keys(cleanAddr).length > 0) {
373
+ invoiceData.clientAddress = cleanAddr;
374
+ }
375
+ continue;
376
+ }
377
+ invoiceData[field] = val;
232
378
  }
233
379
  invoiceData.status = "draft";
234
380
  invoiceData.issueDate = newIssueDate || today;
235
381
  if (newDueDate) {
236
382
  invoiceData.dueDate = newDueDate;
237
383
  }
238
- // 4. Create the duplicate
239
- const result = await client.createInvoice(invoiceData);
384
+ // 5. Create the duplicate (always as draft, never issued or sent)
385
+ const rawResult = await client.createInvoice(invoiceData);
386
+ const result = {
387
+ ...(rawResult && typeof rawResult === "object" ? rawResult : {}),
388
+ };
389
+ for (const nullableField of ["clientId", "issueDate", "dueDate", "status", "notes", "createdAt", "updatedAt"]) {
390
+ if (result[nullableField] === null) {
391
+ delete result[nullableField];
392
+ }
393
+ }
240
394
  return {
241
395
  content: [
242
396
  mutateContent(formatRecord("Invoice duplicated", result) +