JairoCesar commited on
Commit
3502efb
·
verified ·
1 Parent(s): 98679ef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -171
app.py CHANGED
@@ -1,11 +1,11 @@
1
  # ==================== Buscador de Eventos de Notificación SIVIGILA - Colombia 2025 =====================================
2
 
3
-
4
  # JAIRO ALEXANDER ERASO MD U Nacional de Colombia.
5
  # DIANA MILENA SOLER MARTINEZ U Juan N. Corpas
6
 
7
  import streamlit as st
8
  import google.generativeai as genai
 
9
  import os
10
  import json
11
  import logging
@@ -14,7 +14,7 @@ import datetime
14
  # --- CONFIGURACIÓN BÁSICA ---
15
  st.set_page_config(
16
  page_title="Buscador Inteligente SIVIGILA Colombia",
17
- page_icon="🇨🇴",
18
  layout="wide"
19
  )
20
 
@@ -24,9 +24,10 @@ logger = logging.getLogger("sivigila_app")
24
 
25
  # --- CONFIGURACIÓN DE GEMINI ---
26
  try:
 
27
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
28
  if not GEMINI_API_KEY:
29
- st.error("No se encontró la variable de entorno GEMINI_API_KEY. Por favor, configúrala.")
30
  logger.error("GEMINI_API_KEY no encontrada.")
31
  st.stop()
32
  genai.configure(api_key=GEMINI_API_KEY)
@@ -38,7 +39,6 @@ except Exception as e:
38
 
39
  @st.cache_resource
40
  def get_gemini_model():
41
- """Carga el modelo de Gemini y lo cachea para no recargarlo."""
42
  logger.info("🔄 Cargando modelo Gemini (gemini-1.5-flash-latest)...")
43
  try:
44
  return genai.GenerativeModel("gemini-1.5-flash-latest")
@@ -63,32 +63,27 @@ def load_data():
63
  data_notificacion = json.load(f)
64
 
65
  notificacion_map = {item['FICHA']: item for item in data_notificacion}
66
- nombres_eventos_unicos = sorted(list(set(item['NOMBRES DE PROTOCOLOS'] for item in data_codigos)))
 
 
67
 
68
  logger.info(f"✅ Datos cargados: {len(data_codigos)} registros de códigos y {len(data_notificacion)} eventos de notificación.")
69
- return data_codigos, notificacion_map, nombres_eventos_unicos
70
 
71
  except FileNotFoundError as e:
72
  st.error(f"❌ Error: No se encontró el archivo {e.filename}. Asegúrate de que los archivos JSON estén en la carpeta 'PLANTILLAS'.")
73
- logger.error(f"Archivo no encontrado: {e.filename}")
74
- return None, None, None
75
  except json.JSONDecodeError as e:
76
  st.error(f"❌ Error: El archivo JSON tiene un formato incorrecto: {e}")
77
- logger.error(f"Error de formato JSON: {e}")
78
- return None, None, None
79
 
80
- data_codigos, notificacion_map, nombres_eventos_unicos = load_data()
81
 
82
 
83
  # --- LÓGICA DE BÚSQUEDA ---
84
 
85
  def search_direct(query, data, notif_map):
86
- """
87
- Realiza una búsqueda directa y devuelve solo los ítems que coinciden,
88
- agrupados por su Ficha/Evento.
89
- """
90
  query_lower = query.lower().strip()
91
-
92
  matching_items = []
93
  for item in data:
94
  if (query_lower == item.get("CIE 10", "").lower().strip() or
@@ -96,249 +91,179 @@ def search_direct(query, data, notif_map):
96
  query_lower == item.get("FICHA", "").lower().strip() or
97
  query_lower in item.get("NOMBRES DE PROTOCOLOS", "").lower()):
98
  matching_items.append(item)
99
-
100
- if not matching_items:
101
- return []
102
-
103
  grouped_by_ficha = {}
104
  for item in matching_items:
105
  ficha = item.get("FICHA")
106
  if ficha:
107
- if ficha not in grouped_by_ficha:
108
- grouped_by_ficha[ficha] = []
109
  grouped_by_ficha[ficha].append(item)
110
-
111
  final_results = []
112
  for ficha, matched_details in grouped_by_ficha.items():
113
  info_notificacion = notif_map.get(ficha)
114
  if info_notificacion:
115
- final_results.append({
116
- "info_notificacion": info_notificacion,
117
- "detalles_codigos": matched_details
118
- })
119
-
120
  return final_results
121
 
122
 
123
  def search_with_gemini(query, event_list):
124
- """Usa Gemini para encontrar el evento más relevante a partir de una consulta en lenguaje natural."""
125
- if not model:
126
- return "Error: Modelo de IA no disponible."
127
 
128
  system_prompt = f"""
129
  Eres un asesor experto en SIVIGILA, el sistema de vigilancia en salud pública de Colombia.
130
- Tu tarea es identificar el evento de notificación más relevante basado en la consulta de un profesional de la salud.
131
- A continuación, te proporciono una lista de los nombres oficiales de los protocolos de notificación.
132
 
133
- LISTA DE PROTOCOLOS OFICIALES:
134
  {'; '.join(event_list)}
135
 
136
- Analiza la siguiente consulta del usuario y devuelve ÚNICAMENTE el nombre exacto del protocolo o protocolos de la lista que mejor correspondan.
 
137
  Si encuentras varios, sepáralos con un punto y coma (;).
138
- Si la consulta no corresponde a ningún protocolo de la lista, responde con "NO_ENCONTRADO".
139
- No añadas explicaciones, saludos ni texto adicional. Solo el nombre del protocolo.
140
 
141
  Consulta del usuario: "{query}"
142
  """
 
143
 
144
  try:
145
- logger.info(f"🔄 Enviando consulta a Gemini: '{query}'")
146
  response = model.generate_content(system_prompt)
147
 
148
  if response.parts:
149
  result_text = response.text.strip()
150
- logger.info(f"✅ Respuesta de Gemini: '{result_text}'")
151
- if result_text == "NO_ENCONTRADO":
152
- return []
153
  return [name.strip() for name in result_text.split(';') if name.strip()]
154
  else:
155
  logger.warning(f"⚠️ Respuesta de Gemini vacía o bloqueada. Razón: {response.prompt_feedback}")
156
  return "Respuesta bloqueada por filtros de seguridad."
157
 
 
 
 
158
  except Exception as e:
159
  logger.error(f"❌ Error en la llamada a Gemini: {e}")
160
- return f"Error en la comunicación con la IA: {e}"
161
 
162
 
163
  # --- INTERFAZ DE USUARIO (UI) ---
164
 
165
- st.title("👨‍⚕️ Buscador inteligente en Notificación SIVIGILA - Colombia")
166
- st.markdown("Busque una enfermedad por nombre, sinónimo, código CIE-10 o CIE-11 para obtener información de notificación.")
167
 
168
- if 'search_results' not in st.session_state:
169
- st.session_state.search_results = None
170
- if 'last_query' not in st.session_state:
171
- st.session_state.last_query = ""
 
 
 
 
 
172
 
173
  def clear_search_state():
174
- """Función para reiniciar el estado de la búsqueda."""
175
  st.session_state.search_results = None
176
  st.session_state.last_query = ""
177
 
178
  with st.form(key="search_form"):
179
- query = st.text_input(
180
- "Ingrese su búsqueda:",
181
- placeholder="Ej: Lepra, T630, mordedura de serpiente, 3MC...",
182
- help="Puede buscar por nombre de la enfermedad, palabras clave, o códigos CIE."
183
- )
184
  submitted = st.form_submit_button("Buscar", type="primary")
185
 
186
  if submitted:
187
- if not query:
188
- st.warning("Por favor, ingrese un término de búsqueda.")
189
- clear_search_state()
190
- elif data_codigos is None or notificacion_map is None:
191
- st.error("Los datos no se pudieron cargar. La aplicación no puede funcionar.")
192
  else:
193
- with st.spinner("Buscando..."):
194
  results = []
195
  direct_results = search_direct(query, data_codigos, notificacion_map)
196
 
197
  if not direct_results:
198
- logger.info(f"Búsqueda directa para '{query}' no arrojó resultados. Usando Gemini...")
199
- protocol_names = search_with_gemini(query, nombres_eventos_unicos)
200
 
201
- if isinstance(protocol_names, str):
202
- st.error(protocol_names)
203
- protocol_names = []
204
-
205
- if protocol_names:
206
- temp_results = []
207
- for name in protocol_names:
208
- temp_results.extend(search_direct(name, data_codigos, notificacion_map))
 
 
 
209
 
210
- unique_fichas = set()
211
- for res in temp_results:
212
- ficha = res["info_notificacion"]["FICHA"]
213
- if ficha not in unique_fichas:
214
- results.append(res)
215
- unique_fichas.add(ficha)
216
- else:
217
- results = []
218
  else:
219
  results = direct_results
220
 
221
  st.session_state.search_results = results
222
  st.session_state.last_query = query
223
 
224
- if st.session_state.search_results is not None:
225
- st.button("Limpiar Búsqueda", on_click=clear_search_state)
226
 
227
  st.markdown("---")
228
 
229
  if st.session_state.search_results is not None:
230
  results = st.session_state.search_results
231
-
232
  if not results:
233
  st.info(f"No se encontraron eventos de notificación obligatoria para su búsqueda: '{st.session_state.last_query}'.")
234
  else:
235
  st.success(f"Se encontraron {len(results)} evento(s) relacionado(s) con su búsqueda: '{st.session_state.last_query}'")
236
-
237
  for result in results:
238
  info = result["info_notificacion"]
239
  codigos = result["detalles_codigos"]
240
-
241
  with st.expander(f"**Evento: {info.get('Evento', 'Sin nombre')} (Ficha: {info.get('FICHA', 'N/A')})**", expanded=True):
242
-
243
-
244
  tab1, tab2 = st.tabs(["Información General", "📖 Definición de Caso"])
245
-
246
  with tab1:
247
  st.subheader("Información de Notificación")
248
-
249
  notif_super = info.get("Notificación superinmedita", "NO") == "SI"
250
- if notif_super:
251
- st.error(f"**¡ATENCIÓN! Requiere Notificación SUPERINMEDIATA:** {info.get('Descripción superinmedita', '')}")
252
- elif info.get("Notificación Inmediata", "NO") == "SI":
253
- st.warning("**Requiere Notificación Inmediata (dentro de las 24 horas).**")
254
- else:
255
- st.info("**Notificación Semanal Individual.**")
256
-
257
  st.markdown(f"**Requisito:** {info.get('REQUISITO', 'No especificado')}")
258
-
259
  fichas_texto = info.get('Fichas a Utilizar', 'No especificado').replace('+', '\n+ ')
260
  st.markdown(f"**Fichas a utilizar:** {fichas_texto}")
261
-
262
- st.markdown("---")
263
  st.subheader("Clasificación de Caso Permitida")
264
-
265
- classification_fields = [
266
- ("Sospechoso", "Clasificación Permitida Sospechoso"),
267
- ("Probable", "Clasificación Permitida Probable"),
268
- ("Conf. Clínica", "Clasificación Permitida Conf. Clínica"),
269
- ("Conf. Laboratorio", "Clasificación Permitida Conf. Laboratorio"),
270
- ("Conf. Nexo Epi.", "Clasificación Permitida Conf. Nexo Epi."),
271
- ("Descartado", "Clasificación Permitida Descartado")
272
- ]
273
-
274
- col1, col2 = st.columns(2)
275
-
276
- for i, (display_name, field_key) in enumerate(classification_fields):
277
- value = info.get(field_key, "NO")
278
- icon = "✅" if value == "SI" else "❌"
279
- md_string = f"{icon} **{display_name}**"
280
-
281
- if i < len(classification_fields) / 2:
282
- col1.markdown(md_string)
283
- else:
284
- col2.markdown(md_string)
285
-
286
  st.markdown("---")
287
  st.subheader("Rango de Edad Permitido para Notificación")
288
-
289
- age_fields = [
290
- ("Menor de 1 año", "Menor de 1 año"),
291
- ("De 1 a 4 años", "De 1 a 4 años"),
292
- ("De 5 a 14 años", "De 5 a 14 años"),
293
- ("De 15 a 44 años", "De 15 a 44 años"),
294
- ("De 45 a 59 años", "De 45 a 59 años"),
295
- ("De 60 y más", "De 60 y más")
296
- ]
297
-
298
- age_col1, age_col2 = st.columns(2)
299
-
300
- for i, (display_name, field_key) in enumerate(age_fields):
301
- value = info.get(field_key, "NO")
302
- icon = "✅" if value == "SI" else "❌"
303
- md_string = f"{icon} **{display_name}**"
304
-
305
- if i < len(age_fields) / 2:
306
- age_col1.markdown(md_string)
307
- else:
308
- age_col2.markdown(md_string)
309
-
310
  st.markdown("---")
311
  st.subheader("Condición Final Permitida")
312
-
313
- final_condition_fields = [
314
- ("Vivo", "Condición final Vivo"),
315
- ("Muerto", "Condición final Muerto")
316
- ]
317
-
318
- cond_col1, cond_col2 = st.columns(2)
319
-
320
- for i, (display_name, field_key) in enumerate(final_condition_fields):
321
- value = info.get(field_key, "NO")
322
- icon = "✅" if value == "SI" else "❌"
323
- md_string = f"{icon} **{display_name}**"
324
-
325
- if i == 0:
326
- cond_col1.markdown(md_string)
327
- else:
328
- cond_col2.markdown(md_string)
329
-
330
  st.subheader("Códigos Relacionados (CIE-10 y CIE-11)")
331
  for codigo in codigos:
332
- st.markdown(f"""
333
- - **Nombre Protocolo:** `{codigo.get('NOMBRES DE PROTOCOLOS', 'N/A')}`
334
- - **CIE-10:** `{codigo.get('CIE 10', 'N/A')}`
335
- - **CIE-11:** `{codigo.get('CIE 11', 'N/A')}` | **Nombre CIE-11:** *{codigo.get('NOMBRES DE CIE 11', 'N/A')}*
336
- """)
337
-
338
  with tab2:
339
- definicion_de_caso = info.get("Definición de caso", "").strip()
340
- if definicion_de_caso:
341
- st.markdown(definicion_de_caso.replace('\n', ' \n'))
342
- else:
343
- st.info("No se encontró una definición de caso oficial para este evento.")
344
-
 
1
  # ==================== Buscador de Eventos de Notificación SIVIGILA - Colombia 2025 =====================================
2
 
 
3
  # JAIRO ALEXANDER ERASO MD U Nacional de Colombia.
4
  # DIANA MILENA SOLER MARTINEZ U Juan N. Corpas
5
 
6
  import streamlit as st
7
  import google.generativeai as genai
8
+ import google.api_core.exceptions # Import para manejar errores de API
9
  import os
10
  import json
11
  import logging
 
14
  # --- CONFIGURACIÓN BÁSICA ---
15
  st.set_page_config(
16
  page_title="Buscador Inteligente SIVIGILA Colombia",
17
+ page_icon="buho.png", # Añadido el ícono del búho
18
  layout="wide"
19
  )
20
 
 
24
 
25
  # --- CONFIGURACIÓN DE GEMINI ---
26
  try:
27
+
28
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
29
  if not GEMINI_API_KEY:
30
+ st.error("No se encontró la variable de entorno GEMINI_API_KEY. Por favor, configúrala en los Secrets de tu Space.")
31
  logger.error("GEMINI_API_KEY no encontrada.")
32
  st.stop()
33
  genai.configure(api_key=GEMINI_API_KEY)
 
39
 
40
  @st.cache_resource
41
  def get_gemini_model():
 
42
  logger.info("🔄 Cargando modelo Gemini (gemini-1.5-flash-latest)...")
43
  try:
44
  return genai.GenerativeModel("gemini-1.5-flash-latest")
 
63
  data_notificacion = json.load(f)
64
 
65
  notificacion_map = {item['FICHA']: item for item in data_notificacion}
66
+
67
+
68
+ nombres_eventos_limpios = sorted(list(set(item['Evento'] for item in data_notificacion)))
69
 
70
  logger.info(f"✅ Datos cargados: {len(data_codigos)} registros de códigos y {len(data_notificacion)} eventos de notificación.")
71
+ return data_codigos, data_notificacion, notificacion_map, nombres_eventos_limpios
72
 
73
  except FileNotFoundError as e:
74
  st.error(f"❌ Error: No se encontró el archivo {e.filename}. Asegúrate de que los archivos JSON estén en la carpeta 'PLANTILLAS'.")
75
+ return None, None, None, None
 
76
  except json.JSONDecodeError as e:
77
  st.error(f"❌ Error: El archivo JSON tiene un formato incorrecto: {e}")
78
+ return None, None, None, None
 
79
 
80
+ data_codigos, data_notificacion, notificacion_map, nombres_eventos_limpios = load_data()
81
 
82
 
83
  # --- LÓGICA DE BÚSQUEDA ---
84
 
85
  def search_direct(query, data, notif_map):
 
 
 
 
86
  query_lower = query.lower().strip()
 
87
  matching_items = []
88
  for item in data:
89
  if (query_lower == item.get("CIE 10", "").lower().strip() or
 
91
  query_lower == item.get("FICHA", "").lower().strip() or
92
  query_lower in item.get("NOMBRES DE PROTOCOLOS", "").lower()):
93
  matching_items.append(item)
94
+ if not matching_items: return []
 
 
 
95
  grouped_by_ficha = {}
96
  for item in matching_items:
97
  ficha = item.get("FICHA")
98
  if ficha:
99
+ if ficha not in grouped_by_ficha: grouped_by_ficha[ficha] = []
 
100
  grouped_by_ficha[ficha].append(item)
 
101
  final_results = []
102
  for ficha, matched_details in grouped_by_ficha.items():
103
  info_notificacion = notif_map.get(ficha)
104
  if info_notificacion:
105
+ final_results.append({"info_notificacion": info_notificacion, "detalles_codigos": matched_details})
 
 
 
 
106
  return final_results
107
 
108
 
109
  def search_with_gemini(query, event_list):
110
+ """Usa la API de Gemini para encontrar el evento más relevante."""
111
+ if not model: return "Error: Modelo Gemini no disponible."
112
+
113
 
114
  system_prompt = f"""
115
  Eres un asesor experto en SIVIGILA, el sistema de vigilancia en salud pública de Colombia.
116
+ Tu tarea es interpretar la consulta de un profesional de la salud, que puede ser un término coloquial, un sinónimo o una descripción, y asociarla al nombre del evento oficial más relevante de una lista.
 
117
 
118
+ LISTA DE EVENTOS OFICIALES:
119
  {'; '.join(event_list)}
120
 
121
+ Analiza la siguiente consulta y devuelve ÚNICAMENTE el nombre exacto del evento oficial de la lista que mejor corresponda.
122
+ Por ejemplo, si la consulta es 'mordedura de culebra', la respuesta debe ser 'Accidente Ofídico'.
123
  Si encuentras varios, sepáralos con un punto y coma (;).
124
+ Si la consulta no corresponde a ningún evento de la lista, responde con "NO_ENCONTRADO".
125
+ No añadas explicaciones ni texto adicional.
126
 
127
  Consulta del usuario: "{query}"
128
  """
129
+
130
 
131
  try:
132
+ logger.info(f"🔄 Enviando consulta a Gemini API: '{query}'")
133
  response = model.generate_content(system_prompt)
134
 
135
  if response.parts:
136
  result_text = response.text.strip()
137
+ logger.info(f"✅ Respuesta de Gemini API: '{result_text}'")
138
+ if result_text == "NO_ENCONTRADO": return []
 
139
  return [name.strip() for name in result_text.split(';') if name.strip()]
140
  else:
141
  logger.warning(f"⚠️ Respuesta de Gemini vacía o bloqueada. Razón: {response.prompt_feedback}")
142
  return "Respuesta bloqueada por filtros de seguridad."
143
 
144
+ except google.api_core.exceptions.ResourceExhausted as e:
145
+ logger.warning(f"Límite de frecuencia de la API alcanzado: {e}")
146
+ return "Se ha alcanzado el límite de consultas. Por favor, espere un minuto y vuelva a intentarlo."
147
  except Exception as e:
148
  logger.error(f"❌ Error en la llamada a Gemini: {e}")
149
+ return f"Error en la comunicación con la API de Gemini: {e}"
150
 
151
 
152
  # --- INTERFAZ DE USUARIO (UI) ---
153
 
 
 
154
 
155
+ col_img, col_text = st.columns([1, 5], gap="medium")
156
+ with col_img:
157
+ st.image("buho.png", width=100)
158
+ with col_text:
159
+ st.title("Buscador Inteligente SIVIGILA")
160
+ st.markdown("Herramienta de apoyo para la notificación de eventos de salud pública en Colombia.")
161
+
162
+ if 'search_results' not in st.session_state: st.session_state.search_results = None
163
+ if 'last_query' not in st.session_state: st.session_state.last_query = ""
164
 
165
  def clear_search_state():
 
166
  st.session_state.search_results = None
167
  st.session_state.last_query = ""
168
 
169
  with st.form(key="search_form"):
170
+ query = st.text_input("Ingrese su búsqueda:", placeholder="Ej: Lepra, T630, mordedura de serpiente, 3MC...", help="Puede buscar por nombre de la enfermedad, palabras clave, o códigos CIE.")
 
 
 
 
171
  submitted = st.form_submit_button("Buscar", type="primary")
172
 
173
  if submitted:
174
+ if not query: st.warning("Por favor, ingrese un término de búsqueda.")
175
+ elif data_codigos is None: st.error("Los datos no se pudieron cargar.")
 
 
 
176
  else:
177
+ with st.spinner("Buscando con IA (Gemini)..."):
178
  results = []
179
  direct_results = search_direct(query, data_codigos, notificacion_map)
180
 
181
  if not direct_results:
182
+ logger.info(f"Búsqueda directa para '{query}' no arrojó resultados. Usando IA (Gemini)...")
183
+ event_names = search_with_gemini(query, nombres_eventos_limpios)
184
 
185
+ if isinstance(event_names, str):
186
+ st.error(event_names)
187
+ event_names = []
188
+
189
+
190
+ if event_names:
191
+ fichas_encontradas = set()
192
+ for name in event_names:
193
+ for item in data_notificacion:
194
+ if item['Evento'] == name:
195
+ fichas_encontradas.add(item['FICHA'])
196
 
197
+ for ficha in fichas_encontradas:
198
+ info_notif = notificacion_map.get(ficha)
199
+ detalles_cods = [d for d in data_codigos if d.get("FICHA") == ficha]
200
+ if info_notif:
201
+ results.append({"info_notificacion": info_notif, "detalles_codigos": detalles_cods})
202
+
 
 
203
  else:
204
  results = direct_results
205
 
206
  st.session_state.search_results = results
207
  st.session_state.last_query = query
208
 
209
+ if st.session_state.search_results is not None: st.button("Limpiar Búsqueda", on_click=clear_search_state)
 
210
 
211
  st.markdown("---")
212
 
213
  if st.session_state.search_results is not None:
214
  results = st.session_state.search_results
 
215
  if not results:
216
  st.info(f"No se encontraron eventos de notificación obligatoria para su búsqueda: '{st.session_state.last_query}'.")
217
  else:
218
  st.success(f"Se encontraron {len(results)} evento(s) relacionado(s) con su búsqueda: '{st.session_state.last_query}'")
 
219
  for result in results:
220
  info = result["info_notificacion"]
221
  codigos = result["detalles_codigos"]
 
222
  with st.expander(f"**Evento: {info.get('Evento', 'Sin nombre')} (Ficha: {info.get('FICHA', 'N/A')})**", expanded=True):
 
 
223
  tab1, tab2 = st.tabs(["Información General", "📖 Definición de Caso"])
 
224
  with tab1:
225
  st.subheader("Información de Notificación")
 
226
  notif_super = info.get("Notificación superinmedita", "NO") == "SI"
227
+ if notif_super: st.error(f"**¡ATENCIÓN! Requiere Notificación SUPERINMEDIATA:** {info.get('Descripción superinmedita', '')}")
228
+ elif info.get("Notificación Inmediata", "NO") == "SI": st.warning("**Requiere Notificación Inmediata (dentro de las 24 horas).**")
229
+ else: st.info("**Notificación Semanal Individual.**")
 
 
 
 
230
  st.markdown(f"**Requisito:** {info.get('REQUISITO', 'No especificado')}")
 
231
  fichas_texto = info.get('Fichas a Utilizar', 'No especificado').replace('+', '\n+ ')
232
  st.markdown(f"**Fichas a utilizar:** {fichas_texto}")
233
+ st.markdown("---")
 
234
  st.subheader("Clasificación de Caso Permitida")
235
+ classification_fields=[("Sospechoso","Clasificación Permitida Sospechoso"),("Probable","Clasificación Permitida Probable"),("Conf. Clínica","Clasificación Permitida Conf. Clínica"),("Conf. Laboratorio","Clasificación Permitida Conf. Laboratorio"),("Conf. Nexo Epi.","Clasificación Permitida Conf. Nexo Epi."),("Descartado","Clasificación Permitida Descartado")]
236
+ col1,col2=st.columns(2)
237
+ for i,(display_name,field_key) in enumerate(classification_fields):
238
+ value=info.get(field_key,"NO")
239
+ icon="" if value=="SI" else "❌"
240
+ md_string=f"{icon} **{display_name}**"
241
+ if i<len(classification_fields)/2: col1.markdown(md_string)
242
+ else: col2.markdown(md_string)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  st.markdown("---")
244
  st.subheader("Rango de Edad Permitido para Notificación")
245
+ age_fields=[("Menor de 1 año","Menor de 1 año"),("De 1 a 4 años","De 1 a 4 años"),("De 5 a 14 años","De 5 a 14 años"),("De 15 a 44 años","De 15 a 44 años"),("De 45 a 59 años","De 45 a 59 años"),("De 60 y más","De 60 y más")]
246
+ age_col1,age_col2=st.columns(2)
247
+ for i,(display_name,field_key) in enumerate(age_fields):
248
+ value=info.get(field_key,"NO")
249
+ icon="✅" if value=="SI" else "❌"
250
+ md_string=f"{icon} **{display_name}**"
251
+ if i<len(age_fields)/2: age_col1.markdown(md_string)
252
+ else: age_col2.markdown(md_string)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  st.markdown("---")
254
  st.subheader("Condición Final Permitida")
255
+ final_condition_fields=[("Vivo","Condición final Vivo"),("Muerto","Condición final Muerto")]
256
+ cond_col1,cond_col2=st.columns(2)
257
+ for i,(display_name,field_key) in enumerate(final_condition_fields):
258
+ value=info.get(field_key,"NO")
259
+ icon="✅" if value=="SI" else "❌"
260
+ md_string=f"{icon} **{display_name}**"
261
+ if i==0: cond_col1.markdown(md_string)
262
+ else: cond_col2.markdown(md_string)
 
 
 
 
 
 
 
 
 
 
263
  st.subheader("Códigos Relacionados (CIE-10 y CIE-11)")
264
  for codigo in codigos:
265
+ st.markdown(f"""- **Nombre Protocolo:** `{codigo.get('NOMBRES DE PROTOCOLOS', 'N/A')}`\n - **CIE-10:** `{codigo.get('CIE 10', 'N/A')}`\n - **CIE-11:** `{codigo.get('CIE 11', 'N/A')}` | **Nombre CIE-11:** *{codigo.get('NOMBRES DE CIE 11', 'N/A')}*""")
 
 
 
 
 
266
  with tab2:
267
+ definicion_de_caso=info.get("Definición de caso","").strip()
268
+ if definicion_de_caso: st.markdown(definicion_de_caso.replace('\n',' \n'))
269
+ else: st.info("No se encontró una definición de caso oficial para este evento.")