@fhiron/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,403 @@
1
+ // Fhiron MCP — diccionario es/en para mensajes "marco" del connector.
2
+ //
3
+ // Filosofía: el español es el orgullo, el inglés es la cortesía. Default = es.
4
+ //
5
+ // Lo que SE traduce:
6
+ // - Mensajes "marco" del connector (resúmenes, errores de red, banner stderr,
7
+ // descripciones de tools y prompts, summaries de validación).
8
+ //
9
+ // Lo que NO se traduce automáticamente:
10
+ // - El campo `message` de cada LintIssue (lo emite el linter, siempre en es;
11
+ // hay un atajo `message_en` en el catálogo si es indispensable, pero por
12
+ // diseño el `message` se mantiene en español porque la regla CL Core ES
13
+ // chilena. Si lang=en, el connector envuelve el español con una nota
14
+ // "[ES] ..." para no inventar traducciones aproximadas).
15
+
16
+ "use strict";
17
+
18
+ const DICT = {
19
+ // ── Banner de arranque ────────────────────────────────────────────────────
20
+ "banner.ready": {
21
+ es: (v) => `[fhiron-mcp] v${v.version} listo · endpoint=${v.endpoint} · key=${v.keyState} · tools=${v.toolCount} · linter=@fhiron/linter@${v.linterVersion} · lang=${v.lang}`,
22
+ en: (v) => `[fhiron-mcp] v${v.version} ready · endpoint=${v.endpoint} · key=${v.keyState} · tools=${v.toolCount} · linter=@fhiron/linter@${v.linterVersion} · lang=${v.lang}`,
23
+ },
24
+ "banner.key.present": { es: "presente", en: "present" },
25
+ "banner.key.absent": { es: "AUSENTE — pasa --key o define FHIRON_API_KEY", en: "MISSING — pass --key or set FHIRON_API_KEY" },
26
+
27
+ // ── serverInfo description ────────────────────────────────────────────────
28
+ "server.description": {
29
+ es: "Fhiron Bridge — validación FHIR contra CL Core v1.9.4 con errores en español, ejemplos válidos y catálogos terminológicos chilenos (DEIS, TFC, CIE-10).",
30
+ en: "Fhiron Bridge — FHIR validation against CL Core v1.9.4 with educational issue feedback, valid resource examples, and Chilean terminology catalogs (DEIS, TFC, ICD-10). Server messages are localized; rule explanations remain in Spanish (CL Core is a Chilean implementation guide).",
31
+ },
32
+
33
+ // ── Tool descriptions ─────────────────────────────────────────────────────
34
+ // Convención AI-friendly v0.5.0: cada description empieza con [TAG] + verbo
35
+ // + lista explícita de resourceTypes/casos cubiertos. Los modelos pequeños
36
+ // (Gemini Flash, Haiku, Llama-8B) hacen ranking por las primeras 80 chars
37
+ // y no exploran exhaustivamente las 7+ tools — la primera línea decide.
38
+ "tool.validate.desc": {
39
+ es: "[VALIDATE] Valida un recurso FHIR completo (Patient, Practitioner, Encounter, Observation, Condition, AllergyIntolerance, Procedure, Coverage, Organization, Immunization, DiagnosticReport, MedicationRequest, Medication, Bundle) contra CL Core v1.9.4. Corre el linter local primero (gratis) y luego HAPI server-side para slicing y terminología profunda (1 crédito por call). Cada issue incluye code, severity, path FHIRPath, message en español, why pedagógico, profileUrl, suggestion, example y quickFix cuando aplica. ESTA es la tool principal de validación — úsala siempre que tengas un recurso FHIR completo en JSON.",
40
+ en: "[VALIDATE] Validate a complete FHIR resource (Patient, Practitioner, Encounter, Observation, Condition, AllergyIntolerance, Procedure, Coverage, Organization, Immunization, DiagnosticReport, MedicationRequest, Medication, Bundle) against CL Core v1.9.4. Runs the local linter first (free) then HAPI server-side for deep slicing and terminology (1 credit per call). Each issue includes code, severity, FHIRPath, Spanish message, pedagogical why, profileUrl, suggestion, example and quickFix when applicable. THIS is the primary validation tool — use it whenever you have a complete FHIR resource as JSON.",
41
+ },
42
+ "tool.lint.desc": {
43
+ es: "[LINT] Linter local offline (gratis, no descuenta cuota) para recursos FHIR completos. Cubre Patient, Practitioner, Observation, Medication, MedicationRequest, Encounter, Condition, AllergyIntolerance, Procedure, Coverage, Organization, Immunization, DiagnosticReport, Bundle. Devuelve los códigos cl-* con quickFix mecánico aplicable. Llámala antes de fhiron_validate para auto-corregir lo obvio sin gastar cuota — y usa fhiron_validate solo cuando el lint pasa limpio.",
44
+ en: "[LINT] Offline local linter (free, no quota) for complete FHIR resources. Covers Patient, Practitioner, Observation, Medication, MedicationRequest, Encounter, Condition, AllergyIntolerance, Procedure, Coverage, Organization, Immunization, DiagnosticReport, Bundle. Returns cl-* codes with mechanical quickFix. Call it before fhiron_validate to auto-fix obvious issues without spending quota — then call fhiron_validate when lint is clean.",
45
+ },
46
+ "tool.validate_bundle.desc": {
47
+ es: "[VALIDATE] Valida un Bundle FHIR (transaction/batch/collection/document) ENTRY POR ENTRY contra HAPI, no como bundle entero. Devuelve una matriz pass/fail con conteo de errores por entry, agrupado por resourceType. Útil cuando un EHR exporta 50-500 recursos y necesitas saber cuáles fallan CL Core. Descuenta 1 crédito por cada entry validada en el servidor (las que pasan el linter local con errores se cortocircuitan sin gastar cuota).",
48
+ en: "[VALIDATE] Validate a FHIR Bundle (transaction/batch/collection/document) ENTRY BY ENTRY against HAPI, not as a single bundle. Returns a pass/fail matrix with error counts per entry, grouped by resourceType. Useful when an EHR exports 50-500 resources and you need to know which ones fail CL Core. Spends 1 credit per server-validated entry (entries with local errors short-circuit without using quota).",
49
+ },
50
+ "tool.validate_file.desc": {
51
+ es: "[VALIDATE] Lee un archivo .json del filesystem local del usuario y valida su contenido con la misma lógica que fhiron_validate (linter local + HAPI server). Acepta path absoluto o relativo. CRÍTICO usar esta tool cuando el usuario menciona un archivo (.json, .fhir.json) en vez de pegar el JSON inline — evita pedirle al usuario que pegue todo el contenido. Solo lee, nunca modifica el archivo.",
52
+ en: "[VALIDATE] Read a .json file from the user's local filesystem and validate its content with the same logic as fhiron_validate (local linter + HAPI server). Accepts absolute or relative path. CRITICAL: use this tool when the user mentions a file (.json, .fhir.json) instead of pasting JSON inline — avoids asking them to paste the whole content. Read-only, never modifies the file.",
53
+ },
54
+ "tool.compare_profiles.desc": {
55
+ es: "[VALIDATE] Valida el MISMO recurso contra dos perfiles en paralelo (FHIR R4 base vs CL Core) y devuelve el diff de errores. Útil para entender qué exige CL Core sobre FHIR R4 — los errores que aparecen solo en CL Core son las restricciones chilenas. Pedagógico para integradores que vienen de FHIR internacional. Soporta Patient, Practitioner, Encounter, Observation, Condition, MedicationRequest. Descuenta 2 créditos (una llamada por perfil).",
56
+ en: "[VALIDATE] Validate the SAME resource against two profiles in parallel (FHIR R4 base vs CL Core) and return the error diff. Useful to understand what CL Core requires beyond FHIR R4 — errors that appear only in CL Core are the Chilean constraints. Pedagogical for integrators coming from international FHIR. Supports Patient, Practitioner, Encounter, Observation, Condition, MedicationRequest. Spends 2 credits (one call per profile).",
57
+ },
58
+ "tool.score_endpoint.desc": {
59
+ es: "[SCAN] Escanea un FHIR endpoint público remoto (no requiere API key del endpoint, solo URL) y devuelve un FHIR Score: versión FHIR detectada, recursos soportados, perfiles declarados, terminologías referenciadas, score 0-100 con grade A-F y recomendaciones por dimensión. Útil cuando el usuario pregunta '¿qué tan FHIR es este servidor?' o '¿cumple CL Core?'. Lectura solamente, no consume cuota porque no llama al servidor de Fhiron.",
60
+ en: "[SCAN] Scan a remote public FHIR endpoint (no endpoint API key needed, just URL) and return a FHIR Score: detected FHIR version, supported resources, declared profiles, referenced terminologies, 0-100 score with A-F grade and per-dimension recommendations. Useful when the user asks 'how FHIR-compliant is this server?' or 'does it meet CL Core?'. Read-only, no quota consumed (does not call Fhiron's server).",
61
+ },
62
+ "tool.lint_run.desc": {
63
+ es: "[VALIDATE] Valida ÚNICAMENTE un string de RUN chileno aislado (no un recurso FHIR completo). Verifica formato y dígito verificador módulo 11. Para recursos FHIR completos usa fhiron_validate. Esta tool solo aplica cuando tienes un string como '12.345.678-5' suelto y necesitas saber si es válido como RUN, sin más contexto.",
64
+ en: "[VALIDATE] Validate ONLY an isolated Chilean RUN string (not a complete FHIR resource). Checks format and modulus 11 check digit. For complete FHIR resources use fhiron_validate. This tool only applies when you have a standalone string like '12.345.678-5' and want to know if it's a valid RUN, without further context.",
65
+ },
66
+ "tool.apply_fix.desc": {
67
+ es: "[FIX] Aplica un quickFix mecánico sobre un recurso FHIR usando RFC 6901 jsonPointer. Los quickFix vienen del arreglo `issues[]` de fhiron_validate o fhiron_lint. Devuelve el recurso parchado como JSON. Idempotente: aplicar el mismo fix dos veces produce el mismo resultado. Llama esta tool inmediatamente después de validar si el issue tiene `quickFix` — la IA puede auto-corregir sin pedirle al usuario.",
68
+ en: "[FIX] Apply a mechanical quickFix to a FHIR resource using RFC 6901 jsonPointer. QuickFixes come from the `issues[]` array of fhiron_validate or fhiron_lint. Returns the patched resource as JSON. Idempotent: applying the same fix twice yields the same result. Call this tool immediately after validating if the issue has `quickFix` — the AI can auto-correct without asking the user.",
69
+ },
70
+ "tool.get_example.desc": {
71
+ es: "[SCAFFOLD] Devuelve un recurso FHIR válido CL Core v1.9.4 listo para editar (Patient, Practitioner, Observation, Medication, MedicationRequest, Encounter, Condition, AllergyIntolerance, Procedure, Coverage, Organization, Immunization, DiagnosticReport, Bundle). Útil cuando la IA necesita scaffolding sin alucinar codes, systems o el formato del RUN. Llamá esta tool antes de armar un recurso desde cero.",
72
+ en: "[SCAFFOLD] Return a valid CL Core v1.9.4 FHIR resource ready to edit (Patient, Practitioner, Observation, Medication, MedicationRequest, Encounter, Condition, AllergyIntolerance, Procedure, Coverage, Organization, Immunization, DiagnosticReport, Bundle). Useful when the AI needs scaffolding without hallucinating codes, systems or RUN format. Call this tool before building a resource from scratch.",
73
+ },
74
+ "tool.search_terminology.desc": {
75
+ es: "[TERMINOLOGY] Busca códigos en catálogos terminológicos chilenos: comunas DEIS (system 'deis-comuna'), establecimientos DEIS ('deis-establecimiento'), ATC/TFC medicamentos ('tfc'), CIE-10 diagnósticos ('cie10'), CSTipoIdentificador ('identificadores'). Llama esta tool ANTES de inventar un código — la IA debe consultar, no adivinar.",
76
+ en: "[TERMINOLOGY] Search Chilean terminology catalogs: DEIS communes (system 'deis-comuna'), DEIS facilities ('deis-establecimiento'), ATC/TFC medications ('tfc'), ICD-10 diagnoses ('cie10'), CSTipoIdentificador ('identificadores'). Call this tool BEFORE inventing a code — the AI should look up, not guess.",
77
+ },
78
+ "tool.explain_code.desc": {
79
+ es: "[EXPLAIN] Devuelve la explicación pedagógica completa de un código de error (cl-run-01, cl-patient-04, hapi-error, mcp-quota-01, etc.): qué falla, por qué, qué perfil CL Core lo indica, link de docs, fix sugerido y ejemplo correcto. Llamá esta tool cuando el usuario pregunta '¿qué significa este error?' o '¿por qué falla?'.",
80
+ en: "[EXPLAIN] Return the full educational explanation of an error code (cl-run-01, cl-patient-04, hapi-error, mcp-quota-01, etc.): what fails, why, which CL Core profile flags it, docs link, suggested fix and valid example. Call this tool when the user asks 'what does this error mean?' or 'why is it failing?'.",
81
+ },
82
+
83
+ // ── Validación summaries ──────────────────────────────────────────────────
84
+ "validate.summary.ok": {
85
+ es: (v) => `✅ Conforme CL Core v1.9.4 · ${v.resourceType ?? "recurso"}`,
86
+ en: (v) => `✅ Compliant with CL Core v1.9.4 · ${v.resourceType ?? "resource"}`,
87
+ },
88
+ "validate.summary.fail": {
89
+ es: (v) => `❌ ${v.serverErrors} error(es) HAPI · ${v.localIssues} issue(s) local(es)`,
90
+ en: (v) => `❌ ${v.serverErrors} HAPI error(s) · ${v.localIssues} local issue(s)`,
91
+ },
92
+ "validate.local.errors_summary": {
93
+ es: (v) => `❌ ${v.errors} error(es) local(es) — quickFix disponible sin consumir cuota.`,
94
+ en: (v) => `❌ ${v.errors} local error(s) — quickFix available without consuming quota.`,
95
+ },
96
+ "validate.local.note_short_circuit": {
97
+ es: "Se detectaron errores locales antes de llamar al validador HAPI — no se consumió cuota. Aplica los quickFix y reinvoca.",
98
+ en: "Local errors detected before calling the HAPI validator — no quota consumed. Apply the quickFixes and re-invoke.",
99
+ },
100
+ "lint.summary.errors": {
101
+ es: (v) => `⚠️ ${v.errors} error(es) locales · ${v.warnings} advertencia(s)`,
102
+ en: (v) => `⚠️ ${v.errors} local error(s) · ${v.warnings} warning(s)`,
103
+ },
104
+ "lint.summary.warnings_only": {
105
+ es: (v) => `✅ Sin errores locales · ${v.warnings} advertencia(s)`,
106
+ en: (v) => `✅ No local errors · ${v.warnings} warning(s)`,
107
+ },
108
+ "lint.summary.clean": {
109
+ es: "✅ Sin issues locales — listo para $validate en servidor.",
110
+ en: "✅ No local issues — ready for server-side $validate.",
111
+ },
112
+
113
+ // ── RUN ───────────────────────────────────────────────────────────────────
114
+ "run.ok": {
115
+ es: (v) => `✅ RUN válido (${v.normalized}) · CL Core v1.9.4`,
116
+ en: (v) => `✅ Valid RUN (${v.normalized}) · CL Core v1.9.4`,
117
+ },
118
+ "run.ok.with_warning": {
119
+ es: (v) => `✅ RUN válido (${v.normalized}) · CL Core v1.9.4\n⚠️ ${v.warning}`,
120
+ en: (v) => `✅ Valid RUN (${v.normalized}) · CL Core v1.9.4\n⚠️ ${v.warning}`,
121
+ },
122
+ "run.warn.dots": {
123
+ es: "El RUN incluía puntos. CL Core (CorePacienteCl) los proscribe — formato esperado: 12345678-5. Se aceptó tras normalizar (cl-run-01).",
124
+ en: "The RUN contained dots. CL Core (CorePacienteCl) forbids them — expected format: 12345678-5. Accepted after normalization (cl-run-01).",
125
+ },
126
+ "run.empty": { es: "El RUN está vacío.", en: "The RUN is empty." },
127
+
128
+ // ── Errores de red / auth / quota ─────────────────────────────────────────
129
+ "err.api_key.missing": {
130
+ es: "Falta la API key. Pasa --key TU_API_KEY o define FHIRON_API_KEY. Obtén una gratis en https://fhiron.cl/registro.",
131
+ en: "Missing API key. Pass --key YOUR_API_KEY or set FHIRON_API_KEY. Get one for free at https://fhiron.cl/registro.",
132
+ },
133
+ "err.api_key.absent_short": { es: "API key ausente.", en: "API key missing." },
134
+ "err.resource_type.missing": {
135
+ es: "El recurso FHIR debe incluir el campo `resourceType` (por ejemplo 'Patient').",
136
+ en: "The FHIR resource must include a `resourceType` field (e.g. 'Patient').",
137
+ },
138
+ "err.resource_type.short": { es: "Falta resourceType.", en: "Missing resourceType." },
139
+ "err.timeout": {
140
+ es: "Fhiron Bridge tardó más de 10s en responder. Reintenta en un momento.",
141
+ en: "Fhiron Bridge took longer than 10s to respond. Please retry in a moment.",
142
+ },
143
+ "err.network": {
144
+ es: (v) => `No se pudo conectar a Fhiron Bridge (${v.endpoint}). Revisa tu conexión o el estado del servicio en status.fhiron.cl.`,
145
+ en: (v) => `Could not connect to Fhiron Bridge (${v.endpoint}). Check your connection or service status at status.fhiron.cl.`,
146
+ },
147
+ "err.auth": {
148
+ es: (v) => `API key rechazada (HTTP ${v.status}). Verifica el valor en https://fhiron.cl/dashboard/connect-ai.`,
149
+ en: (v) => `API key rejected (HTTP ${v.status}). Check the value at https://fhiron.cl/dashboard/connect-ai.`,
150
+ },
151
+ "err.quota.fallback": { es: "Se agotó la cuota mensual de validaciones.", en: "Monthly validation quota exhausted." },
152
+ "err.quota": {
153
+ es: (v) => `${v.detail} Mejora el plan en https://fhiron.cl/precios.`,
154
+ en: (v) => `${v.detail} Upgrade your plan at https://fhiron.cl/precios.`,
155
+ },
156
+ "err.server_500": {
157
+ es: "Fhiron Bridge tuvo un error interno. Intenta de nuevo en unos segundos.",
158
+ en: "Fhiron Bridge had an internal error. Please retry in a few seconds.",
159
+ },
160
+ "err.server_5xx": {
161
+ es: (v) => `Servicio temporalmente no disponible (HTTP ${v.status}). Reintenta en 1-2 minutos o revisa status.fhiron.cl.`,
162
+ en: (v) => `Service temporarily unavailable (HTTP ${v.status}). Retry in 1-2 minutes or check status.fhiron.cl.`,
163
+ },
164
+ "err.server_other": {
165
+ es: (v) => `Fhiron Bridge respondió ${v.status}: ${v.detail}`,
166
+ en: (v) => `Fhiron Bridge responded ${v.status}: ${v.detail}`,
167
+ },
168
+ "err.unknown_tool": {
169
+ es: (v) => `Herramienta desconocida: ${v.name}. Disponibles: ${v.list}.`,
170
+ en: (v) => `Unknown tool: ${v.name}. Available: ${v.list}.`,
171
+ },
172
+ "err.unknown_method": {
173
+ es: (v) => `Método no soportado: ${v.method}`,
174
+ en: (v) => `Unsupported method: ${v.method}`,
175
+ },
176
+ "err.quickfix.missing_pointer": {
177
+ es: "El argumento `fix.jsonPointer` es obligatorio.",
178
+ en: "The `fix.jsonPointer` argument is required.",
179
+ },
180
+ "ok.quickfix.applied": {
181
+ es: (v) => `✅ QuickFix aplicado en ${v.pointer}`,
182
+ en: (v) => `✅ QuickFix applied at ${v.pointer}`,
183
+ },
184
+
185
+ // ── v0.5.0 — summaries de tools nuevas ───────────────────────────────────
186
+ "validate_bundle.summary": {
187
+ es: (v) => `📦 Bundle validado entry-por-entry: ${v.passed}/${v.total} pasaron · ${v.failed} con errores · ${v.creditsUsed} crédito(s) consumido(s)`,
188
+ en: (v) => `📦 Bundle validated entry-by-entry: ${v.passed}/${v.total} passed · ${v.failed} with errors · ${v.creditsUsed} credit(s) used`,
189
+ },
190
+ "validate_bundle.empty": {
191
+ es: "El Bundle no tiene entries (o `entry` no es un array).",
192
+ en: "The Bundle has no entries (or `entry` is not an array).",
193
+ },
194
+ "validate_bundle.not_bundle": {
195
+ es: "Esta tool requiere un Bundle. Usa fhiron_validate para recursos individuales.",
196
+ en: "This tool requires a Bundle. Use fhiron_validate for individual resources.",
197
+ },
198
+ "validate_file.read_error": {
199
+ es: (v) => `No se pudo leer el archivo '${v.path}': ${v.reason}. Verifica que la ruta exista y tengas permisos de lectura.`,
200
+ en: (v) => `Could not read file '${v.path}': ${v.reason}. Check that the path exists and you have read permissions.`,
201
+ },
202
+ "validate_file.parse_error": {
203
+ es: (v) => `El archivo '${v.path}' no es JSON válido: ${v.reason}.`,
204
+ en: (v) => `File '${v.path}' is not valid JSON: ${v.reason}.`,
205
+ },
206
+ "validate_file.too_big": {
207
+ es: (v) => `El archivo '${v.path}' supera el límite de 5 MB (${v.size} bytes). Si es un Bundle grande, particionalo.`,
208
+ en: (v) => `File '${v.path}' exceeds the 5 MB limit (${v.size} bytes). If it's a large Bundle, partition it.`,
209
+ },
210
+ "validate_file.summary": {
211
+ es: (v) => `📄 ${v.path} — ${v.resourceType} ${v.valid ? "válido" : "inválido"} (${v.errorCount} error/es local/es${v.serverErrorCount != null ? `, ${v.serverErrorCount} server` : ""})`,
212
+ en: (v) => `📄 ${v.path} — ${v.resourceType} ${v.valid ? "valid" : "invalid"} (${v.errorCount} local error(s)${v.serverErrorCount != null ? `, ${v.serverErrorCount} server` : ""})`,
213
+ },
214
+ "compare_profiles.summary": {
215
+ es: (v) => `⚖️ R4 base: ${v.r4Errors} error(es) · CL Core: ${v.clErrors} error(es) · diff CL-only: ${v.clOnly} restricción(es) chilena(s)`,
216
+ en: (v) => `⚖️ R4 base: ${v.r4Errors} error(s) · CL Core: ${v.clErrors} error(s) · CL-only diff: ${v.clOnly} Chilean constraint(s)`,
217
+ },
218
+ "compare_profiles.unsupported": {
219
+ es: (v) => `Esta tool no soporta el perfil de '${v.resourceType}'. Soportados: Patient, Practitioner, Encounter, Observation, Condition, MedicationRequest.`,
220
+ en: (v) => `This tool does not support the profile for '${v.resourceType}'. Supported: Patient, Practitioner, Encounter, Observation, Condition, MedicationRequest.`,
221
+ },
222
+ "score_endpoint.summary": {
223
+ es: (v) => `🎯 ${v.url} — Score ${v.score}/100 (${v.grade}) · FHIR ${v.fhirVersion ?? "?"} · ${v.resources} recurso(s) · ${v.profiles} perfil(es) declarado(s)`,
224
+ en: (v) => `🎯 ${v.url} — Score ${v.score}/100 (${v.grade}) · FHIR ${v.fhirVersion ?? "?"} · ${v.resources} resource(s) · ${v.profiles} declared profile(s)`,
225
+ },
226
+ "score_endpoint.unreachable": {
227
+ es: (v) => `No se pudo conectar a ${v.url}/metadata: ${v.reason}.`,
228
+ en: (v) => `Could not connect to ${v.url}/metadata: ${v.reason}.`,
229
+ },
230
+ "score_endpoint.invalid_metadata": {
231
+ es: (v) => `El endpoint ${v.url} respondió pero no es un CapabilityStatement FHIR válido.`,
232
+ en: (v) => `Endpoint ${v.url} responded but did not return a valid FHIR CapabilityStatement.`,
233
+ },
234
+
235
+ // ── Logging notifications (M8) ────────────────────────────────────────────
236
+ "log.lint.start": { es: (v) => `Linter local sobre ${v.resourceType}…`, en: (v) => `Local linter on ${v.resourceType}…` },
237
+ "log.lint.done": { es: (v) => `Linter local: ${v.errors} error(es), ${v.warnings} advertencia(s).`, en: (v) => `Local linter: ${v.errors} error(s), ${v.warnings} warning(s).` },
238
+ "log.bridge.call": { es: (v) => `Llamando a HAPI Bridge (${v.endpoint})…`, en: (v) => `Calling HAPI Bridge (${v.endpoint})…` },
239
+ "log.bridge.done": { es: (v) => `Bridge respondió en ${v.ms}ms (status ${v.status}).`, en: (v) => `Bridge responded in ${v.ms}ms (status ${v.status}).` },
240
+
241
+ // ── RUN — errores de validación (módulo 11) ───────────────────────────────
242
+ "run.err.no_dash": {
243
+ es: "El RUN debe incluir un guión separando el cuerpo del dígito verificador. Formato esperado: 12345678-5.",
244
+ en: "The RUN must include a dash (guión) separating the body from the check digit. Expected format: 12345678-5.",
245
+ },
246
+ "run.err.body_digits": {
247
+ es: "El cuerpo del RUN (antes del guion) debe contener solo dígitos.",
248
+ en: "The RUN body (before the dash) must contain digits only.",
249
+ },
250
+ "run.err.body_too_long": {
251
+ es: (v) => `El cuerpo del RUN tiene ${v.length} dígitos y excede el máximo de 8.`,
252
+ en: (v) => `The RUN body has ${v.length} digits, which exceeds the maximum of 8.`,
253
+ },
254
+ "run.err.format": {
255
+ es: "Formato de RUN inválido. Esperado: 1 a 8 dígitos, guion y dígito verificador (0-9 o K).",
256
+ en: "Invalid RUN format. Expected: 1 to 8 digits, a dash, and a check digit (0-9 or K).",
257
+ },
258
+ "run.err.dv": {
259
+ es: (v) => `Dígito verificador incorrecto: esperado '${v.expected}', recibido '${v.received}' (cálculo módulo 11).`,
260
+ en: (v) => `Incorrect check digit: expected '${v.expected}', got '${v.received}' (modulus 11 calculation).`,
261
+ },
262
+
263
+ // ── Parseo de entrada ─────────────────────────────────────────────────────
264
+ "parse.err.too_large": {
265
+ es: (v) => `El recurso supera el límite de ${v.limit} bytes para validación inline. Si es un Bundle grande, particiónalo o usa fhiron_validate_file.`,
266
+ en: (v) => `The resource exceeds the ${v.limit} byte limit for inline validation. If it's a large Bundle, partition it or use fhiron_validate_file.`,
267
+ },
268
+ "parse.err.json_invalid": {
269
+ es: (v) => `El JSON no es válido: ${v.detail}.`,
270
+ en: (v) => `The JSON is not valid: ${v.detail}.`,
271
+ },
272
+ "parse.err.invalid_input": {
273
+ es: "La entrada no es un objeto JSON ni un string JSON parseable.",
274
+ en: "The input is neither a JSON object nor a parseable JSON string.",
275
+ },
276
+
277
+ // ── FHIR Score — dimensiones y recomendaciones ────────────────────────────
278
+ "score.dim.fhir_ok": {
279
+ es: "FHIR R4 (4.0.1) detectado — la versión que usa CL Core.",
280
+ en: "FHIR R4 (4.0.1) detected — the version CL Core uses.",
281
+ },
282
+ "score.dim.no_supported_profile": {
283
+ es: "El endpoint no declara perfiles soportados en su CapabilityStatement.",
284
+ en: "The endpoint does not declare supported profiles in its CapabilityStatement.",
285
+ },
286
+ "score.dim.terms_detected": {
287
+ es: (v) => `Terminologías referenciadas: ${v.systems}.`,
288
+ en: (v) => `Referenced terminologies: ${v.systems}.`,
289
+ },
290
+ "score.dim.no_terms": {
291
+ es: "No se detectaron sistemas de terminología en el CapabilityStatement.",
292
+ en: "No terminology systems detected in the CapabilityStatement.",
293
+ },
294
+ "score.rec.migrate_r4": {
295
+ es: "Migra a FHIR R4 (4.0.1) — es la versión sobre la que se define CL Core.",
296
+ en: "Migrate to FHIR R4 (4.0.1) — it's the version CL Core is built on.",
297
+ },
298
+ "score.rec.missing_core": {
299
+ es: (v) => `Agrega soporte para recursos core ausentes: ${v.missing}.`,
300
+ en: (v) => `Add support for missing core resources: ${v.missing}.`,
301
+ },
302
+ "score.rec.declare_supported_profile": {
303
+ es: "Declara los perfiles que soportas en CapabilityStatement.rest.resource.supportedProfile.",
304
+ en: "Declare the profiles you support in CapabilityStatement.rest.resource.supportedProfile.",
305
+ },
306
+ "score.rec.support_interactions": {
307
+ es: "Amplía las interacciones REST soportadas (read, search, create) en los recursos clave.",
308
+ en: "Expand the supported REST interactions (read, search, create) on key resources.",
309
+ },
310
+ "score.rec.reference_terminology": {
311
+ es: "Referencia terminologías estándar (LOINC, SNOMED CT, CIE-10) en tus perfiles.",
312
+ en: "Reference standard terminologies (LOINC, SNOMED CT, ICD-10) in your profiles.",
313
+ },
314
+
315
+ // ── validate_file — errores de acceso a filesystem ────────────────────────
316
+ "validate_file.err.path_required": {
317
+ es: "se requiere un path de archivo",
318
+ en: "a file path is required",
319
+ },
320
+ "validate_file.err.outside_root": {
321
+ es: (v) => `El path '${v.path}' está fuera del root permitido (${v.root}). Por seguridad solo se leen archivos dentro de esa raíz.`,
322
+ en: (v) => `Path '${v.path}' is outside the allowed root (${v.root}). For security, only files within that root are read.`,
323
+ },
324
+ "validate_file.err.bad_extension": {
325
+ es: (v) => `Extensión no soportada: '${v.path}' debe terminar en .json o .fhir.json.`,
326
+ en: (v) => `Unsupported extension: '${v.path}' must end in .json or .fhir.json.`,
327
+ },
328
+ "validate_file.err.not_found": {
329
+ es: "el archivo no existe",
330
+ en: "the file does not exist",
331
+ },
332
+ "validate_file.err.symlink": {
333
+ es: (v) => `'${v.path}' es un enlace simbólico; por seguridad no se siguen symlinks.`,
334
+ en: (v) => `'${v.path}' is a symbolic link; for security, symlinks are not followed.`,
335
+ },
336
+ "validate_file.err.not_file": {
337
+ es: "la ruta no apunta a un archivo regular",
338
+ en: "the path does not point to a regular file",
339
+ },
340
+ "validate_file.err.parse_failed": {
341
+ es: "el contenido no es JSON válido",
342
+ en: "the content is not valid JSON",
343
+ },
344
+
345
+ // ── score_endpoint — guardas de seguridad ─────────────────────────────────
346
+ "score_endpoint.blocked": {
347
+ es: (v) => `La URL ${v.url} apunta a una dirección privada, loopback o de metadata de nube y fue bloqueada por seguridad (protección anti-SSRF). Usa un endpoint FHIR público.`,
348
+ en: (v) => `URL ${v.url} points to a private, loopback or cloud-metadata address and was blocked for security (anti-SSRF protection). Use a public FHIR endpoint.`,
349
+ },
350
+ "score_endpoint.too_large": {
351
+ es: (v) => `La respuesta de ${v.url} supera el límite de ${v.limit} bytes. El CapabilityStatement es inusualmente grande; revisa el endpoint.`,
352
+ en: (v) => `The response from ${v.url} exceeds the ${v.limit} byte limit. The CapabilityStatement is unusually large; check the endpoint.`,
353
+ },
354
+
355
+ // ── Otros fallbacks ───────────────────────────────────────────────────────
356
+ "err.not_initialized": {
357
+ es: (v) => `El método '${v.method}' requiere completar el handshake initialize + notifications/initialized primero (MCP spec).`,
358
+ en: (v) => `Method '${v.method}' requires completing the initialize handshake + notifications/initialized first (MCP spec).`,
359
+ },
360
+ "bridge.fallback.server_error": {
361
+ es: "Fhiron Bridge respondió con un error inesperado. Intenta de nuevo en unos segundos.",
362
+ en: "Fhiron Bridge responded with an unexpected error. Please retry in a few seconds.",
363
+ },
364
+ "explain.fallback.no_context": {
365
+ es: "No hay explicación adicional disponible para este código.",
366
+ en: "No additional explanation is available for this code.",
367
+ },
368
+ };
369
+
370
+ const SUPPORTED = new Set(["es", "en"]);
371
+
372
+ function normalizeLang(lang) {
373
+ if (!lang) return "es";
374
+ const l = String(lang).toLowerCase().split(/[-_]/)[0];
375
+ return SUPPORTED.has(l) ? l : "es";
376
+ }
377
+
378
+ /**
379
+ * t(key, lang, vars?) — devuelve el string localizado.
380
+ * Si la entrada del diccionario es función, la invoca con `vars`.
381
+ * Si no encuentra la key, devuelve la key (modo defensivo) — esto facilita
382
+ * detectar entradas faltantes durante desarrollo.
383
+ *
384
+ * @param {string} key
385
+ * @param {string} lang
386
+ * @param {object} [vars]
387
+ * @returns {string}
388
+ */
389
+ function t(key, lang, vars) {
390
+ const entry = DICT[key];
391
+ const l = normalizeLang(lang);
392
+ if (!entry) return key;
393
+ const v = entry[l] || entry.es;
394
+ if (typeof v === "function") return v(vars || {});
395
+ return v;
396
+ }
397
+
398
+ module.exports = {
399
+ t,
400
+ normalizeLang,
401
+ SUPPORTED_LANGS: Array.from(SUPPORTED),
402
+ DICT,
403
+ };
@@ -0,0 +1,173 @@
1
+ // Fhiron linter — lista oficial de resourceTypes de FHIR R4.
2
+ //
3
+ // Se expone como Set para que `lintResource` pueda emitir un warning barato
4
+ // cuando el usuario envía un resourceType inventado (ej. "FooBarInventedResource").
5
+ // La idea es ahorrarle la llamada al servidor: HAPI lo rechazaría igual, pero
6
+ // costaría una cuota.
7
+
8
+ "use strict";
9
+
10
+ // Lista tomada del índice de recursos de FHIR R4 (http://hl7.org/fhir/R4/resourcelist.html).
11
+ // Mantener ordenado alfabéticamente para facilitar revisiones manuales.
12
+ const FHIR_R4_RESOURCE_TYPES = Object.freeze([
13
+ "Account",
14
+ "ActivityDefinition",
15
+ "AdverseEvent",
16
+ "AllergyIntolerance",
17
+ "Appointment",
18
+ "AppointmentResponse",
19
+ "AuditEvent",
20
+ "Basic",
21
+ "Binary",
22
+ "BiologicallyDerivedProduct",
23
+ "BodyStructure",
24
+ "Bundle",
25
+ "CapabilityStatement",
26
+ "CarePlan",
27
+ "CareTeam",
28
+ "CatalogEntry",
29
+ "ChargeItem",
30
+ "ChargeItemDefinition",
31
+ "Claim",
32
+ "ClaimResponse",
33
+ "ClinicalImpression",
34
+ "CodeSystem",
35
+ "Communication",
36
+ "CommunicationRequest",
37
+ "CompartmentDefinition",
38
+ "Composition",
39
+ "ConceptMap",
40
+ "Condition",
41
+ "Consent",
42
+ "Contract",
43
+ "Coverage",
44
+ "CoverageEligibilityRequest",
45
+ "CoverageEligibilityResponse",
46
+ "DetectedIssue",
47
+ "Device",
48
+ "DeviceDefinition",
49
+ "DeviceMetric",
50
+ "DeviceRequest",
51
+ "DeviceUseStatement",
52
+ "DiagnosticReport",
53
+ "DocumentManifest",
54
+ "DocumentReference",
55
+ "DomainResource",
56
+ "EffectEvidenceSynthesis",
57
+ "Encounter",
58
+ "Endpoint",
59
+ "EnrollmentRequest",
60
+ "EnrollmentResponse",
61
+ "EpisodeOfCare",
62
+ "EventDefinition",
63
+ "Evidence",
64
+ "EvidenceVariable",
65
+ "ExampleScenario",
66
+ "ExplanationOfBenefit",
67
+ "FamilyMemberHistory",
68
+ "Flag",
69
+ "Goal",
70
+ "GraphDefinition",
71
+ "Group",
72
+ "GuidanceResponse",
73
+ "HealthcareService",
74
+ "ImagingStudy",
75
+ "Immunization",
76
+ "ImmunizationEvaluation",
77
+ "ImmunizationRecommendation",
78
+ "ImplementationGuide",
79
+ "InsurancePlan",
80
+ "Invoice",
81
+ "Library",
82
+ "Linkage",
83
+ "List",
84
+ "Location",
85
+ "Measure",
86
+ "MeasureReport",
87
+ "Media",
88
+ "Medication",
89
+ "MedicationAdministration",
90
+ "MedicationDispense",
91
+ "MedicationKnowledge",
92
+ "MedicationRequest",
93
+ "MedicationStatement",
94
+ "MedicinalProduct",
95
+ "MedicinalProductAuthorization",
96
+ "MedicinalProductContraindication",
97
+ "MedicinalProductIndication",
98
+ "MedicinalProductIngredient",
99
+ "MedicinalProductInteraction",
100
+ "MedicinalProductManufactured",
101
+ "MedicinalProductPackaged",
102
+ "MedicinalProductPharmaceutical",
103
+ "MedicinalProductUndesirableEffect",
104
+ "MessageDefinition",
105
+ "MessageHeader",
106
+ "MolecularSequence",
107
+ "NamingSystem",
108
+ "NutritionOrder",
109
+ "Observation",
110
+ "ObservationDefinition",
111
+ "OperationDefinition",
112
+ "OperationOutcome",
113
+ "Organization",
114
+ "OrganizationAffiliation",
115
+ "Parameters",
116
+ "Patient",
117
+ "PaymentNotice",
118
+ "PaymentReconciliation",
119
+ "Person",
120
+ "PlanDefinition",
121
+ "Practitioner",
122
+ "PractitionerRole",
123
+ "Procedure",
124
+ "Provenance",
125
+ "Questionnaire",
126
+ "QuestionnaireResponse",
127
+ "RelatedPerson",
128
+ "RequestGroup",
129
+ "ResearchDefinition",
130
+ "ResearchElementDefinition",
131
+ "ResearchStudy",
132
+ "ResearchSubject",
133
+ "Resource",
134
+ "RiskAssessment",
135
+ "RiskEvidenceSynthesis",
136
+ "Schedule",
137
+ "SearchParameter",
138
+ "ServiceRequest",
139
+ "Slot",
140
+ "Specimen",
141
+ "SpecimenDefinition",
142
+ "StructureDefinition",
143
+ "StructureMap",
144
+ "Subscription",
145
+ "Substance",
146
+ "SubstanceNucleicAcid",
147
+ "SubstancePolymer",
148
+ "SubstanceProtein",
149
+ "SubstanceReferenceInformation",
150
+ "SubstanceSourceMaterial",
151
+ "SubstanceSpecification",
152
+ "SupplyDelivery",
153
+ "SupplyRequest",
154
+ "Task",
155
+ "TerminologyCapabilities",
156
+ "TestReport",
157
+ "TestScript",
158
+ "ValueSet",
159
+ "VerificationResult",
160
+ "VisionPrescription",
161
+ ]);
162
+
163
+ const FHIR_R4_RESOURCE_SET = new Set(FHIR_R4_RESOURCE_TYPES);
164
+
165
+ function isKnownResourceType(name) {
166
+ return typeof name === "string" && FHIR_R4_RESOURCE_SET.has(name);
167
+ }
168
+
169
+ module.exports = {
170
+ FHIR_R4_RESOURCE_TYPES,
171
+ FHIR_R4_RESOURCE_SET,
172
+ isKnownResourceType,
173
+ };