Lukeetah commited on
Commit
14a5254
·
verified ·
1 Parent(s): 47c6db4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +213 -136
app.py CHANGED
@@ -3,87 +3,160 @@ import random
3
  import time
4
  import json
5
  from datetime import datetime, timedelta
 
6
 
7
- # --- MateAI: El Oráculo del Bienestar Argento ---
 
 
 
 
8
  # Una manifestación de inteligencia superior para la integración masiva de IA en la humanidad.
9
- # Diseñado para Argentina, con personalidad, resiliencia y sin costos de token.
10
  # Arquitectura de Micro-Oráculos Contextuales (AMOC) y Motor de Resonancia Cultural Dinámica (MRCD).
 
11
 
12
  # --- Módulo 1: Configuración Global y Constantes ---
13
- # En un sistema de producción, esto estaría en un archivo config.py
14
  class Config:
15
  APP_NAME = "MateAI: El Oráculo del Bienestar Argento"
16
  DEFAULT_USER_PREFS = {"tipo_susurro": "ambos", "frecuencia": "normal", "modo_che_tranqui": False}
17
  ECO_PUNTOS_POR_SUSURRO = 1
18
- MAX_HISTORIAL_SUSURROS = 50 # Para evitar que la simulación de DB crezca infinitamente
19
-
20
- # --- Módulo 2: Base de Datos Simulada (UserManager) ---
21
- # Simula un sistema de gestión de usuarios robusto. En producción, sería Firestore/MongoDB/PostgreSQL.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  class UserManager:
23
- _users_db = {} # {user_id: UserObject}
24
- _current_user_id = None
25
 
26
  @classmethod
27
- def create_user(cls, name, initial_prefs=None):
28
- user_id = f"user_{len(cls._users_db) + 1}_{int(time.time())}" # Unique ID
29
  prefs = initial_prefs if initial_prefs else Config.DEFAULT_USER_PREFS.copy()
30
  new_user = User(user_id, name, prefs)
31
- cls._users_db[user_id] = new_user
32
- cls._current_user_id = user_id
33
- return new_user, f"¡Bienvenido, {name}! Tu ID de usuario es: {user_id}. ¡MateAI está listo para cebarte la vida!"
34
-
35
- @classmethod
36
- def login_user(cls, user_id):
37
- if user_id in cls._users_db:
38
  cls._current_user_id = user_id
39
- return cls._users_db[user_id], f"Perfil de {cls._users_db[user_id].name} cargado con éxito."
40
- return None, "Error: ID de usuario no encontrado. ¡Creá uno nuevo o verificá el ID!"
 
41
 
42
  @classmethod
43
- def get_current_user(cls):
44
- if cls._current_user_id and cls._current_user_id in cls._users_db:
45
- return cls._users_db[cls._current_user_id]
46
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  @classmethod
49
- def update_user_preferences(cls, user_id, new_prefs_dict):
50
- user = cls._users_db.get(user_id)
51
- if user:
52
- user.preferences.update(new_prefs_dict)
53
- return True
54
- return False
55
 
56
  @classmethod
57
- def add_eco_points(cls, user_id, points):
58
- user = cls._users_db.get(user_id)
59
- if user:
60
- user.eco_points += points
61
- return True
62
- return False
63
-
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  @classmethod
65
- def add_nudge_to_history(cls, user_id, nudge_text):
66
- user = cls._users_db.get(user_id)
67
- if user:
68
- user.nudge_history.append(nudge_text)
69
- # Mantener el historial limitado
70
- if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
71
- user.nudge_history.pop(0) # Eliminar el más antiguo
72
  return True
73
- return False
 
 
74
 
75
- # --- Módulo 3: Modelos de Datos (Simulados - Pydantic-like) ---
76
  class User:
77
- def __init__(self, user_id, name, preferences):
78
  self.user_id = user_id
79
  self.name = name
80
  self.preferences = preferences # dict
81
- self.eco_points = 0
82
- self.insignia = "Novato del Mate"
83
- self.nudge_history = []
84
- self.last_oracle_date = None # Para el Oráculo del Día
85
- self.last_nudge_time = None # Para evitar nudges muy seguidos
86
- self.nudge_cooldown = timedelta(minutes=1) # Simula un cooldown
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  class Nudge:
89
  def __init__(self, text, type, context_tags, cultural_tags):
@@ -93,8 +166,7 @@ class Nudge:
93
  self.cultural_tags = cultural_tags # ["mate", "futbol", "asado"]
94
 
95
  # --- Módulo 4: Base de Conocimiento de Nudges (Motor de Resonancia Cultural Dinámica - MRCD) ---
96
- # Esta es la "inteligencia" de MateAI. En un entorno real, sería una DB de conocimiento
97
- # curada y enriquecida por expertos culturales y lingüísticos.
98
  NUDGE_DATABASE = [
99
  # Mañana - Casa
100
  Nudge("¡Che, buen día! Arrancá la mañana con un matecito y un buen estiramiento. ¡Desperezate, chango!", "bienestar", ["Mañana", "Casa"], ["mate", "despertar"]),
@@ -148,18 +220,18 @@ NUDGE_DATABASE = [
148
  Nudge("Escuchá los ruidos de la noche. Un concierto natural para relajar el alma.", "bienestar", ["Noche", "Aire Libre/Calle"], ["sonidos", "calma"]),
149
  Nudge("Una caminata tranquila antes de volver a casa. Despejá la mente y preparate para el 'descanso del guerrero'.", "bienestar", ["Noche", "Aire Libre/Calle"], ["caminata", "descanso"]),
150
 
151
- # Nudges de Ánimo (para estado_animo_simulado == "Bajoneado")
152
  Nudge("¡Arriba ese ánimo! Un matecito y un buen pensamiento pueden cambiar el día. ¡Vos podés!", "bienestar", ["General"], ["animo", "mate"]),
153
  Nudge("Recordá que hasta el día más nublado tiene un solcito escondido. ¡Fuerza, campeón!", "bienestar", ["General"], ["animo", "esperanza"]),
154
  Nudge("Date un gusto chiquito hoy. ¡Te lo merecés! Un alfajor, tu música favorita... ¡lo que sea!", "bienestar", ["General"], ["animo", "recompensa"]),
155
  Nudge("Si te sentís 'bajoneado', un poco de música o una caminata corta pueden ayudar a 'despejar'.", "bienestar", ["General"], ["animo", "actividad"]),
156
 
157
- # Nudges para "Modo Quilombo" (simulado por actividad o contexto social)
158
  Nudge("¡Uf, qué día! Respiro hondo. En medio del 'quilombo', una pausa es oro. ¡Tranqui!", "bienestar", ["General", "Quilombo"], ["estres", "respiracion"]),
159
  Nudge("Si la cosa está 'picada', recordá que no todo depende de vos. Hacé lo que puedas y soltá lo demás.", "bienestar", ["General", "Quilombo"], ["estres", "control"]),
160
  Nudge("En los días de 'locura', un matecito con calma puede ser tu ancla. ¡A no perder la cabeza!", "bienestar", ["General", "Quilombo"], ["estres", "mate"]),
161
 
162
- # Nudges para "Modo Siesta" (simulado por actividad o contexto social)
163
  Nudge("¡Qué lindo para una siestita! Si podés, aprovechá para recargar energías. ¡Es sagrado!", "bienestar", ["General", "Siesta"], ["descanso", "siesta"]),
164
  Nudge("El cuerpo te pide un descanso. Escuchalo. Unos minutos de relax pueden hacer la diferencia.", "bienestar", ["General", "Siesta"], ["descanso", "cuerpo"]),
165
 
@@ -167,7 +239,7 @@ NUDGE_DATABASE = [
167
  Nudge("¡Vamos Argentina! Disfrutá el partido, pero recordá hidratarte bien. ¡Y si ganamos, a festejar con responsabilidad!", "bienestar", ["General", "Viendo un partido"], ["futbol", "hidratacion", "festejo"]),
168
  Nudge("El fútbol es pasión, pero no te olvides de estirar un poco si estás mucho tiempo sentado. ¡A mover el esqueleto!", "bienestar", ["General", "Viendo un partido"], ["futbol", "movimiento"]),
169
 
170
- # Nudges de Desafíos MateAI (simulados)
171
  Nudge("Desafío MateAI: Hoy, intentá reducir el uso de plásticos de un solo uso. ¡Cada acción cuenta!", "eco", ["Desafio"], ["plastico", "desafio"]),
172
  Nudge("Desafío MateAI: Dedicá 10 minutos a meditar o simplemente a respirar conscientemente. ¡Tu mente te lo agradecerá!", "bienestar", ["Desafio"], ["meditacion", "desafio"]),
173
  Nudge("Desafío MateAI: Contactá a un amigo o familiar que hace mucho no ves. ¡Un 'hola' puede alegrar el día!", "bienestar", ["Desafio"], ["social", "desafio"]),
@@ -179,11 +251,15 @@ ORACULO_REVELATIONS = [
179
  "Cada amanecer es una oportunidad para reescribir tu historia. ¿Qué capítulo nuevo elegís empezar hoy?",
180
  "La tierra nos susurra secretos ancestrales. Escuchá el viento, sentí el sol, y recordá que sos parte de algo inmenso y sagrado.",
181
  "El mate compartido es más que una bebida; es un ritual de conexión. ¿Con quién vas a compartir tu energía hoy?",
182
- "En la simpleza de lo cotidiano reside la magia. Encontrá la belleza en un rayo de sol, en el canto de un pájaro, en una sonrisa."
 
 
 
 
183
  ]
184
 
185
  # --- Módulo 5: Motor de Contexto Avanzado (ContextEngine) ---
186
- # Simula la recolección y procesamiento de datos contextuales en tiempo real.
187
  class ContextEngine:
188
  @staticmethod
189
  def get_current_time_context():
@@ -197,13 +273,13 @@ class ContextEngine:
197
  return "Noche"
198
 
199
  @staticmethod
200
- def get_simulated_environmental_context():
201
  """Simula datos ambientales (ej. clima, ruido)."""
202
  climas = ["Soleado", "Nublado", "Lluvioso", "Ventoso"]
203
  return random.choice(climas)
204
 
205
  @staticmethod
206
- def get_simulated_societal_vibe():
207
  """Simula el "pulso social" argentino (ej. feriado, partido)."""
208
  hoy = datetime.now()
209
  # Simulación de feriados y eventos
@@ -211,10 +287,11 @@ class ContextEngine:
211
  if hoy.weekday() == 4: return "Pre-Finde" # Viernes
212
  if hoy.weekday() == 6: return "Domingo Tranqui" # Domingo
213
  if random.random() < 0.1: return "Quilombo" # 10% de chance de un día "quilombero"
 
214
  return "Normal"
215
 
216
  @staticmethod
217
- def get_simulated_user_sentiment(activity, user_input_sentiment):
218
  """Intenta inferir el sentimiento del usuario basado en actividad y input."""
219
  if user_input_sentiment != "Bien":
220
  return user_input_sentiment
@@ -228,11 +305,11 @@ class ContextEngine:
228
  # --- Módulo 6: Generador de Nudges (Arquitectura de Micro-Oráculos Contextuales - AMOC) ---
229
  # El "cerebro" de MateAI. Selecciona el nudge más relevante y personalizado.
230
  class NudgeGenerator:
231
- def __init__(self, user_manager, context_engine):
232
- self.user_manager = user_manager
233
- self.context_engine = context_engine
234
 
235
- def _filter_nudges_by_context(self, nudges, time_context, location, activity, societal_vibe, sentiment):
236
  filtered = []
237
  for nudge in nudges:
238
  match = True
@@ -244,7 +321,6 @@ class NudgeGenerator:
244
 
245
  # Filtrar por actividad (si es relevante)
246
  if activity != "Relajado" and activity not in nudge.context_tags and "General" not in nudge.context_tags:
247
- # Si la actividad es específica y no hay nudge para ella, no es un match perfecto
248
  pass # Permitir que pase si es un nudge general
249
 
250
  # Filtrar por estado de ánimo
@@ -258,30 +334,31 @@ class NudgeGenerator:
258
  pass # Permitir otros nudges, pero priorizar los de "quilombo"
259
  elif "Siesta" in societal_vibe and "Siesta" not in nudge.context_tags and "descanso" not in nudge.cultural_tags:
260
  pass # Permitir otros nudges, pero priorizar los de "siesta"
 
 
261
 
262
  if match:
263
  filtered.append(nudge)
264
  return filtered
265
 
266
- def _apply_user_preferences(self, nudges, preferences):
267
  if preferences.get('tipo_susurro') == 'eco':
268
  return [n for n in nudges if n.type == 'eco' or n.type == 'reflexivo']
269
  elif preferences.get('tipo_susurro') == 'bienestar':
270
  return [n for n in nudges if n.type == 'bienestar' or n.type == 'reflexivo']
271
  return nudges # Si es "ambos" o no definido
272
 
273
- def _avoid_recent_repetitions(self, nudges, user_history):
274
- # Simula un módulo de aprendizaje adaptativo.
275
- # Evita repetir los últimos 5 nudges para el mismo usuario.
276
  if not user_history:
277
  return nudges
278
 
279
- recent_nudges_text = user_history[-5:]
280
  filtered_nudges = [n for n in nudges if n.text not in recent_nudges_text]
281
- return filtered_nudges if filtered_nudges else nudges # Si no hay nuevos, repite
282
 
283
- def generate_nudge(self, user_id, location, activity, user_input_sentiment):
284
- user = self.user_manager.get_current_user()
285
  if not user:
286
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
287
 
@@ -292,20 +369,20 @@ class NudgeGenerator:
292
 
293
  # Obtener contexto completo
294
  time_context = self.context_engine.get_current_time_context()
295
- environmental_context = self.context_engine.get_simulated_environmental_context()
296
- societal_vibe = self.context_engine.get_simulated_societal_vibe()
297
- sentiment = self.context_engine.get_simulated_user_sentiment(activity, user_input_sentiment)
298
 
299
  # Filtrar nudges base
300
- possible_nudges = self._filter_nudges_by_context(
301
  NUDGE_DATABASE, time_context, location, activity, societal_vibe, sentiment
302
  )
303
 
304
  # Aplicar preferencias del usuario
305
- possible_nudges = self._apply_user_preferences(possible_nudges, user.preferences)
306
 
307
  # Evitar repeticiones recientes
308
- possible_nudges = self._avoid_recent_repetitions(possible_nudges, user.nudge_history)
309
 
310
  # Lógica de "Modo Che, Tranqui" / "Modo Quilombo" (AMOC)
311
  if user.preferences.get('modo_che_tranqui') or sentiment == "Bajoneado" or "Quilombo" in societal_vibe:
@@ -313,17 +390,21 @@ class NudgeGenerator:
313
  if calming_nudges:
314
  chosen_nudge = random.choice(calming_nudges)
315
  else:
316
- chosen_nudge = random.choice(possible_nudges) # Fallback
317
  else:
318
  chosen_nudge = random.choice(possible_nudges) if possible_nudges else Nudge("MateAI está meditando... ¡probá otro contexto!", "reflexivo", ["General"], [])
319
 
320
- self.user_manager.add_eco_points(user.user_id, Config.ECO_PUNTOS_POR_SUSURRO)
321
- self.user_manager.add_nudge_to_history(user.user_id, chosen_nudge.text)
322
- user.last_nudge_time = datetime.now() # Actualizar tiempo del último nudge
 
 
 
 
323
  return chosen_nudge.text
324
 
325
- def get_daily_oracle_revelation(self, user_id):
326
- user = self.user_manager.get_current_user()
327
  if not user:
328
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
329
 
@@ -333,20 +414,18 @@ class NudgeGenerator:
333
 
334
  revelation = random.choice(ORACULO_REVELATIONS)
335
  user.last_oracle_date = today
 
336
  return revelation
337
 
338
- def get_mateai_challenge(self, user_id):
339
- user = self.user_manager.get_current_user()
340
  if not user:
341
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
342
 
343
- # Simula un desafío adaptativo
344
  challenge_nudges = [n for n in NUDGE_DATABASE if "Desafio" in n.context_tags]
345
  if not challenge_nudges:
346
  return "MateAI está pensando en un desafío épico... ¡Volvé pronto!"
347
 
348
- # Podría ser más inteligente: elegir un desafío que el usuario no haya completado
349
- # o que se alinee con sus preferencias. Por ahora, es aleatorio.
350
  return random.choice(challenge_nudges).text
351
 
352
  # --- Módulo 7: Motor de Gamificación (GamificationEngine) ---
@@ -356,7 +435,8 @@ class GamificationEngine:
356
  10: "Cebador Consciente",
357
  50: "Guardián del Planeta",
358
  100: "Maestro del Bienestar Argento",
359
- 200: "Oráculo del Conurbano" # Nueva insignia de "inteligencia superior"
 
360
  }
361
 
362
  @classmethod
@@ -376,7 +456,7 @@ class GamificationEngine:
376
  if points < threshold:
377
  next_goal = f"Próxima insignia: '{name}' a los {threshold} Eco-Puntos."
378
  break
379
- return next_goal if next_goal else "¡Ya sos un Maestro del Bienestar Argento! Seguí sumando."
380
 
381
  # --- Instanciación de Módulos ---
382
  user_manager = UserManager()
@@ -466,7 +546,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
466
  )
467
  nuevas_preferencias_frecuencia = gr.Dropdown(
468
  ["normal", "discreto", "proactivo"],
469
- label="Frecuencia de susurros (simulado)",
470
  value="normal"
471
  )
472
  modo_che_tranqui_checkbox = gr.Checkbox(label="Activar 'Modo Che, Tranqui' (prioriza calma)", value=False)
@@ -489,25 +569,25 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
489
 
490
  # --- Tab 3: Susurros del Momento ---
491
  with gr.Tab("Susurros del Momento"):
492
- gr.Markdown("<h2 style='color: #10b981;'>¡Pedí un Susurro Contextual a MateAI!</h2><p>Simulá tu contexto y dejá que MateAI te sorprenda.</p>")
493
  with gr.Row():
494
  hora_del_dia_input = gr.Dropdown(
495
  ["Mañana", "Mediodía/Tarde", "Noche"],
496
  label="Momento del Día",
497
  value="Mañana"
498
  )
499
- ubicacion_simulada_input = gr.Dropdown(
500
  ["Casa", "Oficina/Estudio", "Aire Libre/Calle"],
501
  label="¿Dónde estás?",
502
  value="Casa"
503
  )
504
  with gr.Row():
505
- actividad_simulada_input = gr.Dropdown(
506
  ["Relajado", "Trabajando", "Ejercicio", "Leyendo", "Cocinando", "Viendo un partido"],
507
  label="¿En qué andás?",
508
  value="Relajado"
509
  )
510
- estado_animo_simulado_input = gr.Dropdown(
511
  ["Bien", "Cansado", "Bajoneado", "Motivado"],
512
  label="¿Cómo te sentís?",
513
  value="Bien"
@@ -520,7 +600,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
520
  placeholder="MateAI está esperando tu contexto para cebarte un susurro...",
521
  interactive=False
522
  )
523
- gr.Markdown("<p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>MateAI aplica un pequeño 'cooldown' para que los susurros sean más valiosos.</i></p>")
524
 
525
  # --- Tab 4: Desafíos MateAI ---
526
  with gr.Tab("Desafíos MateAI"):
@@ -532,7 +612,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
532
  interactive=False
533
  )
534
  btn_desafio = gr.Button("¡Quiero un Desafío MateAI!", variant="primary")
535
- gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Completar desafíos (simulado) te dará más Eco-Puntos y te acercará a nuevas insignias.</i></p>")
536
 
537
  # --- Tab 5: Mi Historial y Logros ---
538
  with gr.Tab("Mi Historial & Logros"):
@@ -547,21 +627,21 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
547
  interactive=True
548
  )
549
  btn_descargar_diario = gr.Button("Descargar Diario (sesión actual)", variant="secondary")
550
- gr.Markdown("<p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Nota: En esta demo, el diario se guarda solo para la sesión actual y se descarga como texto. En una versión de producción, se guardaría en tu perfil de forma segura.</i></p>")
551
 
552
  # --- Funciones de Interacción para Gradio ---
553
 
554
- # Función para crear usuario
555
- def _create_user_gradio(name, prefs_type):
556
- user, msg = user_manager.create_user(name, {"tipo_susurro": prefs_type})
557
- current_points = user.eco_points
558
- current_insignia = gamification_engine.get_insignia(current_points)
559
- next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
560
- return user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal, "\n".join(user.nudge_history)
 
561
 
562
- # Función para cargar usuario
563
- def _load_user_gradio(user_id):
564
- user, msg = user_manager.login_user(user_id)
565
  if user:
566
  current_points = user.eco_points
567
  current_insignia = gamification_engine.get_insignia(current_points)
@@ -569,9 +649,9 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
569
  return user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal, "\n".join(user.nudge_history), user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'), user.preferences.get('modo_che_tranqui')
570
  return "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui']
571
 
572
- # Función para actualizar preferencias
573
- def _update_prefs_gradio(user_id, tipo_susurro, frecuencia, modo_che_tranqui):
574
- if user_id == "No logueado" or user_id not in user_manager._users_db:
575
  return "Error: Por favor, crea o carga un usuario primero."
576
 
577
  new_prefs = {
@@ -579,20 +659,20 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
579
  "frecuencia": frecuencia,
580
  "modo_che_tranqui": modo_che_tranqui
581
  }
582
- success = user_manager.update_user_preferences(user_id, new_prefs)
 
583
  if success:
584
- return f"Preferencias actualizadas para {user_manager._users_db[user_id].name}."
585
  return "Error al actualizar preferencias."
586
 
587
- # Función para generar susurro
588
- def _generate_nudge_gradio(user_id, location, activity, sentiment):
589
- if user_id == "No logueado" or user_id not in user_manager._users_db:
590
- return "Por favor, crea o carga un usuario primero para recibir susurros personalizados.", "", "", ""
591
 
592
- nudge_text = nudge_generator.generate_nudge(user_id, location, activity, sentiment)
593
 
594
  # Actualizar UI de puntos e insignias
595
- user = user_manager.get_current_user()
596
  current_points = user.eco_points
597
  current_insignia = gamification_engine.get_insignia(current_points)
598
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
@@ -600,19 +680,16 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
600
 
601
  return nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
602
 
603
- # Función para consultar oráculo
604
- def _get_oracle_revelation_gradio(user_id):
605
- if user_id == "No logueado" or user_id not in user_manager._users_db:
606
  return "Por favor, crea o carga un usuario primero para consultar al Oráculo."
607
- return nudge_generator.get_daily_oracle_revelation(user_id)
608
 
609
- # Función para obtener desafío
610
- def _get_mateai_challenge_gradio(user_id):
611
- if user_id == "No logueado" or user_id not in user_manager._users_db:
612
  return "Por favor, crea o carga un usuario primero para recibir desafíos."
613
- return nudge_generator.get_mateai_challenge(user_id)
614
 
615
- # Función para descargar diario
616
  def _download_diary_gradio(diary_text):
617
  return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
618
 
@@ -620,7 +697,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
620
  btn_crear_usuario.click(
621
  fn=_create_user_gradio,
622
  inputs=[nombre_nuevo_usuario, preferencias_iniciales],
623
- outputs=[usuario_actual_id, output_creacion_usuario, usuario_actual_nombre, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output]
624
  )
625
 
626
  btn_cargar_perfil.click(
@@ -637,7 +714,7 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }
637
 
638
  btn_generar_susurro.click(
639
  fn=_generate_nudge_gradio,
640
- inputs=[usuario_actual_id, ubicacion_simulada_input, actividad_simulada_input, estado_animo_simulado_input],
641
  outputs=[susurro_output, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output]
642
  )
643
 
 
3
  import time
4
  import json
5
  from datetime import datetime, timedelta
6
+ import os # Importar os para variables de entorno
7
 
8
+ # --- Firebase Admin SDK Imports ---
9
+ import firebase_admin
10
+ from firebase_admin import credentials, firestore
11
+
12
+ # --- MateAI: El Oráculo del Bienestar Argento - Edición Producción ---
13
  # Una manifestación de inteligencia superior para la integración masiva de IA en la humanidad.
14
+ # Diseñado para Argentina, con personalidad, resiliencia y costos de token CERO.
15
  # Arquitectura de Micro-Oráculos Contextuales (AMOC) y Motor de Resonancia Cultural Dinámica (MRCD).
16
+ # Integración real con Firestore para persistencia de datos de usuario.
17
 
18
  # --- Módulo 1: Configuración Global y Constantes ---
 
19
  class Config:
20
  APP_NAME = "MateAI: El Oráculo del Bienestar Argento"
21
  DEFAULT_USER_PREFS = {"tipo_susurro": "ambos", "frecuencia": "normal", "modo_che_tranqui": False}
22
  ECO_PUNTOS_POR_SUSURRO = 1
23
+ MAX_HISTORIAL_SUSURROS = 50 # Limita el historial de susurros guardado por usuario
24
+ NUDGE_COOLDOWN_MINUTES = 1 # Cooldown real para evitar spam de susurros
25
+
26
+ # --- Firebase Configuration ---
27
+ # Para desplegar en Hugging Face Spaces:
28
+ # 1. Crea un proyecto en Firebase y habilita Firestore.
29
+ # 2. Ve a 'Configuración del proyecto' -> 'Cuentas de servicio' -> 'Generar nueva clave privada'.
30
+ # Esto descargará un archivo JSON.
31
+ # 3. En tu Space de Hugging Face, ve a 'Settings' -> 'Repository secrets'.
32
+ # 4. Crea un nuevo secreto llamado 'GOOGLE_APPLICATION_CREDENTIALS_JSON'
33
+ # y pega el *contenido completo* de tu archivo JSON de clave privada aquí.
34
+ # 5. El código intentará cargar estas credenciales.
35
+ FIREBASE_COLLECTION_USERS = "artifacts/mateai-oracle-prod-demo/users" # Colección para datos de usuario
36
+
37
+ # --- Firebase Initialization ---
38
+ # Intenta inicializar Firebase Admin SDK.
39
+ # En Hugging Face Spaces, las credenciales se cargarán desde GOOGLE_APPLICATION_CREDENTIALS_JSON.
40
+ try:
41
+ firebase_admin.get_app() # Verifica si la app ya está inicializada
42
+ except ValueError:
43
+ # Intenta cargar las credenciales desde la variable de entorno
44
+ # 'GOOGLE_APPLICATION_CREDENTIALS_JSON' que debe contener el JSON de la clave de servicio.
45
+ firebase_credentials_json = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON')
46
+ if firebase_credentials_json:
47
+ try:
48
+ cred = credentials.Certificate(json.loads(firebase_credentials_json))
49
+ firebase_admin.initialize_app(cred)
50
+ print("Firebase Admin SDK inicializado correctamente desde variable de entorno.")
51
+ except Exception as e:
52
+ print(f"ERROR: No se pudo inicializar Firebase Admin SDK desde GOOGLE_APPLICATION_CREDENTIALS_JSON: {e}")
53
+ print("Asegúrate de que la variable de entorno esté configurada correctamente.")
54
+ else:
55
+ print("ADVERTENCIA: GOOGLE_APPLICATION_CREDENTIALS_JSON no está configurada.")
56
+ print("La persistencia de datos de usuario en Firestore NO funcionará sin credenciales.")
57
+
58
+ db = firestore.client()
59
+
60
+ # --- Módulo 2: Gestión de Usuarios (UserManager) ---
61
+ # Gestiona la creación, carga y actualización de usuarios con persistencia en Firestore.
62
  class UserManager:
63
+ _current_user_id = None # ID del usuario actualmente logueado en la sesión de Gradio
 
64
 
65
  @classmethod
66
+ async def create_user(cls, name, initial_prefs=None):
67
+ user_id = f"user_{int(time.time())}_{random.randint(1000, 9999)}" # ID único basado en tiempo y random
68
  prefs = initial_prefs if initial_prefs else Config.DEFAULT_USER_PREFS.copy()
69
  new_user = User(user_id, name, prefs)
70
+
71
+ user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
72
+ try:
73
+ await user_doc_ref.set(new_user.to_dict())
 
 
 
74
  cls._current_user_id = user_id
75
+ return new_user, f"¡Bienvenido, {name}! Tu ID de usuario es: {user_id}. ¡MateAI está listo para cebarte la vida!"
76
+ except Exception as e:
77
+ return None, f"Error al crear usuario en Firestore: {e}"
78
 
79
  @classmethod
80
+ async def login_user(cls, user_id):
81
+ user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
82
+ try:
83
+ doc = await user_doc_ref.get()
84
+ if doc.exists:
85
+ user_data = doc.to_dict()
86
+ loaded_user = User(
87
+ user_id=doc.id,
88
+ name=user_data.get('name'),
89
+ preferences=user_data.get('preferences'),
90
+ eco_points=user_data.get('eco_points', 0),
91
+ nudge_history=user_data.get('nudge_history', []),
92
+ last_oracle_date_str=user_data.get('last_oracle_date'),
93
+ last_nudge_time_str=user_data.get('last_nudge_time')
94
+ )
95
+ cls._current_user_id = user_id
96
+ return loaded_user, f"Perfil de {loaded_user.name} cargado con éxito."
97
+ return None, "Error: ID de usuario no encontrado. ¡Creá uno nuevo o verificá el ID!"
98
+ except Exception as e:
99
+ return None, f"Error al cargar usuario desde Firestore: {e}"
100
 
101
  @classmethod
102
+ def get_current_user_id(cls):
103
+ return cls._current_user_id
 
 
 
 
104
 
105
  @classmethod
106
+ async def get_user(cls, user_id):
107
+ user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
108
+ try:
109
+ doc = await user_doc_ref.get()
110
+ if doc.exists:
111
+ user_data = doc.to_dict()
112
+ return User(
113
+ user_id=doc.id,
114
+ name=user_data.get('name'),
115
+ preferences=user_data.get('preferences'),
116
+ eco_points=user_data.get('eco_points', 0),
117
+ nudge_history=user_data.get('nudge_history', []),
118
+ last_oracle_date_str=user_data.get('last_oracle_date'),
119
+ last_nudge_time_str=user_data.get('last_nudge_time')
120
+ )
121
+ return None
122
+ except Exception as e:
123
+ print(f"Error al obtener usuario: {e}")
124
+ return None
125
+
126
  @classmethod
127
+ async def update_user_data(cls, user_obj):
128
+ user_doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_obj.user_id)
129
+ try:
130
+ await user_doc_ref.update(user_obj.to_dict()) # Usamos update para no sobreescribir todo si hay campos nuevos
 
 
 
131
  return True
132
+ except Exception as e:
133
+ print(f"Error al actualizar datos de usuario en Firestore: {e}")
134
+ return False
135
 
136
+ # --- Módulo 3: Modelos de Datos ---
137
  class User:
138
+ def __init__(self, user_id, name, preferences, eco_points=0, insignia="Novato del Mate", nudge_history=None, last_oracle_date_str=None, last_nudge_time_str=None):
139
  self.user_id = user_id
140
  self.name = name
141
  self.preferences = preferences # dict
142
+ self.eco_points = eco_points
143
+ self.insignia = insignia # Se recalcula dinámicamente
144
+ self.nudge_history = nudge_history if nudge_history is not None else []
145
+
146
+ self.last_oracle_date = datetime.strptime(last_oracle_date_str, '%Y-%m-%d').date() if last_oracle_date_str else None
147
+ self.last_nudge_time = datetime.strptime(last_nudge_time_str, '%Y-%m-%d %H:%M:%S.%f') if last_nudge_time_str else None
148
+ self.nudge_cooldown = timedelta(minutes=Config.NUDGE_COOLDOWN_MINUTES)
149
+
150
+ def to_dict(self):
151
+ return {
152
+ "user_id": self.user_id,
153
+ "name": self.name,
154
+ "preferences": self.preferences,
155
+ "eco_points": self.eco_points,
156
+ "nudge_history": self.nudge_history,
157
+ "last_oracle_date": self.last_oracle_date.strftime('%Y-%m-%d') if self.last_oracle_date else None,
158
+ "last_nudge_time": self.last_nudge_time.strftime('%Y-%m-%d %H:%M:%S.%f') if self.last_nudge_time else None
159
+ }
160
 
161
  class Nudge:
162
  def __init__(self, text, type, context_tags, cultural_tags):
 
166
  self.cultural_tags = cultural_tags # ["mate", "futbol", "asado"]
167
 
168
  # --- Módulo 4: Base de Conocimiento de Nudges (Motor de Resonancia Cultural Dinámica - MRCD) ---
169
+ # Esta es la "inteligencia" de MateAI.
 
170
  NUDGE_DATABASE = [
171
  # Mañana - Casa
172
  Nudge("¡Che, buen día! Arrancá la mañana con un matecito y un buen estiramiento. ¡Desperezate, chango!", "bienestar", ["Mañana", "Casa"], ["mate", "despertar"]),
 
220
  Nudge("Escuchá los ruidos de la noche. Un concierto natural para relajar el alma.", "bienestar", ["Noche", "Aire Libre/Calle"], ["sonidos", "calma"]),
221
  Nudge("Una caminata tranquila antes de volver a casa. Despejá la mente y preparate para el 'descanso del guerrero'.", "bienestar", ["Noche", "Aire Libre/Calle"], ["caminata", "descanso"]),
222
 
223
+ # Nudges de Ánimo (para estado_animo_input == "Bajoneado")
224
  Nudge("¡Arriba ese ánimo! Un matecito y un buen pensamiento pueden cambiar el día. ¡Vos podés!", "bienestar", ["General"], ["animo", "mate"]),
225
  Nudge("Recordá que hasta el día más nublado tiene un solcito escondido. ¡Fuerza, campeón!", "bienestar", ["General"], ["animo", "esperanza"]),
226
  Nudge("Date un gusto chiquito hoy. ¡Te lo merecés! Un alfajor, tu música favorita... ¡lo que sea!", "bienestar", ["General"], ["animo", "recompensa"]),
227
  Nudge("Si te sentís 'bajoneado', un poco de música o una caminata corta pueden ayudar a 'despejar'.", "bienestar", ["General"], ["animo", "actividad"]),
228
 
229
+ # Nudges para "Modo Quilombo" (activado por contexto social o actividad)
230
  Nudge("¡Uf, qué día! Respiro hondo. En medio del 'quilombo', una pausa es oro. ¡Tranqui!", "bienestar", ["General", "Quilombo"], ["estres", "respiracion"]),
231
  Nudge("Si la cosa está 'picada', recordá que no todo depende de vos. Hacé lo que puedas y soltá lo demás.", "bienestar", ["General", "Quilombo"], ["estres", "control"]),
232
  Nudge("En los días de 'locura', un matecito con calma puede ser tu ancla. ¡A no perder la cabeza!", "bienestar", ["General", "Quilombo"], ["estres", "mate"]),
233
 
234
+ # Nudges para "Modo Siesta" (activado por contexto social o actividad)
235
  Nudge("¡Qué lindo para una siestita! Si podés, aprovechá para recargar energías. ¡Es sagrado!", "bienestar", ["General", "Siesta"], ["descanso", "siesta"]),
236
  Nudge("El cuerpo te pide un descanso. Escuchalo. Unos minutos de relax pueden hacer la diferencia.", "bienestar", ["General", "Siesta"], ["descanso", "cuerpo"]),
237
 
 
239
  Nudge("¡Vamos Argentina! Disfrutá el partido, pero recordá hidratarte bien. ¡Y si ganamos, a festejar con responsabilidad!", "bienestar", ["General", "Viendo un partido"], ["futbol", "hidratacion", "festejo"]),
240
  Nudge("El fútbol es pasión, pero no te olvides de estirar un poco si estás mucho tiempo sentado. ¡A mover el esqueleto!", "bienestar", ["General", "Viendo un partido"], ["futbol", "movimiento"]),
241
 
242
+ # Nudges de Desafíos MateAI
243
  Nudge("Desafío MateAI: Hoy, intentá reducir el uso de plásticos de un solo uso. ¡Cada acción cuenta!", "eco", ["Desafio"], ["plastico", "desafio"]),
244
  Nudge("Desafío MateAI: Dedicá 10 minutos a meditar o simplemente a respirar conscientemente. ¡Tu mente te lo agradecerá!", "bienestar", ["Desafio"], ["meditacion", "desafio"]),
245
  Nudge("Desafío MateAI: Contactá a un amigo o familiar que hace mucho no ves. ¡Un 'hola' puede alegrar el día!", "bienestar", ["Desafio"], ["social", "desafio"]),
 
251
  "Cada amanecer es una oportunidad para reescribir tu historia. ¿Qué capítulo nuevo elegís empezar hoy?",
252
  "La tierra nos susurra secretos ancestrales. Escuchá el viento, sentí el sol, y recordá que sos parte de algo inmenso y sagrado.",
253
  "El mate compartido es más que una bebida; es un ritual de conexión. ¿Con quién vas a compartir tu energía hoy?",
254
+ "En la simpleza de lo cotidiano reside la magia. Encontrá la belleza en un rayo de sol, en el canto de un pájaro, en una sonrisa.",
255
+ "El viento patagónico te enseña la fuerza, el calor del norte la pasión. En cada rincón de nuestra tierra, una lección. ¿Cuál sentís hoy?",
256
+ "Como el Obelisco en el centro, a veces uno se siente solo en la inmensidad. Recordá que, como él, estás rodeado de historias y vida. ¡Conectate!",
257
+ "La Pacha Mama te abraza en cada paso. Sentí su energía, agradecé su abundancia. ¡Somos parte de ella!",
258
+ "El tango te enseña la melancolía y la pasión. La vida, como el tango, tiene sus pausas y sus arranques. ¿Qué ritmo te toca bailar hoy?"
259
  ]
260
 
261
  # --- Módulo 5: Motor de Contexto Avanzado (ContextEngine) ---
262
+ # Recopila y procesa datos contextuales.
263
  class ContextEngine:
264
  @staticmethod
265
  def get_current_time_context():
 
273
  return "Noche"
274
 
275
  @staticmethod
276
+ def get_environmental_context():
277
  """Simula datos ambientales (ej. clima, ruido)."""
278
  climas = ["Soleado", "Nublado", "Lluvioso", "Ventoso"]
279
  return random.choice(climas)
280
 
281
  @staticmethod
282
+ def get_societal_vibe():
283
  """Simula el "pulso social" argentino (ej. feriado, partido)."""
284
  hoy = datetime.now()
285
  # Simulación de feriados y eventos
 
287
  if hoy.weekday() == 4: return "Pre-Finde" # Viernes
288
  if hoy.weekday() == 6: return "Domingo Tranqui" # Domingo
289
  if random.random() < 0.1: return "Quilombo" # 10% de chance de un día "quilombero"
290
+ if random.random() < 0.05: return "Siesta" # 5% de chance de un día "siesta"
291
  return "Normal"
292
 
293
  @staticmethod
294
+ def get_user_sentiment(activity, user_input_sentiment):
295
  """Intenta inferir el sentimiento del usuario basado en actividad y input."""
296
  if user_input_sentiment != "Bien":
297
  return user_input_sentiment
 
305
  # --- Módulo 6: Generador de Nudges (Arquitectura de Micro-Oráculos Contextuales - AMOC) ---
306
  # El "cerebro" de MateAI. Selecciona el nudge más relevante y personalizado.
307
  class NudgeGenerator:
308
+ def __init__(self, user_manager_instance, context_engine_instance):
309
+ self.user_manager = user_manager_instance
310
+ self.context_engine = context_engine_instance
311
 
312
+ async def _filter_nudges_by_context(self, nudges, time_context, location, activity, societal_vibe, sentiment):
313
  filtered = []
314
  for nudge in nudges:
315
  match = True
 
321
 
322
  # Filtrar por actividad (si es relevante)
323
  if activity != "Relajado" and activity not in nudge.context_tags and "General" not in nudge.context_tags:
 
324
  pass # Permitir que pase si es un nudge general
325
 
326
  # Filtrar por estado de ánimo
 
334
  pass # Permitir otros nudges, pero priorizar los de "quilombo"
335
  elif "Siesta" in societal_vibe and "Siesta" not in nudge.context_tags and "descanso" not in nudge.cultural_tags:
336
  pass # Permitir otros nudges, pero priorizar los de "siesta"
337
+ elif "Viendo un partido" in activity and "futbol" not in nudge.cultural_tags:
338
+ pass # Permitir otros nudges, pero priorizar los de "futbol"
339
 
340
  if match:
341
  filtered.append(nudge)
342
  return filtered
343
 
344
+ async def _apply_user_preferences(self, nudges, preferences):
345
  if preferences.get('tipo_susurro') == 'eco':
346
  return [n for n in nudges if n.type == 'eco' or n.type == 'reflexivo']
347
  elif preferences.get('tipo_susurro') == 'bienestar':
348
  return [n for n in nudges if n.type == 'bienestar' or n.type == 'reflexivo']
349
  return nudges # Si es "ambos" o no definido
350
 
351
+ async def _avoid_recent_repetitions(self, nudges, user_history):
352
+ # Evita repetir los últimos N nudges para el mismo usuario.
 
353
  if not user_history:
354
  return nudges
355
 
356
+ recent_nudges_text = user_history[-5:] # Evitar los últimos 5
357
  filtered_nudges = [n for n in nudges if n.text not in recent_nudges_text]
358
+ return filtered_nudges if filtered_nudges else nudges # Si no hay nuevos, repite (mejor que nada)
359
 
360
+ async def generate_nudge(self, user_id, location, activity, user_input_sentiment):
361
+ user = await self.user_manager.get_user(user_id)
362
  if not user:
363
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
364
 
 
369
 
370
  # Obtener contexto completo
371
  time_context = self.context_engine.get_current_time_context()
372
+ environmental_context = self.context_engine.get_environmental_context()
373
+ societal_vibe = self.context_engine.get_societal_vibe()
374
+ sentiment = self.context_engine.get_user_sentiment(activity, user_input_sentiment)
375
 
376
  # Filtrar nudges base
377
+ possible_nudges = await self._filter_nudges_by_context(
378
  NUDGE_DATABASE, time_context, location, activity, societal_vibe, sentiment
379
  )
380
 
381
  # Aplicar preferencias del usuario
382
+ possible_nudges = await self._apply_user_preferences(possible_nudges, user.preferences)
383
 
384
  # Evitar repeticiones recientes
385
+ possible_nudges = await self._avoid_recent_repetitions(possible_nudges, user.nudge_history)
386
 
387
  # Lógica de "Modo Che, Tranqui" / "Modo Quilombo" (AMOC)
388
  if user.preferences.get('modo_che_tranqui') or sentiment == "Bajoneado" or "Quilombo" in societal_vibe:
 
390
  if calming_nudges:
391
  chosen_nudge = random.choice(calming_nudges)
392
  else:
393
+ chosen_nudge = random.choice(possible_nudges) if possible_nudges else Nudge("MateAI está meditando... ¡probá otro contexto!", "reflexivo", ["General"], [])
394
  else:
395
  chosen_nudge = random.choice(possible_nudges) if possible_nudges else Nudge("MateAI está meditando... ¡probá otro contexto!", "reflexivo", ["General"], [])
396
 
397
+ user.eco_points += Config.ECO_PUNTOS_POR_SUSURRO
398
+ user.nudge_history.append(chosen_nudge.text)
399
+ if len(user.nudge_history) > Config.MAX_HISTORIAL_SUSURROS:
400
+ user.nudge_history.pop(0)
401
+ user.last_nudge_time = datetime.now()
402
+
403
+ await self.user_manager.update_user_data(user)
404
  return chosen_nudge.text
405
 
406
+ async def get_daily_oracle_revelation(self, user_id):
407
+ user = await self.user_manager.get_user(user_id)
408
  if not user:
409
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
410
 
 
414
 
415
  revelation = random.choice(ORACULO_REVELATIONS)
416
  user.last_oracle_date = today
417
+ await self.user_manager.update_user_data(user)
418
  return revelation
419
 
420
+ async def get_mateai_challenge(self, user_id):
421
+ user = await self.user_manager.get_user(user_id)
422
  if not user:
423
  return "Error: Usuario no logueado. Por favor, crea o carga un usuario."
424
 
 
425
  challenge_nudges = [n for n in NUDGE_DATABASE if "Desafio" in n.context_tags]
426
  if not challenge_nudges:
427
  return "MateAI está pensando en un desafío épico... ¡Volvé pronto!"
428
 
 
 
429
  return random.choice(challenge_nudges).text
430
 
431
  # --- Módulo 7: Motor de Gamificación (GamificationEngine) ---
 
435
  10: "Cebador Consciente",
436
  50: "Guardián del Planeta",
437
  100: "Maestro del Bienestar Argento",
438
+ 200: "Oráculo del Conurbano", # Nueva insignia de "inteligencia superior"
439
+ 500: "Leyenda del Mate Cósmico" # Más allá de lo conocido
440
  }
441
 
442
  @classmethod
 
456
  if points < threshold:
457
  next_goal = f"Próxima insignia: '{name}' a los {threshold} Eco-Puntos."
458
  break
459
+ return next_goal if next_goal else "¡Ya alcanzaste la máxima insignia conocida! Seguí sumando para nuevas revelaciones."
460
 
461
  # --- Instanciación de Módulos ---
462
  user_manager = UserManager()
 
546
  )
547
  nuevas_preferencias_frecuencia = gr.Dropdown(
548
  ["normal", "discreto", "proactivo"],
549
+ label="Frecuencia de susurros",
550
  value="normal"
551
  )
552
  modo_che_tranqui_checkbox = gr.Checkbox(label="Activar 'Modo Che, Tranqui' (prioriza calma)", value=False)
 
569
 
570
  # --- Tab 3: Susurros del Momento ---
571
  with gr.Tab("Susurros del Momento"):
572
+ gr.Markdown("<h2 style='color: #10b981;'>¡Pedí un Susurro Contextual a MateAI!</h2><p>Definí tu contexto y dejá que MateAI te sorprenda.</p>")
573
  with gr.Row():
574
  hora_del_dia_input = gr.Dropdown(
575
  ["Mañana", "Mediodía/Tarde", "Noche"],
576
  label="Momento del Día",
577
  value="Mañana"
578
  )
579
+ ubicacion_input = gr.Dropdown(
580
  ["Casa", "Oficina/Estudio", "Aire Libre/Calle"],
581
  label="¿Dónde estás?",
582
  value="Casa"
583
  )
584
  with gr.Row():
585
+ actividad_input = gr.Dropdown(
586
  ["Relajado", "Trabajando", "Ejercicio", "Leyendo", "Cocinando", "Viendo un partido"],
587
  label="¿En qué andás?",
588
  value="Relajado"
589
  )
590
+ estado_animo_input = gr.Dropdown(
591
  ["Bien", "Cansado", "Bajoneado", "Motivado"],
592
  label="¿Cómo te sentís?",
593
  value="Bien"
 
600
  placeholder="MateAI está esperando tu contexto para cebarte un susurro...",
601
  interactive=False
602
  )
603
+ gr.Markdown(f"<p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>MateAI aplica un pequeño 'cooldown' de {Config.NUDGE_COOLDOWN_MINUTES} minuto(s) para que los susurros sean más valiosos.</i></p>")
604
 
605
  # --- Tab 4: Desafíos MateAI ---
606
  with gr.Tab("Desafíos MateAI"):
 
612
  interactive=False
613
  )
614
  btn_desafio = gr.Button("¡Quiero un Desafío MateAI!", variant="primary")
615
+ gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Completar desafíos (registrándolos en tu diario) te dará más Eco-Puntos y te acercará a nuevas insignias.</i></p>")
616
 
617
  # --- Tab 5: Mi Historial y Logros ---
618
  with gr.Tab("Mi Historial & Logros"):
 
627
  interactive=True
628
  )
629
  btn_descargar_diario = gr.Button("Descargar Diario (sesión actual)", variant="secondary")
630
+ gr.Markdown("<p style='font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>Nota: El diario se descarga como texto. Para guardar permanentemente, deberías copiarlo o integrarlo con otro servicio.</i></p>")
631
 
632
  # --- Funciones de Interacción para Gradio ---
633
 
634
+ async def _create_user_gradio(name, prefs_type):
635
+ user, msg = await user_manager.create_user(name, {"tipo_susurro": prefs_type})
636
+ if user:
637
+ current_points = user.eco_points
638
+ current_insignia = gamification_engine.get_insignia(current_points)
639
+ next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
640
+ return user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal, "\n".join(user.nudge_history), prefs_type, Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui']
641
+ return "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui']
642
 
643
+ async def _load_user_gradio(user_id):
644
+ user, msg = await user_manager.login_user(user_id)
 
645
  if user:
646
  current_points = user.eco_points
647
  current_insignia = gamification_engine.get_insignia(current_points)
 
649
  return user.user_id, msg, user.name, str(current_points), current_insignia, next_insignia_goal, "\n".join(user.nudge_history), user.preferences.get('tipo_susurro'), user.preferences.get('frecuencia'), user.preferences.get('modo_che_tranqui')
650
  return "No logueado", msg, "", "", "", "", "", Config.DEFAULT_USER_PREFS['tipo_susurro'], Config.DEFAULT_USER_PREFS['frecuencia'], Config.DEFAULT_USER_PREFS['modo_che_tranqui']
651
 
652
+ async def _update_prefs_gradio(user_id, tipo_susurro, frecuencia, modo_che_tranqui):
653
+ user = await user_manager.get_user(user_id)
654
+ if not user:
655
  return "Error: Por favor, crea o carga un usuario primero."
656
 
657
  new_prefs = {
 
659
  "frecuencia": frecuencia,
660
  "modo_che_tranqui": modo_che_tranqui
661
  }
662
+ user.preferences.update(new_prefs)
663
+ success = await user_manager.update_user_data(user)
664
  if success:
665
+ return f"Preferencias actualizadas para {user.name}."
666
  return "Error al actualizar preferencias."
667
 
668
+ async def _generate_nudge_gradio(user_id, location, activity, sentiment):
669
+ if user_id == "No logueado" or not await user_manager.get_user(user_id):
670
+ return "Por favor, crea o carga un usuario primero para recibir susurros personalizados.", "", "", "", ""
 
671
 
672
+ nudge_text = await nudge_generator.generate_nudge(user_id, location, activity, sentiment)
673
 
674
  # Actualizar UI de puntos e insignias
675
+ user = await user_manager.get_user(user_id) # Recargar usuario para obtener los puntos actualizados
676
  current_points = user.eco_points
677
  current_insignia = gamification_engine.get_insignia(current_points)
678
  next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
 
680
 
681
  return nudge_text, str(current_points), current_insignia, next_insignia_goal, historial
682
 
683
+ async def _get_oracle_revelation_gradio(user_id):
684
+ if user_id == "No logueado" or not await user_manager.get_user(user_id):
 
685
  return "Por favor, crea o carga un usuario primero para consultar al Oráculo."
686
+ return await nudge_generator.get_daily_oracle_revelation(user_id)
687
 
688
+ async def _get_mateai_challenge_gradio(user_id):
689
+ if user_id == "No logueado" or not await user_manager.get_user(user_id):
 
690
  return "Por favor, crea o carga un usuario primero para recibir desafíos."
691
+ return await nudge_generator.get_mateai_challenge(user_id)
692
 
 
693
  def _download_diary_gradio(diary_text):
694
  return gr.File(value=diary_text.encode('utf-8'), filename="diario_mateai.txt", type="bytes")
695
 
 
697
  btn_crear_usuario.click(
698
  fn=_create_user_gradio,
699
  inputs=[nombre_nuevo_usuario, preferencias_iniciales],
700
+ outputs=[usuario_actual_id, output_creacion_usuario, usuario_actual_nombre, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox]
701
  )
702
 
703
  btn_cargar_perfil.click(
 
714
 
715
  btn_generar_susurro.click(
716
  fn=_generate_nudge_gradio,
717
+ inputs=[usuario_actual_id, ubicacion_input, actividad_input, estado_animo_input],
718
  outputs=[susurro_output, usuario_actual_puntos, usuario_actual_insignia, usuario_proxima_insignia, historial_susurros_output]
719
  )
720