JairoCesar commited on
Commit
056705e
·
verified ·
1 Parent(s): 6749525

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +151 -427
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ==============================================================================
2
- # EL DETECTIVE DE ALIMENTOS - EDICIÓN HÍBRIDA DE ALTA DISPONIBILIDAD Y ALTA PRECISIÓN BIOMÉDICA
3
  # Desarrollado por: Jairo Alexander & Diana Milena Soler Martinez
4
  # Motor: Gemini Flash + FooDB.ca Graph + Fast-Path Local Engine
5
  # ==============================================================================
@@ -16,6 +16,7 @@ import altair as alt
16
  from io import BytesIO
17
  import docx
18
  import difflib
 
19
 
20
  # --- CONFIGURACIÓN DE PÁGINA Y LOGGING ---
21
  st.set_page_config(page_title="El Detective de Alimentos", page_icon="🍎", layout="wide")
@@ -37,7 +38,7 @@ Este análisis es una herramienta informativa de IA y **NO es un diagnóstico m
37
  **Si tus síntomas son severos, busca ayuda médica urgente.**
38
  """
39
 
40
- # --- CONFIGURACIÓN DE CLIENTE GEMINI CACHEADO CON TIMEOUT SEGURO ---
41
  @st.cache_resource
42
  def get_gemini_client():
43
  api_key = os.getenv("GEMINI_API_KEY")
@@ -59,16 +60,15 @@ def get_gemini_client():
59
  return None
60
 
61
  try:
62
- # Timeout optimizado a 5000 ms (5s) para evitar congelamientos en la UI
63
  return genai.Client(
64
  api_key=api_key,
65
- http_options=types.HttpOptions(timeout=5000)
66
  )
67
  except Exception as e:
68
  logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
69
  return None
70
 
71
- # --- CASCADA DE MODELOS ESTABLES CON BAJA LATENCIA ---
72
  def generate_content_with_fallback(gemini_client, prompt):
73
  models_to_try = [
74
  'gemini-2.5-flash',
@@ -89,7 +89,23 @@ def generate_content_with_fallback(gemini_client, prompt):
89
  continue
90
  raise RuntimeError("Ningún modelo de Gemini respondió adecuadamente.")
91
 
92
- # --- CARGA ULTRA-RÁPIDA DE DATOS Y FOODB (EN MEMORIA CACHEADA) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  @st.cache_resource
94
  def load_data():
95
  try:
@@ -118,7 +134,6 @@ def get_foodb_lower_map(_foodb_index):
118
 
119
  foodb_lower_map = get_foodb_lower_map(foodb_index)
120
 
121
- # --- CACHÉ GLOBAL DE APRENDIZAJE EN RAM ---
122
  @st.cache_resource
123
  def get_global_learned_cache():
124
  return {}
@@ -126,7 +141,7 @@ def get_global_learned_cache():
126
  global_learned_cache = get_global_learned_cache()
127
 
128
  # ==============================================================================
129
- # DICCIONARIOS MASTER BIOMÉDICOS Y FOODB.CA (ENRIQUECIDOS)
130
  # ==============================================================================
131
 
132
  FOOD_TO_COMPOUND_MAP = {
@@ -142,14 +157,12 @@ FOOD_TO_COMPOUND_MAP = {
142
  "maíz": ["zeína", "lectinas", "fitatos", "fodmaps"],
143
  "quinoa": ["saponinas", "oxalatos", "fitatos"],
144
  "alforfón": ["oxalatos", "fagopirina"],
145
- "arroz frito": ["lectinas", "gluten", "soja", "histamina", "fodmaps", "glutamato"],
146
 
147
  # LÁCTEOS
148
  "leche": ["caseína", "lactosa", "fodmaps"],
149
  "queso": ["tiramina", "histamina", "caseína", "lactosa"],
150
  "queso fresco": ["caseína", "lactosa"],
151
  "queso maduro": ["tiramina", "histamina", "caseína"],
152
- "queso azul": ["tiramina", "histamina", "caseína", "lactosa"],
153
  "yogur": ["histamina", "caseína", "lactosa"],
154
  "mantequilla": ["caseína", "lactosa"],
155
  "crema de leche": ["caseína", "lactosa"],
@@ -164,17 +177,13 @@ FOOD_TO_COMPOUND_MAP = {
164
  "empanada": ["gluten", "zeína", "solanina", "lectinas"],
165
  "arepa": ["zeína", "lectinas", "fitatos"],
166
  "lechona": ["histamina", "nitritos", "grasas saturadas", "purinas"],
167
- "cholado": ["fructosa", "lactosa", "histamina", "azúcar refinado", "salicilatos", "fenoles"],
168
 
169
- # FRUTAS, TROPICALES Y EXÓTICAS
170
  "noni": ["escopoletina", "fenoles", "salicilatos", "fructosa", "histamina"],
171
- "jugo de noni": ["escopoletina", "fenoles", "salicilatos", "fructosa", "histamina"],
172
  "chontaduro": ["fenoles", "fructosa", "grasas saturadas", "tiramina", "salicilatos"],
173
- "jugo de chontaduro": ["fenoles", "fructosa", "grasas saturadas", "tiramina", "salicilatos"],
174
  "naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
175
  "jugo de naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
176
  "limón": ["salicilatos", "histamina", "fenoles", "ácido cítrico"],
177
- "mandarina": ["fructosa", "histamina", "salicilatos", "fenoles"],
178
  "manzana": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
179
  "pera": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
180
  "plátano": ["tiramina", "histamina", "salicilatos", "fenoles"],
@@ -184,93 +193,44 @@ FOOD_TO_COMPOUND_MAP = {
184
  "mora": ["salicilatos", "oxalatos", "histamina", "taninos"],
185
  "uva": ["fructosa", "salicilatos", "histamina", "taninos"],
186
  "piña": ["salicilatos", "histamina", "fructosa"],
187
- "papaya": ["salicilatos", "histamina"],
188
  "mango": ["fructosa", "salicilatos", "histamina"],
189
- "kiwi": ["salicilatos", "oxalatos", "histamina"],
190
- "durazno": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
191
- "ciruela": ["sorbitol", "fodmaps", "salicilatos", "histamina"],
192
- "higo": ["histamina", "fructosa", "salicilatos"],
193
- "chirimoya": ["fructosa", "fodmaps", "sorbitol", "taninos"],
194
- "guanábana": ["fructosa", "fodmaps", "taninos", "salicilatos"],
195
  "lulo": ["salicilatos", "fructosa", "ácido cítrico"],
196
- "naranjilla": ["salicilatos", "fructosa", "ácido cítrico"],
197
  "maracuyá": ["fructosa", "histamina", "salicilatos"],
198
  "maracuya": ["fructosa", "histamina", "salicilatos"],
199
  "feijoa": ["fructosa", "salicilatos", "taninos"],
200
- "granadilla": ["fructosa", "salicilatos"],
201
- "curuba": ["fructosa", "salicilatos", "taninos"],
202
  "tomate de árbol": ["solanina", "histamina", "salicilatos"],
203
- "tomate de arbol": ["solanina", "histamina", "salicilatos"],
204
  "uchuva": ["solanina", "histamina", "salicilatos", "oxalatos"],
205
- "guayaba": ["fructosa", "salicilatos", "taninos", "oxalatos"],
206
- "pitahaya": ["fructosa", "fodmaps"],
207
- "zapote": ["fructosa", "taninos"],
208
- "mamoncillo": ["fructosa", "taninos", "salicilatos"],
209
- "borojó": ["fenoles", "fructosa", "tiramina"],
210
- "borojo": ["fenoles", "fructosa", "tiramina"],
211
- "corozo": ["fenoles", "taninos", "fructosa"],
212
- "mangostino": ["fenoles", "taninos", "fructosa"],
213
-
214
- # VERDURAS, TUBÉRCULOS Y SOLANÁCEAS
215
  "tomate": ["histamina", "solanina", "salicilatos", "glutamato"],
216
- "salsa de tomate": ["histamina", "solanina", "salicilatos", "glutamato"],
217
- "patata": ["solanina", "oxalatos", "lectinas"],
218
  "papa": ["solanina", "oxalatos", "lectinas"],
 
219
  "berenjena": ["solanina", "histamina", "salicilatos"],
220
  "pimiento": ["capsaicina", "solanina", "salicilatos"],
221
  "espinaca": ["oxalatos", "nitratos", "histamina"],
222
- "espinacas": ["oxalatos", "nitratos", "histamina"],
223
- "acelga": ["oxalatos", "nitratos"],
224
- "remolacha": ["oxalatos", "nitratos", "fructosa"],
225
  "cebolla": ["fodmaps", "compuestos azufrados", "salicilatos"],
226
  "ajo": ["fodmaps", "compuestos azufrados"],
227
  "brócoli": ["fodmaps", "compuestos azufrados", "salicilatos", "goitrógenos"],
228
- "coliflor": ["fodmaps", "sorbitol", "compuestos azufrados", "goitrógenos"],
229
- "repollo": ["fodmaps", "compuestos azufrados", "goitrógenos"],
230
- "pepino": ["salicilatos"],
231
- "calabaza": ["salicilatos", "fodmaps"],
232
- "zanahoria": ["salicilatos"],
233
  "yuca": ["fodmaps", "fitatos"],
234
  "ñame": ["oxalatos", "fitatos", "fodmaps"],
235
- "name": ["oxalatos", "fitatos", "fodmaps"],
236
- "arracacha": ["fodmaps", "fitatos"],
237
- "malanga": ["oxalatos", "fitatos"],
238
- "habichuela": ["fodmaps", "lectinas"],
239
- "rábano": ["salicilatos", "goitrógenos"],
240
- "apio": ["fodmaps", "salicilatos"],
241
- "puerro": ["fodmaps", "compuestos azufrados"],
242
- "espárragos": ["fodmaps"],
243
-
244
- # LEGUMBRES (PERFIL BIOQUÍMICO COMPLETO)
245
  "frijol": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
246
  "frijoles": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
247
  "frijol paisa": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
248
  "lentejas": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
249
  "garbanzos": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos"],
250
  "arvejas": ["fodmaps", "lectinas", "fitatos", "inhibidores_proteasas"],
251
- "arveja": ["fodmaps", "lectinas", "fitatos", "inhibidores_proteasas"],
252
  "soja": ["soja", "isoflavonas", "lectinas", "fitatos", "fodmaps", "saponinas", "goitrógenos", "inhibidores_proteasas"],
253
  "tofu": ["soja", "isoflavonas", "lectinas", "fitatos"],
254
- "tempeh": ["soja", "isoflavonas"],
255
  "haba": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
256
  "habas": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
257
 
258
- # FRUTOS SECOS Y SEMILLAS
259
- "almendras": ["oxalatos", "salicilatos", "fitatos", "histamina"],
260
- "leche de almendras": ["oxalatos", "salicilatos"],
261
- "nueces": ["histamina", "oxalatos", "salicilatos", "taninos"],
262
- "nuez": ["histamina", "oxalatos", "salicilatos", "taninos"],
263
- "maní": ["histamina", "lectinas", "fitatos", "salicilatos", "inhibidores_proteasas"],
264
- "cacahuate": ["histamina", "lectinas", "fitatos", "salicilatos", "inhibidores_proteasas"],
265
- "marañón": ["oxalatos", "fitatos", "fodmaps"],
266
- "frutos secos": ["histamina", "oxalatos", "salicilatos", "fitatos"],
267
-
268
  # CARNES, EMBUTIDOS Y BEBIDAS
269
  "carne de cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
270
  "cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
271
  "chicharrón": ["histamina", "grasas saturadas"],
272
  "chicharron": ["histamina", "grasas saturadas"],
273
- "tocino": ["histamina", "nitritos", "grasas saturadas"],
274
  "carne de res": ["histamina", "alfa-gal", "purinas"],
275
  "res": ["histamina", "alfa-gal", "purinas"],
276
  "café": ["cafeína", "salicilatos", "oxalatos"],
@@ -279,33 +239,22 @@ FOOD_TO_COMPOUND_MAP = {
279
  "chocolate": ["teobromina", "feniletilamina", "tiramina", "histamina", "oxalatos", "cafeína"],
280
  "vino": ["histamina", "tiramina", "sulfitos", "taninos", "alcohol"],
281
  "cerveza": ["gluten", "histamina", "sulfitos", "alcohol", "purinas"],
282
- "jamón": ["histamina", "tiramina", "nitritos"],
283
- "salchicha": ["histamina", "tiramina", "nitritos", "glutamato"],
284
  "embutidos": ["histamina", "tiramina", "nitritos", "glutamato"],
285
  "pescado": ["histamina", "purinas", "albúmina"],
286
- "atún": ["histamina", "purinas"],
287
- "huevo": ["albúmina"],
288
-
289
- # CONDIMENTOS
290
- "pimienta": ["capsaicina", "salicilatos"],
291
- "chile": ["capsaicina", "salicilatos", "solanina"],
292
- "ají": ["capsaicina", "salicilatos", "solanina"],
293
- "salsa de soja": ["glutamato", "histamina", "tiramina", "gluten", "soja"],
294
- "vinagre": ["histamina", "sulfitos"]
295
  }
296
 
297
  FOOD_NAME_TO_FOODB_KEY = {
298
- "alforfón": ["buckwheat"], "arroz": ["rice"], "avena": ["oat", "oats", "oatmeal"],
299
- "cebada": ["barley"], "queso": ["cheese"], "leche": ["milk"], "yogur": ["yogurt"], "mantequilla": ["butter"],
300
  "vino": ["wine"], "cerveza": ["beer"], "chocolate": ["chocolate", "cocoa"],
301
  "tomate": ["tomato"], "aguacate": ["avocado"], "plátano": ["banana", "plantain"], "banano": ["banana"],
302
  "naranja": ["orange"], "pan": ["bread", "wheat"], "trigo": ["wheat"], "café": ["coffee"], "cafe": ["coffee"],
303
- "manzana": ["apple"], "pera": ["pear"], "fresa": ["strawberry"], "uva": ["grape"], "piña": ["pineapple"],
304
  "papa": ["potato"], "patata": ["potato"], "frijol": ["bean", "beans", "kidney bean", "black bean"],
305
  "frijoles": ["bean", "beans"], "frijol paisa": ["bean", "pork", "rice"],
306
- "lentejas": ["lentil"], "garbanzos": ["chickpea", "garbanzo"], "arveja": ["pea", "peas"],
307
  "haba": ["broad bean", "fava bean"], "habas": ["broad bean", "fava bean"],
308
- "almendras": ["almond"], "nueces": ["walnut"],
309
  "bandeja paisa": ["bean", "pork", "rice", "banana", "egg", "avocado"],
310
  "ajiaco": ["potato", "chicken", "corn", "milk", "cream"],
311
  "sancocho": ["cassava", "plantain", "potato", "pork", "beef", "chicken"],
@@ -314,52 +263,49 @@ FOOD_NAME_TO_FOODB_KEY = {
314
  }
315
 
316
  STRICT_COMPOUND_TRIGGERS = {
317
- "cafeína": ["cafeína", "caffeine", "café", "cafe", "coffee", "espresso"],
318
  "teobromina": ["teobromina", "theobromine", "cacao", "chocolate"],
319
- "solanina": ["solanina", "solanine", "solanáceas", "tomate", "berenjena", "patata", "papa"],
320
- "capsaicina": ["capsaicina", "capsaicin", "chile", "ají", "picante", "pimiento"],
321
- "gluten": ["gluten", "trigo", "wheat", "cebada", "barley", "centeno"],
322
- "caseína": ["caseína", "casein", "casomorfina", "queso", "lácteos"],
323
- "soja": ["soja", "soya", "soy", "soybean", "tofu", "tempeh"],
324
- "albúmina": ["albúmina", "albumin", "huevo", "egg"],
325
  "lactosa": ["lactosa", "lactose"],
326
- "fructosa": ["fructosa", "fructose", "miel"],
327
- "fodmaps": ["fodmap", "fodmaps", "fructanos", "galactanos", "polyols", "sorbitol", "rafinosa", "estaquiosa"],
328
  "histamina": ["histamina", "histamine", "dao"],
329
  "tiramina": ["tiramina", "tyramine"],
330
- "oxalatos": ["oxalato", "oxalatos", "oxalate", "ácido oxálico"],
331
- "saponinas": ["saponina", "saponinas", "saponin"],
332
- "lectinas": ["lectina", "lectinas", "lectin", "frijoles", "legumbres"],
333
- "fitatos": ["fitato", "fitatos", "phytate", "ácido fítico"],
334
- "salicilatos": ["salicilato", "salicilatos", "salicylate"],
335
- "sulfitos": ["sulfito", "sulfitos", "sulfite"],
336
  "glutamato": ["glutamato", "glutamate", "msg"],
337
- "nitritos": ["nitrito", "nitritos", "nitrato", "embutidos"],
338
  "alcohol": ["alcohol", "etanol", "cerveza", "vino"],
339
- "purinas": ["purina", "purinas", "purine", "ácido úrico", "gota"],
340
- "inhibidores_proteasas": ["inhibidor de tripsina", "inhibidores de proteasas", "protease inhibitor", "tripsina"],
341
- "vicina_convicina": ["vicina", "convicina", "favismo", "g6pd", "haba", "habas"],
342
- "isoflavonas": ["isoflavona", "isoflavonas", "fitoestrógenos", "fitoestrogenos", "soja"],
343
- "goitrógenos": ["goitrógeno", "goitrogenos", "goitrogen", "tiroides", "crucíferas"],
344
- "taninos": ["tanino", "taninos", "tannin", "tannins"]
345
  }
346
 
347
  MEDICAL_CONDITION_SAFEGUARDS = {
348
  "farmacos_interacciones": {
349
- "condition_triggers": ["fármaco", "farmaco", "medicamento", "imao", "anticoagulante", "warfarina"],
350
- "required_user_keywords": ["fármaco", "farmaco", "medicamento", "imao", "pastilla", "remedio", "antidepresivo", "receta"]
351
  },
352
  "respiratorio_asma": {
353
- "condition_triggers": ["asma", "sibilancia", "broncoespasmo", "neumonitis"],
354
- "required_user_keywords": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire"]
355
  },
356
  "dermatologico_alergia": {
357
- "condition_triggers": ["urticaria", "angioedema", "anafilaxia", "eccema", "dermatitis"],
358
- "required_user_keywords": ["piel", "pica", "picazón", "manchas", "rojo", "alergia", "ronchas", "granitos"]
359
  },
360
  "articular_gota": {
361
  "condition_triggers": ["gota", "artritis", "hiperuricemia"],
362
- "required_user_keywords": ["articulación", "coyuntura", "dedo gordo", "rodilla", "gota", "artritis"]
363
  },
364
  "renal_litiasis": {
365
  "condition_triggers": ["litiasis", "cálculo renal", "piedras en el riñón"],
@@ -370,8 +316,8 @@ MEDICAL_CONDITION_SAFEGUARDS = {
370
  "required_user_keywords": ["embarazada", "embarazo", "bebé", "lactando"]
371
  },
372
  "hepato_vesicular": {
373
- "condition_triggers": ["vesícula", "vesicula", "colelitiasis", "cirrosis", "hígado"],
374
- "required_user_keywords": ["vesícula", "vesicula", "hígado", "higado", "piel amarilla", "costado derecho"]
375
  }
376
  }
377
 
@@ -381,47 +327,39 @@ INTEGRATED_NEURO_FOOD_MAP = {
381
  "fuentes_comunes": ["pan", "trigo", "pasta", "cebada", "centeno"]
382
  },
383
  "caseína": {
384
- "efecto_neuropsicologico": "Sus péptidos (casomorfinas) pueden actuar sobre receptores opioides en el SNC, afectando la cognición, el estado de ánimo y el comportamiento.",
385
  "fuentes_comunes": ["leche", "queso", "yogur"]
386
  },
387
  "fodmaps": {
388
- "efecto_neuropsicologico": "Mediante el eje intestino-cerebro, la fermentación intestinal excesiva por oligosacáridos (GOS/rafinosa) genera gases y tensión visceral, enviando señales de estrés por el nervio vago que aumentan la ansiedad e hipersensibilidad al dolor.",
389
  "fuentes_comunes": ["frijoles", "lentejas", "cebolla", "ajo", "trigo"]
390
  },
391
  "inhibidores_proteasas": {
392
- "efecto_neuropsicologico": "Impiden la descomposición enzimática adecuada de las proteínas en el lumen intestinal. Esto produce mala digestión, hipoperfusión gastrointestinal y letargo posprandial severo.",
393
- "fuentes_comunes": ["frijoles", "legumbres crudas o mal cocidas", "soja"]
394
  },
395
  "vicina_convicina": {
396
- "efecto_neuropsicologico": "En personas predispuestas con deficiencia de la enzima G6PD (favismo), desencadena estrés oxidativo eritrocitario que puede producir cefalea vascular aguda, astenia y mareos.",
397
  "fuentes_comunes": ["habas"]
398
  },
399
- "isoflavonas": {
400
- "efecto_neuropsicologico": "Fitoestrógenos que pueden interactuar con los receptores de estrógeno en el cerebro, alterando potencialmente el estado de ánimo y la regulación hormonal.",
401
- "fuentes_comunes": ["soja", "tofu", "tempeh"]
402
- },
403
- "goitrógenos": {
404
- "efecto_neuropsicologico": "Interfieren con la absorción del yodo en la glándula tiroides, pudiendo contribuir a la fatiga crónica, lentitud cognitiva e intolerancia al frío.",
405
- "fuentes_comunes": ["soja", "brócoli", "coliflor", "repollo"]
406
- },
407
  "tiramina": {
408
- "efecto_neuropsicologico": "Amina vasoactiva de acción simpaticomimética. Provoca vasoconstricción inicial seguida de vasodilatación cerebral, siendo un potente desencadenante de migrañas.",
409
  "fuentes_comunes": ["quesos madurados", "vino tinto", "embutidos"]
410
  },
411
  "histamina": {
412
- "efecto_neuropsicologico": "Neurotransmisor estimulante central. Su exceso por bajo DAO o consumo elevado provoca migrañas, insomnio, fragmentación del sueño y ansiedad.",
413
  "fuentes_comunes": ["queso", "vino tinto", "tomate", "pescado azul", "conservas"]
414
  },
415
  "salicilatos": {
416
- "efecto_neuropsicologico": "En concentraciones elevadas alteran la enzima PST. En personas sensibles provocan estimulación del SNC, hiperactividad e insomnio.",
417
  "fuentes_comunes": ["cítricos", "manzanas", "tomate", "especias", "té"]
418
  },
419
  "fenoles": {
420
- "efecto_neuropsicologico": "La sobrecarga de fenoles satura las vías de detoxificación hepática de sulfatación, acumulando catecolaminas que producen agitación nocturna e insomnio.",
421
  "fuentes_comunes": ["jugo de naranja", "chontaduro", "noni", "té"]
422
  },
423
  "cafeína": {
424
- "efecto_neuropsicologico": "Bloquea los receptores de adenosina en el cerebro. En personas sensibles provoca hiperactivación del SNC, ansiedad e insomnio.",
425
  "fuentes_comunes": ["café", "té negro", "energizantes", "chocolate"]
426
  },
427
  "lactosa": {
@@ -429,35 +367,25 @@ INTEGRATED_NEURO_FOOD_MAP = {
429
  "fuentes_comunes": ["leche", "queso fresco", "crema de leche"]
430
  },
431
  "fructosa": {
432
- "efecto_neuropsicologico": "La sobrecarga de fructosa disminuye la biodisponibilidad de triptófano en el intestino, interfiriendo en la síntesis de serotonina y melatonina.",
433
  "fuentes_comunes": ["jugos concentrados", "miel", "frutas muy dulces"]
434
  },
435
- "oxalatos": {
436
- "efecto_neuropsicologico": "Pueden interferir en la función mitocondrial de las neuronas, asociados a fatiga crónica y neblina cerebral.",
437
- "fuentes_comunes": ["espinacas", "quinoa", "frutos secos", "cacao"]
438
- },
439
  "solanina": {
440
- "efecto_neuropsicologico": "Glicoalcaloide con leve efecto inhibidor de la acetilcolinesterasa; en sensibles puede provocar letargo y rigidez corporal.",
441
  "fuentes_comunes": ["tomate", "patata", "berenjena"]
442
  }
443
  }
444
 
445
  CONSEJOS_POR_COMPUESTO = {
446
- "fodmaps": "Para reducir los GOS (rafinosa y estaquiosa) de los frijoles y legumbres, realiza un remojo de al menos 12 a 24 horas descartando el agua, y cocina a alta presión adicionando especias carminativas como comino o epazote.",
447
- "inhibidores_proteasas": "La cocción prolongada y el calor de la olla de presión inactivan casi por completo los inhibidores de tripsina en legumbres, mejorando drásticamente la digestibilidad de sus proteínas.",
448
- "vicina_convicina": "Si padeces favismo (deficiencia de la enzima G6PD), debes evitar de forma estricta el consumo de habas en todas sus presentaciones.",
449
- "isoflavonas": "Si buscas consumir soja reduciendo el impacto de fitoestrógenos libres, opta por formas fermentadas tradicionales como el tempeh o la sopa miso.",
450
- "gluten": "La respuesta al gluten debe diferenciar Enfermedad Celíaca, SGNC y alergia al trigo. Se recomienda realizar pruebas serológicas antes de retirarlo de la dieta.",
451
- "caseína": "La proteína A1 de la leche libera beta-casomorfina-7. Se sugiere evaluar variantes A2 o lácteos fermentados bajo supervisión.",
452
- "lactosa": "La intolerancia se debe a baja lactasa en el enterocito. Se puede manejar con lactasa exógena o productos deslactosados.",
453
  "histamina": "La acumulación de histamina se asocia a bajo DAO. Una dieta baja en histamina y rica en vitamina B6 y C ayuda a restaurar el equilibrio.",
454
- "tiramina": "Controlar el consumo de alimentos añejados o fermentados previene picos de presión arterial y cefaleas en personas sensibles a la MAO.",
455
- "salicilatos": "Moderar el consumo concentrado de jugos cítricos y especias ayuda a estabilizar el sistema nervioso y mejorar la calidad del sueño.",
456
- "fenoles": "Reducir la ingesta concentrada de jugos de frutas exóticas alivia la sobrecarga de sulfatación hepática.",
457
- "cafeína": "Metabolizada por CYP1A2. Moderar su consumo antes de mitad de tarde protege la arquitectura del sueño.",
458
- "oxalatos": "Consumir alimentos ricos en oxalatos junto con calcio dietético favorece su eliminación intestinal evitando la formación de cristales renales.",
459
- "saponinas": "El remojo prolongado y el lavado intensivo bajo agua corriente reducen significativamente la espuma de saponinas en quinoa y legumbres.",
460
- "solanina": "Consumir hortalizas bien maduras y retirar la piel verde o los brotes de las papas minimiza la irritación intestinal."
461
  }
462
 
463
  CONSEJO_GENERICO_DEFAULT = "Cada organismo posee una individualidad bioquímica única según su perfil enzimático y digestivo. Monitorear tus respuestas personales te permitirá adaptar la dieta a tu umbral de tolerancia."
@@ -465,76 +393,52 @@ CONSEJO_GENERICO_DEFAULT = "Cada organismo posee una individualidad bioquímica
465
  FOODB_EFFECTS_TRANSLATION = {
466
  "flatugenic": "genera gases/flatulencia", "antioxidant": "antioxidante",
467
  "hypercholesterolemic": "puede elevar colesterol", "triglycerigenic": "puede elevar triglicéridos",
468
- "uricogenic": "puede elevar ácido úrico", "aggregant": "pro-agregante plaquetario",
469
- "preservative": "conservante natural", "sweetener": "edulcorante natural",
470
- "demulcent": "protector de mucosa digestiva", "vasodilator": "vasodilatador (posible migraña)",
471
- "vasoconstrictor": "vasoconstrictor", "histaminic": "actividad histaminérgica",
472
  "laxative": "efecto laxante", "diuretic": "efecto diurético",
473
- "stimulant": "estimulante del SNC", "sedative": "efecto sedante",
474
- "antiinflammatory": "antiinflamatorio", "ulcerogenic": "irritante gástrico"
475
  }
476
 
477
  COMPOUND_SYNONYM_MAP = {
478
- "gluten": ["gluten", "gliadin"],
479
- "caseína": ["casein", "casomorphin"],
480
- "lactosa": ["lactose"],
481
- "fructosa": ["fructose"],
482
- "proteínas": ["protein", "amino acid"]
483
  }
484
 
485
  MASTER_SYMPTOM_MAP = {
486
  "dolor abdominal": {
487
- "frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "calambres abdominales", "gastritis", "acidez", "reflujo", "pesadez"],
488
- "keywords_en": ["abdominal", "pain", "stomach", "cramp", "colic", "gastrointestinal"]
489
  },
490
  "hinchazón": {
491
- "frases_es": ["hinchazón abdominal", "vientre hinchado", "distensión abdominal", "hinchazón", "distensión", "estomago se infla", "inflamado", "inflado", "infla", "hincha", "gases", "flatulencia"],
492
- "keywords_en": ["bloating", "distension", "flatulence", "swelling"]
493
  },
494
  "diarrea": {
495
- "frases_es": ["diarrea", "diarreas", "soltura", "estómago suelto", "deposiciones líquidas", "deposiciones liquidas", "diarrea acuosa"],
496
  "keywords_en": ["diarrhea", "loose stools"]
497
  },
498
  "náuseas": {
499
- "frases_es": ["náuseas", "nauseas", "vómito", "vomito", "ganas de vomitar", "mareo digestivo"],
500
  "keywords_en": ["nausea", "vomiting"]
501
  },
502
  "dolor de cabeza": {
503
- "frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza", "duele la cabeza", "dolor de sien", "dolor en la cabeza", "pesadez de cabeza"],
504
  "keywords_en": ["headache", "migraine"]
505
  },
506
  "ansiedad": {
507
- "frases_es": ["ansiedad", "nerviosismo", "agitación", "angustia", "desrealización", "irritabilidad", "cambios de ánimo"],
508
- "keywords_en": ["anxiety", "nervousness"]
509
  },
510
  "insomnio": {
511
- "frases_es": ["insomnio", "no puedo dormir", "dificultad para dormir", "desvelado", "no duermo", "desvelo", "sueño fragmentado"],
512
- "keywords_en": ["insomnia", "sleep trouble"]
513
  },
514
  "alergia cutánea": {
515
- "frases_es": ["piel", "rasca", "picazón", "picazon", "manchas", "urticaria", "erupción", "erupcion", "alergia", "ronchas", "granitos", "eccema", "salpullido"],
516
- "keywords_en": ["rash", "itching", "urticaria", "eczema"]
517
- },
518
- "reactividad respiratoria": {
519
- "frases_es": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "falta de aire", "congestión nasal", "estornudos", "rinitis"],
520
- "keywords_en": ["asthma", "cough", "wheezing", "rhinitis"]
521
- },
522
- "respuesta cardiovascular": {
523
- "frases_es": ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "presion", "rubor", "sofoco", "picos de presión"],
524
- "keywords_en": ["palpitations", "tachycardia", "flushing"]
525
- },
526
- "dolor articular": {
527
- "frases_es": ["articulación", "articulacion", "rodilla", "coyuntura", "rigidez", "calambre", "gota", "espalda", "músculo", "musculo", "dolor articular"],
528
- "keywords_en": ["joint pain", "gout", "stiffness"]
529
- },
530
- "molestia renal": {
531
- "frases_es": ["riñón", "riñon", "orina", "orinar", "cálculo", "calculo", "piedra", "ardor al orinar", "cólico renal"],
532
- "keywords_en": ["kidney", "urine", "calculus"]
533
  }
534
  }
535
 
536
  # ==============================================================================
537
- # FUNCIONES DE SEGURO LÓGICO, EXTRACCIÓN Y BÚSQUEDA FLEXIBLE
538
  # ==============================================================================
539
 
540
  def sanitize_text(text):
@@ -549,12 +453,7 @@ STOPWORDS_ES = {
549
  "en", "con", "por", "para", "que", "se", "su", "sus", "al", "del", "lo",
550
  "como", "es", "son", "muy", "mas", "más", "no", "si", "sí", "tu", "mi",
551
  "me", "te", "le", "les", "nos", "cuando", "donde", "porque", "pero",
552
- "también", "tambien", "esta", "este", "estos", "estas", "ese", "esa",
553
- "eso", "asi", "así", "ya", "ha", "he", "han", "hay", "fue", "ser",
554
- "tener", "tengo", "tiene", "tienen", "puede", "pueden", "puedo",
555
- "sobre", "entre", "desde", "hasta", "sin", "e", "ni", "todo", "toda",
556
- "todos", "todas", "cada", "otro", "otra", "otros", "otras", "tomo",
557
- "mucho", "poco"
558
  }
559
 
560
  def extract_keywords(text):
@@ -567,24 +466,19 @@ def get_compounds_for_food(food_name, food_map):
567
 
568
  food_clean = food_name.lower().strip()
569
 
570
- # 1. Coincidencia exacta
571
  if food_clean in food_map:
572
  return food_map[food_clean]
573
 
574
- # 2. Normalización de Plurales
575
  for suffix in ["es", "s"]:
576
  if food_clean.endswith(suffix):
577
  norm = food_clean[:-len(suffix)]
578
  if norm in food_map:
579
  return food_map[norm]
580
 
581
- # 3. Corrección difusa de erratas de tipeo
582
  fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
583
  if fuzzy_matches:
584
- logger.info(f"💡 Errata corregida automáticamente: '{food_clean}' -> '{fuzzy_matches[0]}'")
585
  return food_map[fuzzy_matches[0]]
586
 
587
- # 4. Búsqueda por subcadena exacta de palabra
588
  matched_compounds = set()
589
  for key, compounds in food_map.items():
590
  if re.search(r'\b' + re.escape(key) + r'\b', food_clean) or food_clean in key:
@@ -593,33 +487,15 @@ def get_compounds_for_food(food_name, food_map):
593
  if matched_compounds:
594
  return list(matched_compounds)
595
 
596
- # 5. Inferencia por palabras individuales
597
  inferred = set()
598
- words = food_clean.split()
599
- for word in words:
600
- if len(word) > 3:
601
- sub_fuzzy = difflib.get_close_matches(word, list(food_map.keys()), n=1, cutoff=0.7)
602
- if sub_fuzzy:
603
- inferred.update(food_map[sub_fuzzy[0]])
604
-
605
- if inferred:
606
- return list(inferred)
607
-
608
- # 6. INFERENCIA UNIVERSAL POR FAMILIA BIOLÓGICA
609
- if any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "cordero", "chicharrón", "tocino"]):
610
- inferred.update(["histamina", "purinas", "proteínas"])
611
- elif any(w in food_clean for w in ["pescado", "atún", "marisco", "camarón", "sardina"]):
612
- inferred.update(["histamina", "purinas", "albúmina"])
613
- elif any(w in food_clean for w in ["jugo", "zumo", "batido", "fruta", "licuado"]):
614
  inferred.update(["fructosa", "salicilatos", "histamina", "fenoles"])
615
- elif any(w in food_clean for w in ["queso", "leche", "crema", "yogur", "mantequilla"]):
616
  inferred.update(["caseína", "lactosa", "histamina", "tiramina"])
617
- elif any(w in food_clean for w in ["pan", "galleta", "pasta", "harina"]):
618
- inferred.update(["gluten", "fodmaps"])
619
- elif any(w in food_clean for w in ["frijol", "lenteja", "garbanzo", "legumbre", "soja", "haba", "arveja"]):
620
- inferred.update(["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas"])
621
- elif any(w in food_clean for w in ["salsa", "sazonador", "aderezo", "caldo"]):
622
- inferred.update(["glutamato", "histamina", "sulfitos"])
623
 
624
  return list(inferred)
625
 
@@ -630,15 +506,12 @@ def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
630
  user_query_clean = user_query.lower() if user_query else ""
631
  texto_condicion = (entry.get("condicion_asociada", "") + " " + entry.get("compuesto_alimento", "")).lower()
632
 
633
- # 1. EVALUAR SEGUROS POR CATEGORÍA DE CONDICIONES MÉDICAS
634
  for category, rules in MEDICAL_CONDITION_SAFEGUARDS.items():
635
  if any(trig in texto_condicion for trig in rules["condition_triggers"]):
636
  tiene_contexto = any(req_kw in user_query_clean for req_kw in rules["required_user_keywords"])
637
  if not tiene_contexto:
638
- logger.info(f"🚩 SEGURO LÓGICO ACTIVADO [{category.upper()}]: Se descartó '{entry.get('condicion_asociada')}' por falta de contexto clínico.")
639
  return False
640
 
641
- # 2. EVALUAR SEGUROS BIOQUÍMICOS POR COMPUESTOS ESTRICTOS
642
  user_compounds = set()
643
  for food in user_foods:
644
  comps = get_compounds_for_food(food, food_compound_map)
@@ -652,7 +525,6 @@ def validar_seguro_logico(entry, user_foods, user_query, food_compound_map):
652
  for syn in synonyms
653
  )
654
  if not tiene_compuesto:
655
- logger.info(f"🚩 SEGURO LÓGICO ACTIVADO [BIOQUÍMICA]: Se descartó '{entry.get('condicion_asociada')}' porque '{user_foods}' NO contiene {compound_key}.")
656
  return False
657
 
658
  return True
@@ -663,16 +535,12 @@ def extract_entities_with_gemini(query):
663
  return {"alimentos": [], "sintomas": []}
664
 
665
  system_prompt = f"""
666
- Rol: Extractor Médico JSON experto en nutrición y síntomas.
667
- Tarea: Analiza el texto del usuario y extrae un JSON estricto con dos claves:
668
- - "alimentos": lista de alimentos o bebidas mencionados en español (ej: "frijoles", "bandeja paisa", "ajiaco", "queso", "leche", "vino tinto").
669
- - "sintomas": lista de síntomas mencionados en español (ej: "insomnio", "dolor de cabeza", "hinchazón", "diarrea").
670
-
671
  Input: "{query}"
672
- Output (JSON estricto):
673
  """
674
  try:
675
- raw_text, _ = generate_content_with_fallback(gemini_client, system_prompt)
676
  text = raw_text
677
  if "```json" in text:
678
  text = text.split("```json")[1].split("```")[0]
@@ -680,7 +548,7 @@ def extract_entities_with_gemini(query):
680
  text = text.split("```")[1].split("```")[0]
681
  return json.loads(text)
682
  except Exception as e:
683
- logger.warning(f"Extracción por IA no disponible ({e}). Se usará extracción directa por palabras clave.")
684
  return {"alimentos": [], "sintomas": []}
685
 
686
  def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_map):
@@ -691,17 +559,6 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
691
  for food_keyword in food_map.keys():
692
  if re.search(r'\b' + re.escape(food_keyword) + r'\b', query_sanitized):
693
  current_foods.add(food_keyword)
694
-
695
- if not current_foods:
696
- match_jugo = re.search(r'\b(jugo|zumo|batido|licuado)\s+de\s+([a-zA-ZáéíóúñÁÉÍÓÚÑ]+)\b', query_sanitized)
697
- if match_jugo:
698
- current_foods.add(match_jugo.group(0))
699
- else:
700
- match_tomo = re.search(r'\b(como|tomo|consumo|ingiero|da)\s+([a-zA-ZáéíóúñÁÉÍÓÚÑ\s]{3,20})\b', query_sanitized)
701
- if match_tomo:
702
- food_extracted = match_tomo.group(2).strip()
703
- if food_extracted not in STOPWORDS_ES:
704
- current_foods.add(food_extracted)
705
 
706
  entities["alimentos"] = list(current_foods)
707
 
@@ -711,16 +568,6 @@ def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_m
711
  if phrase in query_sanitized:
712
  current_symptoms.add(main_symptom)
713
  break
714
-
715
- if not current_symptoms:
716
- if any(w in query_sanitized for w in ["diarrea", "diarreas", "soltura", "líquida", "suelto"]):
717
- current_symptoms.add("diarrea")
718
- elif any(w in query_sanitized for w in ["hinchazón", "hinchazon", "gases", "inflamado"]):
719
- current_symptoms.add("hinchazón")
720
- elif any(w in query_sanitized for w in ["cabeza", "migraña", "cefalea"]):
721
- current_symptoms.add("dolor de cabeza")
722
- elif any(w in query_sanitized for w in ["dormir", "insomnio", "desvelo"]):
723
- current_symptoms.add("insomnio")
724
 
725
  entities["sintomas"] = list(current_symptoms)
726
  return entities
@@ -789,68 +636,21 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
789
  if not user_foods or not user_symptoms:
790
  return None
791
 
792
- symptom_text = " ".join(user_symptoms).lower()
793
-
794
- es_neurologico = any(k in symptom_text for k in ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "mareo", "vértigo", "cabeza"])
795
- es_psicologico = any(k in symptom_text for k in ["ansiedad", "angustia", "nerviosismo", "depresión", "irritabilidad"])
796
- es_insomnio = any(k in symptom_text for k in ["insomnio", "no puedo dormir", "sueño", "desvelado", "desvelo", "dormir"])
797
- es_gastrointestinal = any(k in symptom_text for k in ["diarrea", "diarreas", "hinchazón", "hinchazon", "gases", "dolor abdominal", "retortijón", "cólico", "inflamado", "estómago", "náuseas"])
798
- es_dermatologico = any(k in symptom_text for k in ["piel", "rasca", "picazón", "manchas", "urticaria", "erupción", "alergia", "ronchas"])
799
- es_respiratorio = any(k in symptom_text for k in ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo", "rinitis"])
800
- es_cardiovascular = any(k in symptom_text for k in ["palpitaciones", "taquicardia", "ritmo cardíaco", "presión", "sofoco"])
801
-
802
  candidates = []
803
  for food in user_foods:
804
  comps = get_compounds_for_food(food, food_compound_map)
805
  for compound in comps:
806
  info = neuro_map.get(compound, {})
807
- effect = info.get('efecto_neuropsicologico', f'Sustancia bioactiva ({compound}) con impacto inmunológico o metabólico en {food}')
808
  candidates.append({"alimento": food, "compuesto": compound, "efecto": effect})
809
 
810
- if not candidates:
811
- alimentos_str = ", ".join(user_foods).capitalize()
812
- sintomas_str = ", ".join(user_symptoms)
813
-
814
- if es_neurologico:
815
- condicion_fallback = f"Cefalea o Migraña Alimentaria relacionada con {alimentos_str}"
816
- elif es_psicologico:
817
- condicion_fallback = f"Alteración Neuropsicológica o Estado de Ánimo relacionado con {alimentos_str}"
818
- elif es_insomnio:
819
- condicion_fallback = f"Alteración del Sueño o Insomnio relacionado con {alimentos_str}"
820
- elif es_gastrointestinal:
821
- condicion_fallback = f"Distensión o Malestar Gastrointestinal relacionado con {alimentos_str}"
822
- elif es_dermatologico:
823
- condicion_fallback = f"Reacción Dermatológica o Urticaria relacionada con {alimentos_str}"
824
- else:
825
- condicion_fallback = f"Sensibilidad o Intolerancia Alimentaria a {alimentos_str}"
826
-
827
- return {
828
- "condicion_asociada": condicion_fallback,
829
- "mecanismo_fisiologico": f"Respuesta inmunológica, enzimática o metabólica inducida por los componentes bioactivos de {alimentos_str}, manifestándose con {sintomas_str}.",
830
- "compuesto_alimento": f"{alimentos_str} (Compuestos bioactivos)",
831
- "sintomas_clave": user_symptoms,
832
- "es_hipotesis_local": True
833
- }
834
-
835
- compuestos_unicos = sorted(set(c['compuesto'] for c in candidates))
836
- alimentos_unicos = sorted(set(c['alimento'] for c in candidates))
837
  compuestos_str = ", ".join(compuestos_unicos)
838
  alimentos_str = ", ".join(alimentos_unicos).capitalize()
839
 
840
- if es_neurologico:
841
- condicion = f"Cefalea o Migraña Vasoactiva asociada a {compuestos_str} en {alimentos_str}"
842
- elif es_psicologico:
843
- condicion = f"Alteración Neuropsicológica / Estado de Ánimo asociada a {compuestos_str} en {alimentos_str}"
844
- elif es_dermatologico:
845
- condicion = f"Reacción Histaminérgica / Dermatológica por {compuestos_str} en {alimentos_str}"
846
- elif es_insomnio:
847
- condicion = f"Alteración del Sueño e Insomnio por respuesta a {compuestos_str} en {alimentos_str}"
848
- elif es_gastrointestinal:
849
- condicion = f"Distensión Abdominal e Intolerancia Digestiva a {compuestos_str} en {alimentos_str}"
850
- else:
851
- condicion = f"Hipersensibilidad o Intolerancia a {compuestos_str} en {alimentos_str}"
852
-
853
- mecanismo = " | ".join(sorted(set(c['efecto'] for c in candidates)))
854
 
855
  return {
856
  "condicion_asociada": condicion,
@@ -860,42 +660,6 @@ def build_local_hypothesis(entities, food_compound_map, neuro_map):
860
  "es_hipotesis_local": True
861
  }
862
 
863
- def generate_ai_hypothesis_on_the_fly(query, entities):
864
- gemini_client = get_gemini_client()
865
- if not gemini_client:
866
- return None
867
-
868
- prompt = (
869
- "Rol: Médico Especialista en Nutrición Funcional e Inmunología de Alimentos.\n"
870
- f'Consulta directa del paciente: "{query}"\n\n'
871
- "Analiza la fisiología de este caso y genera un JSON estricto con las siguientes 4 claves:\n"
872
- '1. "condicion_asociada": Título médico conciso de la hipersensibilidad o intolerancia.\n'
873
- '2. "mecanismo_fisiologico": Explicación concisa del mecanismo enzimático o inmunológico.\n'
874
- '3. "compuesto_alimento": Nombre de los alimentos y moléculas responsables.\n'
875
- '4. "sintomas_clave": Lista de los síntomas expresados por el paciente.\n\n'
876
- "Devuelve ÚNICAMENTE el código JSON válido, sin bloques de código markdown ni texto adicional."
877
- )
878
- try:
879
- raw_text, _ = generate_content_with_fallback(gemini_client, prompt)
880
- raw = raw_text.strip()
881
-
882
- json_match = re.search(r'\{.*\}', raw, re.DOTALL)
883
- if json_match:
884
- raw_clean = json_match.group(0)
885
- else:
886
- raw_clean = raw
887
- if "```json" in raw_clean:
888
- raw_clean = raw_clean.split("```json")[1].split("```")[0]
889
- elif "```" in raw_clean:
890
- raw_clean = raw_clean.split("```")[1].split("```")[0]
891
-
892
- data = json.loads(raw_clean.strip())
893
- data["es_hipotesis_local"] = True
894
- return data
895
- except Exception as e:
896
- logger.error(f"Error en Agente Rescatador Universal de IA: {e}")
897
- return None
898
-
899
  def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_name_map, limit=3):
900
  if not user_foods_es or not _foodb_index:
901
  return []
@@ -932,7 +696,7 @@ def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_
932
  return unique[:limit]
933
 
934
  # ==============================================================================
935
- # GENERADORES DE INFORMES (LOCAL, IA Y MOLECULAR CON FOODB)
936
  # ==============================================================================
937
 
938
  def generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map):
@@ -950,7 +714,7 @@ def generate_local_fallback_analysis(query, match, entities, food_compound_map,
950
 
951
  compuestos_str = ", ".join(sorted(list(compuestos_detectados))) if compuestos_detectados else match.get('compuesto_alimento', 'compuestos bioactivos')
952
 
953
- 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"
954
 
955
  detalles_compuestos = []
956
  for comp in compuestos_detectados:
@@ -958,13 +722,11 @@ def generate_local_fallback_analysis(query, match, entities, food_compound_map,
958
  detalles_compuestos.append(f"- **{comp.capitalize()}**: {neuro_map[comp]['efecto_neuropsicologico']}")
959
 
960
  if detalles_compuestos:
961
- causa += "\n**Mecanismo bioquímico e impacto fisiológico:**\n" + "\n".join(detalles_compuestos) + "\n"
962
- elif match.get('mecanismo_fisiologico'):
963
- causa += f"\n**Mecanismo fisiológico:** {match.get('mecanismo_fisiologico')}\n"
964
 
965
- evitar = f"\n### 🛑 Alimentos a Moderar o Evitar\n- **Alimentos evaluados:** {', '.join(user_foods).capitalize()}\n- Productos que contengan **{compuestos_str}** o derivados con los mismos componentes desencadenantes.\n"
966
 
967
- 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 evaluar la remisión del síntoma.\n- Consultar con un nutricionista funcional o médico especialista si las molestias persisten.\n"
968
 
969
  consejo_texto = None
970
  for comp in compuestos_detectados:
@@ -981,17 +743,10 @@ def generate_local_fallback_analysis(query, match, entities, food_compound_map,
981
  return saludo + hipotesis + causa + evitar + alternativas + consejo + TEXTO_BANDERAS_ROJAS
982
 
983
  def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
984
- cache_lookup_key = f"{hash(query)}_{match.get('condicion_asociada')}"
985
- if cache_lookup_key in global_learned_cache:
986
- logger.info("⚡ Respuesta entregada desde la Caché Global de Aprendizaje en RAM (0.001s).")
987
- return global_learned_cache[cache_lookup_key]
988
-
989
  gemini_client = get_gemini_client()
990
 
991
  if not gemini_client:
992
- local_report = generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
993
- global_learned_cache[cache_lookup_key] = local_report
994
- return local_report
995
 
996
  compound_context = ""
997
  if entities and food_compound_map and neuro_map:
@@ -1002,36 +757,24 @@ def generate_detailed_analysis(query, match, entities=None, food_compound_map=No
1002
  if compound in neuro_map:
1003
  lines.append(f"- {food.capitalize()} contiene {compound}: {neuro_map[compound]['efecto_neuropsicologico']}")
1004
  if lines:
1005
- compound_context = "Compuestos moleculares relevantes detectados:\n" + "\n".join(lines)
1006
 
1007
  prompt = f"""
1008
- Rol: Nutricionista Funcional y experto en bioquímica de alimentos.
1009
- Caso del paciente: {query}
1010
  Hipótesis clínica: {match.get('condicion_asociada')}
1011
- Mecanismo fisiológico: {match.get('mecanismo_fisiologico')}
1012
- Alimentos/compuestos clave: {match.get('compuesto_alimento')}
1013
  {compound_context}
1014
 
1015
- Tarea: Escribe un análisis en Markdown estructurado:
1016
- 1. Saludo breve y empático.
1017
- 2. Frase inicial clara con la hipótesis probable.
1018
- 3. Explicación de las moléculas responsables (ej: GOS/FODMAPs, lectinas, inhibidores de proteasas, histamina, tiramina, gluten, lactosa, caseína).
1019
- 4. Mecanismo fisiológico en términos sencillos.
1020
- 5. Alimentos a evitar o moderar.
1021
- 6. Alternativas o reemplazos sugeridos.
1022
- 7. Consejo práctico final.
1023
  NO incluyas descargos de responsabilidad.
1024
  """
1025
  try:
1026
- raw_text, used_model = generate_content_with_fallback(gemini_client, prompt)
1027
- ai_report = f"🤖 **Análisis Redactado por IA Gemini ({used_model}):**\n\n" + raw_text + TEXTO_BANDERAS_ROJAS
1028
- global_learned_cache[cache_lookup_key] = ai_report
1029
- return ai_report
1030
  except Exception as e:
1031
- logger.warning(f"Error o timeout en Gemini API ({e}). Se activa la respuesta en modo local.")
1032
- local_report = generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
1033
- global_learned_cache[cache_lookup_key] = local_report
1034
- return local_report
1035
 
1036
  def generate_neuro_report_text(entities, food_map, neuro_map):
1037
  report_lines = ["\n### 🧠 Efectos Neuropsicológicos Posibles"]
@@ -1043,15 +786,15 @@ def generate_neuro_report_text(entities, food_map, neuro_map):
1043
  comps = get_compounds_for_food(food, food_map)
1044
  relevant_compounds.update(comps)
1045
 
1046
- found_neuro_effect = False
1047
  if relevant_compounds:
1048
  for compound in sorted(list(relevant_compounds)):
1049
  if compound in neuro_map:
1050
- found_neuro_effect = True
1051
  report_lines.append(f"**{compound.capitalize()}**: _{neuro_map[compound]['efecto_neuropsicologico']}_")
1052
 
1053
- if not found_neuro_effect:
1054
- report_lines.append("No se detectaron efectos neuropsicológicos específicos directos en nuestra base de datos para los alimentos ingresados.")
1055
 
1056
  return "\n".join(report_lines)
1057
 
@@ -1059,7 +802,7 @@ def generate_molecular_report_text(best_match, entities, _foodb_index, _foodb_lo
1059
  report_lines = ["\n### 🔬 Análisis Molecular y Efectos Bioactivos"]
1060
  user_foods = entities.get("alimentos", [])
1061
  if not user_foods:
1062
- return "No hay alimentos seleccionados para analizar."
1063
 
1064
  found_any = False
1065
 
@@ -1081,23 +824,16 @@ def generate_molecular_report_text(best_match, entities, _foodb_index, _foodb_lo
1081
  if not compounds:
1082
  continue
1083
  found_any = True
1084
- report_lines.append(f"\n**{food_key.capitalize()}** (Compuestos y efectos fisiológicos en FooDB):")
1085
 
1086
  for item in compounds[:5]:
1087
  if isinstance(item, dict):
1088
  comp_name = item.get('compound', 'N/A')
1089
  effects = item.get('effects', [])
1090
 
1091
- eff_translated = []
1092
- for eff in effects[:4]:
1093
- eff_clean = str(eff).lower().strip()
1094
- eff_es = FOODB_EFFECTS_TRANSLATION.get(eff_clean, eff_clean)
1095
- eff_translated.append(eff_es)
1096
-
1097
- eff_str = f" — *Efectos registrados: {', '.join(eff_translated)}*" if eff_translated else ""
1098
  report_lines.append(f"- **{comp_name}**{eff_str}")
1099
- else:
1100
- report_lines.append(f"- {str(item)}")
1101
 
1102
  if not found_any:
1103
  report_lines.append("No se encontraron registros moleculares adicionales.")
@@ -1115,7 +851,7 @@ def generate_word_report(report_text):
1115
  doc_io.seek(0)
1116
  return doc_io.getvalue()
1117
  except Exception as e:
1118
- logger.error(f"Error al generar informe Word: {e}")
1119
  return None
1120
 
1121
  def create_relevance_chart(results):
@@ -1129,13 +865,13 @@ def create_relevance_chart(results):
1129
  ).properties(title='Top Coincidencias')
1130
 
1131
  # ==============================================================================
1132
- # INICIALIZACIÓN DE ESTADO PERSISTENTE Y UI STREAMLIT
1133
  # ==============================================================================
1134
 
1135
  if 'search_results' not in st.session_state: st.session_state.search_results = None
1136
  if 'user_query' not in st.session_state: st.session_state.user_query = ""
1137
  if 'entities' not in st.session_state: st.session_state.entities = None
1138
- if 'analysis_cache' not in st.session_state: st.session_state.analysis_cache = {}
1139
  if 'neuro_text_cache' not in st.session_state: st.session_state.neuro_text_cache = ""
1140
  if 'mol_text_cache' not in st.session_state: st.session_state.mol_text_cache = ""
1141
  if 'word_bytes_cache' not in st.session_state: st.session_state.word_bytes_cache = None
@@ -1152,7 +888,7 @@ with col_img2:
1152
 
1153
  gemini_test = get_gemini_client()
1154
  if gemini_test:
1155
- st.caption("🟢 **Motor de IA Gemini Conectado:** Extracción y Redacción Avanzada Activadas")
1156
  else:
1157
  st.caption("🟡 **Modo Local / Alta Disponibilidad:** Activo")
1158
 
@@ -1170,11 +906,8 @@ if submitted:
1170
  current_query = query_input.strip()
1171
  st.session_state.user_query = current_query
1172
  st.session_state.has_searched = True
1173
- st.session_state.analysis_cache = {}
1174
- st.session_state.search_results = None
1175
- st.session_state.entities = None
1176
 
1177
- with st.spinner("🔍 Analizando pistas con Gemini..."):
1178
  raw_entities = extract_entities_with_gemini(current_query)
1179
  reinforced = reinforce_entities_with_keywords(raw_entities, current_query, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
1180
  final_symptoms = translate_symptoms_local(reinforced.get("sintomas", []), MASTER_SYMPTOM_MAP)
@@ -1188,41 +921,32 @@ if submitted:
1188
  if local_hypothesis:
1189
  results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
1190
 
1191
- if not results:
1192
- ai_hypothesis = generate_ai_hypothesis_on_the_fly(current_query, final_entities)
1193
- if ai_hypothesis:
1194
- results = [{'entry': ai_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
1195
-
1196
  st.session_state.search_results = results if results else []
1197
 
1198
- # Pre-cálculo de pestañas en sesión para acelerar la navegación y evitar estado "pensando"
1199
  if st.session_state.search_results:
1200
  best_match = st.session_state.search_results[0]['entry']
1201
-
1202
- # 1. Análisis clínico
1203
  st.session_state.main_analysis = generate_detailed_analysis(
1204
  st.session_state.user_query, best_match,
1205
  st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
1206
  )
1207
-
1208
- # 2. Neuroinforme
1209
  st.session_state.neuro_text_cache = generate_neuro_report_text(
1210
  st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
1211
  )
1212
-
1213
- # 3. Informe Molecular
1214
  st.session_state.mol_text_cache = generate_molecular_report_text(
1215
  best_match, st.session_state.entities, foodb_index, foodb_lower_map,
1216
  FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
1217
  )
1218
 
1219
- # 4. Informe Word
1220
  st.session_state.word_bytes_cache = generate_word_report(f"REPORTE DE SALUD\n\n{st.session_state.main_analysis}")
1221
 
1222
  else:
1223
  st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
1224
 
1225
- # --- DISPLAY DE RESULTADOS OPTIMIZADO ---
1226
  if st.session_state.has_searched:
1227
  results = st.session_state.search_results
1228
 
@@ -1238,10 +962,10 @@ if st.session_state.has_searched:
1238
  st.altair_chart(create_relevance_chart(results), use_container_width=True)
1239
 
1240
  with tab_neuro:
1241
- st.markdown(st.session_state.get('neuro_text_cache', 'No hay datos neuropsicológicos registrados.'))
1242
 
1243
  with tab_mol:
1244
- st.markdown(st.session_state.get('mol_text_cache', 'No hay datos moleculares registrados.'))
1245
 
1246
  word_bytes = st.session_state.get('word_bytes_cache')
1247
  if word_bytes:
@@ -1254,4 +978,4 @@ if st.session_state.has_searched:
1254
  )
1255
  else:
1256
  st.warning("⚠️ No pudimos identificar una causa clara para este caso específico.")
1257
- st.info("Intenta reformular especificando el alimento y el síntoma. Ej: 'Siento [SÍNTOMA] cuando como [ALIMENTO]'.")
 
1
  # ==============================================================================
2
+ # EL DETECTIVE DE ALIMENTOS - EDICIÓN HÍBRIDA CON CORTA-FUEGOS Y ALTA DISPONIBILIDAD
3
  # Desarrollado por: Jairo Alexander & Diana Milena Soler Martinez
4
  # Motor: Gemini Flash + FooDB.ca Graph + Fast-Path Local Engine
5
  # ==============================================================================
 
16
  from io import BytesIO
17
  import docx
18
  import difflib
19
+ import concurrent.futures
20
 
21
  # --- CONFIGURACIÓN DE PÁGINA Y LOGGING ---
22
  st.set_page_config(page_title="El Detective de Alimentos", page_icon="🍎", layout="wide")
 
38
  **Si tus síntomas son severos, busca ayuda médica urgente.**
39
  """
40
 
41
+ # --- CONFIGURACIÓN DE CLIENTE GEMINI CACHEADO ---
42
  @st.cache_resource
43
  def get_gemini_client():
44
  api_key = os.getenv("GEMINI_API_KEY")
 
60
  return None
61
 
62
  try:
 
63
  return genai.Client(
64
  api_key=api_key,
65
+ http_options=types.HttpOptions(timeout=3000) # 3000 ms timeout de cliente
66
  )
67
  except Exception as e:
68
  logger.warning(f"❌ Error instanciando cliente Gemini: {e}")
69
  return None
70
 
71
+ # --- CORTA-FUEGOS CON TIEMPO LÍMITE ESTRICTO DE 3.5 SEGUNDOS ---
72
  def generate_content_with_fallback(gemini_client, prompt):
73
  models_to_try = [
74
  'gemini-2.5-flash',
 
89
  continue
90
  raise RuntimeError("Ningún modelo de Gemini respondió adecuadamente.")
91
 
92
+ def generate_content_with_strict_timeout(gemini_client, prompt, timeout_seconds=3.5):
93
+ """
94
+ Ejecuta la llamada a Gemini dentro de un hilo aislado.
95
+ Si Gemini tarda más de 3.5s, corta la ejecución y activa el motor local.
96
+ """
97
+ def _call():
98
+ return generate_content_with_fallback(gemini_client, prompt)
99
+
100
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
101
+ future = executor.submit(_call)
102
+ try:
103
+ return future.result(timeout=timeout_seconds)
104
+ except concurrent.futures.TimeoutError:
105
+ logger.warning(f"⏳ Gemini tardó más de {timeout_seconds}s. Se corta la espera y se activa el Motor Local.")
106
+ raise TimeoutError("Tiempo límite de IA superado.")
107
+
108
+ # --- CARGA DE DATOS EN MEMORIA ---
109
  @st.cache_resource
110
  def load_data():
111
  try:
 
134
 
135
  foodb_lower_map = get_foodb_lower_map(foodb_index)
136
 
 
137
  @st.cache_resource
138
  def get_global_learned_cache():
139
  return {}
 
141
  global_learned_cache = get_global_learned_cache()
142
 
143
  # ==============================================================================
144
+ # DICCIONARIOS MASTER BIOMÉDICOS
145
  # ==============================================================================
146
 
147
  FOOD_TO_COMPOUND_MAP = {
 
157
  "maíz": ["zeína", "lectinas", "fitatos", "fodmaps"],
158
  "quinoa": ["saponinas", "oxalatos", "fitatos"],
159
  "alforfón": ["oxalatos", "fagopirina"],
 
160
 
161
  # LÁCTEOS
162
  "leche": ["caseína", "lactosa", "fodmaps"],
163
  "queso": ["tiramina", "histamina", "caseína", "lactosa"],
164
  "queso fresco": ["caseína", "lactosa"],
165
  "queso maduro": ["tiramina", "histamina", "caseína"],
 
166
  "yogur": ["histamina", "caseína", "lactosa"],
167
  "mantequilla": ["caseína", "lactosa"],
168
  "crema de leche": ["caseína", "lactosa"],
 
177
  "empanada": ["gluten", "zeína", "solanina", "lectinas"],
178
  "arepa": ["zeína", "lectinas", "fitatos"],
179
  "lechona": ["histamina", "nitritos", "grasas saturadas", "purinas"],
 
180
 
181
+ # FRUTAS Y TROPICALES
182
  "noni": ["escopoletina", "fenoles", "salicilatos", "fructosa", "histamina"],
 
183
  "chontaduro": ["fenoles", "fructosa", "grasas saturadas", "tiramina", "salicilatos"],
 
184
  "naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
185
  "jugo de naranja": ["fructosa", "histamina", "salicilatos", "fenoles"],
186
  "limón": ["salicilatos", "histamina", "fenoles", "ácido cítrico"],
 
187
  "manzana": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
188
  "pera": ["fructosa", "sorbitol", "fodmaps", "salicilatos"],
189
  "plátano": ["tiramina", "histamina", "salicilatos", "fenoles"],
 
193
  "mora": ["salicilatos", "oxalatos", "histamina", "taninos"],
194
  "uva": ["fructosa", "salicilatos", "histamina", "taninos"],
195
  "piña": ["salicilatos", "histamina", "fructosa"],
 
196
  "mango": ["fructosa", "salicilatos", "histamina"],
 
 
 
 
 
 
197
  "lulo": ["salicilatos", "fructosa", "ácido cítrico"],
 
198
  "maracuyá": ["fructosa", "histamina", "salicilatos"],
199
  "maracuya": ["fructosa", "histamina", "salicilatos"],
200
  "feijoa": ["fructosa", "salicilatos", "taninos"],
 
 
201
  "tomate de árbol": ["solanina", "histamina", "salicilatos"],
 
202
  "uchuva": ["solanina", "histamina", "salicilatos", "oxalatos"],
203
+
204
+ # VERDURAS Y SOLANÁCEAS
 
 
 
 
 
 
 
 
205
  "tomate": ["histamina", "solanina", "salicilatos", "glutamato"],
 
 
206
  "papa": ["solanina", "oxalatos", "lectinas"],
207
+ "patata": ["solanina", "oxalatos", "lectinas"],
208
  "berenjena": ["solanina", "histamina", "salicilatos"],
209
  "pimiento": ["capsaicina", "solanina", "salicilatos"],
210
  "espinaca": ["oxalatos", "nitratos", "histamina"],
 
 
 
211
  "cebolla": ["fodmaps", "compuestos azufrados", "salicilatos"],
212
  "ajo": ["fodmaps", "compuestos azufrados"],
213
  "brócoli": ["fodmaps", "compuestos azufrados", "salicilatos", "goitrógenos"],
 
 
 
 
 
214
  "yuca": ["fodmaps", "fitatos"],
215
  "ñame": ["oxalatos", "fitatos", "fodmaps"],
216
+
217
+ # LEGUMBRES (PERFIL COMPLETO)
 
 
 
 
 
 
 
 
218
  "frijol": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
219
  "frijoles": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
220
  "frijol paisa": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
221
  "lentejas": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos", "taninos"],
222
  "garbanzos": ["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas", "oxalatos"],
223
  "arvejas": ["fodmaps", "lectinas", "fitatos", "inhibidores_proteasas"],
 
224
  "soja": ["soja", "isoflavonas", "lectinas", "fitatos", "fodmaps", "saponinas", "goitrógenos", "inhibidores_proteasas"],
225
  "tofu": ["soja", "isoflavonas", "lectinas", "fitatos"],
 
226
  "haba": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
227
  "habas": ["vicina_convicina", "fodmaps", "lectinas", "fitatos", "purinas"],
228
 
 
 
 
 
 
 
 
 
 
 
229
  # CARNES, EMBUTIDOS Y BEBIDAS
230
  "carne de cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
231
  "cerdo": ["histamina", "alfa-gal", "grasas saturadas", "purinas"],
232
  "chicharrón": ["histamina", "grasas saturadas"],
233
  "chicharron": ["histamina", "grasas saturadas"],
 
234
  "carne de res": ["histamina", "alfa-gal", "purinas"],
235
  "res": ["histamina", "alfa-gal", "purinas"],
236
  "café": ["cafeína", "salicilatos", "oxalatos"],
 
239
  "chocolate": ["teobromina", "feniletilamina", "tiramina", "histamina", "oxalatos", "cafeína"],
240
  "vino": ["histamina", "tiramina", "sulfitos", "taninos", "alcohol"],
241
  "cerveza": ["gluten", "histamina", "sulfitos", "alcohol", "purinas"],
 
 
242
  "embutidos": ["histamina", "tiramina", "nitritos", "glutamato"],
243
  "pescado": ["histamina", "purinas", "albúmina"],
244
+ "huevo": ["albúmina"]
 
 
 
 
 
 
 
 
245
  }
246
 
247
  FOOD_NAME_TO_FOODB_KEY = {
248
+ "alforfón": ["buckwheat"], "arroz": ["rice"], "avena": ["oat", "oats"], "cebada": ["barley"],
249
+ "queso": ["cheese"], "leche": ["milk"], "yogur": ["yogurt"], "mantequilla": ["butter"],
250
  "vino": ["wine"], "cerveza": ["beer"], "chocolate": ["chocolate", "cocoa"],
251
  "tomate": ["tomato"], "aguacate": ["avocado"], "plátano": ["banana", "plantain"], "banano": ["banana"],
252
  "naranja": ["orange"], "pan": ["bread", "wheat"], "trigo": ["wheat"], "café": ["coffee"], "cafe": ["coffee"],
253
+ "manzana": ["apple"], "fresa": ["strawberry"], "uva": ["grape"], "piña": ["pineapple"],
254
  "papa": ["potato"], "patata": ["potato"], "frijol": ["bean", "beans", "kidney bean", "black bean"],
255
  "frijoles": ["bean", "beans"], "frijol paisa": ["bean", "pork", "rice"],
256
+ "lentejas": ["lentil"], "garbanzos": ["chickpea", "garbanzo"], "arveja": ["pea"],
257
  "haba": ["broad bean", "fava bean"], "habas": ["broad bean", "fava bean"],
 
258
  "bandeja paisa": ["bean", "pork", "rice", "banana", "egg", "avocado"],
259
  "ajiaco": ["potato", "chicken", "corn", "milk", "cream"],
260
  "sancocho": ["cassava", "plantain", "potato", "pork", "beef", "chicken"],
 
263
  }
264
 
265
  STRICT_COMPOUND_TRIGGERS = {
266
+ "cafeína": ["cafeína", "caffeine", "café", "cafe", "coffee"],
267
  "teobromina": ["teobromina", "theobromine", "cacao", "chocolate"],
268
+ "solanina": ["solanina", "solanine", "solanáceas", "tomate", "patata", "papa"],
269
+ "capsaicina": ["capsaicina", "capsaicin", "chile", "ají", "picante"],
270
+ "gluten": ["gluten", "trigo", "wheat", "cebada", "centeno"],
271
+ "caseína": ["caseína", "casein", "queso", "lácteos"],
272
+ "soja": ["soja", "soya", "soy", "tofu", "tempeh"],
273
+ "albúmina": ["albúmina", "albumin", "huevo"],
274
  "lactosa": ["lactosa", "lactose"],
275
+ "fructosa": ["fructosa", "fructose"],
276
+ "fodmaps": ["fodmap", "fodmaps", "fructanos", "galactanos", "sorbitol", "rafinosa", "estaquiosa"],
277
  "histamina": ["histamina", "histamine", "dao"],
278
  "tiramina": ["tiramina", "tyramine"],
279
+ "oxalatos": ["oxalato", "oxalatos", "oxalate"],
280
+ "saponinas": ["saponina", "saponinas"],
281
+ "lectinas": ["lectina", "lectinas", "frijoles", "legumbres"],
282
+ "fitatos": ["fitato", "fitatos", "ácido fítico"],
283
+ "salicilatos": ["salicilato", "salicilatos"],
284
+ "sulfitos": ["sulfito", "sulfitos"],
285
  "glutamato": ["glutamato", "glutamate", "msg"],
286
+ "nitritos": ["nitrito", "nitritos", "embutidos"],
287
  "alcohol": ["alcohol", "etanol", "cerveza", "vino"],
288
+ "purinas": ["purina", "purinas", "ácido úrico", "gota"],
289
+ "inhibidores_proteasas": ["inhibidor de tripsina", "inhibidores de proteasas", "tripsina"],
290
+ "vicina_convicina": ["vicina", "convicina", "favismo", "g6pd", "haba", "habas"]
 
 
 
291
  }
292
 
293
  MEDICAL_CONDITION_SAFEGUARDS = {
294
  "farmacos_interacciones": {
295
+ "condition_triggers": ["fármaco", "farmaco", "medicamento", "imao", "anticoagulante"],
296
+ "required_user_keywords": ["fármaco", "farmaco", "medicamento", "imao", "pastilla", "remedio", "receta"]
297
  },
298
  "respiratorio_asma": {
299
+ "condition_triggers": ["asma", "sibilancia", "broncoespasmo"],
300
+ "required_user_keywords": ["asma", "tos", "respirar", "pecho", "sibilancia", "ahogo"]
301
  },
302
  "dermatologico_alergia": {
303
+ "condition_triggers": ["urticaria", "angioedema", "anafilaxia", "dermatitis"],
304
+ "required_user_keywords": ["piel", "pica", "picazón", "manchas", "rojo", "alergia", "ronchas"]
305
  },
306
  "articular_gota": {
307
  "condition_triggers": ["gota", "artritis", "hiperuricemia"],
308
+ "required_user_keywords": ["articulación", "coyuntura", "dedo gordo", "rodilla", "gota"]
309
  },
310
  "renal_litiasis": {
311
  "condition_triggers": ["litiasis", "cálculo renal", "piedras en el riñón"],
 
316
  "required_user_keywords": ["embarazada", "embarazo", "bebé", "lactando"]
317
  },
318
  "hepato_vesicular": {
319
+ "condition_triggers": ["vesícula", "vesicula", "colelitiasis", "hígado"],
320
+ "required_user_keywords": ["vesícula", "vesicula", "hígado", "higado", "piel amarilla"]
321
  }
322
  }
323
 
 
327
  "fuentes_comunes": ["pan", "trigo", "pasta", "cebada", "centeno"]
328
  },
329
  "caseína": {
330
+ "efecto_neuropsicologico": "Sus péptidos (casomorfinas) pueden actuar sobre receptores opioides en el SNC, afectando la cognición y el estado de ánimo.",
331
  "fuentes_comunes": ["leche", "queso", "yogur"]
332
  },
333
  "fodmaps": {
334
+ "efecto_neuropsicologico": "Mediante el eje intestino-cerebro, la fermentación intestinal excesiva por oligosacáridos (GOS/rafinosa) genera distensión visceral, enviando señales de estrés por el nervio vago que aumentan la ansiedad e hipersensibilidad.",
335
  "fuentes_comunes": ["frijoles", "lentejas", "cebolla", "ajo", "trigo"]
336
  },
337
  "inhibidores_proteasas": {
338
+ "efecto_neuropsicologico": "Impiden la descomposición enzimática adecuada de proteínas, produciendo indigestión severa, hipoperfusión gastrointestinal y letargo posprandial.",
339
+ "fuentes_comunes": ["frijoles", "legumbres mal cocidas", "soja"]
340
  },
341
  "vicina_convicina": {
342
+ "efecto_neuropsicologico": "En personas con deficiencia de la enzima G6PD (favismo), desencadena estrés oxidativo eritrocitario que produce cefalea vascular, astenia y mareos.",
343
  "fuentes_comunes": ["habas"]
344
  },
 
 
 
 
 
 
 
 
345
  "tiramina": {
346
+ "efecto_neuropsicologico": "Amina vasoactiva. Provoca vasoconstricción inicial seguida de vasodilatación cerebral, desencadenando migrañas.",
347
  "fuentes_comunes": ["quesos madurados", "vino tinto", "embutidos"]
348
  },
349
  "histamina": {
350
+ "efecto_neuropsicologico": "Neurotransmisor estimulante central. Su exceso por bajo DAO provoca migrañas, insomnio, fragmentación del sueño y ansiedad.",
351
  "fuentes_comunes": ["queso", "vino tinto", "tomate", "pescado azul", "conservas"]
352
  },
353
  "salicilatos": {
354
+ "efecto_neuropsicologico": "En personas sensibles provocan estimulación excesiva del SNC, hiperactividad e insomnio.",
355
  "fuentes_comunes": ["cítricos", "manzanas", "tomate", "especias", "té"]
356
  },
357
  "fenoles": {
358
+ "efecto_neuropsicologico": "La sobrecarga de fenoles satura las vías de sulfatación hepática, acumulando catecolaminas que producen agitación nocturna e insomnio.",
359
  "fuentes_comunes": ["jugo de naranja", "chontaduro", "noni", "té"]
360
  },
361
  "cafeína": {
362
+ "efecto_neuropsicologico": "Bloquea los receptores de adenosina en el cerebro. Provoca hiperactivación del SNC, ansiedad e insomnio.",
363
  "fuentes_comunes": ["café", "té negro", "energizantes", "chocolate"]
364
  },
365
  "lactosa": {
 
367
  "fuentes_comunes": ["leche", "queso fresco", "crema de leche"]
368
  },
369
  "fructosa": {
370
+ "efecto_neuropsicologico": "Disminuye la biodisponibilidad de triptófano en el intestino, interfiriendo en la síntesis de serotonina y melatonina.",
371
  "fuentes_comunes": ["jugos concentrados", "miel", "frutas muy dulces"]
372
  },
 
 
 
 
373
  "solanina": {
374
+ "efecto_neuropsicologico": "Glicoalcaloide con leve efecto inhibidor de la acetilcolinesterasa; puede provocar letargo y rigidez corporal.",
375
  "fuentes_comunes": ["tomate", "patata", "berenjena"]
376
  }
377
  }
378
 
379
  CONSEJOS_POR_COMPUESTO = {
380
+ "fodmaps": "Para reducir los GOS (rafinosa y estaquiosa) de los frijoles y legumbres, realiza un remojo de 12 a 24 horas desechando el agua, y cocina a alta presión adicionando comino o epazote.",
381
+ "inhibidores_proteasas": "La cocción prolongada y el calor de la olla a presión inactivan casi por completo los inhibidores de tripsina en legumbres.",
382
+ "vicina_convicina": "Si padeces favismo (deficiencia de G6PD), evita de forma estricta el consumo de habas.",
383
+ "gluten": "La respuesta al gluten debe diferenciar Enfermedad Celíaca, SGNC y alergia al trigo. Realiza pruebas serológicas antes de retirarlo.",
384
+ "lactosa": "La intolerancia se debe a baja lactasa. Se puede manejar con lactasa exógena o productos deslactosados.",
 
 
385
  "histamina": "La acumulación de histamina se asocia a bajo DAO. Una dieta baja en histamina y rica en vitamina B6 y C ayuda a restaurar el equilibrio.",
386
+ "tiramina": "Controlar el consumo de alimentos añejados previene picos de presión arterial y cefaleas.",
387
+ "salicilatos": "Moderar el consumo concentrado de jugos cítricos y especias ayuda a estabilizar el sueño.",
388
+ "cafeína": "Moderar su consumo antes de mitad de tarde protege la arquitectura del sueño."
 
 
 
 
389
  }
390
 
391
  CONSEJO_GENERICO_DEFAULT = "Cada organismo posee una individualidad bioquímica única según su perfil enzimático y digestivo. Monitorear tus respuestas personales te permitirá adaptar la dieta a tu umbral de tolerancia."
 
393
  FOODB_EFFECTS_TRANSLATION = {
394
  "flatugenic": "genera gases/flatulencia", "antioxidant": "antioxidante",
395
  "hypercholesterolemic": "puede elevar colesterol", "triglycerigenic": "puede elevar triglicéridos",
396
+ "uricogenic": "puede elevar ácido úrico", "vasodilator": "vasodilatador (posible migraña)",
 
 
 
397
  "laxative": "efecto laxante", "diuretic": "efecto diurético",
398
+ "stimulant": "estimulante del SNC", "antiinflammatory": "antiinflamatorio"
 
399
  }
400
 
401
  COMPOUND_SYNONYM_MAP = {
402
+ "gluten": ["gluten", "gliadin"], "caseína": ["casein"], "lactosa": ["lactose"], "fructosa": ["fructose"]
 
 
 
 
403
  }
404
 
405
  MASTER_SYMPTOM_MAP = {
406
  "dolor abdominal": {
407
+ "frases_es": ["dolor de estómago", "dolor de panza", "dolor abdominal", "retortijones", "cólicos", "gastritis", "acidez", "reflujo", "pesadez"],
408
+ "keywords_en": ["abdominal", "pain", "stomach", "cramp"]
409
  },
410
  "hinchazón": {
411
+ "frases_es": ["hinchazón abdominal", "vientre hinchado", "distensión abdominal", "hinchazón", "distensión", "inflamado", "gases", "flatulencia"],
412
+ "keywords_en": ["bloating", "distension", "flatulence"]
413
  },
414
  "diarrea": {
415
+ "frases_es": ["diarrea", "diarreas", "soltura", "estómago suelto", "deposiciones líquidas"],
416
  "keywords_en": ["diarrhea", "loose stools"]
417
  },
418
  "náuseas": {
419
+ "frases_es": ["náuseas", "nauseas", "vómito", "vomito", "ganas de vomitar"],
420
  "keywords_en": ["nausea", "vomiting"]
421
  },
422
  "dolor de cabeza": {
423
+ "frases_es": ["dolor de cabeza", "migraña", "cefalea", "jaqueca", "me duele la cabeza"],
424
  "keywords_en": ["headache", "migraine"]
425
  },
426
  "ansiedad": {
427
+ "frases_es": ["ansiedad", "nerviosismo", "angustia", "irritabilidad"],
428
+ "keywords_en": ["anxiety"]
429
  },
430
  "insomnio": {
431
+ "frases_es": ["insomnio", "no puedo dormir", "desvelado", "no duermo", "desvelo"],
432
+ "keywords_en": ["insomnia"]
433
  },
434
  "alergia cutánea": {
435
+ "frases_es": ["piel", "rasca", "picazón", "urticaria", "erupción", "alergia", "ronchas"],
436
+ "keywords_en": ["rash", "itching"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
  }
439
 
440
  # ==============================================================================
441
+ # FUNCIONES AUXILIARES Y DE BÚSQUEDA
442
  # ==============================================================================
443
 
444
  def sanitize_text(text):
 
453
  "en", "con", "por", "para", "que", "se", "su", "sus", "al", "del", "lo",
454
  "como", "es", "son", "muy", "mas", "más", "no", "si", "sí", "tu", "mi",
455
  "me", "te", "le", "les", "nos", "cuando", "donde", "porque", "pero",
456
+ "tomo", "como", "da"
 
 
 
 
 
457
  }
458
 
459
  def extract_keywords(text):
 
466
 
467
  food_clean = food_name.lower().strip()
468
 
 
469
  if food_clean in food_map:
470
  return food_map[food_clean]
471
 
 
472
  for suffix in ["es", "s"]:
473
  if food_clean.endswith(suffix):
474
  norm = food_clean[:-len(suffix)]
475
  if norm in food_map:
476
  return food_map[norm]
477
 
 
478
  fuzzy_matches = difflib.get_close_matches(food_clean, list(food_map.keys()), n=1, cutoff=0.6)
479
  if fuzzy_matches:
 
480
  return food_map[fuzzy_matches[0]]
481
 
 
482
  matched_compounds = set()
483
  for key, compounds in food_map.items():
484
  if re.search(r'\b' + re.escape(key) + r'\b', food_clean) or food_clean in key:
 
487
  if matched_compounds:
488
  return list(matched_compounds)
489
 
 
490
  inferred = set()
491
+ if any(w in food_clean for w in ["frijol", "lenteja", "garbanzo", "legumbre", "soja", "haba", "arveja"]):
492
+ inferred.update(["fodmaps", "lectinas", "fitatos", "saponinas", "inhibidores_proteasas", "purinas"])
493
+ elif any(w in food_clean for w in ["carne", "cerdo", "res", "pollo", "pavo", "chicharrón"]):
494
+ inferred.update(["histamina", "purinas"])
495
+ elif any(w in food_clean for w in ["jugo", "zumo", "batido", "fruta"]):
 
 
 
 
 
 
 
 
 
 
 
496
  inferred.update(["fructosa", "salicilatos", "histamina", "fenoles"])
497
+ elif any(w in food_clean for w in ["queso", "leche", "crema", "yogur"]):
498
  inferred.update(["caseína", "lactosa", "histamina", "tiramina"])
 
 
 
 
 
 
499
 
500
  return list(inferred)
501
 
 
506
  user_query_clean = user_query.lower() if user_query else ""
507
  texto_condicion = (entry.get("condicion_asociada", "") + " " + entry.get("compuesto_alimento", "")).lower()
508
 
 
509
  for category, rules in MEDICAL_CONDITION_SAFEGUARDS.items():
510
  if any(trig in texto_condicion for trig in rules["condition_triggers"]):
511
  tiene_contexto = any(req_kw in user_query_clean for req_kw in rules["required_user_keywords"])
512
  if not tiene_contexto:
 
513
  return False
514
 
 
515
  user_compounds = set()
516
  for food in user_foods:
517
  comps = get_compounds_for_food(food, food_compound_map)
 
525
  for syn in synonyms
526
  )
527
  if not tiene_compuesto:
 
528
  return False
529
 
530
  return True
 
535
  return {"alimentos": [], "sintomas": []}
536
 
537
  system_prompt = f"""
538
+ Rol: Extractor Médico JSON de alimentos y síntomas.
539
+ Extrae un JSON estricto con dos claves: "alimentos" (lista) y "sintomas" (lista).
 
 
 
540
  Input: "{query}"
 
541
  """
542
  try:
543
+ raw_text, _ = generate_content_with_strict_timeout(gemini_client, system_prompt, timeout_seconds=3.5)
544
  text = raw_text
545
  if "```json" in text:
546
  text = text.split("```json")[1].split("```")[0]
 
548
  text = text.split("```")[1].split("```")[0]
549
  return json.loads(text)
550
  except Exception as e:
551
+ logger.warning(f"Extracción por IA no disponible o cancelada ({e}). Se usa extracción directa local.")
552
  return {"alimentos": [], "sintomas": []}
553
 
554
  def reinforce_entities_with_keywords(entities, query, food_map, master_symptom_map):
 
559
  for food_keyword in food_map.keys():
560
  if re.search(r'\b' + re.escape(food_keyword) + r'\b', query_sanitized):
561
  current_foods.add(food_keyword)
 
 
 
 
 
 
 
 
 
 
 
562
 
563
  entities["alimentos"] = list(current_foods)
564
 
 
568
  if phrase in query_sanitized:
569
  current_symptoms.add(main_symptom)
570
  break
 
 
 
 
 
 
 
 
 
 
571
 
572
  entities["sintomas"] = list(current_symptoms)
573
  return entities
 
636
  if not user_foods or not user_symptoms:
637
  return None
638
 
 
 
 
 
 
 
 
 
 
 
639
  candidates = []
640
  for food in user_foods:
641
  comps = get_compounds_for_food(food, food_compound_map)
642
  for compound in comps:
643
  info = neuro_map.get(compound, {})
644
+ effect = info.get('efecto_neuropsicologico', f'Sustancia bioactiva ({compound}) en {food}')
645
  candidates.append({"alimento": food, "compuesto": compound, "efecto": effect})
646
 
647
+ compuestos_unicos = sorted(set(c['compuesto'] for c in candidates)) if candidates else ["Compuestos bioactivos"]
648
+ alimentos_unicos = sorted(set(user_foods))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  compuestos_str = ", ".join(compuestos_unicos)
650
  alimentos_str = ", ".join(alimentos_unicos).capitalize()
651
 
652
+ condicion = f"Hipersensibilidad o Intolerancia a {compuestos_str} en {alimentos_str}"
653
+ mecanismo = " | ".join(sorted(set(c['efecto'] for c in candidates))) if candidates else "Proceso de malabsorción o reactividad digestiva/metabólica."
 
 
 
 
 
 
 
 
 
 
 
 
654
 
655
  return {
656
  "condicion_asociada": condicion,
 
660
  "es_hipotesis_local": True
661
  }
662
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  def find_best_foodb_matches(user_foods_es, _foodb_index, _foodb_lower_map, food_name_map, limit=3):
664
  if not user_foods_es or not _foodb_index:
665
  return []
 
696
  return unique[:limit]
697
 
698
  # ==============================================================================
699
+ # GENERADORES DE INFORMES
700
  # ==============================================================================
701
 
702
  def generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map):
 
714
 
715
  compuestos_str = ", ".join(sorted(list(compuestos_detectados))) if compuestos_detectados else match.get('compuesto_alimento', 'compuestos bioactivos')
716
 
717
+ 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 **{compuestos_str}**.\n"
718
 
719
  detalles_compuestos = []
720
  for comp in compuestos_detectados:
 
722
  detalles_compuestos.append(f"- **{comp.capitalize()}**: {neuro_map[comp]['efecto_neuropsicologico']}")
723
 
724
  if detalles_compuestos:
725
+ causa += "\n**Mecanismo bioquímico:**\n" + "\n".join(detalles_compuestos) + "\n"
 
 
726
 
727
+ evitar = f"\n### 🛑 Alimentos a Moderar o Evitar\n- **Alimentos evaluados:** {', '.join(user_foods).capitalize()}\n- Productos ricos en **{compuestos_str}**.\n"
728
 
729
+ alternativas = "\n### 💡 Sugerencias\n- Llevar un diario de síntomas registrando alimentos y horas de consumo.\n- Reducir la ingesta del alimento para evaluar remisión de síntomas.\n"
730
 
731
  consejo_texto = None
732
  for comp in compuestos_detectados:
 
743
  return saludo + hipotesis + causa + evitar + alternativas + consejo + TEXTO_BANDERAS_ROJAS
744
 
745
  def generate_detailed_analysis(query, match, entities=None, food_compound_map=None, neuro_map=None):
 
 
 
 
 
746
  gemini_client = get_gemini_client()
747
 
748
  if not gemini_client:
749
+ return generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
 
 
750
 
751
  compound_context = ""
752
  if entities and food_compound_map and neuro_map:
 
757
  if compound in neuro_map:
758
  lines.append(f"- {food.capitalize()} contiene {compound}: {neuro_map[compound]['efecto_neuropsicologico']}")
759
  if lines:
760
+ compound_context = "Compuestos detectados:\n" + "\n".join(lines)
761
 
762
  prompt = f"""
763
+ Rol: Nutricionista Funcional.
764
+ Caso: {query}
765
  Hipótesis clínica: {match.get('condicion_asociada')}
766
+ Mecanismo: {match.get('mecanismo_fisiologico')}
 
767
  {compound_context}
768
 
769
+ Escribe un análisis estructurado en Markdown (saludo, hipótesis, moléculas responsables, alimentos a evitar, alternativas y consejo).
 
 
 
 
 
 
 
770
  NO incluyas descargos de responsabilidad.
771
  """
772
  try:
773
+ raw_text, used_model = generate_content_with_strict_timeout(gemini_client, prompt, timeout_seconds=3.5)
774
+ return f"🤖 **Análisis Redactado por IA Gemini ({used_model}):**\n\n" + raw_text + TEXTO_BANDERAS_ROJAS
 
 
775
  except Exception as e:
776
+ logger.warning(f" Gemini no respondió en < 3.5s ({e}). Se activa la respuesta en modo local.")
777
+ return generate_local_fallback_analysis(query, match, entities, food_compound_map, neuro_map)
 
 
778
 
779
  def generate_neuro_report_text(entities, food_map, neuro_map):
780
  report_lines = ["\n### 🧠 Efectos Neuropsicológicos Posibles"]
 
786
  comps = get_compounds_for_food(food, food_map)
787
  relevant_compounds.update(comps)
788
 
789
+ found = False
790
  if relevant_compounds:
791
  for compound in sorted(list(relevant_compounds)):
792
  if compound in neuro_map:
793
+ found = True
794
  report_lines.append(f"**{compound.capitalize()}**: _{neuro_map[compound]['efecto_neuropsicologico']}_")
795
 
796
+ if not found:
797
+ report_lines.append("No se detectaron efectos neuropsicológicos específicos directos.")
798
 
799
  return "\n".join(report_lines)
800
 
 
802
  report_lines = ["\n### 🔬 Análisis Molecular y Efectos Bioactivos"]
803
  user_foods = entities.get("alimentos", [])
804
  if not user_foods:
805
+ return "No hay alimentos seleccionados."
806
 
807
  found_any = False
808
 
 
824
  if not compounds:
825
  continue
826
  found_any = True
827
+ report_lines.append(f"\n**{food_key.capitalize()}** (Efectos en FooDB):")
828
 
829
  for item in compounds[:5]:
830
  if isinstance(item, dict):
831
  comp_name = item.get('compound', 'N/A')
832
  effects = item.get('effects', [])
833
 
834
+ eff_translated = [FOODB_EFFECTS_TRANSLATION.get(str(e).lower().strip(), str(e)) for e in effects[:4]]
835
+ eff_str = f" — *Efectos: {', '.join(eff_translated)}*" if eff_translated else ""
 
 
 
 
 
836
  report_lines.append(f"- **{comp_name}**{eff_str}")
 
 
837
 
838
  if not found_any:
839
  report_lines.append("No se encontraron registros moleculares adicionales.")
 
851
  doc_io.seek(0)
852
  return doc_io.getvalue()
853
  except Exception as e:
854
+ logger.error(f"Error generando informe Word: {e}")
855
  return None
856
 
857
  def create_relevance_chart(results):
 
865
  ).properties(title='Top Coincidencias')
866
 
867
  # ==============================================================================
868
+ # INTERFAZ STREAMLIT
869
  # ==============================================================================
870
 
871
  if 'search_results' not in st.session_state: st.session_state.search_results = None
872
  if 'user_query' not in st.session_state: st.session_state.user_query = ""
873
  if 'entities' not in st.session_state: st.session_state.entities = None
874
+ if 'main_analysis' not in st.session_state: st.session_state.main_analysis = ""
875
  if 'neuro_text_cache' not in st.session_state: st.session_state.neuro_text_cache = ""
876
  if 'mol_text_cache' not in st.session_state: st.session_state.mol_text_cache = ""
877
  if 'word_bytes_cache' not in st.session_state: st.session_state.word_bytes_cache = None
 
888
 
889
  gemini_test = get_gemini_client()
890
  if gemini_test:
891
+ st.caption("🟢 **Motor de IA Gemini Conectado:** Extracción y Redacción Activadas (Timeout: 3.5s)")
892
  else:
893
  st.caption("🟡 **Modo Local / Alta Disponibilidad:** Activo")
894
 
 
906
  current_query = query_input.strip()
907
  st.session_state.user_query = current_query
908
  st.session_state.has_searched = True
 
 
 
909
 
910
+ with st.spinner("🔍 Analizando caso con corta-fuegos activado..."):
911
  raw_entities = extract_entities_with_gemini(current_query)
912
  reinforced = reinforce_entities_with_keywords(raw_entities, current_query, FOOD_TO_COMPOUND_MAP, MASTER_SYMPTOM_MAP)
913
  final_symptoms = translate_symptoms_local(reinforced.get("sintomas", []), MASTER_SYMPTOM_MAP)
 
921
  if local_hypothesis:
922
  results = [{'entry': local_hypothesis, 'score': {'total': 50}, 'matched_keywords': []}]
923
 
 
 
 
 
 
924
  st.session_state.search_results = results if results else []
925
 
 
926
  if st.session_state.search_results:
927
  best_match = st.session_state.search_results[0]['entry']
928
+
929
+ # Pre-generación en segundo plano (imposible de bloquear la UI)
930
  st.session_state.main_analysis = generate_detailed_analysis(
931
  st.session_state.user_query, best_match,
932
  st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
933
  )
934
+
 
935
  st.session_state.neuro_text_cache = generate_neuro_report_text(
936
  st.session_state.entities, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
937
  )
938
+
 
939
  st.session_state.mol_text_cache = generate_molecular_report_text(
940
  best_match, st.session_state.entities, foodb_index, foodb_lower_map,
941
  FOOD_NAME_TO_FOODB_KEY, COMPOUND_SYNONYM_MAP, FOOD_TO_COMPOUND_MAP, INTEGRATED_NEURO_FOOD_MAP
942
  )
943
 
 
944
  st.session_state.word_bytes_cache = generate_word_report(f"REPORTE DE SALUD\n\n{st.session_state.main_analysis}")
945
 
946
  else:
947
  st.warning("⚠️ Por favor describe tus síntomas o alimentos consumidos antes de analizar.")
948
 
949
+ # --- MOSTRAR RESULTADOS ---
950
  if st.session_state.has_searched:
951
  results = st.session_state.search_results
952
 
 
962
  st.altair_chart(create_relevance_chart(results), use_container_width=True)
963
 
964
  with tab_neuro:
965
+ st.markdown(st.session_state.get('neuro_text_cache', 'No hay datos neuropsicológicos.'))
966
 
967
  with tab_mol:
968
+ st.markdown(st.session_state.get('mol_text_cache', 'No hay datos moleculares.'))
969
 
970
  word_bytes = st.session_state.get('word_bytes_cache')
971
  if word_bytes:
 
978
  )
979
  else:
980
  st.warning("⚠️ No pudimos identificar una causa clara para este caso específico.")
981
+ st.info("Intenta especificar el alimento y el síntoma. Ej: 'Siento [SÍNTOMA] cuando como [ALIMENTO]'.")