JairoCesar commited on
Commit
9f64c01
·
verified ·
1 Parent(s): 9eb1c3f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -82
app.py CHANGED
@@ -1,6 +1,5 @@
1
- # ==================== El Detective de Alimentos v 2.0
2
- # ==================== El Detective de Alimentos v 2.0
3
- # Por: JAIRO CESAR ALEXANDER E. MD DIANA MILENA SOLER MARTINEZ PSI. ESP. U JUAN N CORPAS
4
  import streamlit as st
5
  import google.generativeai as genai
6
  import google.api_core.exceptions
@@ -14,7 +13,6 @@ from datetime import datetime
14
  from tenacity import retry, stop_after_attempt, wait_random_exponential
15
  from io import BytesIO
16
  import docx
17
-
18
  st.set_page_config(page_title="El Detective de Alimentos", page_icon="🍎", layout="wide")
19
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
20
  logger = logging.getLogger("food_detective_app")
@@ -76,13 +74,11 @@ KNOWN_TRIGGERS_MAP = {
76
  "Phenylethylamine": ["dolor de cabeza", "migraña"],
77
  "Cadaverine": ["inflamación", "dolor de cabeza"],
78
  "Putrescine": ["inflamación", "dolor de cabeza"],
79
-
80
  # --- Compuestos de Gluten y Lácteos ---
81
  "Gluten": ["niebla mental", "dolor abdominal", "diarrea", "inflamación", "dolor articular", "ataxia", "neuropatía periférica"],
82
  "Gliadin": ["niebla mental", "dolor abdominal", "diarrea", "inflamación"], # Componente principal del Gluten
83
  "Casein": ["niebla mental", "inflamación", "acné", "congestión nasal", "estreñimiento"],
84
  "Lactose": ["hinchazón", "gases", "diarrea", "dolor abdominal"],
85
-
86
  # --- Antinutrientes y Compuestos de Defensa Vegetal ---
87
  "Oxalates": ["dolor articular", "dolor muscular", "cálculos renales"],
88
  "Lectins": ["inflamación", "dolor articular", "hinchazón", "dolor abdominal", "erupción"],
@@ -92,7 +88,6 @@ KNOWN_TRIGGERS_MAP = {
92
  "Solanine": ["dolor articular", "inflamación", "dolor muscular"],
93
  "Avenin": ["inflamación", "dolor abdominal"], # En avena, similar al gluten
94
  "Quercetin": ["dolor de cabeza", "migraña"], # Específicamente en el contexto del vino
95
-
96
  # --- FODMAPs (Oligosacáridos, Disacáridos, Monosacáridos y Polioles Fermentables) ---
97
  "Fructans": ["hinchazón", "gases", "dolor abdominal", "diarrea", "estreñimiento"],
98
  "GOS (Galactooligosaccharides)": ["hinchazón", "gases", "dolor abdominal"],
@@ -100,14 +95,12 @@ KNOWN_TRIGGERS_MAP = {
100
  "Polyols": ["diarrea", "hinchazón", "gases"], # Incluye Sorbitol, Manitol, Xilitol
101
  "Sorbitol": ["diarrea", "hinchazón", "gases"],
102
  "Mannitol": ["diarrea", "hinchazón", "gases"],
103
-
104
  # --- Estimulantes, Alcaloides y Compuestos del Sistema Nervioso ---
105
  "Caffeine": ["ansiedad", "dolor de cabeza", "insomnio", "palpitaciones", "acidez"],
106
  "Theobromine": ["ansiedad", "dolor de cabeza", "insomnio"], # Similar a la cafeína, en chocolate
107
  "Capsaicin": ["dolor", "acidez", "ardor"],
108
  "Alcohol": ["dolor de cabeza", "niebla mental", "inflamación", "fatiga", "náuseas"],
109
  "Acetaldehyde": ["dolor de cabeza", "náuseas", "enrojecimiento facial"], # Metabolito del alcohol
110
-
111
  # --- Aditivos Alimentarios y Compuestos de Procesamiento ---
112
  "Glutamate (MSG)": ["dolor de cabeza", "náuseas", "debilidad muscular", "palpitaciones"],
113
  "Sulfites": ["dolor de cabeza", "sibilancias", "erupción", "congestión nasal"],
@@ -119,14 +112,12 @@ KNOWN_TRIGGERS_MAP = {
119
  "Artificial colorings": ["erupción", "hiperactividad"], # Ej. Tartrazine
120
  "Benzoates": ["erupción", "asma", "hiperactividad"],
121
  "Acrylamide": ["neuropatía periférica", "debilidad muscular"], # Formado en frituras
122
-
123
  # --- Toxinas Naturales y Compuestos Específicos ---
124
  "Aflatoxins": ["fatiga", "náuseas", "dolor abdominal"], # De mohos en frutos secos/granos
125
  "Cyanogenic glycosides": ["mareo/vértigo", "dolor de cabeza", "náuseas", "vómito"], # En almendras amargas, yuca
126
  "Arsenic": ["fatiga", "náuseas", "neuropatía periférica"], # En arroz
127
  "Mercury": ["fatiga", "niebla mental", "neuropatía periférica"], # En pescados grandes
128
  "Alpha-Gal": ["erupción", "hinchazón", "náuseas"], # Alérgeno de carne roja
129
-
130
  # --- Minerales y Elementos (en contexto de exceso o sensibilidad) ---
131
  "Nickel": ["erupción", "dermatitis", "dolor abdominal", "hinchazón"],
132
  "Iodine": ["acné", "erupción", "fatiga"], # En personas con sensibilidad o problemas tiroideos
@@ -619,25 +610,17 @@ def sanitize_text(text):
619
  return re.sub(r'[.,;()]', '', text).lower().strip()
620
 
621
  @retry(wait=wait_random_exponential(min=1, max=10), stop=stop_after_attempt(3))
 
622
  def extract_entities_with_gemini(query):
623
- if not model: return None
624
- logger.info("Intentando extracción de entidades con Gemini...")
625
- system_prompt = f"""
626
- Eres un asistente de triaje clínico experto. Tu única tarea es analizar la consulta de un usuario y extraer dos tipos de información:
627
- 1. `alimentos`: Una lista exhaustiva de todos los alimentos, bebidas o ingredientes consumidos mencionados.
628
- 2. `sintomas`: Una lista exhaustiva de todos los síntomas, sensaciones o signos clínicos descritos.
629
- Devuelve la respuesta ÚNICAMENTE en formato JSON estricto. No incluyas explicaciones ni texto adicional.
630
- Consulta: "{query}"
631
- """
632
  try:
633
  response = model.generate_content(system_prompt)
634
- json_text_match = re.search(r'```json\s*(\{.*?\})\s*```', response.text, re.DOTALL)
635
- if json_text_match:
636
- json_text = json_text_match.group(1)
637
- else:
638
- json_text = re.search(r'\{.*\}', response.text, re.DOTALL).group(0)
639
  logger.info("Extracción con Gemini exitosa.")
640
- return json.loads(json_text)
641
  except (Exception, google.api_core.exceptions.GoogleAPICallError) as e:
642
  logger.error(f"Error en la extracción con Gemini (puede ser reintentado): {e}")
643
  raise e
@@ -677,78 +660,129 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
677
  entities["sintomas"] = list(set(current_symptoms))
678
  return entities
679
 
 
680
  def find_best_matches_hybrid(entities, data):
 
 
 
 
681
  if not entities or not data: return []
682
- user_symptoms = set(sanitize_text(s) for s in entities.get("sintomas", []))
683
- user_foods = set(sanitize_text(f) for f in entities.get("alimentos", []))
 
 
684
 
685
- # --- LISTA DE CONDICIONES RARAS (Centralizada para fácil mantenimiento) ---
686
- # Cualquier condición en esta lista recibirá una penalización en su puntuación.
687
- # El resto se considerará de probabilidad normal.
688
  RARE_CONDITIONS = [
689
- "Porfiria Aguda Intermitente (PAI).",
690
- "Enfermedad de Refsum del Adulto.",
691
- "Ataxia por Gluten.",
692
- "Encefalopatía por Gluten.",
693
- "Enfermedad de Wilson.",
694
- "Aciduria Argininosuccínica.",
695
- "Síndrome de Alagille."
696
- # Añade aquí otras enfermedades raras a medida que las incorpores a tu JSON.
697
  ]
698
 
699
- candidate_terms = set(user_foods)
700
- for food in user_foods:
701
- if food in FOOD_TO_COMPOUND_MAP:
702
- candidate_terms.update(c.lower() for c in FOOD_TO_COMPOUND_MAP[food])
703
-
704
  results = []
705
  for entry in data:
706
- entry_compounds_text = sanitize_text(entry.get("compuesto_alimento", ""))
707
- food_match = any(term in entry_compounds_text for term in candidate_terms)
 
 
708
 
709
- if not food_match:
710
- continue
711
-
712
- score_details = {'food': 20, 'symptoms': 0}
713
 
714
- entry_symptoms_keys = set(sanitize_text(s) for s in entry.get("sintomas_clave", []))
 
 
 
 
 
 
 
 
 
 
715
  symptom_score = 0
716
  matched_symptoms = []
717
  for user_symptom in user_symptoms:
718
- for key in entry_symptoms_keys:
719
- if key in user_symptom or user_symptom in key:
720
- symptom_score += 30
721
- matched_symptoms.append(key)
722
- break
723
-
724
  score_details['symptoms'] = symptom_score
725
 
726
- if score_details['symptoms'] > 0:
727
- base_score = score_details['food'] + score_details['symptoms']
728
-
729
- # --- LÓGICA DE PONDERACIÓN SIMPLIFICADA ---
730
- condition_name = entry.get("condicion_asociada", "")
731
-
732
- if condition_name in RARE_CONDITIONS:
733
- # Penalización fuerte para condiciones raras
734
- final_score = base_score * 0.4
735
- else:
736
- # Puntuación completa para el resto (comunes y poco comunes)
737
- final_score = base_score * 1.0
738
-
739
- score_details['total'] = int(final_score)
740
-
741
  results.append({
742
  'entry': entry,
743
  'score': score_details,
744
- 'matched_symptoms': list(set(matched_symptoms))
745
  })
746
 
747
  if not results: return []
748
  return sorted(results, key=lambda x: x['score']['total'], reverse=True)
749
 
750
-
751
  @retry(wait=wait_random_exponential(min=1, max=10), stop=stop_after_attempt(3))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
 
753
 
754
  def find_best_foodb_matches(user_foods_es, foodb_index_keys, food_name_map, limit=3):
@@ -1042,26 +1076,47 @@ if st.session_state.start_analysis:
1042
  elif alimentos_data is None:
1043
  st.error("La base de datos de alimentos no está disponible.")
1044
  else:
1045
- entities = None
1046
  with st.spinner("🧠 Interpretando tu caso y buscando pistas..."):
 
 
1047
  try:
1048
- entities = extract_entities_with_gemini(query_to_analyze)
1049
  except Exception as e:
1050
  logger.warning(f"La extracción con Gemini falló; se usará el sistema de respaldo: {e}")
1051
 
1052
- # --- Aquí se usa el nuevo Diccionario Maestro ---
1053
- entities = reinforce_entities_with_keywords(entities, query_to_analyze, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
1054
- st.session_state.entities = entities
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
 
1056
- if entities and (entities.get("alimentos") or entities.get("sintomas")):
1057
- info_str = f"Pistas identificadas - Alimentos: {', '.join(entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(entities.get('sintomas',[])) or 'Ninguno'}"
1058
  st.info(info_str)
1059
  with st.spinner("🔬 Cruzando información y calculando relevancia..."):
1060
- results = find_best_matches_hybrid(entities, alimentos_data)
1061
  st.session_state.search_results = results
1062
  else:
1063
  st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser más específico.")
1064
  st.session_state.search_results = []
 
 
1065
  if st.session_state.search_results is not None:
1066
  results = st.session_state.search_results
1067
 
 
1
+ # ==================== El Detective de Alimentos
2
+ # JAIRO CESAR ALEXANDER E. MD DIANA MILENA SOLER MARTINEZ PSI. ESP. U JUAN N CORPAS
 
3
  import streamlit as st
4
  import google.generativeai as genai
5
  import google.api_core.exceptions
 
13
  from tenacity import retry, stop_after_attempt, wait_random_exponential
14
  from io import BytesIO
15
  import docx
 
16
  st.set_page_config(page_title="El Detective de Alimentos", page_icon="🍎", layout="wide")
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger("food_detective_app")
 
74
  "Phenylethylamine": ["dolor de cabeza", "migraña"],
75
  "Cadaverine": ["inflamación", "dolor de cabeza"],
76
  "Putrescine": ["inflamación", "dolor de cabeza"],
 
77
  # --- Compuestos de Gluten y Lácteos ---
78
  "Gluten": ["niebla mental", "dolor abdominal", "diarrea", "inflamación", "dolor articular", "ataxia", "neuropatía periférica"],
79
  "Gliadin": ["niebla mental", "dolor abdominal", "diarrea", "inflamación"], # Componente principal del Gluten
80
  "Casein": ["niebla mental", "inflamación", "acné", "congestión nasal", "estreñimiento"],
81
  "Lactose": ["hinchazón", "gases", "diarrea", "dolor abdominal"],
 
82
  # --- Antinutrientes y Compuestos de Defensa Vegetal ---
83
  "Oxalates": ["dolor articular", "dolor muscular", "cálculos renales"],
84
  "Lectins": ["inflamación", "dolor articular", "hinchazón", "dolor abdominal", "erupción"],
 
88
  "Solanine": ["dolor articular", "inflamación", "dolor muscular"],
89
  "Avenin": ["inflamación", "dolor abdominal"], # En avena, similar al gluten
90
  "Quercetin": ["dolor de cabeza", "migraña"], # Específicamente en el contexto del vino
 
91
  # --- FODMAPs (Oligosacáridos, Disacáridos, Monosacáridos y Polioles Fermentables) ---
92
  "Fructans": ["hinchazón", "gases", "dolor abdominal", "diarrea", "estreñimiento"],
93
  "GOS (Galactooligosaccharides)": ["hinchazón", "gases", "dolor abdominal"],
 
95
  "Polyols": ["diarrea", "hinchazón", "gases"], # Incluye Sorbitol, Manitol, Xilitol
96
  "Sorbitol": ["diarrea", "hinchazón", "gases"],
97
  "Mannitol": ["diarrea", "hinchazón", "gases"],
 
98
  # --- Estimulantes, Alcaloides y Compuestos del Sistema Nervioso ---
99
  "Caffeine": ["ansiedad", "dolor de cabeza", "insomnio", "palpitaciones", "acidez"],
100
  "Theobromine": ["ansiedad", "dolor de cabeza", "insomnio"], # Similar a la cafeína, en chocolate
101
  "Capsaicin": ["dolor", "acidez", "ardor"],
102
  "Alcohol": ["dolor de cabeza", "niebla mental", "inflamación", "fatiga", "náuseas"],
103
  "Acetaldehyde": ["dolor de cabeza", "náuseas", "enrojecimiento facial"], # Metabolito del alcohol
 
104
  # --- Aditivos Alimentarios y Compuestos de Procesamiento ---
105
  "Glutamate (MSG)": ["dolor de cabeza", "náuseas", "debilidad muscular", "palpitaciones"],
106
  "Sulfites": ["dolor de cabeza", "sibilancias", "erupción", "congestión nasal"],
 
112
  "Artificial colorings": ["erupción", "hiperactividad"], # Ej. Tartrazine
113
  "Benzoates": ["erupción", "asma", "hiperactividad"],
114
  "Acrylamide": ["neuropatía periférica", "debilidad muscular"], # Formado en frituras
 
115
  # --- Toxinas Naturales y Compuestos Específicos ---
116
  "Aflatoxins": ["fatiga", "náuseas", "dolor abdominal"], # De mohos en frutos secos/granos
117
  "Cyanogenic glycosides": ["mareo/vértigo", "dolor de cabeza", "náuseas", "vómito"], # En almendras amargas, yuca
118
  "Arsenic": ["fatiga", "náuseas", "neuropatía periférica"], # En arroz
119
  "Mercury": ["fatiga", "niebla mental", "neuropatía periférica"], # En pescados grandes
120
  "Alpha-Gal": ["erupción", "hinchazón", "náuseas"], # Alérgeno de carne roja
 
121
  # --- Minerales y Elementos (en contexto de exceso o sensibilidad) ---
122
  "Nickel": ["erupción", "dermatitis", "dolor abdominal", "hinchazón"],
123
  "Iodine": ["acné", "erupción", "fatiga"], # En personas con sensibilidad o problemas tiroideos
 
610
  return re.sub(r'[.,;()]', '', text).lower().strip()
611
 
612
  @retry(wait=wait_random_exponential(min=1, max=10), stop=stop_after_attempt(3))
613
+
614
  def extract_entities_with_gemini(query):
615
+ # ... (el prompt es el mismo)
 
 
 
 
 
 
 
 
616
  try:
617
  response = model.generate_content(system_prompt)
618
+ # ... (lógica de extracción de JSON es la misma)
619
+ extracted_data = json.loads(json_text)
620
+ # Guardar los síntomas originales para la traducción posterior
621
+ extracted_data['sintomas_originales_ia'] = extracted_data.get('sintomas', [])
 
622
  logger.info("Extracción con Gemini exitosa.")
623
+ return extracted_data
624
  except (Exception, google.api_core.exceptions.GoogleAPICallError) as e:
625
  logger.error(f"Error en la extracción con Gemini (puede ser reintentado): {e}")
626
  raise e
 
660
  entities["sintomas"] = list(set(current_symptoms))
661
  return entities
662
 
663
+
664
  def find_best_matches_hybrid(entities, data):
665
+ """
666
+ Nueva función de búsqueda flexible y ponderada.
667
+ No descarta coincidencias y añade un bonus por confianza en el alimento.
668
+ """
669
  if not entities or not data: return []
670
+
671
+ user_symptoms = set(s.lower() for s in entities.get("sintomas", []))
672
+ user_foods_text = " ".join(entities.get("alimentos", [])).lower()
673
+ user_food_keywords = set(re.findall(r'\b\w+\b', user_foods_text))
674
 
 
 
 
675
  RARE_CONDITIONS = [
676
+ "Porfiria Aguda Intermitente (PAI).", "Enfermedad de Refsum del Adulto.",
677
+ "Ataxia por Gluten.", "Encefalopatía por Gluten.", "Enfermedad de Wilson."
 
 
 
 
 
 
678
  ]
679
 
 
 
 
 
 
680
  results = []
681
  for entry in data:
682
+ # Puntuación de Alimento con Bonus de Confianza
683
+ score_details = {'food': 0, 'symptoms': 0, 'bonus': 0}
684
+ db_food_text = entry.get("compuesto_alimento", "").lower()
685
+ db_food_keywords = set(re.findall(r'\b\w+\b', re.sub(r'\(.*?\)', '', db_food_text)))
686
 
687
+ common_food_keywords = user_food_keywords.intersection(db_food_keywords)
 
 
 
688
 
689
+ if common_food_keywords:
690
+ score_details['food'] = 20 # Puntuación base por encontrar cualquier coincidencia
691
+ # Bonus por alta confianza: si más del 50% de las palabras del usuario coinciden
692
+ if len(common_food_keywords) / len(user_food_keywords) > 0.5:
693
+ score_details['bonus'] = 30
694
+ else:
695
+ # Si no hay ninguna coincidencia de palabras clave de alimento, saltar esta entrada
696
+ continue
697
+
698
+ # Puntuación de Síntomas
699
+ db_symptoms_keys = set(s.lower() for s in entry.get("sintomas_clave", []))
700
  symptom_score = 0
701
  matched_symptoms = []
702
  for user_symptom in user_symptoms:
703
+ if user_symptom in db_symptoms_keys:
704
+ symptom_score += 30
705
+ matched_symptoms.append(user_symptom)
 
 
 
706
  score_details['symptoms'] = symptom_score
707
 
708
+ # Calcular puntuación total ponderada
709
+ base_score = score_details['food'] + score_details['symptoms'] + score_details['bonus']
710
+
711
+ condition_name = entry.get("condicion_asociada", "")
712
+ if condition_name in RARE_CONDITIONS:
713
+ final_score = base_score * 0.4
714
+ else:
715
+ final_score = base_score * 1.0
716
+
717
+ score_details['total'] = int(final_score)
718
+
719
+ # Añadir a resultados si tiene una puntuación mínima
720
+ if score_details['total'] > 10:
 
 
721
  results.append({
722
  'entry': entry,
723
  'score': score_details,
724
+ 'matched_symptoms': matched_symptoms
725
  })
726
 
727
  if not results: return []
728
  return sorted(results, key=lambda x: x['score']['total'], reverse=True)
729
 
 
730
  @retry(wait=wait_random_exponential(min=1, max=10), stop=stop_after_attempt(3))
731
+ @retry(wait=wait_random_exponential(min=1, max=10), stop=stop_after_attempt(3))
732
+ def translate_symptoms_with_gemini(symptoms_list, master_symptom_map):
733
+ """
734
+ Usa la IA para traducir síntomas coloquiales a términos clínicos estandarizados
735
+ de nuestro MASTER_SYMPTOM_MAP.
736
+ """
737
+ if not symptoms_list or not model:
738
+ return []
739
+
740
+ # Crear una lista de los términos clínicos que la IA puede elegir
741
+ clinical_terms = list(master_symptom_map.keys())
742
+
743
+ # Crear una descripción para cada término clínico para darle más contexto a la IA
744
+ contextual_terms = []
745
+ for term in clinical_terms:
746
+ description = ", ".join(master_symptom_map[term].get("frases_es", []))
747
+ contextual_terms.append(f"- {term}: (descrito como: {description})")
748
+
749
+ contextual_terms_str = "\n".join(contextual_terms)
750
+ symptoms_str = ", ".join(symptoms_list)
751
+
752
+ system_prompt = f"""
753
+ Eres un experto en terminología médica. Tu única tarea es mapear una lista de síntomas descritos por un usuario a una lista de términos clínicos estandarizados.
754
+
755
+ LISTA DE TÉRMINOS CLÍNICOS POSIBLES:
756
+ {contextual_terms_str}
757
+
758
+ SÍNTOMAS DEL USUARIO A ANALIZAR:
759
+ "{symptoms_str}"
760
+
761
+ INSTRUCCIONES:
762
+ 1. Lee cada síntoma del usuario.
763
+ 2. Encuentra el término clínico más apropiado de la lista proporcionada.
764
+ 3. Si un síntoma del usuario ya es un término clínico, simplemente inclúyelo.
765
+ 4. Si no encuentras una coincidencia clara para un síntoma, ignóralo.
766
+ 5. Devuelve ÚNICAMENTE una lista JSON con los términos clínicos estandarizados.
767
+
768
+ Ejemplo:
769
+ Si los síntomas del usuario son ["crecimiento de un bulto en el cuello", "cansancio"], la respuesta debe ser:
770
+ ["bocio", "fatiga"]
771
+ """
772
+
773
+ try:
774
+ response = model.generate_content(system_prompt)
775
+ # Extraer la lista JSON de la respuesta
776
+ match = re.search(r'\[.*?\]', response.text.replace("'", '"'))
777
+ if match:
778
+ translated_list = json.loads(match.group(0))
779
+ logger.info(f"Síntomas traducidos por IA: {symptoms_list} -> {translated_list}")
780
+ return translated_list
781
+ except (Exception, google.api_core.exceptions.GoogleAPICallError) as e:
782
+ logger.error(f"Error en la traducción de síntomas con Gemini: {e}")
783
+ raise e # Para que tenacity reintente
784
+
785
+ return []
786
 
787
 
788
  def find_best_foodb_matches(user_foods_es, foodb_index_keys, food_name_map, limit=3):
 
1076
  elif alimentos_data is None:
1077
  st.error("La base de datos de alimentos no está disponible.")
1078
  else:
 
1079
  with st.spinner("🧠 Interpretando tu caso y buscando pistas..."):
1080
+ # Paso A: Extracción inicial
1081
+ initial_entities = None
1082
  try:
1083
+ initial_entities = extract_entities_with_gemini(query_to_analyze)
1084
  except Exception as e:
1085
  logger.warning(f"La extracción con Gemini falló; se usará el sistema de respaldo: {e}")
1086
 
1087
+ # Paso B: Refuerzo y normalización con el sistema de respaldo
1088
+ reinforced_entities = reinforce_entities_with_keywords(initial_entities, query_to_analyze, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
1089
+
1090
+ # --- PASO C: TRADUCCIÓN DE SÍNTOMAS CON IA (NUEVA LÓGICA) ---
1091
+ final_symptoms = set(reinforced_entities.get("sintomas", []))
1092
+ untranslated_symptoms = reinforced_entities.get("sintomas_originales_ia", reinforced_entities.get("sintomas", []))
1093
+
1094
+ if untranslated_symptoms:
1095
+ try:
1096
+ with st.spinner("🧠 Profundizando en la interpretación de los síntomas..."):
1097
+ translated_symptoms = translate_symptoms_with_gemini(untranslated_symptoms, MASTER_SYMPTOM_MAP)
1098
+ final_symptoms.update(translated_symptoms)
1099
+ except Exception as e:
1100
+ logger.error("La traducción de síntomas con IA falló después de varios intentos.")
1101
+
1102
+ # Unir todo en la entidad final
1103
+ final_entities = {
1104
+ "alimentos": reinforced_entities.get("alimentos", []),
1105
+ "sintomas": list(final_symptoms)
1106
+ }
1107
+ st.session_state.entities = final_entities
1108
 
1109
+ if final_entities and (final_entities.get("alimentos") or final_entities.get("sintomas")):
1110
+ info_str = f"Pistas identificadas - Alimentos: {', '.join(final_entities.get('alimentos',[])) or 'Ninguno'}, Síntomas: {', '.join(final_entities.get('sintomas',[])) or 'Ninguno'}"
1111
  st.info(info_str)
1112
  with st.spinner("🔬 Cruzando información y calculando relevancia..."):
1113
+ results = find_best_matches_hybrid(final_entities, alimentos_data)
1114
  st.session_state.search_results = results
1115
  else:
1116
  st.error("No se pudieron identificar alimentos o síntomas claros en tu descripción. Intenta ser más específico.")
1117
  st.session_state.search_results = []
1118
+
1119
+
1120
  if st.session_state.search_results is not None:
1121
  results = st.session_state.search_results
1122