JairoCesar commited on
Commit
57cd752
·
verified ·
1 Parent(s): fc2b4ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -50
app.py CHANGED
@@ -1,8 +1,8 @@
1
- # El Detective de Alimentos (Versión Actualizada a google-genai V3.0 by Jairo Alexander
2
- # Diana Milena Soler Martinez desarrolladores)
3
  import streamlit as st
4
- from google import genai # NUEVA LIBRERÍA
5
- from google.genai import types # TIPOS DE DATOS NUEVOS
6
  import os
7
  import json
8
  import logging
@@ -44,20 +44,18 @@ def get_gemini_client():
44
  APIkeydetective = os.getenv("APIkeydetective")
45
 
46
  if not APIkeydetective:
47
- st.error("No se encontró la APIkeydetective.")
48
  return None
49
 
50
  return genai.Client(
51
  api_key=APIkeydetective,
52
- http_options=types.HttpOptions(timeout=30000) # 30 segundos
53
  )
54
  except Exception as e:
55
- st.error(f"❌ Error al configurar Gemini API: {e}")
56
  return None
57
 
58
  client = get_gemini_client()
59
- if not client:
60
- st.stop()
61
 
62
  # --- CARGA ULTRA-RÁPIDA DE DATOS ---
63
  @st.cache_resource
@@ -97,6 +95,7 @@ FOOD_TO_COMPOUND_MAP = {
97
  "pan": ["gluten", "fodmaps"],
98
  "trigo": ["gluten", "fodmaps"],
99
  "vino": ["histamina", "tiramina", "sulfitos"],
 
100
  "cerveza": ["gluten", "histamina", "sulfitos"],
101
  "chocolate": ["tiramina", "feniletilamina", "histamina"],
102
  "tomate": ["histamina", "solanina"],
@@ -112,7 +111,7 @@ FOOD_TO_COMPOUND_MAP = {
112
  "cholado": ["fructosa", "lactosa", "histamina", "azúcar refinado"]
113
  }
114
 
115
- # --- SEGURO LÓGICO DE INCOMPATIBILIDAD BIOQUÍMICA ---
116
  STRICT_COMPOUND_TRIGGERS = {
117
  "cafeína": ["cafeína", "caffeine", "café"],
118
  "gluten": ["gluten", "trigo", "cebada", "centeno"],
@@ -123,18 +122,28 @@ STRICT_COMPOUND_TRIGGERS = {
123
  "solanina": ["solanina", "solanine"]
124
  }
125
 
126
- def validar_seguro_logico(entry, user_foods, food_compound_map):
 
 
127
  if not user_foods:
128
  return True
129
 
 
 
 
 
 
 
 
 
 
 
130
  user_compounds = set()
131
  for food in user_foods:
132
  food_clean = food.lower().strip()
133
  user_compounds.update(food_compound_map.get(food_clean, []))
134
  user_compounds.add(food_clean)
135
 
136
- texto_condicion = (entry.get("condicion_asociada", "") + " " + entry.get("compuesto_alimento", "")).lower()
137
-
138
  for compound_key, synonyms in STRICT_COMPOUND_TRIGGERS.items():
139
  if any(syn in texto_condicion for syn in synonyms):
140
  tiene_compuesto = any(
@@ -173,7 +182,7 @@ MASTER_SYMPTOM_MAP = {
173
  "keywords_en": ["bloating", "distension", "flatulence", "swelling"]
174
  },
175
  "dolor de cabeza": {
176
- "frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca"],
177
  "keywords_en": ["headache", "migraine"]
178
  },
179
  "ansiedad": {
@@ -198,6 +207,7 @@ FOOD_NAME_TO_FOODB_KEY = {
198
  "yogur": ["yogurt", "yoghurt"],
199
  "mantequilla": ["butter"],
200
  "vino": ["wine"],
 
201
  "cerveza": ["beer"],
202
  "chocolate": ["chocolate", "cocoa"],
203
  "tomate": ["tomato"],
@@ -230,12 +240,16 @@ INTEGRATED_NEURO_FOOD_MAP = {
230
  "fuentes_comunes": ["trigo", "cebolla", "ajo", "legumbres", "manzanas", "miel"]
231
  },
232
  "tiramina": {
233
- "efecto_neuropsicologico": "Amina vasoactiva que altera el tono vascular cerebral, siendo un potente desencadenante de migrañas y cefaleas pulsátiles.",
234
  "fuentes_comunes": ["quesos madurados", "vino tinto", "embutidos", "alimentos fermentados"]
235
  },
236
  "histamina": {
237
- "efecto_neuropsicologico": "Neurotransmisor estimulante central. Su exceso por baja degradación causa insomnio, fragmentación del sueño, ansiedad y migrañas.",
238
- "fuentes_comunes": ["queso", "vino", "tomate", "pescado azul", "jugo de naranja", "alimentos conservados"]
 
 
 
 
239
  },
240
  "cafeína": {
241
  "efecto_neuropsicologico": "Bloquea los receptores de adenosina en el cerebro. En dosis altas o personas sensibles provoca hiperactivación del SNC, ansiedad e insomnio.",
@@ -252,25 +266,45 @@ INTEGRATED_NEURO_FOOD_MAP = {
252
  "fructosa": {
253
  "efecto_neuropsicologico": "La sobrecarga o malabsorción de fructosa disminuye la biodisponibilidad de triptófano en el intestino, interfiriendo en la síntesis de serotonina y melatonina (desregulando el sueño).",
254
  "fuentes_comunes": ["jugo de naranja", "frutas muy dulces", "miel", "jarabe de maíz"]
255
- },
256
- "sulfitos": {
257
- "efecto_neuropsicologico": "Pueden inducir estrés oxidativo y respuesta histaminérgica, manifestándose como embotamiento cerebral y cefaleas.",
258
- "fuentes_comunes": ["vino", "cerveza", "frutos secos desecados"]
259
- },
260
- "saponinas": {
261
- "efecto_neuropsicologico": "Aumentan la permeabilidad intestinal ('intestino permeable'), facilitando la migración de lipopolisacáridos e induciendo neuroinflamación.",
262
- "fuentes_comunes": ["quinoa", "legumbres", "soja"]
263
- },
264
- "oxalatos": {
265
- "efecto_neuropsicologico": "Pueden interferir en la función mitocondrial de las neuronas, asociados a fatiga crónica y neblina cerebral.",
266
- "fuentes_comunes": ["espinacas", "quinoa", "frutos secos", "cacao"]
267
- },
268
- "solanina": {
269
- "efecto_neuropsicologico": "Glicoalcaloide con leve efecto inhibidor de la acetilcolinesterasa; en sensibles puede provocar letargo y rigidez corporal.",
270
- "fuentes_comunes": ["tomate", "patata", "berenjena"]
271
  }
272
  }
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  # --- FUNCIONES DE APOYO Y EXTRACCIÓN ---
275
  @st.cache_data(show_spinner=False)
276
  def generate_word_report_cached(report_text):
@@ -315,7 +349,6 @@ def extract_keywords(text):
315
  words = re.findall(r'\b\w+\b', sanitize_text(text))
316
  return set(w for w in words if w not in STOPWORDS_ES and len(w) > 2)
317
 
318
- @retry(wait=wait_fixed(2), stop=stop_after_attempt(2))
319
  def extract_entities_with_gemini(query):
320
  gemini_client = get_gemini_client()
321
  if not gemini_client: return {"alimentos": [], "sintomas": []}
@@ -323,8 +356,8 @@ def extract_entities_with_gemini(query):
323
  system_prompt = f"""
324
  Rol: Extractor Médico JSON experto en nutrición y síntomas.
325
  Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
326
- - "alimentos": lista de alimentos o bebidas mencionados en español, forma simple (ej: "jugo de naranja", "queso", "vino").
327
- - "sintomas": lista de síntomas mencionados en español, forma clínica simple (ej: "insomnio", "dolor de cabeza", "hinchazón").
328
 
329
  Input: "{query}"
330
  Output (JSON estricto):
@@ -339,7 +372,7 @@ def extract_entities_with_gemini(query):
339
  elif "```" in text: text = text.split("```")[1].split("```")[0]
340
  return json.loads(text)
341
  except Exception as e:
342
- logger.error(f"Error en extracción: {e}")
343
  return {"alimentos": [], "sintomas": []}
344
 
345
  def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_map):
@@ -376,7 +409,7 @@ def translate_symptoms_local(symptoms_list, master_symptom_map):
376
  translated_symptoms.add(symptom)
377
  return list(translated_symptoms)
378
 
379
- def find_best_matches_hybrid(entities, data, food_compound_map=None):
380
  if not entities or not data: return []
381
  user_symptoms_list = entities.get("sintomas", [])
382
  user_foods_list = entities.get("alimentos", [])
@@ -394,7 +427,7 @@ def find_best_matches_hybrid(entities, data, food_compound_map=None):
394
  results = []
395
  for entry in data:
396
  # SEGURO LÓGICO
397
- if food_compound_map and not validar_seguro_logico(entry, user_foods_list, food_compound_map):
398
  continue
399
 
400
  db_food_keywords = extract_keywords(entry.get("compuesto_alimento", ""))
@@ -445,7 +478,7 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
445
  alimentos_str = ", ".join(alimentos_unicos)
446
 
447
  if es_dolor_cabeza:
448
- condicion = f"Posible migraña o cefalea alimentaria por sensibilidad a aminas vasoactivas ({compuestos_str})"
449
  elif es_insomnio:
450
  condicion = f"Alteración del sueño / insomnio por respuesta a {compuestos_str} presente en {alimentos_str}"
451
  else:
@@ -493,10 +526,13 @@ def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_
493
 
494
  return unique[:limit]
495
 
496
- @retry(wait=wait_fixed(2), stop=stop_after_attempt(2))
497
  def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
498
  gemini_client = get_gemini_client()
499
- if not gemini_client: return "Error: IA no disponible."
 
 
 
 
500
 
501
  compound_context = ""
502
  if entities and food_compound_map and neuro_map:
@@ -519,7 +555,7 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
519
  Tarea: Escribe un análisis en Markdown estructurado:
520
  1. Saludo breve y empático.
521
  2. Frase inicial clara con la hipótesis probable.
522
- 3. Explicación de las moléculas responsables (ej: histamina, fructosa, tiramina, gluten).
523
  4. Mecanismo fisiológico en términos sencillos.
524
  5. Alimentos a evitar o moderar.
525
  6. Alternativas o reemplazos sugeridos.
@@ -533,8 +569,8 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
533
  )
534
  return response.text + TEXTO_BANDERAS_ROJAS
535
  except Exception as e:
536
- logger.error(f"Error análisis: {e}")
537
- return "No se pudo generar el análisis detallado. Intenta de nuevo."
538
 
539
  def generate_neuro_report_text(entities, food_map, neuro_map):
540
  report_lines = ["\n### 🧠 Efectos Neuropsicológicos Posibles"]
@@ -624,7 +660,7 @@ with st.form(key="search_form", clear_on_submit=False):
624
  query_input = st.text_area(
625
  "Tu Caso:",
626
  height=150,
627
- placeholder="Ej: Cuando tomo mucho jugo de naranja no puedo dormir.",
628
  key="input_query_text"
629
  )
630
  submitted = st.form_submit_button("Analizar Caso", type="primary")
@@ -634,7 +670,7 @@ if submitted:
634
  current_query = query_input.strip()
635
  st.session_state.user_query = current_query
636
  st.session_state.has_searched = True
637
- st.session_state.analysis_cache = {} # Limpiar análisis previo
638
  st.session_state.search_results = None
639
  st.session_state.entities = None
640
 
@@ -645,7 +681,7 @@ if submitted:
645
  final_entities = {"alimentos": reinforced.get("alimentos", []), "sintomas": final_symptoms}
646
  st.session_state.entities = final_entities
647
 
648
- results = find_best_matches_hybrid(final_entities, alimentos_data, FOOD_TO_COMPOUND_MAP)
649
 
650
  if not results:
651
  local_hypothesis = build_local_hypothesis(final_entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP)
@@ -656,7 +692,7 @@ if submitted:
656
  else:
657
  st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
658
 
659
- # --- DISPLAY DE RESULTADOS INDEPENDIENTE Y REUTILIZABLE ---
660
  if st.session_state.has_searched:
661
  results = st.session_state.search_results
662
 
@@ -664,7 +700,6 @@ if st.session_state.has_searched:
664
  best_match = results[0]['entry']
665
  st.success(f"🔎 Coincidencia Principal: **{best_match.get('condicion_asociada')}**")
666
 
667
- # Clave de caché única por consulta y condición
668
  cache_key = f"analysis_{hash(st.session_state.user_query)}_{best_match.get('condicion_asociada')}"
669
  if cache_key not in st.session_state.analysis_cache:
670
  with st.spinner("✍️ Redactando informe..."):
 
1
+ # El Detective de Alimentos (Versión Actualizada a google-genai V3.0 + Modo Local Automático
2
+ # by Jairo Alexander & Diana Milena Soler Martinez desarrolladores)
3
  import streamlit as st
4
+ from google import genai
5
+ from google.genai import types
6
  import os
7
  import json
8
  import logging
 
44
  APIkeydetective = os.getenv("APIkeydetective")
45
 
46
  if not APIkeydetective:
47
+ logger.warning("No se encontró la APIkeydetective. Se usará el modo local automático.")
48
  return None
49
 
50
  return genai.Client(
51
  api_key=APIkeydetective,
52
+ http_options=types.HttpOptions(timeout=10000) # Timeout rápido (10 segundos)
53
  )
54
  except Exception as e:
55
+ logger.warning(f"Error al configurar Gemini API: {e}. Se usará el modo local.")
56
  return None
57
 
58
  client = get_gemini_client()
 
 
59
 
60
  # --- CARGA ULTRA-RÁPIDA DE DATOS ---
61
  @st.cache_resource
 
95
  "pan": ["gluten", "fodmaps"],
96
  "trigo": ["gluten", "fodmaps"],
97
  "vino": ["histamina", "tiramina", "sulfitos"],
98
+ "vino tinto": ["histamina", "tiramina", "sulfitos", "taninos"],
99
  "cerveza": ["gluten", "histamina", "sulfitos"],
100
  "chocolate": ["tiramina", "feniletilamina", "histamina"],
101
  "tomate": ["histamina", "solanina"],
 
111
  "cholado": ["fructosa", "lactosa", "histamina", "azúcar refinado"]
112
  }
113
 
114
+ # --- SEGURO LÓGICO DE INCOMPATIBILIDAD BIOQUÍMICA Y FÁRMACOS ---
115
  STRICT_COMPOUND_TRIGGERS = {
116
  "cafeína": ["cafeína", "caffeine", "café"],
117
  "gluten": ["gluten", "trigo", "cebada", "centeno"],
 
122
  "solanina": ["solanina", "solanine"]
123
  }
124
 
125
+ DRUG_KEYWORDS = ["fármaco", "farmaco", "medicamento", "imao", "remedio", "pastilla", "tratamiento", "antidepresiv"]
126
+
127
+ def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
128
  if not user_foods:
129
  return True
130
 
131
+ user_query_clean = user_query.lower() if user_query else ""
132
+ texto_condicion = (entry.get("condicion_asociada", "") + " " + entry.get("compuesto_alimento", "")).lower()
133
+
134
+ # REGLA 1: Descartar interacciones de medicamentos/IMAO si el usuario no los mencionó
135
+ if any(k in texto_condicion for k in ["fármaco", "farmaco", "imao", "interacción fármaco"]):
136
+ if not any(dk in user_query_clean for dk in DRUG_KEYWORDS):
137
+ logger.info(f"🚩 SEGURO LÓGICO ACTIVADO: Se descartó interacción fármaco/IMAO porque el usuario no mencionó medicamentos.")
138
+ return False
139
+
140
+ # REGLA 2: Validar compuestos estrictos
141
  user_compounds = set()
142
  for food in user_foods:
143
  food_clean = food.lower().strip()
144
  user_compounds.update(food_compound_map.get(food_clean, []))
145
  user_compounds.add(food_clean)
146
 
 
 
147
  for compound_key, synonyms in STRICT_COMPOUND_TRIGGERS.items():
148
  if any(syn in texto_condicion for syn in synonyms):
149
  tiene_compuesto = any(
 
182
  "keywords_en": ["bloating", "distension", "flatulence", "swelling"]
183
  },
184
  "dolor de cabeza": {
185
+ "frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza"],
186
  "keywords_en": ["headache", "migraine"]
187
  },
188
  "ansiedad": {
 
207
  "yogur": ["yogurt", "yoghurt"],
208
  "mantequilla": ["butter"],
209
  "vino": ["wine"],
210
+ "vino tinto": ["wine", "red wine"],
211
  "cerveza": ["beer"],
212
  "chocolate": ["chocolate", "cocoa"],
213
  "tomate": ["tomato"],
 
240
  "fuentes_comunes": ["trigo", "cebolla", "ajo", "legumbres", "manzanas", "miel"]
241
  },
242
  "tiramina": {
243
+ "efecto_neuropsicologico": "Amina vasoactiva de acción simpaticomimética indirecta. Provoca vasoconstricción inicial seguida de vasodilatación cerebral, siendo uno de los principales desencadenantes de cefaleas y migrañas pulsátiles.",
244
  "fuentes_comunes": ["quesos madurados", "vino tinto", "embutidos", "alimentos fermentados"]
245
  },
246
  "histamina": {
247
+ "efecto_neuropsicologico": "Neurotransmisor estimulante central. Su exceso por bajo DAO o consumo elevado provoca migrañas, insomnio, fragmentación del sueño, ansiedad y vasodilatación cerebral.",
248
+ "fuentes_comunes": ["queso", "vino tinto", "vino", "tomate", "pescado azul", "jugo de naranja"]
249
+ },
250
+ "sulfitos": {
251
+ "efecto_neuropsicologico": "Conservantes orgánicos que inducen estrés oxidativo, vasodilatación periférica y liberación de histamina, manifestándose frecuentemente como dolores de cabeza e pesadez cerebral.",
252
+ "fuentes_comunes": ["vino tinto", "vino", "cerveza", "frutos secos desecados"]
253
  },
254
  "cafeína": {
255
  "efecto_neuropsicologico": "Bloquea los receptores de adenosina en el cerebro. En dosis altas o personas sensibles provoca hiperactivación del SNC, ansiedad e insomnio.",
 
266
  "fructosa": {
267
  "efecto_neuropsicologico": "La sobrecarga o malabsorción de fructosa disminuye la biodisponibilidad de triptófano en el intestino, interfiriendo en la síntesis de serotonina y melatonina (desregulando el sueño).",
268
  "fuentes_comunes": ["jugo de naranja", "frutas muy dulces", "miel", "jarabe de maíz"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }
270
  }
271
 
272
+ # --- GENERADOR DE INFORME LOCAL AUTOMÁTICO (SIN IA) ---
273
+ def generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map):
274
+ saludo = "### 📋 Informe de Análisis Clínico-Nutricional (Modo Local / Alta Disponibilidad)\n"
275
+ hipotesis = f"**Hipótesis Probable:** Es muy probable que tus síntomas estén relacionados con **{match.get('condicion_asociada', 'una sensibilidad a compuestos alimentarios')}**.\n"
276
+
277
+ user_foods = entities.get("alimentos", [])
278
+ user_symptoms = entities.get("sintomas", [])
279
+
280
+ compuestos_detectados = set()
281
+ for food in user_foods:
282
+ comps = food_compound_map.get(food.lower().strip(), [])
283
+ if comps:
284
+ compuestos_detectados.update(comps)
285
+
286
+ compuestos_str = ", ".join(sorted(list(compuestos_detectados))) if compuestos_detectados else match.get('compuesto_alimento', 'compuestos bioactivos')
287
+
288
+ causa = f"\n### 🧬 Causa e Identificación Molecular\nLos síntomas descritos (**{', '.join(user_symptoms)}**) tras el consumo de **{', '.join(user_foods)}** están asociados a la presencia de sustancias biológicamente activas como **{compuestos_str}**.\n"
289
+
290
+ detalles_compuestos = []
291
+ for comp in compuestos_detectados:
292
+ if comp in neuro_map:
293
+ detalles_compuestos.append(f"- **{comp.capitalize()}**: {neuro_map[comp]['efecto_neuropsicologico']}")
294
+
295
+ if detalles_compuestos:
296
+ causa += "\n**Mecanismo bioquímico e impacto neurológico:**\n" + "\n".join(detalles_compuestos) + "\n"
297
+ elif match.get('mecanismo_fisiologico'):
298
+ causa += f"\n**Mecanismo fisiológico:** {match.get('mecanismo_fisiologico')}\n"
299
+
300
+ evitar = f"\n### 🛑 Alimentos a Moderar o Evitar\n- **Alimentos evaluados:** {', '.join(user_foods).capitalize()}\n- Alimentos ricos en **{compuestos_str}** o fermentados que contengan aminas vasoactivas similares.\n"
301
+
302
+ alternativas = "\n### 💡 Sugerencias y Alternativas\n- Llevar un diario de síntomas registrando alimentos, hora de consumo y aparición de molestias.\n- Reducir temporalmente la ingesta del alimento sospechoso para verificar la remisión del síntoma.\n- Consultar con un nutricionista funcional o médico especialista si las molestias persisten.\n"
303
+
304
+ consejo = "\n### 📌 Consejo Práctico Final\nCada organismo posee una capacidad individual diferente para metabolizar aminas e histamina según la actividad de enzimas como la DAO (Diamino Oxidasa) o la MAO-MTHFR.\n"
305
+
306
+ return saludo + hipotesis + causa + evitar + alternativas + consejo + TEXTO_BANDERAS_ROJAS
307
+
308
  # --- FUNCIONES DE APOYO Y EXTRACCIÓN ---
309
  @st.cache_data(show_spinner=False)
310
  def generate_word_report_cached(report_text):
 
349
  words = re.findall(r'\b\w+\b', sanitize_text(text))
350
  return set(w for w in words if w not in STOPWORDS_ES and len(w) > 2)
351
 
 
352
  def extract_entities_with_gemini(query):
353
  gemini_client = get_gemini_client()
354
  if not gemini_client: return {"alimentos": [], "sintomas": []}
 
356
  system_prompt = f"""
357
  Rol: Extractor Médico JSON experto en nutrición y síntomas.
358
  Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
359
+ - "alimentos": lista de alimentos o bebidas mencionados en español, forma simple (ej: "vino tinto", "queso", "jugo de naranja").
360
+ - "sintomas": lista de síntomas mencionados en español, forma clínica simple (ej: "dolor de cabeza", "insomnio", "hinchazón").
361
 
362
  Input: "{query}"
363
  Output (JSON estricto):
 
372
  elif "```" in text: text = text.split("```")[1].split("```")[0]
373
  return json.loads(text)
374
  except Exception as e:
375
+ logger.warning(f"Extracción por IA no disponible ({e}). Se usa extracción por diccionario.")
376
  return {"alimentos": [], "sintomas": []}
377
 
378
  def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_map):
 
409
  translated_symptoms.add(symptom)
410
  return list(translated_symptoms)
411
 
412
+ def find_best_matches_hybrid(entities, data, user_query="", food_compound_map=None):
413
  if not entities or not data: return []
414
  user_symptoms_list = entities.get("sintomas", [])
415
  user_foods_list = entities.get("alimentos", [])
 
427
  results = []
428
  for entry in data:
429
  # SEGURO LÓGICO
430
+ if food_compound_map and not validar_seguro_logico(entry, user_foods_list, user_query, food_compound_map):
431
  continue
432
 
433
  db_food_keywords = extract_keywords(entry.get("compuesto_alimento", ""))
 
478
  alimentos_str = ", ".join(alimentos_unicos)
479
 
480
  if es_dolor_cabeza:
481
+ condicion = f"Cefalea o migraña por aminas vasoactivas / compuestos ({compuestos_str}) en {alimentos_str}"
482
  elif es_insomnio:
483
  condicion = f"Alteración del sueño / insomnio por respuesta a {compuestos_str} presente en {alimentos_str}"
484
  else:
 
526
 
527
  return unique[:limit]
528
 
 
529
  def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
530
  gemini_client = get_gemini_client()
531
+
532
+ # MODO LOCAL AUTOMÁTICO SI LA API NO ESTÁ DISPONIBLE
533
+ if not gemini_client:
534
+ logger.info("Usando informe local por falta de cliente Gemini API.")
535
+ return generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
536
 
537
  compound_context = ""
538
  if entities and food_compound_map and neuro_map:
 
555
  Tarea: Escribe un análisis en Markdown estructurado:
556
  1. Saludo breve y empático.
557
  2. Frase inicial clara con la hipótesis probable.
558
+ 3. Explicación de las moléculas responsables (ej: histamina, tiramina, sulfitos, fructosa, gluten).
559
  4. Mecanismo fisiológico en términos sencillos.
560
  5. Alimentos a evitar o moderar.
561
  6. Alternativas o reemplazos sugeridos.
 
569
  )
570
  return response.text + TEXTO_BANDERAS_ROJAS
571
  except Exception as e:
572
+ logger.warning(f"Error o timeout en Gemini API ({e}). Se activa la respuesta automática en modo local.")
573
+ return generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
574
 
575
  def generate_neuro_report_text(entities, food_map, neuro_map):
576
  report_lines = ["\n### 🧠 Efectos Neuropsicológicos Posibles"]
 
660
  query_input = st.text_area(
661
  "Tu Caso:",
662
  height=150,
663
+ placeholder="Ej: Si tomo mucho vino tinto me duele la cabeza.",
664
  key="input_query_text"
665
  )
666
  submitted = st.form_submit_button("Analizar Caso", type="primary")
 
670
  current_query = query_input.strip()
671
  st.session_state.user_query = current_query
672
  st.session_state.has_searched = True
673
+ st.session_state.analysis_cache = {}
674
  st.session_state.search_results = None
675
  st.session_state.entities = None
676
 
 
681
  final_entities = {"alimentos": reinforced.get("alimentos", []), "sintomas": final_symptoms}
682
  st.session_state.entities = final_entities
683
 
684
+ results = find_best_matches_hybrid(final_entities, alimentos_data, current_query, FOOD_TO_COMPOUND_MAP)
685
 
686
  if not results:
687
  local_hypothesis = build_local_hypothesis(final_entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP)
 
692
  else:
693
  st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
694
 
695
+ # --- DISPLAY DE RESULTADOS REUTILIZABLE ---
696
  if st.session_state.has_searched:
697
  results = st.session_state.search_results
698
 
 
700
  best_match = results[0]['entry']
701
  st.success(f"🔎 Coincidencia Principal: **{best_match.get('condicion_asociada')}**")
702
 
 
703
  cache_key = f"analysis_{hash(st.session_state.user_query)}_{best_match.get('condicion_asociada')}"
704
  if cache_key not in st.session_state.analysis_cache:
705
  with st.spinner("✍️ Redactando informe..."):