@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,265 @@
1
+ // Fhiron linter — catálogos terminológicos chilenos esenciales.
2
+ //
3
+ // Para `fhiron_search_terminology`: cuando el agente IA dice "necesito el
4
+ // código DEIS de Las Condes" o "el TFC de paracetamol", llama esta tool en
5
+ // vez de alucinar un código.
6
+ //
7
+ // Sistemas soportados:
8
+ // - "deis-comuna" → Codificación DEIS de comunas (decreto 817/MINSAL)
9
+ // - "deis-establecimiento" → Establecimientos de salud DEIS (sin URI canónica oficial)
10
+ // - "tfc" → Tabla de Fármacos (códigos ATC — http://www.whocc.no/atc)
11
+ // - "cie10" → CIE-10 chileno
12
+ // - "identificadores" → CSTipoIdentificador (RUN, pasaporte, etc.)
13
+ //
14
+ // Búsqueda: normalización lower + sin tildes + match parcial. Devuelve hasta
15
+ // `limit` resultados (default 10). Si query es vacío, devuelve los primeros.
16
+
17
+ "use strict";
18
+
19
+ const { CS } = require("./catalog.js");
20
+
21
+ // ── Comunas DEIS (decreto 817 — subset representativo) ───────────────────────
22
+
23
+ const COMUNAS = [
24
+ { code: "13101", name: "Santiago", region: "Metropolitana de Santiago" },
25
+ { code: "13114", name: "Las Condes", region: "Metropolitana de Santiago" },
26
+ { code: "13123", name: "Providencia", region: "Metropolitana de Santiago" },
27
+ { code: "13125", name: "Ñuñoa", region: "Metropolitana de Santiago" },
28
+ { code: "13110", name: "La Florida", region: "Metropolitana de Santiago" },
29
+ { code: "13119", name: "Maipú", region: "Metropolitana de Santiago" },
30
+ { code: "13130", name: "Puente Alto", region: "Metropolitana de Santiago" },
31
+ { code: "13127", name: "Peñalolén", region: "Metropolitana de Santiago" },
32
+ { code: "13104", name: "Conchalí", region: "Metropolitana de Santiago" },
33
+ { code: "13132", name: "Quilicura", region: "Metropolitana de Santiago" },
34
+ { code: "13301", name: "Colina", region: "Metropolitana de Santiago" },
35
+ { code: "01101", name: "Iquique", region: "Tarapacá" },
36
+ { code: "02101", name: "Antofagasta", region: "Antofagasta" },
37
+ { code: "03101", name: "Copiapó", region: "Atacama" },
38
+ { code: "04101", name: "La Serena", region: "Coquimbo" },
39
+ { code: "04102", name: "Coquimbo", region: "Coquimbo" },
40
+ { code: "05101", name: "Valparaíso", region: "Valparaíso" },
41
+ { code: "05109", name: "Viña del Mar", region: "Valparaíso" },
42
+ { code: "05804", name: "Quilpué", region: "Valparaíso" },
43
+ { code: "06101", name: "Rancagua", region: "O'Higgins" },
44
+ { code: "07101", name: "Talca", region: "Maule" },
45
+ { code: "08101", name: "Concepción", region: "Biobío" },
46
+ { code: "08102", name: "Talcahuano", region: "Biobío" },
47
+ { code: "09101", name: "Temuco", region: "Araucanía" },
48
+ { code: "10101", name: "Puerto Montt", region: "Los Lagos" },
49
+ { code: "11101", name: "Coyhaique", region: "Aysén" },
50
+ { code: "12101", name: "Punta Arenas", region: "Magallanes" },
51
+ { code: "14101", name: "Valdivia", region: "Los Ríos" },
52
+ { code: "15101", name: "Arica", region: "Arica y Parinacota" },
53
+ { code: "16101", name: "Chillán", region: "Ñuble" },
54
+ ];
55
+
56
+ // ── Establecimientos DEIS (subset top hospitales) ────────────────────────────
57
+
58
+ const ESTABLECIMIENTOS = [
59
+ { code: "100311", name: "Hospital Clínico Universidad de Chile", region: "Metropolitana de Santiago" },
60
+ { code: "100312", name: "Hospital del Salvador", region: "Metropolitana de Santiago" },
61
+ { code: "102100", name: "Hospital San Juan de Dios", region: "Metropolitana de Santiago" },
62
+ { code: "103100", name: "Hospital Sótero del Río", region: "Metropolitana de Santiago" },
63
+ { code: "104100", name: "Hospital Barros Luco Trudeau", region: "Metropolitana de Santiago" },
64
+ { code: "104201", name: "Hospital Exequiel González Cortés", region: "Metropolitana de Santiago" },
65
+ { code: "105100", name: "Hospital Padre Hurtado", region: "Metropolitana de Santiago" },
66
+ { code: "106101", name: "Hospital San José", region: "Metropolitana de Santiago" },
67
+ { code: "108101", name: "Hospital de Urgencia Asistencia Pública (Posta Central)", region: "Metropolitana de Santiago" },
68
+ { code: "111301", name: "Hospital Dr. Luis Calvo Mackenna", region: "Metropolitana de Santiago" },
69
+ { code: "112101", name: "Hospital Dr. Roberto del Río", region: "Metropolitana de Santiago" },
70
+ { code: "201100", name: "Hospital Regional de Iquique Dr. Ernesto Torres Galdames", region: "Tarapacá" },
71
+ { code: "301100", name: "Hospital Dr. Leonardo Guzmán de Antofagasta", region: "Antofagasta" },
72
+ { code: "401100", name: "Hospital San Juan de Dios de La Serena", region: "Coquimbo" },
73
+ { code: "501100", name: "Hospital Carlos van Buren", region: "Valparaíso" },
74
+ { code: "501101", name: "Hospital Dr. Gustavo Fricke", region: "Valparaíso" },
75
+ { code: "601100", name: "Hospital Regional Libertador Bernardo O'Higgins", region: "O'Higgins" },
76
+ { code: "701100", name: "Hospital Regional de Talca", region: "Maule" },
77
+ { code: "801100", name: "Hospital Clínico Regional Dr. Guillermo Grant Benavente", region: "Biobío" },
78
+ { code: "901100", name: "Hospital Hernán Henríquez Aravena de Temuco", region: "Araucanía" },
79
+ { code: "1001100", name: "Hospital Base Dr. Eduardo Schütz Schroeder", region: "Los Lagos" },
80
+ ];
81
+
82
+ // ── TFC (Tabla de Fármacos Catalogados — subset esencial) ────────────────────
83
+
84
+ const TFC = [
85
+ { code: "C09AA02", name: "Enalapril 10 mg comprimido", atc: "C09AA02", indication: "Hipertensión" },
86
+ { code: "C09AA05", name: "Ramipril 5 mg cápsula", atc: "C09AA05", indication: "Hipertensión" },
87
+ { code: "C07AB07", name: "Bisoprolol 5 mg comprimido", atc: "C07AB07", indication: "Cardiovascular" },
88
+ { code: "C03CA01", name: "Furosemida 40 mg comprimido", atc: "C03CA01", indication: "Diurético" },
89
+ { code: "A10BA02", name: "Metformina 850 mg comprimido", atc: "A10BA02", indication: "Diabetes" },
90
+ { code: "A10AB04", name: "Insulina lispro 100 UI/mL", atc: "A10AB04", indication: "Diabetes" },
91
+ { code: "C10AA01", name: "Simvastatina 20 mg comprimido", atc: "C10AA01", indication: "Dislipidemia" },
92
+ { code: "C10AA05", name: "Atorvastatina 20 mg comprimido", atc: "C10AA05", indication: "Dislipidemia" },
93
+ { code: "N02BE01", name: "Paracetamol 500 mg comprimido", atc: "N02BE01", indication: "Analgésico" },
94
+ { code: "M01AE01", name: "Ibuprofeno 400 mg comprimido", atc: "M01AE01", indication: "AINE" },
95
+ { code: "N02AA01", name: "Morfina 10 mg/mL ampolla", atc: "N02AA01", indication: "Analgésico opioide" },
96
+ { code: "J01CA04", name: "Amoxicilina 500 mg cápsula", atc: "J01CA04", indication: "Antibiótico" },
97
+ { code: "J01CR02", name: "Amoxicilina/Ác. clavulánico 875/125", atc: "J01CR02", indication: "Antibiótico" },
98
+ { code: "J01FA10", name: "Azitromicina 500 mg comprimido", atc: "J01FA10", indication: "Antibiótico" },
99
+ { code: "R03AC02", name: "Salbutamol 100 mcg inhalador", atc: "R03AC02", indication: "Asma/EPOC" },
100
+ { code: "R03BA01", name: "Beclometasona 250 mcg inhalador", atc: "R03BA01", indication: "Asma" },
101
+ { code: "N05BA01", name: "Diazepam 10 mg comprimido", atc: "N05BA01", indication: "Ansiolítico" },
102
+ { code: "N06AB03", name: "Fluoxetina 20 mg cápsula", atc: "N06AB03", indication: "Antidepresivo" },
103
+ { code: "B01AA03", name: "Warfarina 5 mg comprimido", atc: "B01AA03", indication: "Anticoagulante" },
104
+ { code: "B01AC06", name: "Ácido acetilsalicílico 100 mg", atc: "B01AC06", indication: "Antiagregante" },
105
+ ];
106
+
107
+ // ── CIE-10 (subset diagnósticos comunes en Chile) ────────────────────────────
108
+
109
+ const CIE10 = [
110
+ { code: "I10", name: "Hipertensión esencial (primaria)" },
111
+ { code: "I11.9", name: "Cardiopatía hipertensiva sin insuficiencia cardiaca" },
112
+ { code: "I20.9", name: "Angina de pecho, no especificada" },
113
+ { code: "I21.9", name: "Infarto agudo de miocardio, no especificado" },
114
+ { code: "I50.9", name: "Insuficiencia cardiaca, no especificada" },
115
+ { code: "I63.9", name: "Infarto cerebral, no especificado" },
116
+ { code: "E10.9", name: "Diabetes mellitus tipo 1 sin complicaciones" },
117
+ { code: "E11.9", name: "Diabetes mellitus tipo 2 sin complicaciones" },
118
+ { code: "E78.5", name: "Hiperlipidemia, no especificada" },
119
+ { code: "E66.9", name: "Obesidad, no especificada" },
120
+ { code: "J18.9", name: "Neumonía, no especificada" },
121
+ { code: "J44.9", name: "EPOC, no especificada" },
122
+ { code: "J45.9", name: "Asma, no especificada" },
123
+ { code: "J06.9", name: "Infección aguda de las vías respiratorias superiores" },
124
+ { code: "K29.7", name: "Gastritis, no especificada" },
125
+ { code: "K35.8", name: "Apendicitis aguda no especificada" },
126
+ { code: "K80.2", name: "Cálculo de la vesícula biliar sin colecistitis" },
127
+ { code: "N39.0", name: "Infección de vías urinarias, sitio no especificado" },
128
+ { code: "N18.9", name: "Enfermedad renal crónica, no especificada" },
129
+ { code: "F32.9", name: "Episodio depresivo, no especificado" },
130
+ { code: "F41.9", name: "Trastorno de ansiedad, no especificado" },
131
+ { code: "M54.5", name: "Lumbago" },
132
+ { code: "M17.9", name: "Gonartrosis, no especificada" },
133
+ { code: "C50.9", name: "Tumor maligno de mama, no especificado" },
134
+ { code: "C61", name: "Tumor maligno de la próstata" },
135
+ { code: "Z00.0", name: "Examen médico general" },
136
+ { code: "Z34.9", name: "Supervisión de embarazo normal, no especificado" },
137
+ { code: "O80", name: "Parto único espontáneo" },
138
+ ];
139
+
140
+ // ── Identificadores chilenos (CSTipoIdentificador) ───────────────────────────
141
+
142
+ const IDENTIFICADORES = [
143
+ { code: "01", name: "RUN — Rol Único Nacional", system: CS("CSTipoIdentificador") },
144
+ { code: "02", name: "Pasaporte (extranjero)", system: CS("CSTipoIdentificador") },
145
+ { code: "03", name: "DNI (extranjero)", system: CS("CSTipoIdentificador") },
146
+ { code: "04", name: "Identificador provisorio MINSAL", system: CS("CSTipoIdentificador") },
147
+ ];
148
+
149
+ // ── Tabla de sistemas disponibles ────────────────────────────────────────────
150
+
151
+ const SYSTEMS = {
152
+ "deis-comuna": {
153
+ label: "Comunas DEIS (decreto 817 / MINSAL)",
154
+ canonical: CS("CodigoComunaDEIS"),
155
+ rows: COMUNAS,
156
+ },
157
+ "deis-establecimiento": {
158
+ label: "Establecimientos de salud DEIS",
159
+ // MINSAL aún no publica URI canónica oficial para establecimientos.
160
+ canonical: null,
161
+ rows: ESTABLECIMIENTOS,
162
+ },
163
+ "deis": {
164
+ // alias agregado: combina comunas + establecimientos
165
+ label: "DEIS (comunas + establecimientos)",
166
+ canonical: null,
167
+ rows: [
168
+ ...COMUNAS.map((r) => ({ ...r, kind: "comuna" })),
169
+ ...ESTABLECIMIENTOS.map((r) => ({ ...r, kind: "establecimiento" })),
170
+ ],
171
+ },
172
+ "tfc": {
173
+ label: "Tabla de Fármacos (códigos ATC)",
174
+ canonical: "http://www.whocc.no/atc",
175
+ rows: TFC,
176
+ },
177
+ "cie10": {
178
+ label: "CIE-10 (Clasificación Internacional de Enfermedades)",
179
+ canonical: "http://hl7.org/fhir/sid/icd-10",
180
+ rows: CIE10,
181
+ },
182
+ "identificadores": {
183
+ label: "Identificadores chilenos (CSTipoIdentificador)",
184
+ canonical: CS("CSTipoIdentificador"),
185
+ rows: IDENTIFICADORES,
186
+ },
187
+ };
188
+
189
+ // ── Búsqueda ─────────────────────────────────────────────────────────────────
190
+
191
+ function normalize(s) {
192
+ return String(s ?? "")
193
+ .toLowerCase()
194
+ .normalize("NFD")
195
+ .replace(/[̀-ͯ]/g, ""); // strip diacritics
196
+ }
197
+
198
+ /**
199
+ * Busca en un sistema terminológico.
200
+ *
201
+ * @param {string} system — uno de SYSTEMS
202
+ * @param {string} query — texto libre. Si vacío, devuelve los primeros `limit`.
203
+ * @param {{limit?:number}} [opts]
204
+ * @returns {{ system:string, label:string, canonical:string, total:number, results:Array<object> }}
205
+ */
206
+ function search(system, query, opts) {
207
+ const limit = (opts && opts.limit) || 10;
208
+ const sys = SYSTEMS[system];
209
+ if (!sys) {
210
+ return {
211
+ system,
212
+ label: null,
213
+ canonical: null,
214
+ total: 0,
215
+ results: [],
216
+ error: `Sistema desconocido. Disponibles: ${Object.keys(SYSTEMS).join(", ")}.`,
217
+ };
218
+ }
219
+
220
+ const q = normalize(query || "").trim();
221
+ let matches;
222
+ if (!q) {
223
+ matches = sys.rows.slice(0, limit);
224
+ } else {
225
+ matches = sys.rows.filter((row) => {
226
+ const haystack = normalize([row.code, row.name, row.region, row.indication, row.atc].filter(Boolean).join(" "));
227
+ return haystack.includes(q);
228
+ }).slice(0, limit);
229
+ }
230
+
231
+ return {
232
+ system,
233
+ label: sys.label,
234
+ canonical: sys.canonical,
235
+ total: matches.length,
236
+ results: matches.map((r) => ({
237
+ ...r,
238
+ system: sys.canonical,
239
+ })),
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Lista los sistemas terminológicos disponibles para `fhiron_search_terminology`.
245
+ * @returns {Array<{system:string, label:string, canonical:string, count:number}>}
246
+ */
247
+ function listSystems() {
248
+ return Object.entries(SYSTEMS).map(([key, sys]) => ({
249
+ system: key,
250
+ label: sys.label,
251
+ canonical: sys.canonical,
252
+ count: sys.rows.length,
253
+ }));
254
+ }
255
+
256
+ module.exports = {
257
+ search,
258
+ listSystems,
259
+ SYSTEMS,
260
+ COMUNAS,
261
+ ESTABLECIMIENTOS,
262
+ TFC,
263
+ CIE10,
264
+ IDENTIFICADORES,
265
+ };
package/src/errors.js ADDED
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Jerarquía de errores del SDK. Todos extienden FhironError, así que un
5
+ * `catch (err) { if (err instanceof FhironError) ... }` captura cualquier
6
+ * fallo del SDK. Las subclases permiten distinguir el caso sin parsear
7
+ * mensajes (recomendado para reintentos/UX).
8
+ */
9
+ class FhironError extends Error {
10
+ constructor(message, details) {
11
+ super(message);
12
+ this.name = 'FhironError';
13
+ if (details) Object.assign(this, details);
14
+ }
15
+ }
16
+
17
+ /** HTTP 401/403 — API key ausente, inválida o sin permisos. */
18
+ class FhironAuthError extends FhironError {
19
+ constructor(message, details) {
20
+ super(message, details);
21
+ this.name = 'FhironAuthError';
22
+ }
23
+ }
24
+
25
+ /** HTTP 429 — cuota mensual agotada. `upgradeUrl` apunta al upgrade. */
26
+ class FhironQuotaError extends FhironError {
27
+ constructor(message, details) {
28
+ super(message, details);
29
+ this.name = 'FhironQuotaError';
30
+ }
31
+ }
32
+
33
+ /** HTTP 400/415 — el recurso o la petición tienen un problema de forma. */
34
+ class FhironRequestError extends FhironError {
35
+ constructor(message, details) {
36
+ super(message, details);
37
+ this.name = 'FhironRequestError';
38
+ }
39
+ }
40
+
41
+ /** HTTP 5xx — el servidor o el motor de validación fallaron. Reintentable. */
42
+ class FhironServerError extends FhironError {
43
+ constructor(message, details) {
44
+ super(message, details);
45
+ this.name = 'FhironServerError';
46
+ }
47
+ }
48
+
49
+ /** Timeout de la petición (supera `timeout` ms). Reintentable. */
50
+ class FhironTimeoutError extends FhironError {
51
+ constructor(message, details) {
52
+ super(message, details);
53
+ this.name = 'FhironTimeoutError';
54
+ }
55
+ }
56
+
57
+ /** Fallo de red (DNS, conexión rechazada, offline). Reintentable. */
58
+ class FhironNetworkError extends FhironError {
59
+ constructor(message, details) {
60
+ super(message, details);
61
+ this.name = 'FhironNetworkError';
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ FhironError,
67
+ FhironAuthError,
68
+ FhironQuotaError,
69
+ FhironRequestError,
70
+ FhironServerError,
71
+ FhironTimeoutError,
72
+ FhironNetworkError,
73
+ };
package/src/http.js ADDED
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ FhironAuthError,
5
+ FhironQuotaError,
6
+ FhironRequestError,
7
+ FhironServerError,
8
+ FhironTimeoutError,
9
+ FhironNetworkError,
10
+ } = require('./errors.js');
11
+
12
+ /**
13
+ * POST JSON a la API de Fhiron y mapea los errores HTTP a la jerarquía
14
+ * tipada del SDK. Devuelve el body parseado (objeto) en caso 2xx.
15
+ *
16
+ * Zero-dep: usa el `fetch` global (Node 18+). El timeout se implementa con
17
+ * AbortController para no colgar el proceso del cliente.
18
+ *
19
+ * @param {string} url
20
+ * @param {object} opts
21
+ * @param {string} opts.apiKey
22
+ * @param {unknown} opts.body recurso FHIR crudo (se serializa)
23
+ * @param {Record<string,string>} [opts.headers]
24
+ * @param {number} [opts.timeout]
25
+ * @param {string} [opts.userAgent]
26
+ * @returns {Promise<any>}
27
+ */
28
+ async function postJson(url, opts) {
29
+ const {
30
+ apiKey,
31
+ body,
32
+ headers = {},
33
+ timeout = 10_000,
34
+ userAgent = 'fhiron-sdk',
35
+ } = opts;
36
+
37
+ const controller = new AbortController();
38
+ const timer = setTimeout(() => controller.abort(), timeout);
39
+
40
+ let res;
41
+ try {
42
+ res = await fetch(url, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ Accept: 'application/json',
47
+ 'User-Agent': userAgent,
48
+ ...(apiKey ? { 'X-API-Key': apiKey } : {}),
49
+ ...headers,
50
+ },
51
+ body: typeof body === 'string' ? body : JSON.stringify(body),
52
+ signal: controller.signal,
53
+ });
54
+ } catch (err) {
55
+ clearTimeout(timer);
56
+ if (err && (err.name === 'AbortError' || err.name === 'TimeoutError')) {
57
+ throw new FhironTimeoutError(`La petición superó el timeout de ${timeout}ms.`, {
58
+ url,
59
+ timeout,
60
+ });
61
+ }
62
+ throw new FhironNetworkError(
63
+ `No se pudo conectar al servidor de Fhiron (${url}): ${err && err.message ? err.message : err}.`,
64
+ { url, cause: err },
65
+ );
66
+ }
67
+ clearTimeout(timer);
68
+
69
+ const text = await res.text();
70
+ let payload = null;
71
+ if (text) {
72
+ try {
73
+ payload = JSON.parse(text);
74
+ } catch {
75
+ payload = text;
76
+ }
77
+ }
78
+
79
+ if (res.ok) return payload;
80
+
81
+ throwForStatus(res.status, payload);
82
+ }
83
+
84
+ function throwForStatus(status, payload) {
85
+ const serverMsg =
86
+ payload && typeof payload === 'object' && (payload.error || payload.message)
87
+ ? payload.error || payload.message
88
+ : typeof payload === 'string' && payload
89
+ ? payload
90
+ : null;
91
+
92
+ const details = { status, body: payload };
93
+
94
+ if (status === 401) {
95
+ throw new FhironAuthError(
96
+ serverMsg || 'Falta la API key o es inválida. Genera una en https://fhiron.cl/dashboard.',
97
+ details,
98
+ );
99
+ }
100
+ if (status === 403) {
101
+ throw new FhironAuthError(
102
+ serverMsg || 'La API key no tiene permisos para esta operación.',
103
+ details,
104
+ );
105
+ }
106
+ if (status === 429) {
107
+ throw new FhironQuotaError(
108
+ serverMsg || 'Cuota de validaciones agotada para tu plan.',
109
+ { ...details, upgradeUrl: (payload && payload.upgrade_url) || 'https://fhiron.cl/precios' },
110
+ );
111
+ }
112
+ if (status === 400 || status === 415 || status === 422) {
113
+ throw new FhironRequestError(
114
+ serverMsg || `La petición no es válida (HTTP ${status}).`,
115
+ details,
116
+ );
117
+ }
118
+ if (status >= 500) {
119
+ throw new FhironServerError(
120
+ serverMsg || `Error del servidor de Fhiron (HTTP ${status}). Reintenta en unos segundos.`,
121
+ details,
122
+ );
123
+ }
124
+ throw new FhironRequestError(serverMsg || `HTTP ${status}.`, details);
125
+ }
126
+
127
+ module.exports = { postJson, throwForStatus };
package/src/index.d.ts ADDED
@@ -0,0 +1,125 @@
1
+ // Type declarations para @fhiron/sdk. El runtime es JS puro (CommonJS + ESM
2
+ // wrapper); estos tipos dan autocompletado y chequeo estricto en proyectos
3
+ // TypeScript que consumen el SDK.
4
+
5
+ export type Severity = 'error' | 'warning' | 'information' | 'info';
6
+
7
+ export interface QuickFix {
8
+ title: string;
9
+ jsonPointer: string;
10
+ replacement: unknown;
11
+ }
12
+
13
+ /** Un hallazgo de validación, ya en español y con referencia al perfil. */
14
+ export interface Issue {
15
+ /** Identificador estable del hallazgo, ej. "cl-run-01". */
16
+ code: string;
17
+ severity: Severity;
18
+ /** Ruta FHIR con punto (Patient.identifier[0].value). "" para la raíz. */
19
+ path: string;
20
+ /** Mensaje en español, con referencia al perfil cuando aplica. */
21
+ message: string;
22
+ /** Por qué es un problema (contexto pedagógico), cuando está disponible. */
23
+ why?: string;
24
+ /** URL canónica del StructureDefinition / CodeSystem que falla. */
25
+ profileUrl?: string;
26
+ /** URL de documentación humana (docs.fhiron.cl/errores/{code}). */
27
+ docsUrl?: string;
28
+ /** Sugerencia textual de corrección. */
29
+ suggestion?: string;
30
+ /** Corrección automática estructurada, cuando existe. */
31
+ quickFix?: QuickFix;
32
+ }
33
+
34
+ export interface ValidateResult {
35
+ /** true si el motor remoto no encontró errores (y no quedó degradado). */
36
+ valid: boolean;
37
+ /** Atajo: `valid && !engineDegraded`. Úsalo para decidir si dejar pasar. */
38
+ ok: boolean;
39
+ /** El motor remoto no pudo validar (problema sistémico). NO se descontó cuota. */
40
+ engineDegraded: boolean;
41
+ resourceType: string | null;
42
+ /** Perfil CL Core detectado por el motor, si lo reporta. */
43
+ profile: string | null;
44
+ /** Todos los hallazgos (locales + del servidor), normalizados. */
45
+ issues: Issue[];
46
+ /** Atajo: issues con severidad error. */
47
+ errors: Issue[];
48
+ /** Atajo: issues con severidad warning. */
49
+ warnings: Issue[];
50
+ /** Atajo: issues informativos. */
51
+ information: Issue[];
52
+ /** Mensajes crudos del servidor (string[]), por si los necesitas tal cual. */
53
+ messages: { errors: string[]; warnings: string[] };
54
+ /** Respuesta cruda del endpoint, sin normalizar. */
55
+ raw: Record<string, unknown>;
56
+ }
57
+
58
+ export interface FhironOptions {
59
+ /** API key del tenant. Default: process.env.FHIRON_API_KEY. */
60
+ apiKey?: string;
61
+ /** Base URL de la API. Default: https://fhiron.cl (o FHIRON_API_URL). */
62
+ baseUrl?: string;
63
+ /** Timeout por petición en ms. Default: 10000. */
64
+ timeout?: number;
65
+ /** Versión de CL Core a forzar, ej. "1.9.4". */
66
+ clCoreVersion?: string;
67
+ /** Idioma de los mensajes: "es" (default) | "en". */
68
+ language?: string;
69
+ }
70
+
71
+ export interface ValidateOptions {
72
+ /** "hl7.fhir.cl.clcore@1.9.4" o "1.9.4". Se traduce a la versión de CL Core. */
73
+ profile?: string;
74
+ /** Override puntual de la versión de CL Core. */
75
+ clCoreVersion?: string;
76
+ /** Timeout puntual en ms. */
77
+ timeout?: number;
78
+ /** Reservado para cancelación externa. */
79
+ signal?: AbortSignal;
80
+ }
81
+
82
+ export declare class Fhiron {
83
+ apiKey: string | null;
84
+ baseUrl: string;
85
+ timeout: number;
86
+ clCoreVersion: string | null;
87
+ language: string;
88
+ constructor(options?: FhironOptions);
89
+ /** Valida un recurso (o Bundle) contra CL Core en el motor remoto. */
90
+ validate(resource: object, opts?: ValidateOptions): Promise<ValidateResult>;
91
+ /** Lint estructural offline (sin red, sin cuota). */
92
+ lint(resource: object): Issue[];
93
+ /** Lint local + validación remota; adjunta `localIssues`. */
94
+ check(
95
+ resource: object,
96
+ opts?: ValidateOptions,
97
+ ): Promise<ValidateResult & { localIssues: Issue[] }>;
98
+ }
99
+
100
+ // ── Errores ──────────────────────────────────────────────────────────────
101
+ export declare class FhironError extends Error {
102
+ status?: number;
103
+ body?: unknown;
104
+ }
105
+ /** HTTP 401/403. */
106
+ export declare class FhironAuthError extends FhironError {}
107
+ /** HTTP 429. */
108
+ export declare class FhironQuotaError extends FhironError {
109
+ upgradeUrl?: string;
110
+ }
111
+ /** HTTP 400/415/422. */
112
+ export declare class FhironRequestError extends FhironError {}
113
+ /** HTTP 5xx (reintentable). */
114
+ export declare class FhironServerError extends FhironError {}
115
+ /** Timeout (reintentable). */
116
+ export declare class FhironTimeoutError extends FhironError {
117
+ timeout?: number;
118
+ }
119
+ /** Fallo de red (reintentable). */
120
+ export declare class FhironNetworkError extends FhironError {}
121
+
122
+ /** "hl7.fhir.cl.clcore@1.9.4" → "1.9.4"; otros → null. */
123
+ export declare function versionFromProfile(profile: string): string | null;
124
+
125
+ export declare const VERSION: string;