conalep-agente commited on
Commit
157db14
·
verified ·
1 Parent(s): 8c1a680

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1129 -19
app.py CHANGED
@@ -1,27 +1,1137 @@
1
- from gemini import responder_con_gemini
 
 
 
 
 
 
 
2
  import gradio as gr
 
 
 
3
 
 
 
 
4
 
5
- import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # Obtener la API Key desde Secrets
8
- def responder(mensaje):
9
- return responder_con_gemini(mensaje)
10
-
11
- # Interfaz
12
- demo = gr.Interface(
13
- fn=responder,
14
- inputs=gr.Textbox(
15
- lines=4,
16
- placeholder="Escribe un mensaje para Gemini..."
17
- ),
18
- outputs=gr.Textbox(
19
- lines=10,
20
- label="Respuesta"
21
- ),
22
- title="Prueba de Gemini",
23
- description="Primer laboratorio con Gemini"
24
  )
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  if __name__ == "__main__":
 
27
  demo.launch()
 
1
+ import re
2
+ import os
3
+ import json
4
+ import random
5
+ import uuid
6
+ import pandas as pd
7
+ from datetime import datetime
8
+
9
  import gradio as gr
10
+ from transformers import pipeline
11
+ import gspread
12
+ from oauth2client.service_account import ServiceAccountCredentials
13
 
14
+ # =====================================================
15
+ # CONFIGURACIÓN GENERAL
16
+ # =====================================================
17
 
18
+ SPREADSHEET_ID = "1wR5Pf9k0NBIPbDqJdBdQQJ2F5n6MYLbPLPXUwNjxeTM"
19
+ NOMBRE_HOJA = "Hoja 1" # Cambia esto si tu pestaña tiene otro nombre exacto
20
+
21
+ ENCABEZADOS = [
22
+ "fecha_hora",
23
+ "folio_atencion",
24
+ "matricula",
25
+ "nombre",
26
+ "grupo",
27
+ "mensaje",
28
+ "clasificacion",
29
+ "intencion",
30
+ "tema",
31
+ "label",
32
+ "score",
33
+ "nivel_riesgo",
34
+ "semaforo",
35
+ "coincidencias_riesgo",
36
+ "probabilidades",
37
+ "tipo_riesgo",
38
+ "ideacion_suicida",
39
+ "desesperanza",
40
+ "aislamiento",
41
+ "alerta_urgente",
42
+ "respuesta_agente"
43
+ ]
44
 
45
+ # =====================================================
46
+ # CARGAR MODELO DE SENTIMIENTO
47
+ # =====================================================
48
+ print("Cargando modelo de sentimiento...")
49
+
50
+ modelo_sentimiento = pipeline(
51
+ "text-classification",
52
+ model="pysentimiento/robertuito-sentiment-analysis",
53
+ top_k=None
 
 
 
 
 
 
 
 
54
  )
55
 
56
+ print("Modelo cargado correctamente")
57
+
58
+ # =====================================================
59
+ # CONEXIÓN A GOOGLE SHEETS
60
+ # =====================================================
61
+
62
+ def conectar_sheet():
63
+ """
64
+ Conecta con Google Sheets usando el Secret GOOGLE_CREDENTIALS.
65
+ En Hugging Face Spaces, debes guardar el JSON completo del service account
66
+ en Settings > Secrets con el nombre exacto: GOOGLE_CREDENTIALS
67
+ """
68
+ scope = [
69
+ "https://spreadsheets.google.com/feeds",
70
+ "https://www.googleapis.com/auth/drive"
71
+ ]
72
+
73
+ creds_json = os.getenv("GOOGLE_CREDENTIALS")
74
+
75
+ if not creds_json:
76
+ raise ValueError(
77
+ "No se encontró el Secret GOOGLE_CREDENTIALS. "
78
+ "Verifica en Hugging Face: Settings > Secrets."
79
+ )
80
+
81
+ try:
82
+ creds_dict = json.loads(creds_json)
83
+ except Exception as e:
84
+ raise ValueError(
85
+ f"El Secret GOOGLE_CREDENTIALS no tiene formato JSON válido: {repr(e)}"
86
+ )
87
+
88
+ creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope)
89
+ client = gspread.authorize(creds)
90
+ archivo = client.open_by_key(SPREADSHEET_ID)
91
+
92
+ try:
93
+ sheet = archivo.worksheet(NOMBRE_HOJA)
94
+ except Exception:
95
+ # Si no encuentra la pestaña por nombre, usa la primera hoja.
96
+ sheet = archivo.sheet1
97
+
98
+ return sheet
99
+
100
+
101
+ def asegurar_encabezados(sheet):
102
+ """
103
+ Coloca encabezados si la hoja está vacía.
104
+ Esto ayuda a que los datos no se escriban en columnas desordenadas.
105
+ """
106
+ try:
107
+ primera_fila = sheet.row_values(1)
108
+ if not primera_fila:
109
+ sheet.append_row(ENCABEZADOS, value_input_option="USER_ENTERED")
110
+ print("Encabezados agregados correctamente")
111
+ except Exception as e:
112
+ print(f"No se pudieron verificar/agregar encabezados: {repr(e)}")
113
+
114
+ # =====================================================
115
+ # FUNCIONES DE RESPALDO FOSO II
116
+ # =====================================================
117
+
118
+ def detectar_pregunta_foso2(mensaje):
119
+ return False
120
+
121
+
122
+ def responder_foso2(mensaje):
123
+ return (
124
+ "Gracias por tu pregunta. En este momento no se detectó una consulta pedagógica "
125
+ "específica de FOSO II, pero puedes escribir con más detalle tu duda."
126
+ )
127
+
128
+ # =====================================================
129
+ # GENERAR FOLIO DE ATENCIÓN
130
+ # =====================================================
131
+
132
+ def generar_folio_atencion():
133
+ return "ATN-" + str(uuid.uuid4())[:8].upper()
134
+
135
+ # =====================================================
136
+ # PATRONES DE RIESGO
137
+ # =====================================================
138
+
139
+ PATRONES_RIESGO_ALTO = [
140
+ r"\bme quiero morir\b",
141
+ r"\bquiero morir\b",
142
+ r"\bno quiero vivir\b",
143
+ r"\bquiero terminar con todo\b",
144
+ r"\bquiero acabar con todo\b",
145
+ r"\bquiero matarme\b",
146
+ r"\bme voy a suicidar\b",
147
+ r"\bsuicidarme\b",
148
+ r"\bquitarme la vida\b",
149
+ r"\bya no quiero vivir\b",
150
+ r"\bsería mejor morir\b",
151
+ r"\bseria mejor morir\b",
152
+ r"\bestoy pensando en matarme\b",
153
+ ]
154
+
155
+ PATRONES_RIESGO_MODERADO = [
156
+ r"\bnada tiene sentido\b",
157
+ r"\bno veo salida\b",
158
+ r"\bya no puedo\b",
159
+ r"\bquiero desaparecer\b",
160
+ r"\bme siento mal\b",
161
+ r"\bestoy muy triste\b",
162
+ r"\bestoy deprimid[oa]\b",
163
+ r"\bme siento solo\b",
164
+ r"\bme siento sola\b",
165
+ r"\bnadie me entiende\b",
166
+ r"\btengo miedo\b",
167
+ r"\bestoy muy estresad[oa]\b",
168
+ r"\bme siento estresad[oa]\b",
169
+ r"\btengo muchas tareas\b",
170
+ r"\bestoy cansad[oa]\b",
171
+ r"\bme siento abrumad[oa]\b",
172
+ r"\bno puedo más\b",
173
+ r"\bno puedo mas\b",
174
+ r"\bya no aguanto\b",
175
+ r"\btodo sería mejor sin mí\b",
176
+ r"\btodo seria mejor sin mi\b",
177
+ ]
178
+
179
+ PATRONES_DESESPERANZA = [
180
+ r"\bnada tiene sentido\b",
181
+ r"\bno veo salida\b",
182
+ r"\bya no puedo\b",
183
+ r"\bno puedo más\b",
184
+ r"\bno puedo mas\b",
185
+ r"\bya no aguanto\b",
186
+ r"\bquisiera desaparecer\b",
187
+ r"\btodo sería mejor sin mí\b",
188
+ r"\btodo seria mejor sin mi\b",
189
+ r"\bmi vida no vale\b",
190
+ r"\bsoy una carga\b",
191
+ ]
192
+
193
+ PATRONES_AISLAMIENTO = [
194
+ r"\bme siento solo\b",
195
+ r"\bme siento sola\b",
196
+ r"\bnadie me entiende\b",
197
+ r"\bestoy solo\b",
198
+ r"\bestoy sola\b",
199
+ r"\bno tengo a nadie\b",
200
+ r"\bquiero aislarme\b",
201
+ r"\bno quiero hablar con nadie\b",
202
+ ]
203
+
204
+ PATRONES_IDEACION_SUICIDA = [
205
+ r"\bme quiero morir\b",
206
+ r"\bquiero morir\b",
207
+ r"\bno quiero vivir\b",
208
+ r"\bquiero matarme\b",
209
+ r"\bme voy a suicidar\b",
210
+ r"\bsuicidarme\b",
211
+ r"\bquitarme la vida\b",
212
+ r"\bestoy pensando en matarme\b",
213
+ r"\bquisiera dormirme y no despertar\b",
214
+ ]
215
+
216
+ # =====================================================
217
+ # FUNCIÓN PARA CALCULAR NIVEL DE RIESGO
218
+ # =====================================================
219
+
220
+ def calcular_nivel_riesgo(texto, label, score):
221
+ t = str(texto or "").lower().strip()
222
+
223
+ coincidencias_altas = [p for p in PATRONES_RIESGO_ALTO if re.search(p, t)]
224
+ coincidencias_moderadas = [p for p in PATRONES_RIESGO_MODERADO if re.search(p, t)]
225
+
226
+ if coincidencias_altas:
227
+ return "alto", "🔴", coincidencias_altas + coincidencias_moderadas
228
+
229
+ if coincidencias_moderadas:
230
+ return "moderado", "🟠", coincidencias_moderadas
231
+
232
+ if label == "NEG" and score >= 0.90:
233
+ return "moderado", "🟠", ["modelo_NEG_alto"]
234
+
235
+ return "bajo", "🟢", []
236
+
237
+ # =====================================================
238
+ # DETECTAR TEMA PRINCIPAL DEL MENSAJE
239
+ # =====================================================
240
+
241
+ def detectar_tema(texto):
242
+ t = str(texto or "").lower().strip()
243
+
244
+ if any(p in t for p in ["no puedo dormir", "insomnio", "duermo mal", "no duermo"]):
245
+ return "sueno"
246
+
247
+ if any(p in t for p in ["triste", "tristeza", "deprimido", "deprimida", "llorar", "vacío", "vacio"]):
248
+ return "tristeza"
249
+
250
+ if any(p in t for p in ["ansiedad", "ansioso", "ansiosa", "estres", "estrés", "abrumado", "abrumada", "presión", "presion"]):
251
+ return "ansiedad"
252
+
253
+ if any(p in t for p in ["tarea", "tareas", "examen", "materia", "profe", "profesor", "calificación", "calificacion", "estudiar", "actividad"]):
254
+ return "academico"
255
+
256
+ if any(p in t for p in ["familia", "mamá", "mama", "papá", "papa", "casa", "hermano", "hermana", "padres"]):
257
+ return "familia"
258
+
259
+ if any(p in t for p in ["novio", "novia", "pareja", "terminé", "termine", "terminamos", "relación", "relacion"]):
260
+ return "pareja"
261
+
262
+ if any(p in t for p in ["solo", "sola", "nadie me entiende", "sin amigos", "me siento aislado", "me siento aislada"]):
263
+ return "soledad"
264
+
265
+ if any(p in t for p in ["horario", "documento", "trámite", "tramite", "grupo", "boleta", "inscripción", "inscripcion"]):
266
+ return "administrativo"
267
+
268
+ return "general"
269
+
270
+ # =====================================================
271
+ # DETECCIÓN DE INDICADORES DE RIESGO SUICIDA
272
+ # =====================================================
273
+
274
+ def detectar_indicadores_suicidio(texto):
275
+ t = str(texto or "").lower().strip()
276
+
277
+ ideacion = any(re.search(p, t) for p in PATRONES_IDEACION_SUICIDA)
278
+ desesperanza = any(re.search(p, t) for p in PATRONES_DESESPERANZA)
279
+ aislamiento = any(re.search(p, t) for p in PATRONES_AISLAMIENTO)
280
+
281
+ if ideacion:
282
+ tipo_riesgo = "riesgo_suicida_alto"
283
+ alerta_urgente = "si"
284
+ elif desesperanza or aislamiento:
285
+ tipo_riesgo = "riesgo_socioemocional_moderado"
286
+ alerta_urgente = "no"
287
+ else:
288
+ tipo_riesgo = "malestar_general_o_sin_indicadores"
289
+ alerta_urgente = "no"
290
+
291
+ return {
292
+ "ideacion_suicida": "si" if ideacion else "no",
293
+ "desesperanza": "si" if desesperanza else "no",
294
+ "aislamiento": "si" if aislamiento else "no",
295
+ "tipo_riesgo": tipo_riesgo,
296
+ "alerta_urgente": alerta_urgente
297
+ }
298
+
299
+ # =====================================================
300
+ # ANALIZAR MENSAJE
301
+ # =====================================================
302
+
303
+ def analizar_frase(texto):
304
+ texto = str(texto or "").strip()
305
+ if not texto:
306
+ return None
307
+
308
+ resultado_modelo = modelo_sentimiento(texto)[0]
309
+ mejor = max(resultado_modelo, key=lambda x: x["score"])
310
+ label = mejor["label"]
311
+ score = float(mejor["score"])
312
+
313
+ probabilidades = {
314
+ x["label"]: round(float(x["score"]), 3)
315
+ for x in resultado_modelo
316
+ }
317
+
318
+ texto_lower = texto.lower()
319
+
320
+ nivel_riesgo, semaforo, coincidencias_riesgo = calcular_nivel_riesgo(
321
+ texto_lower, label, score
322
+ )
323
+
324
+ if nivel_riesgo == "alto":
325
+ clasificacion = "alto_riesgo"
326
+ elif nivel_riesgo == "moderado":
327
+ clasificacion = "riesgo_moderado"
328
+ else:
329
+ clasificacion = "normal"
330
+
331
+ if any(p in texto_lower for p in [
332
+ "tarea", "tareas", "profe", "profesor", "materia", "ejercicio",
333
+ "actividad", "tema", "examen", "calificación", "calificacion",
334
+ "muchas tareas", "no entiendo", "estudiar"
335
+ ]):
336
+ intencion = "academico_emocional"
337
+ elif any(p in texto_lower for p in [
338
+ "horario", "documento", "trámite", "tramite", "grupo", "boleta",
339
+ "inscripción", "inscripcion"
340
+ ]):
341
+ intencion = "administrativo"
342
+ else:
343
+ intencion = "emocional"
344
+
345
+ tema = detectar_tema(texto)
346
+ indicadores = detectar_indicadores_suicidio(texto)
347
+
348
+ return {
349
+ "clasificacion": clasificacion,
350
+ "intencion": intencion,
351
+ "label": label,
352
+ "score": score,
353
+ "probabilidades": probabilidades,
354
+ "nivel_riesgo": nivel_riesgo,
355
+ "semaforo": semaforo,
356
+ "tema": tema,
357
+ "coincidencias_riesgo": coincidencias_riesgo,
358
+ "tipo_riesgo": indicadores["tipo_riesgo"],
359
+ "ideacion_suicida": indicadores["ideacion_suicida"],
360
+ "desesperanza": indicadores["desesperanza"],
361
+ "aislamiento": indicadores["aislamiento"],
362
+ "alerta_urgente": indicadores["alerta_urgente"]
363
+ }
364
+
365
+ # =====================================================
366
+ # BLOQUES DE TEXTO VARIABLES
367
+ # =====================================================
368
+
369
+ def elegir(lista):
370
+ return random.choice(lista)
371
+
372
+
373
+ def construir_sugerencias_por_tema(tema, intencion):
374
+ sugerencias_generales = [
375
+ "tratar de poner en palabras lo que estás sintiendo para entenderlo mejor",
376
+ "buscar a una persona de confianza con quien puedas hablar",
377
+ "hacer una pausa breve, respirar despacio y bajar un poco la tensión del momento",
378
+ "evitar cargar con todo tú solo o tú sola",
379
+ ]
380
+
381
+ mapa = {
382
+ "sueno": [
383
+ "evitar usar el celular unos minutos antes de dormir",
384
+ "hacer respiraciones lentas y profundas antes de acostarte",
385
+ "anotar las preocupaciones que te están quitando el sueño",
386
+ "pedir apoyo si el insomnio se está volviendo frecuente"
387
+ ],
388
+ "tristeza": [
389
+ "expresar lo que sientes con alguien de confianza",
390
+ "no aislarte por completo en este momento",
391
+ "realizar una actividad sencilla que te ayude a salir un poco de la carga emocional",
392
+ "buscar acompañamiento del área de orientación o psicología"
393
+ ],
394
+ "ansiedad": [
395
+ "hacer pausas cortas durante el día para disminuir la saturación mental",
396
+ "dividir el problema en partes pequeñas en lugar de querer resolver todo al mismo tiempo",
397
+ "practicar respiración profunda por unos minutos",
398
+ "identificar qué situación te está generando más presión"
399
+ ],
400
+ "academico": [
401
+ "organizar tus tareas por prioridad y fecha de entrega",
402
+ "empezar por una sola actividad en vez de pensar en todo al mismo tiempo",
403
+ "pedir que te expliquen nuevamente el tema que no entiendes",
404
+ "buscar apoyo con tu docente, tutor o un compañero de confianza"
405
+ ],
406
+ "familia": [
407
+ "hablar con calma con una persona de tu familia que te genere confianza",
408
+ "buscar apoyo adulto si en casa te sientes sobrepasado o sobrepasada",
409
+ "tratar de identificar qué situación familiar te está afectando más",
410
+ "pedir acompañamiento institucional si el conflicto continúa"
411
+ ],
412
+ "pareja": [
413
+ "tomarte un momento para entender qué fue lo que más te lastimó",
414
+ "evitar tomar decisiones impulsivas cuando la emoción está muy intensa",
415
+ "hablar con alguien de confianza para no quedarte solo o sola con esto",
416
+ "recordar que una situación afectiva difícil también necesita acompañamiento"
417
+ ],
418
+ "soledad": [
419
+ "acercarte a una persona con quien te sientas mínimamente en confianza",
420
+ "evitar encerrarte completamente con lo que sientes",
421
+ "buscar apoyo en orientación o tutoría",
422
+ "dar un primer paso pequeño para pedir ayuda"
423
+ ],
424
+ "administrativo": [
425
+ "explicar con más detalle cuál es el trámite o la duda que necesitas resolver",
426
+ "revisar si tu duda corresponde a horario, grupo, boleta o documento",
427
+ "acercarte al área del plantel que lleve ese proceso",
428
+ "tener a la mano tus datos escolares para recibir una orientación más clara"
429
+ ],
430
+ "general": sugerencias_generales
431
+ }
432
+
433
+ sugerencias = mapa.get(tema, sugerencias_generales)
434
+
435
+ if intencion == "administrativo":
436
+ sugerencias = mapa["administrativo"]
437
+ elif intencion == "academico_emocional" and tema == "general":
438
+ sugerencias = mapa["academico"]
439
+
440
+ random.shuffle(sugerencias)
441
+ return sugerencias[:3]
442
+
443
+ # =====================================================
444
+ # RESPUESTA DE CRISIS
445
+ # =====================================================
446
+
447
+ def construir_respuesta_crisis(nombre="Estudiante"):
448
+ return (
449
+ f"Hola {nombre}. Gracias por decir lo que estás sintiendo.\n\n"
450
+ "Lo que acabas de expresar requiere atención inmediata. No estás solo o sola.\n\n"
451
+ "Por favor, haz esto ahora mismo:\n"
452
+ "1. Busca a un adulto, familiar, docente, tutor, orientador u otra persona de confianza y dile que necesitas acompañamiento inmediato.\n"
453
+ "2. No te quedes solo o sola en este momento.\n"
454
+ "3. Acércate de inmediato al área de orientación, tutoría o psicología de tu plantel.\n"
455
+ "4. Si sientes que podrías lastimarte o existe peligro inmediato, llama al 911 o comunícate con la Línea de la Vida: 800 911 2000.\n\n"
456
+ "Tu bienestar importa y pedir ayuda ahora es muy importante.\n\n"
457
+ "Respóndeme con una sola palabra:\n"
458
+ "- 'solo' si estás sin compañía\n"
459
+ "- 'acompañado' si hay alguien contigo"
460
+ )
461
+
462
+ # =====================================================
463
+ # RESPUESTA AMPLIA DEL AGENTE
464
+ # =====================================================
465
+
466
+ def construir_respuesta_amplia(resultado, nombre="Estudiante"):
467
+ if not resultado:
468
+ return (
469
+ f"Hola {nombre}. Gracias por escribir.\n\n"
470
+ "Quiero leerte con atención. Cuéntame un poco más sobre lo que estás viviendo "
471
+ "para poder orientarte mejor."
472
+ )
473
+
474
+ clasificacion = resultado.get("clasificacion", "normal")
475
+ intencion = resultado.get("intencion", "emocional")
476
+ nivel_riesgo = resultado.get("nivel_riesgo", "bajo")
477
+ tema = resultado.get("tema", "general")
478
+
479
+ aperturas = [
480
+ f"Hola {nombre}. Gracias por confiar en este espacio para expresar lo que estás viviendo.",
481
+ f"Hola {nombre}. Gracias por compartir lo que sientes; hacerlo ya es un paso importante.",
482
+ f"Hola {nombre}. Valoro que hayas escrito sobre esto, porque lo que te pasa sí importa."
483
+ ]
484
+
485
+ empatia_moderada = [
486
+ "Lo que estás sintiendo merece atención, acompañamiento y un espacio para hablarlo con calma.",
487
+ "A veces la presión emocional, personal o escolar puede sentirse muy pesada, y no tienes por qué cargar con todo sin apoyo.",
488
+ "Cuando varias cosas se juntan al mismo tiempo, es normal sentirse cansado, abrumado o sin mucha claridad."
489
+ ]
490
+
491
+ preguntas_finales = [
492
+ "Si quieres, puedo ayudarte a identificar qué te está afectando más en este momento.",
493
+ "Si te parece, podemos ir paso a paso para ubicar qué situación te está pesando más.",
494
+ "Cuéntame un poco más y te ayudo a ordenar lo que estás sintiendo."
495
+ ]
496
+
497
+ if clasificacion == "alto_riesgo" or nivel_riesgo == "alto":
498
+ return construir_respuesta_crisis(nombre)
499
+
500
+ if clasificacion == "riesgo_moderado" or nivel_riesgo == "moderado":
501
+ sugerencias = construir_sugerencias_por_tema(tema, intencion)
502
+ texto_tema = {
503
+ "sueno": "El problema para dormir muchas veces aparece cuando hay preocupaciones, tensión acumulada o malestar emocional.",
504
+ "tristeza": "La tristeza sostenida puede ser una señal de que algo importante necesita atención y apoyo.",
505
+ "ansiedad": "La ansiedad o el estrés pueden afectar tanto tus pensamientos como tu cuerpo, tu concentración y tu descanso.",
506
+ "academico": "La carga escolar puede generar mucha presión cuando se acumulan tareas, exámenes o temas difíciles.",
507
+ "familia": "Los problemas familiares también pueden afectar mucho el ánimo, la concentración y la sensación de estabilidad.",
508
+ "pareja": "Las situaciones afectivas pueden doler profundamente y alterar el equilibrio emocional.",
509
+ "soledad": "Sentirse solo o sola puede volver más pesada cualquier situación difícil.",
510
+ "administrativo": "Cuando una duda escolar o administrativa no se resuelve, también puede generar estrés y frustración.",
511
+ "general": "A veces cuesta poner en orden lo que sentimos, sobre todo cuando hay varias preocupaciones al mismo tiempo."
512
+ }
513
+
514
+ return (
515
+ f"{elegir(aperturas)}\n\n"
516
+ f"{elegir(empatia_moderada)} {texto_tema.get(tema, texto_tema['general'])}\n\n"
517
+ "Además de reconocer lo que te está pasando, también es importante pensar en pequeños pasos que sí puedes dar desde ahora.\n\n"
518
+ "Estas opciones podrían ayudarte:\n"
519
+ f"- {sugerencias[0]}\n"
520
+ f"- {sugerencias[1]}\n"
521
+ f"- {sugerencias[2]}\n\n"
522
+ "También es recomendable que busques acompañamiento en tu plantel para no enfrentar esto en soledad. "
523
+ "Puedes acercarte a tutoría, orientación educativa o psicología.\n\n"
524
+ f"{elegir(preguntas_finales)} "
525
+ "Puedes decirme si esto se relaciona más con la escuela, con tus emociones, con tu familia o con algún problema personal."
526
+ )
527
+
528
+ if intencion == "academico_emocional":
529
+ sugerencias = construir_sugerencias_por_tema("academico", intencion)
530
+ return (
531
+ f"{elegir(aperturas)}\n\n"
532
+ "Entiendo que la carga académica puede llegar a sentirse muy pesada, especialmente cuando se juntan tareas, exámenes, temas difíciles o presión por las calificaciones.\n\n"
533
+ "Eso no significa que no puedas avanzar; muchas veces significa que necesitas apoyo, orden y empezar por una sola parte del problema.\n\n"
534
+ "Podrías intentar lo siguiente:\n"
535
+ f"- {sugerencias[0]}\n"
536
+ f"- {sugerencias[1]}\n"
537
+ f"- {sugerencias[2]}\n\n"
538
+ "También puedo ayudarte a revisar contigo qué te está costando más: una materia, una tarea, una instrucción, un tema específico o simplemente la acumulación de trabajo.\n\n"
539
+ "Cuéntame qué materia o actividad te está preocupando más y avanzamos paso a paso."
540
+ )
541
+
542
+ if intencion == "administrativo":
543
+ sugerencias = construir_sugerencias_por_tema("administrativo", intencion)
544
+ return (
545
+ f"{elegir(aperturas)}\n\n"
546
+ "Puedo orientarte con dudas escolares o administrativas relacionadas con horario, grupo, documentos, boletas, trámites o indicaciones generales del plantel.\n\n"
547
+ "Para darte una respuesta más útil, necesito que me escribas con un poco más de detalle qué es exactamente lo que necesitas resolver.\n\n"
548
+ "Por ejemplo, puedes especificar si tu duda se relaciona con:\n"
549
+ f"- {sugerencias[0]}\n"
550
+ f"- {sugerencias[1]}\n"
551
+ f"- {sugerencias[2]}\n\n"
552
+ "Entre más claro me expliques tu situación, más precisa podrá ser mi orientación."
553
+ )
554
+
555
+ sugerencias = construir_sugerencias_por_tema(tema, intencion)
556
+ return (
557
+ f"{elegir(aperturas)}\n\n"
558
+ "A veces poner en palabras lo que nos pasa ayuda a entender mejor la situación y a encontrar un primer paso para afrontarla. "
559
+ "No necesitas resolver todo de una sola vez.\n\n"
560
+ "Algunas opciones que podrían ayudarte en este momento son:\n"
561
+ f"- {sugerencias[0]}\n"
562
+ f"- {sugerencias[1]}\n"
563
+ f"- {sugerencias[2]}\n\n"
564
+ "Si quieres, puedo seguir acompañándote para identificar qué es lo que más te preocupa y cómo podrías empezar a manejarlo."
565
+ )
566
+
567
+ # =====================================================
568
+ # ANÁLISIS TEXTUAL
569
+ # =====================================================
570
+
571
+ def construir_analisis_textual(frase, resultado):
572
+ if not resultado:
573
+ return "Sin análisis."
574
+
575
+ probs = resultado.get("probabilidades", {})
576
+
577
+ texto = []
578
+ texto.append("ANÁLISIS EMOCIONAL")
579
+ texto.append(f"Frase: {frase}")
580
+ texto.append(f"Clasificación: {resultado.get('clasificacion', '')}")
581
+ texto.append(f"Intención detectada: {resultado.get('intencion', '')}")
582
+ texto.append(f"Tema detectado: {resultado.get('tema', '')}")
583
+ texto.append(f"Etiqueta principal: {resultado.get('label', '')}")
584
+ texto.append(f"Confianza: {float(resultado.get('score', 0.0)):.3f}")
585
+ texto.append(f"Nivel de riesgo: {resultado.get('nivel_riesgo', '')}")
586
+ texto.append(f"Semáforo: {resultado.get('semaforo', '')}")
587
+ texto.append(f"Coincidencias de riesgo: {resultado.get('coincidencias_riesgo', '')}")
588
+ texto.append(f"Tipo de riesgo: {resultado.get('tipo_riesgo', '')}")
589
+ texto.append(f"Ideación suicida: {resultado.get('ideacion_suicida', '')}")
590
+ texto.append(f"Desesperanza: {resultado.get('desesperanza', '')}")
591
+ texto.append(f"Aislamiento: {resultado.get('aislamiento', '')}")
592
+ texto.append(f"Alerta urgente: {resultado.get('alerta_urgente', '')}")
593
+ texto.append("")
594
+ texto.append("Probabilidades:")
595
+ texto.append(f"- NEG: {float(probs.get('NEG', 0.0)):.3f}")
596
+ texto.append(f"- NEU: {float(probs.get('NEU', 0.0)):.3f}")
597
+ texto.append(f"- POS: {float(probs.get('POS', 0.0)):.3f}")
598
+
599
+ return "\n".join(texto)
600
+
601
+ # =====================================================
602
+ # GUARDAR EN GOOGLE SHEETS
603
+ # =====================================================
604
+ def guardar_interaccion(folio_atencion, matricula, nombre, grupo, mensaje, resultado, respuesta):
605
+ try:
606
+ print("ENTRANDO A guardar_interaccion...")
607
+
608
+ sheet = conectar_sheet()
609
+ print("Conexión con Google Sheets correcta")
610
+
611
+ if resultado is None:
612
+ resultado = {}
613
+
614
+ fila = [
615
+ datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
616
+ str(folio_atencion or ""),
617
+ str(matricula or ""),
618
+ str(nombre or ""),
619
+ str(grupo or ""),
620
+ str(mensaje or ""),
621
+ str(resultado.get("clasificacion", "")),
622
+ str(resultado.get("intencion", "")),
623
+ str(resultado.get("tema", "")),
624
+ str(resultado.get("label", "")),
625
+ str(resultado.get("score", "")),
626
+ str(resultado.get("nivel_riesgo", "")),
627
+ str(resultado.get("semaforo", "")),
628
+ str(resultado.get("coincidencias_riesgo", "")),
629
+ str(resultado.get("probabilidades", "")),
630
+ str(resultado.get("tipo_riesgo", "")),
631
+ str(resultado.get("ideacion_suicida", "")),
632
+ str(resultado.get("desesperanza", "")),
633
+ str(resultado.get("aislamiento", "")),
634
+ str(resultado.get("alerta_urgente", "")),
635
+ str(respuesta or "")
636
+ ]
637
+
638
+ # Guarda SIEMPRE al final, sin borrar registros anteriores
639
+ sheet.append_row(
640
+ fila,
641
+ value_input_option="USER_ENTERED",
642
+ insert_data_option="INSERT_ROWS"
643
+ )
644
+
645
+ print("✅ Datos guardados correctamente en Google Sheets")
646
+ return True, "Guardado correcto"
647
+
648
+ except Exception as e:
649
+ error = f"❌ Error al guardar en Google Sheets: {repr(e)}"
650
+ print(error)
651
+ return False, error
652
+ # =====================================================
653
+ # RESPONDER CHAT
654
+ # =====================================================
655
+
656
+ def responder_chat(matricula, nombre, grupo, mensaje, historial):
657
+ mensaje = str(mensaje or "").strip()
658
+ historial = str(historial or "").strip()
659
+
660
+ if not mensaje:
661
+ return historial, "", "Escribe algo.", "Sin análisis"
662
+
663
+ try:
664
+ nombre_mostrar = nombre.strip() if str(nombre or "").strip() else "Estudiante"
665
+ matricula_final = matricula.strip() if str(matricula or "").strip() else "ANONIMO"
666
+ grupo_final = grupo.strip() if str(grupo or "").strip() else ""
667
+ folio_atencion = generar_folio_atencion()
668
+
669
+ # Consulta FOSO II. También se guarda registro.
670
+ if detectar_pregunta_foso2(mensaje):
671
+ respuesta_base = responder_foso2(mensaje)
672
+ respuesta = f"📌 Folio de atención: {folio_atencion}\n\n{respuesta_base}"
673
+ analisis = "Consulta pedagógica detectada: FOSO II"
674
+
675
+ resultado_foso = {
676
+ "clasificacion": "consulta_pedagogica",
677
+ "intencion": "foso_ii",
678
+ "tema": "foso_ii",
679
+ "label": "",
680
+ "score": "",
681
+ "nivel_riesgo": "bajo",
682
+ "semaforo": "🟢",
683
+ "coincidencias_riesgo": "",
684
+ "probabilidades": "",
685
+ "tipo_riesgo": "consulta_pedagogica",
686
+ "ideacion_suicida": "no",
687
+ "desesperanza": "no",
688
+ "aislamiento": "no",
689
+ "alerta_urgente": "no"
690
+ }
691
+
692
+ guardado, mensaje_guardado = guardar_interaccion(
693
+ folio_atencion,
694
+ matricula_final,
695
+ nombre_mostrar,
696
+ grupo_final,
697
+ mensaje,
698
+ resultado_foso,
699
+ respuesta
700
+ )
701
+ print(mensaje_guardado)
702
+
703
+ bloque_usuario = f"Tú: {mensaje}"
704
+ bloque_agente = f"Agente: {respuesta}"
705
+ nuevo_historial = historial + "\n\n" + bloque_usuario + "\n" + bloque_agente if historial else bloque_usuario + "\n" + bloque_agente
706
+
707
+ return nuevo_historial, "", respuesta, analisis
708
+
709
+ resultado = analizar_frase(mensaje)
710
+ respuesta_base = construir_respuesta_amplia(resultado, nombre_mostrar)
711
+ respuesta = f"📌 Folio de atención: {folio_atencion}\n\n{respuesta_base}"
712
+ analisis = construir_analisis_textual(mensaje, resultado)
713
+
714
+ if resultado is not None:
715
+ guardado, mensaje_guardado = guardar_interaccion(
716
+ folio_atencion,
717
+ matricula_final,
718
+ nombre_mostrar,
719
+ grupo_final,
720
+ mensaje,
721
+ resultado,
722
+ respuesta
723
+ )
724
+ print(mensaje_guardado)
725
+ if not guardado:
726
+ analisis += f"\n\nERROR DE GUARDADO:\n{mensaje_guardado}"
727
+
728
+ bloque_usuario = f"Tú: {mensaje}"
729
+ bloque_agente = f"Agente: {respuesta}"
730
+ nuevo_historial = historial + "\n\n" + bloque_usuario + "\n" + bloque_agente if historial else bloque_usuario + "\n" + bloque_agente
731
+
732
+ return nuevo_historial, "", respuesta, analisis
733
+
734
+ except Exception as e:
735
+ print(f"Error interno: {repr(e)}")
736
+ return historial, "", f"Error interno: {repr(e)}", "Sin análisis"
737
+
738
+ # =====================================================
739
+ # LIMPIAR CHAT
740
+ # =====================================================
741
+
742
+ def limpiar_chat():
743
+ return "", "", "", ""
744
+
745
+ # =====================================================
746
+ # RESPUESTA RÁPIDA DE CRISIS DESDE BOTÓN
747
+ # =====================================================
748
+
749
+ def activar_ayuda_urgente(nombre):
750
+ nombre_mostrar = str(nombre or "").strip() or "Estudiante"
751
+ folio_atencion = generar_folio_atencion()
752
+
753
+ respuesta_base = construir_respuesta_crisis(nombre_mostrar)
754
+ respuesta = f"📌 Folio de atención: {folio_atencion}\n\n{respuesta_base}"
755
+
756
+ resultado = {
757
+ "clasificacion": "alto_riesgo",
758
+ "intencion": "emocional",
759
+ "tema": "crisis",
760
+ "label": "NEG",
761
+ "score": 1.0,
762
+ "nivel_riesgo": "alto",
763
+ "semaforo": "🔴",
764
+ "coincidencias_riesgo": "boton_ayuda_urgente",
765
+ "probabilidades": "",
766
+ "tipo_riesgo": "riesgo_suicida_alto",
767
+ "ideacion_suicida": "si",
768
+ "desesperanza": "no",
769
+ "aislamiento": "no",
770
+ "alerta_urgente": "si"
771
+ }
772
+
773
+ guardado, mensaje_guardado = guardar_interaccion(
774
+ folio_atencion,
775
+ "BOTON_URGENCIA",
776
+ nombre_mostrar,
777
+ "",
778
+ "El estudiante presionó el botón de ayuda urgente",
779
+ resultado,
780
+ respuesta
781
+ )
782
+ print(mensaje_guardado)
783
+
784
+ analisis = "ALERTA MANUAL: solicitud de ayuda urgente"
785
+ if not guardado:
786
+ analisis += f"\n\nERROR DE GUARDADO:\n{mensaje_guardado}"
787
+
788
+ return "", respuesta, analisis
789
+
790
+ # =====================================================
791
+ # INTERFAZ VISUAL MEJORADA Y RESPONSIVE
792
+ # =====================================================
793
+
794
+ css = """
795
+ body {
796
+ background: #f5f7f6;
797
+ font-family: Arial, sans-serif;
798
+ color: #111827;
799
+ }
800
+ .gradio-container {
801
+ max-width: 1450px !important;
802
+ padding-left: 12px !important;
803
+ padding-right: 12px !important;
804
+ }
805
+ p, li, span, div, label {
806
+ color: #111827 !important;
807
+ }
808
+ h1, h2, h3, h4 {
809
+ color: #0f172a !important;
810
+ font-weight: 800 !important;
811
+ }
812
+ .bloque {
813
+ background: #ffffff;
814
+ border-radius: 18px;
815
+ padding: 18px;
816
+ box-shadow: 0 2px 10px rgba(0,0,0,0.10);
817
+ margin-bottom: 12px;
818
+ }
819
+ .header-box {
820
+ background: linear-gradient(90deg, #0b6b43, #0f8a57);
821
+ color: white !important;
822
+ border-radius: 22px;
823
+ padding: 24px;
824
+ margin-bottom: 15px;
825
+ box-shadow: 0 4px 14px rgba(0,0,0,0.12);
826
+ }
827
+ .header-box,
828
+ .header-box div,
829
+ .header-box span,
830
+ .header-box b,
831
+ .header-box .titulo,
832
+ .header-box .subtitulo {
833
+ color: white !important;
834
+ }
835
+ .titulo {
836
+ font-size: 34px;
837
+ font-weight: 900;
838
+ margin-bottom: 6px;
839
+ line-height: 1.15;
840
+ }
841
+ .subtitulo {
842
+ font-size: 17px;
843
+ font-weight: 500;
844
+ opacity: 1;
845
+ line-height: 1.4;
846
+ }
847
+ .seguro {
848
+ background: #f8fafc;
849
+ border-radius: 16px;
850
+ padding: 18px;
851
+ font-size: 17px;
852
+ line-height: 1.65;
853
+ margin-bottom: 12px;
854
+ color: #111827 !important;
855
+ border: 1px solid #e5e7eb;
856
+ }
857
+ .estado {
858
+ background: #ecfdf3;
859
+ border-radius: 12px;
860
+ padding: 12px;
861
+ font-weight: 800;
862
+ color: #0b6b43 !important;
863
+ text-align: center;
864
+ margin-top: 10px;
865
+ border: 1px solid #bbf7d0;
866
+ }
867
+ .footer-note {
868
+ font-size: 13px;
869
+ color: #374151 !important;
870
+ margin-top: 8px;
871
+ }
872
+ textarea, input {
873
+ font-size: 16px !important;
874
+ color: #111827 !important;
875
+ font-weight: 500 !important;
876
+ }
877
+ ::placeholder {
878
+ color: #6b7280 !important;
879
+ opacity: 1 !important;
880
+ }
881
+ label {
882
+ font-size: 14px !important;
883
+ font-weight: 700 !important;
884
+ color: #111827 !important;
885
+ }
886
+ button {
887
+ min-height: 44px !important;
888
+ font-size: 15px !important;
889
+ font-weight: 700 !important;
890
+ border-radius: 12px !important;
891
+ }
892
+ textarea[aria-label="Conversación"] {
893
+ min-height: 260px !important;
894
+ color: #111827 !important;
895
+ }
896
+ .header-box img {
897
+ max-width: 70px;
898
+ max-height: 70px;
899
+ }
900
+ @media (max-width: 768px) {
901
+ .gradio-container {
902
+ padding-left: 8px !important;
903
+ padding-right: 8px !important;
904
+ }
905
+ .header-box {
906
+ border-radius: 18px;
907
+ padding: 18px;
908
+ margin-bottom: 12px;
909
+ flex-direction: row;
910
+ align-items: center;
911
+ gap: 10px;
912
+ }
913
+ .header-box img {
914
+ width: 50px !important;
915
+ height: 50px !important;
916
+ }
917
+ .titulo {
918
+ font-size: 23px !important;
919
+ line-height: 1.2;
920
+ font-weight: 900 !important;
921
+ }
922
+ .subtitulo {
923
+ font-size: 15px !important;
924
+ line-height: 1.35;
925
+ font-weight: 500 !important;
926
+ }
927
+ .bloque {
928
+ padding: 14px;
929
+ border-radius: 14px;
930
+ margin-bottom: 10px;
931
+ }
932
+ .bloque h2,
933
+ .bloque h3 {
934
+ font-size: 22px !important;
935
+ font-weight: 900 !important;
936
+ color: #111827 !important;
937
+ }
938
+ .bloque p,
939
+ .bloque li,
940
+ .seguro {
941
+ font-size: 17px !important;
942
+ line-height: 1.5 !important;
943
+ color: #111827 !important;
944
+ font-weight: 500 !important;
945
+ }
946
+ .seguro {
947
+ padding: 14px;
948
+ border-radius: 14px;
949
+ }
950
+ .estado {
951
+ padding: 10px;
952
+ font-size: 15px !important;
953
+ }
954
+ label {
955
+ font-size: 15px !important;
956
+ font-weight: 800 !important;
957
+ }
958
+ button {
959
+ width: 100% !important;
960
+ margin-bottom: 8px !important;
961
+ font-size: 15px !important;
962
+ }
963
+ textarea[aria-label="Conversación"] {
964
+ min-height: 180px !important;
965
+ }
966
+ textarea[aria-label="Análisis emocional"] {
967
+ min-height: 160px !important;
968
+ }
969
+ }
970
+ textarea,
971
+ input {
972
+ background: #ffffff !important;
973
+ color: #111827 !important;
974
+ -webkit-text-fill-color: #111827 !important;
975
+ }
976
+ textarea[aria-label="Conversación"] {
977
+ background: #ffffff !important;
978
+ color: #111827 !important;
979
+ }
980
+ textarea[aria-label="Respuesta del agente"] {
981
+ background: #ffffff !important;
982
+ color: #111827 !important;
983
+ }
984
+ textarea[aria-label="Análisis emocional"] {
985
+ background: #ffffff !important;
986
+ color: #111827 !important;
987
+ }
988
+ html {
989
+ color-scheme: light !important;
990
+ }
991
+ ::placeholder {
992
+ color: #6b7280 !important;
993
+ opacity: 1 !important;
994
+ }
995
+ """
996
+
997
+ tema_visual = gr.themes.Soft(primary_hue="green")
998
+
999
+ with gr.Blocks(theme=tema_visual, css=css, title="Agente Conversacional CONALEP") as demo:
1000
+
1001
+ gr.HTML("""
1002
+ <div class="header-box" style="display:flex; align-items:center; gap:14px;">
1003
+ <img src="/gradio-api/file=logo_conalep.png"
1004
+ alt="Logo CONALEP"
1005
+ style="width:70px; height:70px; object-fit:contain; background:white; border-radius:12px; padding:6px;">
1006
+ <div>
1007
+ <div class="titulo">🤖 Agente Conversacional Pedagógico CONALEP</div>
1008
+ <div class="subtitulo">Apoyo socioemocional y detección de riesgos</div>
1009
+ <div style="font-size:12px; margin-top:4px;">
1010
+ <span style="color:white !important;">
1011
+ Desarrollado por <b style="color:white !important;">Doctorante. Jorge Noé Gámez Mora</b>
1012
+ </span>
1013
+ </div>
1014
+ </div>
1015
+ </div>
1016
+ """)
1017
+
1018
+ with gr.Row():
1019
+ with gr.Column(scale=1):
1020
+ gr.HTML("""
1021
+ <div class="bloque">
1022
+ <h2>¿Qué puedo hacer por ti?</h2>
1023
+ <p>✅ Brindarte acompañamiento inicial</p>
1024
+ <p>✅ Detectar señales de riesgo socioemocional</p>
1025
+ <p>✅ Orientarte en temas emocionales y escolares</p>
1026
+ <p>✅ Canalizarte si necesitas ayuda urgente</p>
1027
+ </div>
1028
+ """)
1029
+
1030
+ gr.HTML("""
1031
+ <div class="bloque">
1032
+ <h3>🔒 Espacio confidencial</h3>
1033
+ <p>Este agente no sustituye atención psicológica, médica o de emergencia.</p>
1034
+ <p>
1035
+ Si existe peligro inmediato, llama al
1036
+ <span style="font-weight:900; font-size:18px; color:#d32f2f;">911</span>
1037
+ o a la
1038
+ <span style="font-weight:900; font-size:18px; color:#b71c1c;">Línea de la Vida: 800 911 2000</span>.
1039
+ </p>
1040
+ </div>
1041
+ """)
1042
+
1043
+ matricula = gr.Textbox(
1044
+ label="Matrícula (opcional)",
1045
+ placeholder="Ej. 22012345 o deja vacío si prefieres anonimato"
1046
+ )
1047
+ nombre = gr.Textbox(label="Nombre", placeholder="Escribe tu nombre")
1048
+ grupo = gr.Textbox(label="Grupo", placeholder="Ej. 210")
1049
+
1050
+ with gr.Column(scale=2):
1051
+ gr.HTML("""
1052
+ <div class="bloque">
1053
+ <p style="font-size:19px; font-weight:900; color:#0b6b43; margin-bottom:10px;">
1054
+ 🔒 Hola, este es un espacio seguro para ti.
1055
+ </p>
1056
+ <p style="font-size:16px; line-height:1.6; color:#333;">
1057
+ Puedes hablar conmigo con confianza.<br>
1058
+ Todo lo que compartas será tratado con seriedad, respeto y
1059
+ <b>confidencialidad</b>, conforme al aviso de privacidad institucional.<br><br>
1060
+ Estoy aquí para apoyarte. Si te sientes en riesgo o necesitas ayuda urgente,
1061
+ busca a tu tutor, orientador del bienestar o a un adulto de confianza.<br><br>
1062
+ <span style="font-weight:900; font-size:17px;">No estás solo.</span>
1063
+ </p>
1064
+ </div>
1065
+ """)
1066
+
1067
+ historial = gr.Textbox(
1068
+ label="Conversación",
1069
+ lines=16,
1070
+ interactive=False,
1071
+ placeholder="Aquí aparecerá la conversación..."
1072
+ )
1073
+
1074
+ mensaje = gr.Textbox(
1075
+ label="Mensaje del estudiante",
1076
+ placeholder="Escribe aquí cómo te sientes o qué necesitas..."
1077
+ )
1078
+
1079
+ with gr.Row():
1080
+ enviar = gr.Button("Enviar", variant="primary")
1081
+ limpiar = gr.Button("Limpiar")
1082
+
1083
+ respuesta_box = gr.Textbox(label="Respuesta del agente", lines=10)
1084
+
1085
+ with gr.Column(scale=1):
1086
+ gr.HTML("""
1087
+ <div class="bloque">
1088
+ <h2>Acciones rápidas</h2>
1089
+ <p>📋 Evaluación rápida</p>
1090
+ <p>📚 Recursos de apoyo</p>
1091
+ <p>📞 Contactar tutor u orientador</p>
1092
+ <p>🧠 Acompañamiento emocional inicial</p>
1093
+ </div>
1094
+ """)
1095
+
1096
+ gr.HTML("""
1097
+ <div class="bloque estado">
1098
+ Estado del agente: En línea 🟢
1099
+ </div>
1100
+ """)
1101
+
1102
+ crisis_btn = gr.Button("🚨 ¡Necesito ayuda urgente!", variant="stop")
1103
+ analisis_box = gr.Textbox(label="Análisis emocional", lines=18, visible=False)
1104
+
1105
+ gr.HTML("""
1106
+ <div class="footer-note">
1107
+ Herramienta de apoyo inicial con fines de orientación y detección temprana.
1108
+ </div>
1109
+ """)
1110
+
1111
+ enviar.click(
1112
+ responder_chat,
1113
+ inputs=[matricula, nombre, grupo, mensaje, historial],
1114
+ outputs=[historial, mensaje, respuesta_box, analisis_box]
1115
+ )
1116
+
1117
+ mensaje.submit(
1118
+ responder_chat,
1119
+ inputs=[matricula, nombre, grupo, mensaje, historial],
1120
+ outputs=[historial, mensaje, respuesta_box, analisis_box]
1121
+ )
1122
+
1123
+ limpiar.click(
1124
+ limpiar_chat,
1125
+ inputs=[],
1126
+ outputs=[historial, mensaje, respuesta_box, analisis_box]
1127
+ )
1128
+
1129
+ crisis_btn.click(
1130
+ activar_ayuda_urgente,
1131
+ inputs=[nombre],
1132
+ outputs=[mensaje, respuesta_box, analisis_box]
1133
+ )
1134
+
1135
  if __name__ == "__main__":
1136
+ print("Iniciando app tesis...")
1137
  demo.launch()