Lukeetah commited on
Commit
47c6db4
·
verified ·
1 Parent(s): 2aec566

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +664 -0
app.py ADDED
@@ -0,0 +1,664 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ 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):
90
+ self.text = text
91
+ self.type = type # "eco", "bienestar", "reflexivo"
92
+ self.context_tags = context_tags # ["Mañana", "Casa", "Trabajando"]
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"]),
101
+ Nudge("Antes de prender todo, ¿entra el solcito? Aprovechá la luz natural, que es gratis y buena onda.", "eco", ["Mañana", "Casa"], ["sol", "ahorro"]),
102
+ Nudge("Mirá por la ventana, ¿cómo está el cielo? Un ratito de aire fresco te 'desenchufa' para arrancar el día.", "bienestar", ["Mañana", "Casa"], ["naturaleza", "calma"]),
103
+ Nudge("Si tenés plantitas, ¿ya las regaste? Un mimo verde para empezar el día con buena energía.", "eco", ["Mañana", "Casa"], ["plantas", "cuidado"]),
104
+ Nudge("¿Ya pensaste qué vas a desayunar? Elegí algo que te dé energía para el día, como frutas o tostadas con dulce de leche. ¡A cargar pilas!", "bienestar", ["Mañana", "Casa", "Cocinando"], ["desayuno", "energia"]),
105
+
106
+ # Mañana - Oficina/Estudio
107
+ Nudge("Mientras esperás que hierva el agua para el café, pensá en una meta chiquita para hoy. ¡Ponele garra!", "bienestar", ["Mañana", "Oficina/Estudio", "Trabajando"], ["cafe", "metas"]),
108
+ Nudge("Si vas en bondi o subte, mirá el paisaje. Desconectate un toque del celu y observá la ciudad que no duerme.", "bienestar", ["Mañana", "Oficina/Estudio", "Transporte"], ["ciudad", "atencion_plena"]),
109
+ Nudge("Unos mates con los compañeros para arrancar la jornada. ¡La buena onda se contagia!", "bienestar", ["Mañana", "Oficina/Estudio"], ["mate", "social"]),
110
+ Nudge("Antes de arrancar con el 'quilombo', ordená tu escritorio. Un espacio despejado, una mente más clara.", "bienestar", ["Mañana", "Oficina/Estudio", "Trabajando"], ["orden", "productividad"]),
111
+
112
+ # Mañana - Aire Libre/Calle
113
+ Nudge("¡Qué lindo día para respirar hondo! Llenate los pulmones de aire puro y sentí la energía.", "bienestar", ["Mañana", "Aire Libre/Calle", "Ejercicio"], ["respiracion", "energia"]),
114
+ Nudge("Escuchá los ruidos de la calle. ¿Qué te cuentan? Los sonidos de la ciudad también son música.", "bienestar", ["Mañana", "Aire Libre/Calle"], ["sonidos", "atencion_plena"]),
115
+ Nudge("Si ves un árbol copado, frená un segundo. La naturaleza siempre nos regala un respiro.", "bienestar", ["Mañana", "Aire Libre/Calle"], ["naturaleza", "pausa"]),
116
+
117
+ # Mediodía/Tarde - Casa
118
+ Nudge("¿Un bajón? Cortá la rutina con una fruta o un vaso de agua. ¡Hidratarse es clave!", "bienestar", ["Mediodía/Tarde", "Casa"], ["hidratacion", "snack"]),
119
+ Nudge("Antes de prender la tele, ¿qué tal un libro o charlar con alguien? Desconectarse es reconectarse.", "bienestar", ["Mediodía/Tarde", "Casa"], ["ocio", "conexion"]),
120
+ Nudge("¿Hay algo para reciclar? Separá los cartones, plásticos... ¡Cada granito de arena suma!", "eco", ["Mediodía/Tarde", "Casa"], ["reciclaje", "accion"]),
121
+ Nudge("Si tenés ropa para lavar, ¿aprovechás el solcito? Secar al aire ahorra energía y deja un olorcito a campo.", "eco", ["Mediodía/Tarde", "Casa"], ["ahorro", "sol"]),
122
+ Nudge("¿Ya almorzaste? Recordá que una buena comida casera te da la nafta para seguir el día. ¡Buen provecho!", "bienestar", ["Mediodía/Tarde", "Casa", "Cocinando"], ["almuerzo", "nutricion"]),
123
+
124
+ # Mediodía/Tarde - Oficina/Estudio
125
+ Nudge("Si estás 'quemado', levantate y estirá las piernas. Unos pasos te despejan la cabeza.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["pausa", "movimiento"]),
126
+ Nudge("¿Reunión eterna? Hacé una pausa mental de 30 segundos. Solo respirá y volvé a la carga.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["respiracion", "estres"]),
127
+ Nudge("Apagá las luces si salís un rato. ¡Cuidar la energía es cuidar el bolsillo y el planeta!", "eco", ["Mediodía/Tarde", "Oficina/Estudio"], ["ahorro", "energia"]),
128
+ Nudge("Antes de seguir con el laburo, ¿ya almorzaste? Una buena comida te da la nafta para seguir.", "bienestar", ["Mediodía/Tarde", "Oficina/Estudio", "Trabajando"], ["almuerzo", "energia"]),
129
+
130
+ # Mediodía/Tarde - Aire Libre/Calle
131
+ Nudge("El solcito de la tarde es ideal para recargar pilas. ¡Disfrutá el momento!", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["sol", "energia"]),
132
+ Nudge("Mirá las nubes, ¿qué formas ves? La imaginación vuela libre.", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["nubes", "imaginacion"]),
133
+ Nudge("Si encontrás un banquito, sentate un rato. Observá la gente, la vida que pasa. ¡Es un 'recreo' para el alma!", "bienestar", ["Mediodía/Tarde", "Aire Libre/Calle"], ["pausa", "observacion"]),
134
+
135
+ # Noche - Casa
136
+ Nudge("Antes de cenar, pensá en algo bueno que te pasó hoy. ¡Agradecer es un 'mimo' para el alma!", "bienestar", ["Noche", "Casa"], ["gratitud", "reflexion"]),
137
+ Nudge("Dejá la ropa lista para mañana. Un pequeño orden te ahorra el estrés mañanero.", "bienestar", ["Noche", "Casa"], ["orden", "estres"]),
138
+ Nudge("¿Ya guardaste el celu? Desconectate de las pantallas al menos media hora antes de dormir. ¡Tu cabeza te lo va a agradecer!", "bienestar", ["Noche", "Casa"], ["descanso", "pantallas"]),
139
+ Nudge("Si vas a cocinar, ¿ya pensaste en aprovechar las sobras? ¡Acá no se tira nada!", "eco", ["Noche", "Casa", "Cocinando"], ["desperdicio_cero", "cocina"]),
140
+
141
+ # Noche - Oficina/Estudio
142
+ Nudge("Terminá el día haciendo una lista de lo que lograste. ¡Valorá tu esfuerzo, campeón!", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["logros", "autoestima"]),
143
+ Nudge("Dejá todo ordenado para mañana. Un buen cierre de día es un buen comienzo del siguiente.", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["orden", "productividad"]),
144
+ Nudge("Evitá los mails o noticias que te estresen antes de irte. ¡Protegé tu descanso, que es sagrado!", "bienestar", ["Noche", "Oficina/Estudio", "Trabajando"], ["estres", "descanso"]),
145
+
146
+ # Noche - Aire Libre/Calle
147
+ Nudge("Si es seguro, mirá las estrellas. Te recuerdan lo inmenso que es todo y lo chiquitos que somos. ¡Bajá un cambio!", "bienestar", ["Noche", "Aire Libre/Calle"], ["estrellas", "reflexion"]),
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
+
166
+ # Nudges para "Viendo un partido"
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"]),
174
+ ]
175
+
176
+ ORACULO_REVELATIONS = [
177
+ "La verdadera riqueza no se mide en bienes, sino en la calma del alma y la conexión con el entorno. ¿Qué tesoro descubriste hoy?",
178
+ "El río de la vida fluye constante. No te aferres a la orilla; aprendé a navegar sus corrientes con sabiduría y gratitud.",
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():
190
+ """Determina la hora del día para el susurro."""
191
+ hora_actual = datetime.now().hour
192
+ if 6 <= hora_actual < 12:
193
+ return "Mañana"
194
+ elif 12 <= hora_actual < 19:
195
+ return "Mediodía/Tarde"
196
+ else:
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
210
+ if hoy.month == 12 and hoy.day in [24, 25, 31]: return "Festivo"
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
221
+
222
+ if "Trabajando" in activity and random.random() < 0.3: # 30% chance de cansancio si trabaja
223
+ return "Cansado"
224
+ if "Ejercicio" in activity and random.random() < 0.2: # 20% chance de motivacion si hace ejercicio
225
+ return "Motivado"
226
+ return "Bien"
227
+
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
239
+ # Filtrar por tiempo y ubicación (obligatorio)
240
+ if time_context not in nudge.context_tags:
241
+ match = False
242
+ if location not in nudge.context_tags and "General" not in nudge.context_tags:
243
+ match = False
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
251
+ if sentiment == "Bajoneado" and "animo" not in nudge.cultural_tags:
252
+ match = False
253
+ elif sentiment == "Motivado" and "motivacion" not in nudge.cultural_tags and "General" not in nudge.context_tags:
254
+ pass # No filtrar si no es de motivación, permitir otros
255
+
256
+ # Filtrar por pulso social (MRCD)
257
+ if "Quilombo" in societal_vibe and "Quilombo" not in nudge.context_tags and "estres" not in nudge.cultural_tags:
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
+
288
+ # Cooldown para evitar spam de nudges
289
+ if user.last_nudge_time and (datetime.now() - user.last_nudge_time) < user.nudge_cooldown:
290
+ remaining_time = user.nudge_cooldown - (datetime.now() - user.last_nudge_time)
291
+ return f"MateAI necesita un respiro. Volvé a pedir un susurro en {remaining_time.seconds} segundos. ¡La paciencia es una virtud!"
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:
312
+ calming_nudges = [n for n in possible_nudges if "calma" in n.cultural_tags or "estres" in n.cultural_tags or "respiracion" in n.cultural_tags]
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
+
330
+ today = datetime.now().date()
331
+ if user.last_oracle_date and user.last_oracle_date == today:
332
+ return "El Oráculo ya te ha hablado hoy. Volvé mañana para una nueva revelación."
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) ---
353
+ class GamificationEngine:
354
+ INSIGNIAS_MAP = {
355
+ 0: "Novato del Mate",
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
363
+ def get_insignia(cls, points):
364
+ insignia = "Novato del Mate"
365
+ for threshold, name in sorted(cls.INSIGNIAS_MAP.items()):
366
+ if points >= threshold:
367
+ insignia = name
368
+ else:
369
+ break
370
+ return insignia
371
+
372
+ @classmethod
373
+ def get_next_insignia_goal(cls, points):
374
+ next_goal = None
375
+ for threshold, name in sorted(cls.INSIGNIAS_MAP.items()):
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()
383
+ context_engine = ContextEngine()
384
+ nudge_generator = NudgeGenerator(user_manager, context_engine)
385
+ gamification_engine = GamificationEngine() # No necesita instanciar, es de clase
386
+
387
+ # --- Módulo 8: Interfaz Gradio (UI/UX Argento de Nivel Superior) ---
388
+ with gr.Blocks(theme=gr.themes.Soft(), css="footer { display: none !important; }") as demo:
389
+ gr.Markdown(
390
+ """
391
+ <h1 style="text-align: center; color: #10b981; font-size: 3.5em; font-weight: bold; margin-bottom: 0.5em; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
392
+ 🧉 MateAI: El Oráculo del Bienestar Argento 🧉
393
+ </h1>
394
+ <p style="text-align: center; color: #4b5563; font-size: 1.3em; margin-bottom: 2em; line-height: 1.5;">
395
+ Una inteligencia superior diseñada para cebarte la vida con sabiduría contextual y sin costo.
396
+ <br><b>¡Sin APIs de pago, sin consumo de tokens!</b> Solo buena onda, insights profundos y el "sabor" de casa.
397
+ <br><i>La IA que se integra a la humanidad, comprendiendo tu mundo.</i>
398
+ </p>
399
+ <div style="text-align: center; margin-bottom: 40px;">
400
+ <div style="
401
+ width: 220px;
402
+ height: 220px;
403
+ background-color: #38bdf8; /* sky-400 */
404
+ border-radius: 50%;
405
+ display: flex;
406
+ flex-direction: column;
407
+ align-items: center;
408
+ justify-content: center;
409
+ color: white;
410
+ font-weight: 800;
411
+ font-size: 2.8em;
412
+ margin: 0 auto;
413
+ box-shadow: 0 10px 25px rgba(0,0,0,0.4);
414
+ animation: pulse-argentina-superior 3s infinite ease-in-out;
415
+ border: 6px solid #10b981; /* emerald-600 */
416
+ text-shadow: 1px 1px 3px rgba(0,0,0,0.3);
417
+ ">
418
+ <span style="font-size: 0.8em;">✨ Oráculo ✨</span>
419
+ <span>🇦🇷 MateAI 🇦🇷</span>
420
+ </div>
421
+ <style>
422
+ @keyframes pulse-argentina-superior {
423
+ 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.7); }
424
+ 50% { transform: scale(1.05); box-shadow: 0 0 0 20px rgba(56, 189, 248, 0); }
425
+ 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(56, 189, 248, 0); }
426
+ }
427
+ </style>
428
+ </div>
429
+ """
430
+ )
431
+
432
+ # --- Tab 1: Inicio y Perfil ---
433
+ with gr.Tab("Inicio & Perfil"):
434
+ gr.Markdown("<h2 style='color: #10b981;'>¡Bienvenido a tu Espacio MateAI!</h2><p>Acá manejás tu perfil para que MateAI te conozca a fondo.</p>")
435
+ with gr.Row():
436
+ with gr.Column():
437
+ gr.Markdown("<h3 style='color: #38bdf8;'>Crear Nuevo Usuario</h3>")
438
+ nombre_nuevo_usuario = gr.Textbox(label="Tu Nombre o Apodo", placeholder="Ej: Juan, La Colo, El Pibe")
439
+ preferencias_iniciales = gr.Dropdown(
440
+ ["eco", "bienestar", "ambos"],
441
+ label="¿Qué tipo de sabiduría preferís de MateAI?",
442
+ value="ambos"
443
+ )
444
+ btn_crear_usuario = gr.Button("¡Crear Nuevo Usuario MateAI!", variant="primary")
445
+ output_creacion_usuario = gr.Textbox(label="Mensaje de Creación", interactive=False, lines=2)
446
+ with gr.Column():
447
+ gr.Markdown("<h3 style='color: #38bdf8;'>Cargar Perfil Existente</h3>")
448
+ user_id_existente = gr.Textbox(label="Si ya tenés ID, ponelo acá", placeholder="Ej: user_1_1678901234")
449
+ btn_cargar_perfil = gr.Button("Cargar Perfil Existente", variant="secondary")
450
+ output_cargar_perfil = gr.Textbox(label="Estado del Perfil", interactive=False, lines=2)
451
+
452
+ gr.Markdown("<h2 style='color: #10b981; margin-top: 2em;'>Tus Datos MateAI</h2>")
453
+ with gr.Row():
454
+ usuario_actual_id = gr.Textbox(label="ID de Usuario Actual", interactive=False, value="No logueado", show_copy_button=True)
455
+ usuario_actual_nombre = gr.Textbox(label="Nombre", interactive=False)
456
+ with gr.Row():
457
+ usuario_actual_puntos = gr.Textbox(label="Eco-Puntos Gauchos", interactive=False)
458
+ usuario_actual_insignia = gr.Textbox(label="Tu Insignia MateAI", interactive=False)
459
+ usuario_proxima_insignia = gr.Textbox(label="Próxima Meta de Insignia", interactive=False)
460
+
461
+ gr.Markdown("<h3 style='color: #10b981; margin-top: 1.5em;'>Ajustar Preferencias MateAI</h3>")
462
+ nuevas_preferencias_tipo = gr.Dropdown(
463
+ ["eco", "bienestar", "ambos"],
464
+ label="Tipo de susurro preferido",
465
+ value="ambos"
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)
473
+
474
+ btn_actualizar_preferencias = gr.Button("Actualizar Preferencias", variant="primary")
475
+ output_actualizar_preferencias = gr.Textbox(label="Estado de Actualización", interactive=False)
476
+
477
+ # --- Tab 2: El Oráculo del Día ---
478
+ with gr.Tab("El Oráculo del Día"):
479
+ gr.Markdown("<h2 style='text-align: center; color: #10b981;'>✨ La Revelación Diaria del Oráculo MateAI ✨</h2><p style='text-align: center;'>Una sabiduría profunda para iluminar tu jornada. El Oráculo te habla una vez al día.</p>")
480
+ oraculo_output = gr.Textbox(
481
+ label="La Revelación del Oráculo",
482
+ lines=4,
483
+ placeholder="El Oráculo espera para susurrarte su sabiduría...",
484
+ interactive=False,
485
+ elem_id="oraculo-output"
486
+ )
487
+ btn_oraculo = gr.Button("¡Consultar al Oráculo MateAI!", variant="primary")
488
+ gr.Markdown("<p style='text-align: center; font-size: 0.9em; color: #6b7280; margin-top: 1em;'><i>El Oráculo te hablará una vez por día. Volvé mañana para una nueva revelación.</i></p>")
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"
514
+ )
515
+
516
+ btn_generar_susurro = gr.Button("¡Che, MateAI, tirame un susurro!", variant="primary")
517
+ susurro_output = gr.Textbox(
518
+ label="Tu Susurro de Bienestar y Sostenibilidad",
519
+ lines=5,
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"):
527
+ gr.Markdown("<h2 style='text-align: center; color: #10b981;'>🎯 Desafíos MateAI: ¡Ponete a Prueba! 🎯</h2><p style='text-align: center;'>MateAI te propone desafíos para crecer en bienestar y sostenibilidad. ¡Aceptá la misión!</p>")
528
+ desafio_output = gr.Textbox(
529
+ label="Tu Desafío del Día",
530
+ lines=3,
531
+ placeholder="Pedí un desafío para empezar a sumar Eco-Puntos y crecer con MateAI.",
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"):
539
+ gr.Markdown("<h2 style='color: #10b981;'>Tu Viaje con MateAI</h2><p>Acá podés ver tus susurros pasados y tus logros.</p>")
540
+ historial_susurros_output = gr.Textbox(label="Tus Últimos Susurros Recibidos", lines=10, interactive=False)
541
+
542
+ gr.Markdown("<h3 style='color: #10b981; margin-top: 1.5em;'>Tu Diario de Reflexión (Sesión Actual)</h3>")
543
+ diario_reflexion = gr.Textbox(
544
+ label="Escribí acá tus reflexiones del día...",
545
+ lines=10,
546
+ placeholder="¿Qué te hizo pensar el susurro de MateAI? ¿Qué aprendiste hoy? ¿Cómo te sentís?",
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)
568
+ next_insignia_goal = gamification_engine.get_next_insignia_goal(current_points)
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 = {
578
+ "tipo_susurro": tipo_susurro,
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)
599
+ historial = "\n".join(user.nudge_history)
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
+
619
+ # --- Conexión de Eventos ---
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(
627
+ fn=_load_user_gradio,
628
+ inputs=[user_id_existente],
629
+ outputs=[usuario_actual_id, output_cargar_perfil, 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]
630
+ )
631
+
632
+ btn_actualizar_preferencias.click(
633
+ fn=_update_prefs_gradio,
634
+ inputs=[usuario_actual_id, nuevas_preferencias_tipo, nuevas_preferencias_frecuencia, modo_che_tranqui_checkbox],
635
+ outputs=output_actualizar_preferencias
636
+ )
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
+
644
+ btn_oraculo.click(
645
+ fn=_get_oracle_revelation_gradio,
646
+ inputs=[usuario_actual_id],
647
+ outputs=oraculo_output
648
+ )
649
+
650
+ btn_desafio.click(
651
+ fn=_get_mateai_challenge_gradio,
652
+ inputs=[usuario_actual_id],
653
+ outputs=desafio_output
654
+ )
655
+
656
+ btn_descargar_diario.click(
657
+ fn=_download_diary_gradio,
658
+ inputs=[diario_reflexion],
659
+ outputs=gr.File(label="Descargar tu Diario")
660
+ )
661
+
662
+ # Para ejecutar localmente (descomentar si no es para Hugging Face):
663
+ # if __name__ == "__main__":
664
+ # demo.launch()