Jose Salazar Claude Opus 5 commited on
Commit
e6acd07
·
1 Parent(s): 5b69117

Los cortes IRIS también pasan a data/ajustes_clinicos.json

Browse files

Cierra lo que quedaba de §4.6 en el motor: `CORTES_IRIS_CREAT`, `CORTES_IRIS_SDMA`
y `CORTES_IRIS_UPC` eran las últimas reglas clínicas incrustadas en el código.

No había duplicación que eliminar —el estadiaje es sólo del cliente, no hay
contraparte en Python— así que el motivo aquí es únicamente el que dio origen a
todo esto: son decisiones clínicas que un veterinario debería poder revisar y
ajustar sin desarrollador ni build. Los cortes de la guía IRIS se revisan
periódicamente (la tabla vigente es la modificada en 2026); tenerlos en
TypeScript convertía una actualización de guía en un cambio de código.

Viaja con ellos su procedencia (guía IRIS 2026 / Fundamentals 3.a ed., p. 573,
Tabla 8.4) y, en `_limitacion`, la nota de por qué el motor sólo estadia cuando
creatinina o SDMA salen de rango: la guía estadia pacientes YA diagnosticados de
ERC sobre muestras seriadas, y aquí sólo hay una analítica suelta.

Los 5 tests dorados del bloque IRIS —estadiaje por creatinina, escalada por SDMA
discrepante, subestadiaje de proteinuria por especie y gravedad por estadio en vez
de por ancho de rango— pasan sin cambiar expectativas: la extracción no alteró
nada. Se añade el test que muta los cortes y exige que el estadio cambie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

data/ajustes_clinicos.json CHANGED
@@ -1,107 +1,288 @@
1
  {
2
  "_acerca_de": "Reglas clínicas del suelo de seguridad: umbrales de gravedad, categorías de edad y factores de ajuste por edad y raza. ÚNICA FUENTE DE VERDAD: la leen el motor del navegador (frontend/src/analisis.ts) y el del servidor (backend/app/motor/gravedad.py). Antes estaban duplicadas como constantes en ambos, que es lo que de verdad se desincroniza: la lógica de comparar no cambia casi nunca, los umbrales sí. Editar aquí cambia los dos motores a la vez, sin recompilar nada. backend/tests/test_paridad_motor.py compara ambos motores y avisa si un cambio los separa.",
3
  "_convenciones": "Los factores son MULTIPLICADORES sobre los límites del rango de referencia de data/valores_referencia.json (1.0 = sin cambio); si sólo se declara 'superior', el inferior queda intacto. En los cortes de gravedad, 'moderado_hasta': null significa que ese analito NUNCA llega a 'grave' (no hay corte superior).",
4
-
5
  "umbrales_gravedad": {
6
  "_fuente": "La desviación se mide en múltiplos del ancho del rango de referencia. Ej: rango WBC 6-17 (ancho 11), WBC 28 → 11/11 = 1.0 → moderado.",
7
  "leve": 0.5,
8
  "moderado": 1.5
9
  },
10
-
11
  "limites_edad_meses": {
12
  "_fuente": "Cortes de las categorías que seleccionan los ajustes de edad. Un valor por debajo del límite cae en esa categoría; por encima del último, en la siguiente.",
13
- "canino": { "cachorro": 12, "adulto": 84, "senior": 120 },
14
- "felino": { "cachorro": 12, "adulto": 120 }
 
 
 
 
 
 
 
15
  },
16
-
17
  "cortes_gravedad_bajo": {
18
  "_fuente": "Cortes clínicos explícitos del lado BAJO, en la unidad del analito. La regla genérica de anchos de rango no sabe expresar estos dos: con rango 24-45 un gato necesitaría un hematocrito NEGATIVO para llegar a 'grave', y con 200-500 unas plaquetas de -250. Es decir, ni una anemia felina ni una trombocitopenia disparaban nunca el suelo de derivación. PCV <= 20% es umbral de transfusión en perro y gato, y <= 12% (Hgb < 4 g/dL) potencialmente mortal (Thrall 3.a ed., p. 240); PCV 13 en gato se describe como 'markedly anemic' y 24 como 'mildly anemic' (p. 800-801). El riesgo hemorrágico por trombocitopenia aparece por debajo de 30x10^3/uL (Fundamentals 3.a ed., p. 310). Los cortes interiores del gato (14, 20) siguen la gradación de uso común.",
19
  "hct": {
20
- "canino": { "leve_hasta": 30, "moderado_hasta": 20 },
21
- "felino": { "leve_hasta": 20, "moderado_hasta": 14 }
 
 
 
 
 
 
22
  },
23
  "plt": {
24
- "canino": { "leve_hasta": 100, "moderado_hasta": 30 },
25
- "felino": { "leve_hasta": 100, "moderado_hasta": 30 }
 
 
 
 
 
 
26
  }
27
  },
28
-
29
  "cortes_gravedad_alto": {
30
  "_fuente": "Cortes explícitos del lado ALTO. Los renales salen del estadiaje IRIS de ERC (guía modificada en 2026; misma tabla en Fundamentals 3.a ed., p. 573, Tabla 8.4), mapeando los cuatro estadios sobre las tres gravedades del motor: estadio 2 → leve, 3 → moderado, 4 → grave. El estadio 1 queda dentro del rango de referencia y no genera hallazgo.",
31
  "creat": {
32
- "canino": { "leve_hasta": 2.8, "moderado_hasta": 5.0 },
33
- "felino": { "leve_hasta": 2.8, "moderado_hasta": 5.0 }
 
 
 
 
 
 
34
  },
35
  "sdma": {
36
- "canino": { "leve_hasta": 35, "moderado_hasta": 54 },
37
- "felino": { "leve_hasta": 25, "moderado_hasta": 38 }
 
 
 
 
 
 
38
  },
39
  "upc": {
40
  "_fuente": "La proteinuria IRIS no se subestadia más allá de 'proteinúrico': nunca llega a grave.",
41
- "canino": { "leve_hasta": 0.5, "moderado_hasta": null },
42
- "felino": { "leve_hasta": 0.4, "moderado_hasta": null }
 
 
 
 
 
 
43
  }
44
  },
45
-
46
  "ajustes_edad": {
47
  "_fuente": "El fósforo del animal en crecimiento es el hueco que más falsos positivos generaba: sin él, TODO cachorro sale hiperfosforémico y arrastra patrones renales falsos. Fundamentals 3.a ed., p. 831: cachorros < 12 sem 5,7-10,8 mg/dL frente a 2,5-5,5 del adulto; gatitos 5,0-10,0 frente a 1,8-6,4. La FAL del gatito va a x3,0: gatitos de 4 semanas 97-274 U/L frente a 10-80 del adulto (x3,4 el límite superior, Thrall 3.a ed., p. 447), y Fundamentals p. 831 pone el techo general del joven en '< 3 veces el límite superior del adulto'.",
48
  "canino": {
49
- "cachorro": { "fal": { "superior": 3.0 }, "wbc": { "superior": 1.25 }, "fosf": { "superior": 1.8 } },
 
 
 
 
 
 
 
 
 
 
50
  "adulto": {},
51
- "senior": { "bun": { "superior": 1.15 }, "creat": { "superior": 1.15 } },
52
- "geriatrico": { "bun": { "superior": 1.25 }, "creat": { "superior": 1.25 }, "fal": { "superior": 1.4 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  },
54
  "felino": {
55
- "cachorro": { "fal": { "superior": 3.0 }, "wbc": { "superior": 1.2 }, "fosf": { "superior": 1.3 } },
 
 
 
 
 
 
 
 
 
 
56
  "adulto": {},
57
- "senior": { "bun": { "superior": 1.2 }, "creat": { "superior": 1.2 } }
 
 
 
 
 
 
 
58
  }
59
  },
60
-
61
  "ajustes_raza": {
62
  "_convencion": "Se combinan TODOS los grupos que casan, no sólo el primero: un shiba inu pertenece a la vez al grupo de microcitosis fisiológica y al de plaquetas bajas, y quedarse con el primero perdía el segundo en silencio. El nombre de la raza se compara en minúsculas por SUBCADENA.",
63
  "canino": [
64
  {
65
  "_fuente": "Lebreles. Fundamentals 3.a ed., p. 213-214: los RI de Hct, Hgb y RBC son más altos en greyhounds, afganos, salukis y whippets; plaquetas más bajas en lebreles (p. 307-310). La T4 es la trampa clínica: tT4 y fT4 por debajo del RI canino general en ~90% de los galgos sanos (p. 1065), así que sin este ajuste un galgo sano sale hipotiroideo. Creatinina más alta por masa muscular (Thrall 3.a ed., p. 363).",
66
- "razas": ["galgo", "greyhound", "whippet", "lebrel", "afgano", "afghan", "saluki", "sloughi"],
 
 
 
 
 
 
 
 
 
67
  "ajustes": {
68
- "rbc": { "inferior": 1.15, "superior": 1.15 },
69
- "hgb": { "inferior": 1.12, "superior": 1.12 },
70
- "hct": { "inferior": 1.12, "superior": 1.12 },
71
- "plt": { "inferior": 0.75, "superior": 0.75 },
72
- "t4_total": { "inferior": 0.5, "superior": 0.8 },
73
- "t4_libre": { "inferior": 0.5, "superior": 0.8 },
74
- "creat": { "superior": 1.15 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
  },
77
  {
78
  "_fuente": "Razas asiáticas con microcitosis fisiológica. Fundamentals p. 221: 'some healthy Akitas, shibas, Jindos, chow-chows, and shar-peis have lower MCV' — VCM bajo SIN anemia. Antes se les subía RBC/Hct/Hgb, que generaba falsas anemias; bajar el VCM evita la falsa 'anemia microcítica'.",
79
- "razas": ["shiba", "akita", "jindo", "chow", "shar pei", "shar-pei", "sharpei"],
80
- "ajustes": { "vcm": { "inferior": 0.85, "superior": 0.92 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  },
82
  {
83
  "_fuente": "El shiba inu además tiene plaquetas sustancialmente más bajas (Fundamentals p. 307-310).",
84
- "razas": ["shiba"],
85
- "ajustes": { "plt": { "inferior": 0.75, "superior": 0.9 } }
 
 
 
 
 
 
 
86
  }
87
  ],
88
  "felino": [
89
  {
90
  "_fuente": "Fundamentals p. 213-214: los límites inferiores de Hgb y Hct del Maine Coon son sustancialmente mayores que los de las razas felinas típicas.",
91
- "razas": ["maine coon", "maine"],
92
- "ajustes": { "hct": { "inferior": 1.15 }, "hgb": { "inferior": 1.15 } }
 
 
 
 
 
 
 
 
 
 
93
  },
94
  {
95
  "_fuente": "Fundamentals p. 585: la creatinina de gatos Birman clínicamente sanos puede superar el límite superior de los RI felinos de rutina.",
96
- "razas": ["birman", "sagrado de birmania"],
97
- "ajustes": { "creat": { "superior": 1.2 } }
 
 
 
 
 
 
 
98
  }
99
  ]
100
  },
101
-
102
  "ajustes_sexo": {
103
  "_fuente": "VACÍO A PROPÓSITO, no es un olvido. Se retiró el ajuste que subía un 15% el techo de creatinina en el gato MACHO: no aparece en ninguna de las dos obras del corpus. La secreción tubular de creatinina es cosa del perro macho —'cats and ponies do not secrete or reabsorb Ct in their kidneys' (Thrall p. 363)— y se describe como clínicamente intrascendente; tolerar un 15% más de creatinina justo en la población con más ERC era infradetección sin respaldo. Lo único con respaldo por sexo es menor: en perro las plaquetas son hasta un 10% mayores en hembras y en enteros (Fundamentals p. 310), por debajo de la resolución clínica del motor.",
104
  "canino": {},
105
  "felino": {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
107
  }
 
1
  {
2
  "_acerca_de": "Reglas clínicas del suelo de seguridad: umbrales de gravedad, categorías de edad y factores de ajuste por edad y raza. ÚNICA FUENTE DE VERDAD: la leen el motor del navegador (frontend/src/analisis.ts) y el del servidor (backend/app/motor/gravedad.py). Antes estaban duplicadas como constantes en ambos, que es lo que de verdad se desincroniza: la lógica de comparar no cambia casi nunca, los umbrales sí. Editar aquí cambia los dos motores a la vez, sin recompilar nada. backend/tests/test_paridad_motor.py compara ambos motores y avisa si un cambio los separa.",
3
  "_convenciones": "Los factores son MULTIPLICADORES sobre los límites del rango de referencia de data/valores_referencia.json (1.0 = sin cambio); si sólo se declara 'superior', el inferior queda intacto. En los cortes de gravedad, 'moderado_hasta': null significa que ese analito NUNCA llega a 'grave' (no hay corte superior).",
 
4
  "umbrales_gravedad": {
5
  "_fuente": "La desviación se mide en múltiplos del ancho del rango de referencia. Ej: rango WBC 6-17 (ancho 11), WBC 28 → 11/11 = 1.0 → moderado.",
6
  "leve": 0.5,
7
  "moderado": 1.5
8
  },
 
9
  "limites_edad_meses": {
10
  "_fuente": "Cortes de las categorías que seleccionan los ajustes de edad. Un valor por debajo del límite cae en esa categoría; por encima del último, en la siguiente.",
11
+ "canino": {
12
+ "cachorro": 12,
13
+ "adulto": 84,
14
+ "senior": 120
15
+ },
16
+ "felino": {
17
+ "cachorro": 12,
18
+ "adulto": 120
19
+ }
20
  },
 
21
  "cortes_gravedad_bajo": {
22
  "_fuente": "Cortes clínicos explícitos del lado BAJO, en la unidad del analito. La regla genérica de anchos de rango no sabe expresar estos dos: con rango 24-45 un gato necesitaría un hematocrito NEGATIVO para llegar a 'grave', y con 200-500 unas plaquetas de -250. Es decir, ni una anemia felina ni una trombocitopenia disparaban nunca el suelo de derivación. PCV <= 20% es umbral de transfusión en perro y gato, y <= 12% (Hgb < 4 g/dL) potencialmente mortal (Thrall 3.a ed., p. 240); PCV 13 en gato se describe como 'markedly anemic' y 24 como 'mildly anemic' (p. 800-801). El riesgo hemorrágico por trombocitopenia aparece por debajo de 30x10^3/uL (Fundamentals 3.a ed., p. 310). Los cortes interiores del gato (14, 20) siguen la gradación de uso común.",
23
  "hct": {
24
+ "canino": {
25
+ "leve_hasta": 30,
26
+ "moderado_hasta": 20
27
+ },
28
+ "felino": {
29
+ "leve_hasta": 20,
30
+ "moderado_hasta": 14
31
+ }
32
  },
33
  "plt": {
34
+ "canino": {
35
+ "leve_hasta": 100,
36
+ "moderado_hasta": 30
37
+ },
38
+ "felino": {
39
+ "leve_hasta": 100,
40
+ "moderado_hasta": 30
41
+ }
42
  }
43
  },
 
44
  "cortes_gravedad_alto": {
45
  "_fuente": "Cortes explícitos del lado ALTO. Los renales salen del estadiaje IRIS de ERC (guía modificada en 2026; misma tabla en Fundamentals 3.a ed., p. 573, Tabla 8.4), mapeando los cuatro estadios sobre las tres gravedades del motor: estadio 2 → leve, 3 → moderado, 4 → grave. El estadio 1 queda dentro del rango de referencia y no genera hallazgo.",
46
  "creat": {
47
+ "canino": {
48
+ "leve_hasta": 2.8,
49
+ "moderado_hasta": 5.0
50
+ },
51
+ "felino": {
52
+ "leve_hasta": 2.8,
53
+ "moderado_hasta": 5.0
54
+ }
55
  },
56
  "sdma": {
57
+ "canino": {
58
+ "leve_hasta": 35,
59
+ "moderado_hasta": 54
60
+ },
61
+ "felino": {
62
+ "leve_hasta": 25,
63
+ "moderado_hasta": 38
64
+ }
65
  },
66
  "upc": {
67
  "_fuente": "La proteinuria IRIS no se subestadia más allá de 'proteinúrico': nunca llega a grave.",
68
+ "canino": {
69
+ "leve_hasta": 0.5,
70
+ "moderado_hasta": null
71
+ },
72
+ "felino": {
73
+ "leve_hasta": 0.4,
74
+ "moderado_hasta": null
75
+ }
76
  }
77
  },
 
78
  "ajustes_edad": {
79
  "_fuente": "El fósforo del animal en crecimiento es el hueco que más falsos positivos generaba: sin él, TODO cachorro sale hiperfosforémico y arrastra patrones renales falsos. Fundamentals 3.a ed., p. 831: cachorros < 12 sem 5,7-10,8 mg/dL frente a 2,5-5,5 del adulto; gatitos 5,0-10,0 frente a 1,8-6,4. La FAL del gatito va a x3,0: gatitos de 4 semanas 97-274 U/L frente a 10-80 del adulto (x3,4 el límite superior, Thrall 3.a ed., p. 447), y Fundamentals p. 831 pone el techo general del joven en '< 3 veces el límite superior del adulto'.",
80
  "canino": {
81
+ "cachorro": {
82
+ "fal": {
83
+ "superior": 3.0
84
+ },
85
+ "wbc": {
86
+ "superior": 1.25
87
+ },
88
+ "fosf": {
89
+ "superior": 1.8
90
+ }
91
+ },
92
  "adulto": {},
93
+ "senior": {
94
+ "bun": {
95
+ "superior": 1.15
96
+ },
97
+ "creat": {
98
+ "superior": 1.15
99
+ }
100
+ },
101
+ "geriatrico": {
102
+ "bun": {
103
+ "superior": 1.25
104
+ },
105
+ "creat": {
106
+ "superior": 1.25
107
+ },
108
+ "fal": {
109
+ "superior": 1.4
110
+ }
111
+ }
112
  },
113
  "felino": {
114
+ "cachorro": {
115
+ "fal": {
116
+ "superior": 3.0
117
+ },
118
+ "wbc": {
119
+ "superior": 1.2
120
+ },
121
+ "fosf": {
122
+ "superior": 1.3
123
+ }
124
+ },
125
  "adulto": {},
126
+ "senior": {
127
+ "bun": {
128
+ "superior": 1.2
129
+ },
130
+ "creat": {
131
+ "superior": 1.2
132
+ }
133
+ }
134
  }
135
  },
 
136
  "ajustes_raza": {
137
  "_convencion": "Se combinan TODOS los grupos que casan, no sólo el primero: un shiba inu pertenece a la vez al grupo de microcitosis fisiológica y al de plaquetas bajas, y quedarse con el primero perdía el segundo en silencio. El nombre de la raza se compara en minúsculas por SUBCADENA.",
138
  "canino": [
139
  {
140
  "_fuente": "Lebreles. Fundamentals 3.a ed., p. 213-214: los RI de Hct, Hgb y RBC son más altos en greyhounds, afganos, salukis y whippets; plaquetas más bajas en lebreles (p. 307-310). La T4 es la trampa clínica: tT4 y fT4 por debajo del RI canino general en ~90% de los galgos sanos (p. 1065), así que sin este ajuste un galgo sano sale hipotiroideo. Creatinina más alta por masa muscular (Thrall 3.a ed., p. 363).",
141
+ "razas": [
142
+ "galgo",
143
+ "greyhound",
144
+ "whippet",
145
+ "lebrel",
146
+ "afgano",
147
+ "afghan",
148
+ "saluki",
149
+ "sloughi"
150
+ ],
151
  "ajustes": {
152
+ "rbc": {
153
+ "inferior": 1.15,
154
+ "superior": 1.15
155
+ },
156
+ "hgb": {
157
+ "inferior": 1.12,
158
+ "superior": 1.12
159
+ },
160
+ "hct": {
161
+ "inferior": 1.12,
162
+ "superior": 1.12
163
+ },
164
+ "plt": {
165
+ "inferior": 0.75,
166
+ "superior": 0.75
167
+ },
168
+ "t4_total": {
169
+ "inferior": 0.5,
170
+ "superior": 0.8
171
+ },
172
+ "t4_libre": {
173
+ "inferior": 0.5,
174
+ "superior": 0.8
175
+ },
176
+ "creat": {
177
+ "superior": 1.15
178
+ }
179
  }
180
  },
181
  {
182
  "_fuente": "Razas asiáticas con microcitosis fisiológica. Fundamentals p. 221: 'some healthy Akitas, shibas, Jindos, chow-chows, and shar-peis have lower MCV' — VCM bajo SIN anemia. Antes se les subía RBC/Hct/Hgb, que generaba falsas anemias; bajar el VCM evita la falsa 'anemia microcítica'.",
183
+ "razas": [
184
+ "shiba",
185
+ "akita",
186
+ "jindo",
187
+ "chow",
188
+ "shar pei",
189
+ "shar-pei",
190
+ "sharpei"
191
+ ],
192
+ "ajustes": {
193
+ "vcm": {
194
+ "inferior": 0.85,
195
+ "superior": 0.92
196
+ }
197
+ }
198
  },
199
  {
200
  "_fuente": "El shiba inu además tiene plaquetas sustancialmente más bajas (Fundamentals p. 307-310).",
201
+ "razas": [
202
+ "shiba"
203
+ ],
204
+ "ajustes": {
205
+ "plt": {
206
+ "inferior": 0.75,
207
+ "superior": 0.9
208
+ }
209
+ }
210
  }
211
  ],
212
  "felino": [
213
  {
214
  "_fuente": "Fundamentals p. 213-214: los límites inferiores de Hgb y Hct del Maine Coon son sustancialmente mayores que los de las razas felinas típicas.",
215
+ "razas": [
216
+ "maine coon",
217
+ "maine"
218
+ ],
219
+ "ajustes": {
220
+ "hct": {
221
+ "inferior": 1.15
222
+ },
223
+ "hgb": {
224
+ "inferior": 1.15
225
+ }
226
+ }
227
  },
228
  {
229
  "_fuente": "Fundamentals p. 585: la creatinina de gatos Birman clínicamente sanos puede superar el límite superior de los RI felinos de rutina.",
230
+ "razas": [
231
+ "birman",
232
+ "sagrado de birmania"
233
+ ],
234
+ "ajustes": {
235
+ "creat": {
236
+ "superior": 1.2
237
+ }
238
+ }
239
  }
240
  ]
241
  },
 
242
  "ajustes_sexo": {
243
  "_fuente": "VACÍO A PROPÓSITO, no es un olvido. Se retiró el ajuste que subía un 15% el techo de creatinina en el gato MACHO: no aparece en ninguna de las dos obras del corpus. La secreción tubular de creatinina es cosa del perro macho —'cats and ponies do not secrete or reabsorb Ct in their kidneys' (Thrall p. 363)— y se describe como clínicamente intrascendente; tolerar un 15% más de creatinina justo en la población con más ERC era infradetección sin respaldo. Lo único con respaldo por sexo es menor: en perro las plaquetas son hasta un 10% mayores en hembras y en enteros (Fundamentals p. 310), por debajo de la resolución clínica del motor.",
244
  "canino": {},
245
  "felino": {}
246
+ },
247
+ "iris": {
248
+ "_fuente": "Estadiaje IRIS de la enfermedad renal crónica. IRIS Staging of CKD (modificado 2026), indexada en el RAG; la misma tabla aparece en Fundamentals 3.a ed., p. 573 (Tabla 8.4). Estadio por creatinina, con la escalada por discrepancia con SDMA que describe la propia guía, y subestadio por proteinuria (UP/C).",
249
+ "_limitacion": "DELIBERADA: la guía estadia pacientes YA diagnosticados de ERC, en ayunas y sobre muestras seriadas estables. El motor sólo ve una analítica suelta, así que el estadio se emite únicamente cuando la creatinina o la SDMA salen del rango de referencia, y se etiqueta como orientativo. Un perro con creatinina 1,5 (estadio 2 IRIS pero dentro de nuestro rango) no se estadia: haría falta el diagnóstico previo de ERC, que el motor no tiene.",
250
+ "cortes_creatinina": {
251
+ "_unidad": "mg/dL. Límite superior de los estadios 1, 2 y 3; por encima del último → estadio 4.",
252
+ "canino": [
253
+ 1.4,
254
+ 2.8,
255
+ 5.0
256
+ ],
257
+ "felino": [
258
+ 1.6,
259
+ 2.8,
260
+ 5.0
261
+ ]
262
+ },
263
+ "cortes_sdma": {
264
+ "_unidad": "µg/dL. SDMA que hace subir de estadio cuando discrepa con la creatinina (>18 en el estadio 1, >35/25 en el 2, >54/38 en el 3).",
265
+ "canino": [
266
+ 18,
267
+ 35,
268
+ 54
269
+ ],
270
+ "felino": [
271
+ 18,
272
+ 25,
273
+ 38
274
+ ]
275
+ },
276
+ "cortes_upc": {
277
+ "_unidad": "UP/C. [no proteinúrico por debajo de, proteinúrico por encima de].",
278
+ "canino": [
279
+ 0.2,
280
+ 0.5
281
+ ],
282
+ "felino": [
283
+ 0.2,
284
+ 0.4
285
+ ]
286
+ }
287
  }
288
  }
frontend/src/analisis.ts CHANGED
@@ -158,34 +158,9 @@ const ajustarReferencias = (
158
  }, {});
159
  };
160
 
161
- // Estadiaje IRIS de la enfermedad renal crónica
162
- //
163
- // Fuente: IRIS Staging of CKD (modificado 2026), indexada en el RAG; la misma tabla aparece en
164
- // Fundamentals 3.a ed., p. 573 (Tabla 8.4). Estadio por creatinina, con la escalada por
165
- // discrepancia con SDMA que describe la propia guía, y subestadio por proteinuria (UP/C).
166
- //
167
- // Limitación deliberada: la guía estadia pacientes YA diagnosticados de ERC, en ayunas y sobre
168
- // muestras seriadas estables. El motor sólo ve una analítica suelta, así que el estadio se
169
- // emite únicamente cuando la creatinina o la SDMA salen del rango de referencia, y se etiqueta
170
- // como orientativo. Un perro con creatinina 1,5 (estadio 2 IRIS pero dentro de nuestro rango)
171
- // no se estadia: haría falta el diagnóstico previo de ERC, que el motor no tiene.
172
- const CORTES_IRIS_CREAT: Record<Especie, number[]> = {
173
- // Límite superior de los estadios 1, 2 y 3 en mg/dL (por encima del último → estadio 4).
174
- canino: [1.4, 2.8, 5.0],
175
- felino: [1.6, 2.8, 5.0],
176
- };
177
-
178
- const CORTES_IRIS_SDMA: Record<Especie, number[]> = {
179
- // SDMA que hace subir de estadio cuando discrepa con la creatinina (µg/dL).
180
- canino: [18, 35, 54],
181
- felino: [18, 25, 38],
182
- };
183
-
184
- // UP/C: [no proteinúrico por debajo de, proteinúrico por encima de]
185
- const CORTES_IRIS_UPC: Record<Especie, [number, number]> = {
186
- canino: [0.2, 0.5],
187
- felino: [0.2, 0.4],
188
- };
189
 
190
  type EstadioIris = { estadio: number; subestadio: string; nota: string };
191
 
@@ -194,11 +169,12 @@ const estadificarIris = (
194
  sdma: number | null,
195
  upc: number | null,
196
  especie: Especie,
 
197
  ): EstadioIris | null => {
198
  if (creat === null && sdma === null) return null;
199
 
200
- const cortesCreat = CORTES_IRIS_CREAT[especie];
201
- const cortesSdma = CORTES_IRIS_SDMA[especie];
202
 
203
  const porCreatinina = creat === null
204
  ? 1
@@ -213,7 +189,7 @@ const estadificarIris = (
213
 
214
  if (escalado < 2) return null; // estadio 1: sin azotemia ni SDMA alta, nada que informar
215
 
216
- const [noProteinurico, proteinurico] = CORTES_IRIS_UPC[especie];
217
  const subestadio = upc === null
218
  ? ''
219
  : upc > proteinurico ? 'proteinúrico'
@@ -229,7 +205,12 @@ const estadificarIris = (
229
 
230
  // Detección de patrones clínicos
231
 
232
- const detectarPatrones = (hallazgos: Hallazgo[], especie: Especie, alt: Alteraciones): Patron[] => {
 
 
 
 
 
233
  const mapa = hallazgos.reduce<Record<string, Hallazgo>>((acc, h) => { acc[h.clave] = h; return acc; }, {});
234
 
235
  const esAlto = (clave: string): boolean => mapa[clave]?.direccion === 'alto';
@@ -398,7 +379,7 @@ const detectarPatrones = (hallazgos: Hallazgo[], especie: Especie, alt: Alteraci
398
 
399
  // Estadiaje IRIS. Se emite como patrón aparte de la azotemia porque responde a otra
400
  // pregunta: la azotemia dice QUÉ está alterado, el estadio dice CUÁNTO y guía el manejo.
401
- const estadio = estadificarIris(valor('creat'), valor('sdma'), valor('upc'), especie);
402
  if (estadio) agregar({
403
  nombre: `${alt.erc_iris.nombre} — estadio ${estadio.estadio}${estadio.subestadio ? `, ${estadio.subestadio}` : ''}`,
404
  descripcion: `${alt.erc_iris.descripcion} ${estadio.nota}`,
@@ -962,7 +943,7 @@ export const analizarResultados = (
962
  }
963
  }
964
 
965
- return { hallazgos, patrones: detectarPatrones(hallazgos, especie, alteraciones) };
966
  };
967
 
968
  // Exportado para pruebas unitarias del cálculo de gravedad de forma aislada.
 
158
  }, {});
159
  };
160
 
161
+ // Estadiaje IRIS de la enfermedad renal crónica.
162
+ // Los cortes (creatinina, SDMA y UP/C) viven en data/ajustes_clinicos.json → `iris`, junto a su
163
+ // procedencia bibliográfica y la limitación deliberada del estadiaje sobre una analítica suelta.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  type EstadioIris = { estadio: number; subestadio: string; nota: string };
166
 
 
169
  sdma: number | null,
170
  upc: number | null,
171
  especie: Especie,
172
+ ajustes: AjustesClinicos,
173
  ): EstadioIris | null => {
174
  if (creat === null && sdma === null) return null;
175
 
176
+ const cortesCreat = ajustes.iris.cortes_creatinina[especie] ?? [];
177
+ const cortesSdma = ajustes.iris.cortes_sdma[especie] ?? [];
178
 
179
  const porCreatinina = creat === null
180
  ? 1
 
189
 
190
  if (escalado < 2) return null; // estadio 1: sin azotemia ni SDMA alta, nada que informar
191
 
192
+ const [noProteinurico, proteinurico] = ajustes.iris.cortes_upc[especie] ?? [0, 0];
193
  const subestadio = upc === null
194
  ? ''
195
  : upc > proteinurico ? 'proteinúrico'
 
205
 
206
  // Detección de patrones clínicos
207
 
208
+ const detectarPatrones = (
209
+ hallazgos: Hallazgo[],
210
+ especie: Especie,
211
+ alt: Alteraciones,
212
+ ajustes: AjustesClinicos,
213
+ ): Patron[] => {
214
  const mapa = hallazgos.reduce<Record<string, Hallazgo>>((acc, h) => { acc[h.clave] = h; return acc; }, {});
215
 
216
  const esAlto = (clave: string): boolean => mapa[clave]?.direccion === 'alto';
 
379
 
380
  // Estadiaje IRIS. Se emite como patrón aparte de la azotemia porque responde a otra
381
  // pregunta: la azotemia dice QUÉ está alterado, el estadio dice CUÁNTO y guía el manejo.
382
+ const estadio = estadificarIris(valor('creat'), valor('sdma'), valor('upc'), especie, ajustes);
383
  if (estadio) agregar({
384
  nombre: `${alt.erc_iris.nombre} — estadio ${estadio.estadio}${estadio.subestadio ? `, ${estadio.subestadio}` : ''}`,
385
  descripcion: `${alt.erc_iris.descripcion} ${estadio.nota}`,
 
943
  }
944
  }
945
 
946
+ return { hallazgos, patrones: detectarPatrones(hallazgos, especie, alteraciones, ajustes) };
947
  };
948
 
949
  // Exportado para pruebas unitarias del cálculo de gravedad de forma aislada.
frontend/src/tipos.ts CHANGED
@@ -66,4 +66,9 @@ export interface AjustesClinicos {
66
  ajustes_edad: Partial<Record<Especie, Record<string, TablaAjustes>>>;
67
  ajustes_raza: Partial<Record<Especie, GrupoRaza[]>>;
68
  ajustes_sexo: Partial<Record<Especie, Record<string, TablaAjustes>>>;
 
 
 
 
 
69
  }
 
66
  ajustes_edad: Partial<Record<Especie, Record<string, TablaAjustes>>>;
67
  ajustes_raza: Partial<Record<Especie, GrupoRaza[]>>;
68
  ajustes_sexo: Partial<Record<Especie, Record<string, TablaAjustes>>>;
69
+ iris: {
70
+ cortes_creatinina: Partial<Record<Especie, number[]>>;
71
+ cortes_sdma: Partial<Record<Especie, number[]>>;
72
+ cortes_upc: Partial<Record<Especie, number[]>>;
73
+ };
74
  }
frontend/tests/analisis.test.ts CHANGED
@@ -383,6 +383,17 @@ describe('las reglas clínicas son datos, no código', () => {
383
  expect(r.hallazgos.find((h) => h.clave === 'hct')?.gravedad).toBe('leve');
384
  });
385
 
 
 
 
 
 
 
 
 
 
 
 
386
  it('los límites de edad salen del JSON', () => {
387
  const adelantado = conAjustes((a) => {
388
  a.limites_edad_meses.canino!.cachorro = 1;
 
383
  expect(r.hallazgos.find((h) => h.clave === 'hct')?.gravedad).toBe('leve');
384
  });
385
 
386
+ it('los cortes IRIS salen del JSON', () => {
387
+ // Se desplaza el estadio 2 canino de 1,4 a 4,0: una creatinina de 2,0 deja de ser estadio 2.
388
+ const desplazado = conAjustes((a) => {
389
+ a.iris.cortes_creatinina.canino = [4.0, 6.0, 8.0];
390
+ });
391
+ const p = analizarResultados({ creat: 2.0 }, paciente(), referencias, alteraciones, desplazado)
392
+ .patrones.find((x) => x.nombre.startsWith(alteraciones.erc_iris.nombre));
393
+ // Estadio 1 con los cortes nuevos → no se informa estadio.
394
+ expect(p).toBeUndefined();
395
+ });
396
+
397
  it('los límites de edad salen del JSON', () => {
398
  const adelantado = conAjustes((a) => {
399
  a.limites_edad_meses.canino!.cachorro = 1;