@frihet/mcp-server 1.16.6 → 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 (108) hide show
  1. package/README.md +168 -82
  2. package/dist/agent-onboarding.d.ts +98 -0
  3. package/dist/agent-onboarding.js +275 -0
  4. package/dist/api-origin.d.ts +9 -0
  5. package/dist/api-origin.js +41 -0
  6. package/dist/canary/mcp-harness.d.ts +77 -0
  7. package/dist/canary/mcp-harness.js +552 -0
  8. package/dist/capability-truth.d.ts +29 -0
  9. package/dist/capability-truth.js +262 -0
  10. package/dist/claude-candidate-contract.d.ts +482 -0
  11. package/dist/claude-candidate-contract.js +87 -0
  12. package/dist/claude-output-privacy.d.ts +17 -0
  13. package/dist/claude-output-privacy.js +240 -0
  14. package/dist/claude-profile.d.ts +18 -0
  15. package/dist/claude-profile.js +260 -0
  16. package/dist/client-interface.d.ts +47 -33
  17. package/dist/client.d.ts +172 -40
  18. package/dist/client.js +708 -165
  19. package/dist/demo-client.d.ts +41 -32
  20. package/dist/demo-client.js +202 -31
  21. package/dist/demo-fixtures.d.ts +3 -3
  22. package/dist/demo-fixtures.js +7 -7
  23. package/dist/deposit-contract.d.ts +29 -0
  24. package/dist/deposit-contract.js +76 -0
  25. package/dist/fetch-no-redirect.d.ts +3 -0
  26. package/dist/fetch-no-redirect.js +16 -0
  27. package/dist/fiscal-aliases.d.ts +44 -0
  28. package/dist/fiscal-aliases.js +62 -0
  29. package/dist/fiscal-period.d.ts +26 -0
  30. package/dist/fiscal-period.js +31 -0
  31. package/dist/google-profile.d.ts +19 -0
  32. package/dist/google-profile.js +122 -0
  33. package/dist/index.js +68 -59
  34. package/dist/logger.d.ts +2 -2
  35. package/dist/logger.js +140 -8
  36. package/dist/muse-profile.d.ts +19 -0
  37. package/dist/muse-profile.js +122 -0
  38. package/dist/observability.d.ts +18 -29
  39. package/dist/observability.js +194 -106
  40. package/dist/openai-profile.d.ts +26 -22
  41. package/dist/openai-profile.js +1193 -157
  42. package/dist/openai-review-contract.d.ts +15 -6
  43. package/dist/openai-review-contract.js +714 -41
  44. package/dist/openai-review-oauth.d.ts +149 -2
  45. package/dist/openai-review-oauth.js +220 -6
  46. package/dist/prompts/register-all.d.ts +2 -0
  47. package/dist/prompts/register-all.js +34 -78
  48. package/dist/public-capability-contract.d.ts +39 -0
  49. package/dist/public-capability-contract.js +186 -0
  50. package/dist/redaction.d.ts +15 -0
  51. package/dist/redaction.js +83 -0
  52. package/dist/resources/register-all.js +154 -64
  53. package/dist/retry-after.d.ts +19 -0
  54. package/dist/retry-after.js +84 -0
  55. package/dist/server-composition.d.ts +19 -0
  56. package/dist/server-composition.js +74 -0
  57. package/dist/tool-exposure.d.ts +31 -27
  58. package/dist/tool-exposure.js +211 -87
  59. package/dist/tools/accountingClose.d.ts +14 -5
  60. package/dist/tools/accountingClose.js +41 -23
  61. package/dist/tools/audit_gl.js +8 -8
  62. package/dist/tools/backend-availability.d.ts +8 -29
  63. package/dist/tools/backend-availability.js +31 -30
  64. package/dist/tools/bank_rules.js +66 -15
  65. package/dist/tools/banking.js +5 -13
  66. package/dist/tools/clients.js +261 -15
  67. package/dist/tools/crm.js +22 -21
  68. package/dist/tools/deposits.js +114 -85
  69. package/dist/tools/einvoice.d.ts +16 -2
  70. package/dist/tools/einvoice.js +59 -12
  71. package/dist/tools/expenses.d.ts +16 -0
  72. package/dist/tools/expenses.js +47 -27
  73. package/dist/tools/fiscal.d.ts +3 -4
  74. package/dist/tools/fiscal.js +93 -60
  75. package/dist/tools/gestoria.js +15 -7
  76. package/dist/tools/hr.d.ts +2 -3
  77. package/dist/tools/hr.js +49 -20
  78. package/dist/tools/igic.d.ts +9 -8
  79. package/dist/tools/igic.js +34 -64
  80. package/dist/tools/impuesto_sociedades.d.ts +4 -5
  81. package/dist/tools/impuesto_sociedades.js +16 -37
  82. package/dist/tools/intelligence.js +166 -12
  83. package/dist/tools/invoices.js +224 -54
  84. package/dist/tools/kitchen.js +19 -9
  85. package/dist/tools/payroll.d.ts +5 -8
  86. package/dist/tools/payroll.js +19 -32
  87. package/dist/tools/permissions.d.ts +4 -7
  88. package/dist/tools/permissions.js +11 -14
  89. package/dist/tools/products.js +6 -6
  90. package/dist/tools/quotes.js +172 -26
  91. package/dist/tools/recurring.js +26 -25
  92. package/dist/tools/register-all.d.ts +2 -2
  93. package/dist/tools/register-all.js +9 -6
  94. package/dist/tools/search.d.ts +19 -0
  95. package/dist/tools/search.js +84 -0
  96. package/dist/tools/shared.d.ts +1445 -109
  97. package/dist/tools/shared.js +478 -102
  98. package/dist/tools/team.js +24 -28
  99. package/dist/tools/time.js +4 -12
  100. package/dist/tools/vendors.js +249 -15
  101. package/dist/tools/webhooks.js +35 -30
  102. package/dist/types.d.ts +31 -5
  103. package/dist/workflows/daily-operational-brief.d.ts +163 -0
  104. package/dist/workflows/daily-operational-brief.js +881 -0
  105. package/docs/agent-onboarding.json +391 -0
  106. package/package.json +49 -12
  107. package/dist/tools/onboarding.d.ts +0 -19
  108. package/dist/tools/onboarding.js +0 -64
@@ -12,14 +12,82 @@
12
12
  *
13
13
  * NOTE: ticketbai_status is registered by einvoice.ts (canonical, more complete impl).
14
14
  *
15
- * REST surface: /v1/fiscal/* (documented: pending — backend ships separately)
16
- *
17
- * NOTE: ERP backend endpoints /v1/fiscal/* are planned. Tools are wired
18
- * and will surface 404 errors until the backend ships.
15
+ * REST surface: Frihet-ERP functions/src/publicApi.ts serves READ-ONLY
16
+ * GET /v1/fiscal/modelo/{303,130,390,347}. Modelo 180 has no route; its tool
17
+ * returns NOT_DEPLOYED without calling. Period params: src/fiscal-period.ts.
19
18
  */
20
19
  import { z } from "zod/v4";
21
- import { withToolLogging, formatRecord, getContent, mutateContent, READ_ONLY_ANNOTATIONS, ERROR_CONTENT_ANNOTATIONS, fiscalModeloSummaryOutput, verifactuStatusOutput, } from "./shared.js";
22
- import { withBackendGuard, isBackendNotFound } from "./backend-availability.js";
20
+ import { withToolLogging, confirmationRequiredError, formatRecord, getContent, mutateContent, READ_ONLY_ANNOTATIONS, ERROR_CONTENT_ANNOTATIONS, fiscalModeloSummaryOutput, verifactuStatusOutput, } from "./shared.js";
21
+ import { withBackendGuard, isBackendNotFound, notDeployedError } from "./backend-availability.js";
22
+ import { FISCAL_PERIOD_RULES } from "../fiscal-period.js";
23
+ function fiscalPeriodError(toolName, structured, message) {
24
+ return {
25
+ content: [{ type: "text", text: `Error: ${message}`, annotations: ERROR_CONTENT_ANNOTATIONS }],
26
+ structuredContent: { ...structured, tool: toolName, message },
27
+ isError: true,
28
+ };
29
+ }
30
+ /**
31
+ * READ-ONLY modelo summary with a fail-closed period contract:
32
+ * 1. a supplied period must match the backend's own format, or no call is made;
33
+ * 2. the modelo the backend reports (`modeloCode`/`model`) must be the one
34
+ * requested, or the figures are withheld (MODELO_MISMATCH);
35
+ * 3. the period the backend reports must be well-formed and, when one was
36
+ * requested, identical to it — otherwise the figures belong to another
37
+ * period and are withheld (PERIOD_MISMATCH), never shown under the
38
+ * requested label.
39
+ */
40
+ function invalidFiscalPeriod(toolName, modeloCode, period) {
41
+ const rule = FISCAL_PERIOD_RULES[modeloCode];
42
+ if (!rule) {
43
+ return notDeployedError(toolName, `/v1/fiscal/modelo/${modeloCode}`);
44
+ }
45
+ if (period === undefined || rule.pattern.test(period))
46
+ return undefined;
47
+ return fiscalPeriodError(toolName, { error: "invalid_period", code: "INVALID_PERIOD", modeloCode, requested: period, expectedFormat: rule.format }, `Invalid period for Modelo ${modeloCode}: use ${rule.format}. No request was sent. ` +
48
+ `/ Periodo no valido para el Modelo ${modeloCode}: usa ${rule.format}. No se ha enviado ninguna peticion.`);
49
+ }
50
+ async function fiscalModeloSummary(client, toolName, modeloCode, period, title) {
51
+ const rule = FISCAL_PERIOD_RULES[modeloCode];
52
+ if (!rule) {
53
+ return notDeployedError(toolName, `/v1/fiscal/modelo/${modeloCode}`);
54
+ }
55
+ const result = await client.getFiscalModeloSummary(modeloCode, period);
56
+ // The backend names the modelo in `modeloCode` and/or legacy `model`
57
+ // (Frihet-ERP publicApi.ts). Every key present must name the requested
58
+ // modelo, and at least one must be present; otherwise another modelo's
59
+ // figures would be shown under this one's label.
60
+ const returnedModelos = [result["modeloCode"], result["model"]].filter((v) => v !== undefined);
61
+ const modeloAgrees = returnedModelos.length > 0 && returnedModelos.every((v) => v === modeloCode);
62
+ if (!modeloAgrees) {
63
+ return fiscalPeriodError(toolName, {
64
+ error: "modelo_mismatch",
65
+ code: "MODELO_MISMATCH",
66
+ requestedModelo: modeloCode,
67
+ returnedModelo: returnedModelos.map((v) => (typeof v === "string" ? v : null)),
68
+ }, `Frihet returned a summary that is not identifiable as Modelo ${modeloCode}, so no figures are shown ` +
69
+ `(they would be mislabelled). Do NOT state any amount for this modelo; retry or contact support. ` +
70
+ `/ Frihet devolvio un resumen que no corresponde al Modelo ${modeloCode}; no se muestran cifras. ` +
71
+ `NO indiques ningun importe para este modelo.`);
72
+ }
73
+ const returned = typeof result["period"] === "string" ? result["period"] : null;
74
+ const year = result["year"];
75
+ const yearAgrees = year === undefined || (returned !== null && String(year) === returned);
76
+ const matches = returned !== null &&
77
+ rule.pattern.test(returned) &&
78
+ yearAgrees &&
79
+ (period === undefined || returned === period);
80
+ if (!matches) {
81
+ return fiscalPeriodError(toolName, { error: "period_mismatch", code: "PERIOD_MISMATCH", modeloCode, requested: period ?? null, returned }, `Frihet returned a Modelo ${modeloCode} summary whose period does not match the request, so no figures are shown ` +
82
+ `(they would be mislabelled). Do NOT state any amount for this period; retry or contact support. ` +
83
+ `/ Frihet devolvio un resumen del Modelo ${modeloCode} de un periodo distinto al solicitado; no se muestran cifras. ` +
84
+ `NO indiques ningun importe para este periodo.`);
85
+ }
86
+ return {
87
+ content: [getContent(formatRecord(title, result))],
88
+ structuredContent: result,
89
+ };
90
+ }
23
91
  export function registerFiscalTools(server, client) {
24
92
  // -- get_modelo_303_summary --
25
93
  server.registerTool("get_modelo_303_summary", {
@@ -34,16 +102,11 @@ export function registerFiscalTools(server, client) {
34
102
  period: z
35
103
  .string()
36
104
  .optional()
37
- .describe("Period in format YYYY-QN (e.g. '2026-Q1') or YYYY for annual / Periodo en formato YYYY-QN o YYYY"),
105
+ .describe("Quarter in format YYYY-QN (e.g. '2026-Q1'); defaults to the current quarter / Trimestre en formato YYYY-QN; por defecto el trimestre actual"),
38
106
  },
39
107
  outputSchema: fiscalModeloSummaryOutput,
40
- }, async ({ period }) => withToolLogging("get_modelo_303_summary", () => withBackendGuard("get_modelo_303_summary", "/v1/fiscal/303", async () => {
41
- const result = await client.getFiscalModeloSummary("303", period);
42
- return {
43
- content: [getContent(formatRecord("Modelo 303 Summary", result))],
44
- structuredContent: result,
45
- };
46
- })));
108
+ }, async ({ period }) => withToolLogging("get_modelo_303_summary", async () => invalidFiscalPeriod("get_modelo_303_summary", "303", period) ??
109
+ withBackendGuard("get_modelo_303_summary", "/v1/fiscal/modelo/303", () => fiscalModeloSummary(client, "get_modelo_303_summary", "303", period, "Modelo 303 Summary"))));
47
110
  // -- get_modelo_130_summary --
48
111
  server.registerTool("get_modelo_130_summary", {
49
112
  title: "Get Modelo 130 Summary (IRPF Estimated Payment)",
@@ -57,16 +120,11 @@ export function registerFiscalTools(server, client) {
57
120
  period: z
58
121
  .string()
59
122
  .optional()
60
- .describe("Period in format YYYY-QN (e.g. '2026-Q1') / Periodo en formato YYYY-QN"),
123
+ .describe("Quarter in format YYYY-QN (e.g. '2026-Q1'); defaults to the current quarter / Trimestre en formato YYYY-QN; por defecto el trimestre actual"),
61
124
  },
62
125
  outputSchema: fiscalModeloSummaryOutput,
63
- }, async ({ period }) => withToolLogging("get_modelo_130_summary", () => withBackendGuard("get_modelo_130_summary", "/v1/fiscal/130", async () => {
64
- const result = await client.getFiscalModeloSummary("130", period);
65
- return {
66
- content: [getContent(formatRecord("Modelo 130 Summary", result))],
67
- structuredContent: result,
68
- };
69
- })));
126
+ }, async ({ period }) => withToolLogging("get_modelo_130_summary", async () => invalidFiscalPeriod("get_modelo_130_summary", "130", period) ??
127
+ withBackendGuard("get_modelo_130_summary", "/v1/fiscal/modelo/130", () => fiscalModeloSummary(client, "get_modelo_130_summary", "130", period, "Modelo 130 Summary"))));
70
128
  // -- get_modelo_390_summary --
71
129
  server.registerTool("get_modelo_390_summary", {
72
130
  title: "Get Modelo 390 Summary (IVA Annual Recap)",
@@ -83,21 +141,15 @@ export function registerFiscalTools(server, client) {
83
141
  .describe("Year in format YYYY (e.g. '2025') / Ejercicio en formato YYYY"),
84
142
  },
85
143
  outputSchema: fiscalModeloSummaryOutput,
86
- }, async ({ period }) => withToolLogging("get_modelo_390_summary", () => withBackendGuard("get_modelo_390_summary", "/v1/fiscal/390", async () => {
87
- const result = await client.getFiscalModeloSummary("390", period);
88
- return {
89
- content: [getContent(formatRecord("Modelo 390 Summary", result))],
90
- structuredContent: result,
91
- };
92
- })));
144
+ }, async ({ period }) => withToolLogging("get_modelo_390_summary", async () => invalidFiscalPeriod("get_modelo_390_summary", "390", period) ??
145
+ withBackendGuard("get_modelo_390_summary", "/v1/fiscal/modelo/390", () => fiscalModeloSummary(client, "get_modelo_390_summary", "390", period, "Modelo 390 Summary"))));
93
146
  // -- get_modelo_180_summary --
94
147
  server.registerTool("get_modelo_180_summary", {
95
148
  title: "Get Modelo 180 Summary (IRPF Rentals Annual)",
96
- description: "Get IRPF annual informative summary for rental income withholdings (Modelo 180, Spain). " +
97
- "Returns total retentions per tenant, property, and annual aggregate. " +
98
- "Example: period='2025' / " +
99
- "Obtiene el resumen anual de retenciones sobre alquileres para el Modelo 180. " +
100
- "Devuelve retenciones totales por inquilino, inmueble y agregado anual.",
149
+ description: "NOT DEPLOYED: Modelo 180 (annual informative return of IRPF withholdings on rental income, Spain) has no Frihet backend yet; " +
150
+ "calling this tool returns a NOT_DEPLOYED error and never data. " +
151
+ "/ NO DESPLEGADO: el Modelo 180 (resumen anual de retenciones sobre alquileres) aun no tiene backend en Frihet; " +
152
+ "esta herramienta devuelve un error NOT_DEPLOYED y nunca datos.",
101
153
  annotations: READ_ONLY_ANNOTATIONS,
102
154
  inputSchema: {
103
155
  period: z
@@ -106,13 +158,7 @@ export function registerFiscalTools(server, client) {
106
158
  .describe("Year in format YYYY (e.g. '2025') / Ejercicio en formato YYYY"),
107
159
  },
108
160
  outputSchema: fiscalModeloSummaryOutput,
109
- }, async ({ period }) => withToolLogging("get_modelo_180_summary", () => withBackendGuard("get_modelo_180_summary", "/v1/fiscal/180", async () => {
110
- const result = await client.getFiscalModeloSummary("180", period);
111
- return {
112
- content: [getContent(formatRecord("Modelo 180 Summary", result))],
113
- structuredContent: result,
114
- };
115
- })));
161
+ }, async () => withToolLogging("get_modelo_180_summary", async () => notDeployedError("get_modelo_180_summary", "/v1/fiscal/modelo/180")));
116
162
  // -- get_modelo_347_summary --
117
163
  server.registerTool("get_modelo_347_summary", {
118
164
  title: "Get Modelo 347 Summary (Operations >€3,005 Annual Recap)",
@@ -129,13 +175,8 @@ export function registerFiscalTools(server, client) {
129
175
  .describe("Year in format YYYY (e.g. '2025') / Ejercicio en formato YYYY"),
130
176
  },
131
177
  outputSchema: fiscalModeloSummaryOutput,
132
- }, async ({ period }) => withToolLogging("get_modelo_347_summary", () => withBackendGuard("get_modelo_347_summary", "/v1/fiscal/347", async () => {
133
- const result = await client.getFiscalModeloSummary("347", period);
134
- return {
135
- content: [getContent(formatRecord("Modelo 347 Summary", result))],
136
- structuredContent: result,
137
- };
138
- })));
178
+ }, async ({ period }) => withToolLogging("get_modelo_347_summary", async () => invalidFiscalPeriod("get_modelo_347_summary", "347", period) ??
179
+ withBackendGuard("get_modelo_347_summary", "/v1/fiscal/modelo/347", () => fiscalModeloSummary(client, "get_modelo_347_summary", "347", period, "Modelo 347 Summary"))));
139
180
  // -- verifactu_status --
140
181
  server.registerTool("verifactu_status", {
141
182
  title: "Get VeriFactu Submission Status",
@@ -222,18 +263,10 @@ export function registerFiscalTools(server, client) {
222
263
  outputSchema: verifactuStatusOutput,
223
264
  }, async ({ invoiceId, confirm }) => withToolLogging("verifactu_resubmit", async () => {
224
265
  if (!confirm) {
225
- return {
226
- content: [
227
- {
228
- type: "text",
229
- text: "Error: confirm=true is required for VeriFactu resubmission. " +
230
- "This is a COMPLIANCE action that submits fiscal data to AEAT. " +
231
- "Verify the invoice data is correct before setting confirm=true. / " +
232
- "Se requiere confirm=true para el reenvio VeriFactu. Accion de COMPLIANCE que envia datos fiscales a AEAT.",
233
- },
234
- ],
235
- isError: true,
236
- };
266
+ return confirmationRequiredError("Error: confirm=true is required for VeriFactu resubmission. " +
267
+ "This is a COMPLIANCE action that submits fiscal data to AEAT. " +
268
+ "Verify the invoice data is correct before setting confirm=true. / " +
269
+ "Se requiere confirm=true para el reenvio VeriFactu. Accion de COMPLIANCE que envia datos fiscales a AEAT.");
237
270
  }
238
271
  return withBackendGuard("verifactu_resubmit", "/v1/fiscal/verifactu/resubmit", async () => {
239
272
  const result = await client.resubmitVerifactu(invoiceId);
@@ -224,22 +224,30 @@ export function registerGestoriaTools(server, client) {
224
224
  title: "Consolidated AR Aging (Gestoria)",
225
225
  description: "Get a cross-client AR aging report for a gestor — totals bucketed by current / 30-60 / " +
226
226
  "60-90 / 90+ days overdue, a per-workspace breakdown, and the top overdue invoices. " +
227
- "Defaults to the authenticated gestor; pass `ownerUid` to query a specific gestor " +
228
- "(requires elevated scope). " +
227
+ "The ERP requires POST /v1/gestoria/aging with a body listing the workspaces to include " +
228
+ "(defaults to the authenticated gestor when `workspaceIds` is omitted). " +
229
229
  "Useful for dunning prioritisation and end-of-month chase lists. " +
230
- "/ Obtiene un informe de antiguedad de saldos cruzando todos los clientes del gestor — " +
230
+ "/ Obtiene un informe de antiguedad de saldos cruzando los clientes del gestor — " +
231
231
  "totales por tramo (al dia / 30-60 / 60-90 / 90+ dias), desglose por espacio y top vencidas. " +
232
232
  "Util para priorizar reclamaciones y listas de cobro de fin de mes.",
233
233
  annotations: READ_ONLY_ANNOTATIONS,
234
234
  inputSchema: {
235
- ownerUid: z
235
+ workspaceIds: z
236
+ .array(z.string().min(1))
237
+ .optional()
238
+ .describe("Workspace IDs to include (defaults to the authenticated gestor when omitted) / IDs de espacios a incluir (por defecto los del gestor)"),
239
+ asOf: z
236
240
  .string()
237
241
  .optional()
238
- .describe("Gestor UID to query (defaults to authenticated caller; elevated scope required for other UIDs) / UID del gestor (por defecto el llamante)"),
242
+ .describe("Reference date in ISO 8601 (YYYY-MM-DD). Defaults to today / Fecha de referencia"),
243
+ bustCache: z
244
+ .boolean()
245
+ .optional()
246
+ .describe("Force-refresh server cache (default false) / Forzar refresco de cache del servidor"),
239
247
  },
240
248
  outputSchema: gestoriaAgingConsolidatedOutput,
241
- }, async ({ ownerUid }) => withToolLogging("gestoria_aging_consolidated", () => withBackendGuard("gestoria_aging_consolidated", "/v1/gestoria/aging/consolidated", async () => {
242
- const result = await client.getGestoriaAgingConsolidated({ ownerUid });
249
+ }, async ({ workspaceIds, asOf, bustCache }) => withToolLogging("gestoria_aging_consolidated", () => withBackendGuard("gestoria_aging_consolidated", "/v1/gestoria/aging", async () => {
250
+ const result = await client.getGestoriaAgingConsolidated({ workspaceIds, asOf, bustCache });
243
251
  return {
244
252
  content: [getContent(formatRecord("Aging consolidated", result))],
245
253
  structuredContent: result,
@@ -14,9 +14,8 @@
14
14
  *
15
15
  * REST surface: /v1/leaves, /v1/time-entries, /v1/anomalies
16
16
  *
17
- * NOTE: ERP backend endpoints land in parallel D4-A wave. Tools wired —
18
- * 404s propagate as isError until backend ships. TODO: wire to CF
19
- * logLeaveDecision callable when REST shell lands.
17
+ * These ERP REST routes are live. The backend guard remains a rollout/workspace
18
+ * safety net so an unavailable route is never misreported as an empty result.
20
19
  */
21
20
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
21
  import type { IFrihetClient } from "../client-interface.js";
package/dist/tools/hr.js CHANGED
@@ -14,9 +14,8 @@
14
14
  *
15
15
  * REST surface: /v1/leaves, /v1/time-entries, /v1/anomalies
16
16
  *
17
- * NOTE: ERP backend endpoints land in parallel D4-A wave. Tools wired —
18
- * 404s propagate as isError until backend ships. TODO: wire to CF
19
- * logLeaveDecision callable when REST shell lands.
17
+ * These ERP REST routes are live. The backend guard remains a rollout/workspace
18
+ * safety net so an unavailable route is never misreported as an empty result.
20
19
  */
21
20
  import { z } from "zod/v4";
22
21
  import { withToolLogging, formatPaginatedResponse, formatRecord, listContent, getContent, mutateContent, READ_ONLY_ANNOTATIONS, CREATE_ANNOTATIONS, UPDATE_ANNOTATIONS, paginatedOutput, leaveRequestItemOutput, attendanceEntryItemOutput, overtimeReportOutput, anomalyItemOutput, } from "./shared.js";
@@ -26,13 +25,15 @@ export function registerHrTools(server, client) {
26
25
  server.registerTool("leave_request_create", {
27
26
  title: "Create Leave Request",
28
27
  description: "Create a new leave/PTO request for an employee. " +
29
- "Types: 'vacation', 'sick', 'personal', 'parental', 'unpaid', 'training'. " +
28
+ "Types: 'vacation', 'sick', 'personal', 'parental', 'bereavement', 'other'. " +
30
29
  "Dates must be ISO 8601 (YYYY-MM-DD). Status starts as 'pending' awaiting manager approval. " +
31
30
  "/ Crea una nueva solicitud de vacaciones/permiso. Estado inicial 'pending' pendiente aprobacion.",
32
31
  annotations: CREATE_ANNOTATIONS,
33
32
  inputSchema: {
34
33
  employeeId: z.string().describe("Employee ID / ID del empleado"),
35
- type: z.string().describe("Leave type slug (vacation, sick, personal, parental, unpaid, training) / Tipo de permiso"),
34
+ type: z
35
+ .enum(["vacation", "sick", "personal", "parental", "bereavement", "other"])
36
+ .describe("Leave type slug (vacation, sick, personal, parental, bereavement, other) / Tipo de permiso"),
36
37
  startDate: z.string().describe("Start date ISO 8601 (YYYY-MM-DD) / Fecha inicio"),
37
38
  endDate: z.string().describe("End date ISO 8601 (YYYY-MM-DD) / Fecha fin"),
38
39
  reason: z.string().optional().describe("Optional reason / Motivo opcional"),
@@ -141,10 +142,19 @@ export function registerHrTools(server, client) {
141
142
  employeeId: z.string().describe("Employee ID / ID del empleado"),
142
143
  mood: z.string().optional().describe("Optional mood slug (e.g. 'great','ok','tired') / Estado de animo"),
143
144
  location: z.string().optional().describe("Optional location ('remote','office','site') / Ubicacion"),
145
+ idempotencyKey: z
146
+ .string()
147
+ .max(64)
148
+ .optional()
149
+ .describe("Optional idempotency key, max 64 chars. One is generated per call when omitted or blank; " +
150
+ "pass your own to make a retry replay the stored open entry instead of creating a second clock-in. " +
151
+ "Reusing a key with a different body returns 409 IDEMPOTENCY_KEY_REUSED — reconcile, do not " +
152
+ "retry with a new key. / Clave de idempotencia opcional (max 64). Se genera una por llamada si se omite."),
144
153
  },
145
154
  outputSchema: attendanceEntryItemOutput,
146
155
  }, async (input) => withToolLogging("attendance_clock_in", () => withBackendGuard("attendance_clock_in", "/v1/time-entries/clock-in", async () => {
147
- const result = await client.attendanceClockIn(input);
156
+ const { idempotencyKey, ...data } = input;
157
+ const result = await client.attendanceClockIn(data, idempotencyKey);
148
158
  return {
149
159
  content: [mutateContent(formatRecord("Clocked in", result))],
150
160
  structuredContent: result,
@@ -159,10 +169,18 @@ export function registerHrTools(server, client) {
159
169
  annotations: UPDATE_ANNOTATIONS,
160
170
  inputSchema: {
161
171
  entryId: z.string().describe("Open attendance entry ID / ID de la entrada abierta"),
172
+ idempotencyKey: z
173
+ .string()
174
+ .max(64)
175
+ .optional()
176
+ .describe("Optional idempotency key, max 64 chars. One is generated per call when omitted or blank; " +
177
+ "pass your own to make a retry replay the stored close instead of erroring on a closed entry. " +
178
+ "/ Clave de idempotencia opcional (max 64). Se genera una por llamada si se omite."),
162
179
  },
163
180
  outputSchema: attendanceEntryItemOutput,
164
- }, async ({ entryId }) => withToolLogging("attendance_clock_out", () => withBackendGuard("attendance_clock_out", "/v1/time-entries/clock-out", async () => {
165
- const result = await client.attendanceClockOut(entryId);
181
+ }, async (input) => withToolLogging("attendance_clock_out", () => withBackendGuard("attendance_clock_out", "/v1/time-entries/clock-out", async () => {
182
+ const { entryId, idempotencyKey } = input;
183
+ const result = await client.attendanceClockOut(entryId, idempotencyKey);
166
184
  return {
167
185
  content: [mutateContent(formatRecord("Clocked out", result))],
168
186
  structuredContent: result,
@@ -171,14 +189,14 @@ export function registerHrTools(server, client) {
171
189
  // -- overtime_report --
172
190
  server.registerTool("overtime_report", {
173
191
  title: "Overtime Report",
174
- description: "Generate an overtime report for a period. Aggregates regular vs overtime hours " +
175
- "per employee + total estimated cost in EUR. Useful for payroll prep and labor-law audits. " +
176
- "Period format: 'YYYY-MM' (monthly) or 'YYYY-QN' (quarterly) or 'YYYY' (annual). " +
177
- "/ Informe de horas extra por periodo. Agrega horas regulares vs extra por empleado + coste estimado.",
192
+ description: "Read the ERP overtime computation for a calendar month or year: daily and weekly overtime, " +
193
+ "worked/regular/overtime minutes, overtime hours, and compliance alerts computed from records in the selected period. " +
194
+ "Optionally filter the attendance records by employee. Period format: 'YYYY-MM' or 'YYYY'. " +
195
+ "/ Lee desgloses diarios y semanales, y minutos/horas agregados calculados con los registros del periodo seleccionado, con alertas.",
178
196
  annotations: READ_ONLY_ANNOTATIONS,
179
197
  inputSchema: {
180
- period: z.string().describe("Period (YYYY-MM, YYYY-QN, or YYYY) / Periodo"),
181
- employeeId: z.string().optional().describe("Optional filter by employee / Filtrar por empleado opcional"),
198
+ period: z.string().regex(/^\d{4}(-(0[1-9]|1[0-2]))?$/).describe("Period (YYYY-MM or YYYY) / Periodo"),
199
+ employeeId: z.string().min(1).optional().describe("Optional filter by employee / Filtrar por empleado opcional"),
182
200
  },
183
201
  outputSchema: overtimeReportOutput,
184
202
  }, async ({ period, employeeId }) => withToolLogging("overtime_report", () => withBackendGuard("overtime_report", "/v1/time-entries/overtime", async () => {
@@ -192,19 +210,30 @@ export function registerHrTools(server, client) {
192
210
  server.registerTool("anomaly_list", {
193
211
  title: "List Anomalies",
194
212
  description: "List HR / operational / financial anomalies detected by the system. " +
195
- "Filter by type (duplicate_clock_in, overtime_spike, missing_clock_out, expense_outlier, etc.), " +
196
- "severity (low/medium/high/critical), or period. " +
213
+ "Filter by type (daily_exceeded, weekly_exceeded, annual_approaching, " +
214
+ "annual_exceeded, missing_break), severity (warning/critical), or period. " +
215
+ "Anomalies are COMPUTE-ON-READ over the live attendance records (Art.34/35 ET " +
216
+ "overtime engine) — there is no stored anomalies collection. " +
197
217
  "Useful for daily HR review and compliance audits. " +
198
218
  "/ Lista anomalias detectadas (RRHH/operativas/financieras) con filtros opcionales.",
199
219
  annotations: READ_ONLY_ANNOTATIONS,
200
220
  inputSchema: {
201
- type: z.string().optional().describe("Filter by anomaly type slug / Tipo"),
221
+ type: z
222
+ .enum([
223
+ "daily_exceeded",
224
+ "weekly_exceeded",
225
+ "annual_approaching",
226
+ "annual_exceeded",
227
+ "missing_break",
228
+ ])
229
+ .optional()
230
+ .describe("Filter by anomaly type slug / Tipo"),
202
231
  severity: z
203
- .enum(["low", "medium", "high", "critical"])
232
+ .enum(["warning", "critical"])
204
233
  .optional()
205
234
  .describe("Filter by severity / Severidad"),
206
- from: z.string().optional().describe("Period start ISO 8601 / Inicio"),
207
- to: z.string().optional().describe("Period end ISO 8601 / Fin"),
235
+ from: z.string().optional().describe("Period start ISO 8601 (YYYY-MM-DD) / Inicio"),
236
+ to: z.string().optional().describe("Period end ISO 8601 (YYYY-MM-DD) / Fin"),
208
237
  limit: z.number().int().min(1).max(100).optional().describe("Max results / Maximos"),
209
238
  offset: z.number().int().min(0).optional().describe("Offset / Desplazamiento"),
210
239
  },
@@ -2,18 +2,19 @@
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 type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
18
  import type { IFrihetClient } from "../client-interface.js";
18
- export declare function registerIgicTools(server: McpServer, client: IFrihetClient): void;
19
+ export declare function registerIgicTools(server: McpServer, _client: IFrihetClient): void;
19
20
  //# sourceMappingURL=igic.d.ts.map
@@ -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