import re import os import json import random import uuid import html import pandas as pd from datetime import datetime import gradio as gr from transformers import pipeline import gspread from oauth2client.service_account import ServiceAccountCredentials # ===================================================== # CONFIGURACIÓN GENERAL # ===================================================== SPREADSHEET_ID = "1wR5Pf9k0NBIPbDqJdBdQQJ2F5n6MYLbPLPXUwNjxeTM" NOMBRE_HOJA = "Hoja 1" # Pestaña principal de interacciones NOMBRE_HOJA_CONSENTIMIENTOS = "Consentimientos" # Se crea automáticamente si no existe ENCABEZADOS = [ "fecha_hora", "folio_atencion", "matricula", "nombre", "grupo", "mensaje", "codigo_pregunta", "dimension_guion", "numero_pregunta", "tipo_intervencion", "pregunta_guion", "clasificacion", "intencion", "tema", "label", "score", "nivel_riesgo", "semaforo", "coincidencias_riesgo", "probabilidades", "tipo_riesgo", "ideacion_suicida", "desesperanza", "aislamiento", "alerta_urgente", "respuesta_agente" ] # ===================================================== # CONFIGURACIÓN DEL CONSENTIMIENTO # ===================================================== VERSION_CONSENTIMIENTO = "1.0" # ===================================================== # GUIÓN CONVERSACIONAL PARA EL PILOTAJE # ===================================================== GUION_CONVERSACIONAL = [ {"codigo": "P01", "dimension": "experiencias_socioemocionales", "pregunta": "Para comenzar, ¿cómo te has sentido emocionalmente durante los últimos días?", "profundizacion": "¿Podrías contarme un poco más sobre lo que te hizo sentir así?"}, {"codigo": "P02", "dimension": "experiencias_socioemocionales", "pregunta": "¿Hay alguna experiencia reciente dentro de la escuela que haya influido en tu estado de ánimo?", "profundizacion": "¿Qué ocurrió y qué emociones experimentaste en ese momento?"}, {"codigo": "P03", "dimension": "contexto_escolar", "pregunta": "¿Cómo describirías el ambiente que se vive actualmente en tu grupo y en tu escuela?", "profundizacion": "¿Qué aspectos del ambiente escolar te hacen sentir cómodo o incómodo?"}, {"codigo": "P04", "dimension": "contexto_escolar", "pregunta": "¿Cómo describirías tu relación con tus compañeros y docentes?", "profundizacion": "¿Te sientes escuchado, respetado e integrado dentro de tu grupo?"}, {"codigo": "P05", "dimension": "estres_escolar", "pregunta": "¿Qué situaciones escolares te generan actualmente mayor estrés, preocupación o malestar?", "profundizacion": "¿Cómo afectan esas situaciones tu concentración, ánimo o desempeño escolar?"}, {"codigo": "P06", "dimension": "percepcion_apoyo", "pregunta": "Cuando atraviesas una situación difícil, ¿cuentas con alguna persona que pueda escucharte o apoyarte?", "profundizacion": "¿A quién recurres y qué tipo de apoyo recibes?"}, {"codigo": "P07", "dimension": "estrategias_afrontamiento", "pregunta": "Cuando te sientes preocupado, triste, enojado o estresado, ¿qué haces normalmente para afrontar la situación?", "profundizacion": "¿Consideras que esa estrategia realmente te ayuda?"}, {"codigo": "P08", "dimension": "necesidades_apoyo", "pregunta": "¿Qué tipo de apoyo, actividad o recurso consideras que ayudaría a los estudiantes a sentirse mejor en la escuela?", "profundizacion": "¿Utilizarías un espacio de escucha o una herramienta digital para solicitar apoyo?"} ] # ===================================================== # CARGAR MODELO DE SENTIMIENTO # ===================================================== print("Cargando modelo de sentimiento...") modelo_sentimiento = pipeline( "text-classification", model="pysentimiento/robertuito-sentiment-analysis", top_k=None ) print("Modelo cargado correctamente") # ===================================================== # CONEXIÓN A GOOGLE SHEETS # ===================================================== def conectar_sheet(): """ Conecta con Google Sheets usando el Secret GOOGLE_CREDENTIALS. En Hugging Face Spaces, debes guardar el JSON completo del service account en Settings > Secrets con el nombre exacto: GOOGLE_CREDENTIALS """ scope = [ "https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive" ] creds_json = os.getenv("GOOGLE_CREDENTIALS") if not creds_json: raise ValueError( "No se encontró el Secret GOOGLE_CREDENTIALS. " "Verifica en Hugging Face: Settings > Secrets." ) try: creds_dict = json.loads(creds_json) except Exception as e: raise ValueError( f"El Secret GOOGLE_CREDENTIALS no tiene formato JSON válido: {repr(e)}" ) creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope) client = gspread.authorize(creds) archivo = client.open_by_key(SPREADSHEET_ID) try: sheet = archivo.worksheet(NOMBRE_HOJA) except Exception: # Si no encuentra la pestaña por nombre, usa la primera hoja. sheet = archivo.sheet1 return sheet def conectar_sheet_consentimientos(): """Conecta con la pestaña Consentimientos y la crea si no existe.""" scope = [ "https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive" ] creds_json = os.getenv("GOOGLE_CREDENTIALS") if not creds_json: raise ValueError( "No se encontró el Secret GOOGLE_CREDENTIALS. " "Verifica en Hugging Face: Settings > Secrets." ) try: creds_dict = json.loads(creds_json) except Exception as e: raise ValueError( f"El Secret GOOGLE_CREDENTIALS no tiene formato JSON válido: {repr(e)}" ) creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope) client = gspread.authorize(creds) archivo = client.open_by_key(SPREADSHEET_ID) try: sheet = archivo.worksheet(NOMBRE_HOJA_CONSENTIMIENTOS) except Exception: sheet = archivo.add_worksheet( title=NOMBRE_HOJA_CONSENTIMIENTOS, rows=1000, cols=8 ) return sheet def guardar_consentimiento(folio_atencion): """Guarda la aceptación del consentimiento en una pestaña independiente.""" try: sheet = conectar_sheet_consentimientos() encabezados = [ "fecha_hora", "folio_atencion", "consentimiento_aceptado", "version_consentimiento", "tipo_registro", "observaciones" ] if not sheet.row_values(1): sheet.append_row(encabezados, value_input_option="USER_ENTERED") fila = [ datetime.now().strftime("%Y-%m-%d %H:%M:%S"), str(folio_atencion or ""), "si", str(VERSION_CONSENTIMIENTO), "consentimiento_informado", "Aceptación voluntaria registrada antes de iniciar la entrevista" ] sheet.append_row( fila, value_input_option="USER_ENTERED", insert_data_option="INSERT_ROWS" ) print(f"✅ Consentimiento guardado. Folio: {folio_atencion}") return True, "Consentimiento guardado correctamente" except Exception as e: error = f"❌ Error al guardar consentimiento: {repr(e)}" print(error) return False, error def asegurar_encabezados(sheet): """ Coloca encabezados si la hoja está vacía. Esto ayuda a que los datos no se escriban en columnas desordenadas. """ try: primera_fila = sheet.row_values(1) if not primera_fila: sheet.append_row(ENCABEZADOS, value_input_option="USER_ENTERED") print("Encabezados agregados correctamente") except Exception as e: print(f"No se pudieron verificar/agregar encabezados: {repr(e)}") # ===================================================== # FUNCIONES DE RESPALDO FOSO II # ===================================================== def detectar_pregunta_foso2(mensaje): return False def responder_foso2(mensaje): return ( "Gracias por tu pregunta. En este momento no se detectó una consulta pedagógica " "específica de FOSO II, pero puedes escribir con más detalle tu duda." ) # ===================================================== # GENERAR FOLIO DE ATENCIÓN # ===================================================== def generar_folio_atencion(): return "ATN-" + str(uuid.uuid4())[:8].upper() # ===================================================== # PATRONES DE RIESGO # ===================================================== PATRONES_RIESGO_ALTO = [ r"\bme quiero morir\b", r"\bquiero morir\b", r"\bno quiero vivir\b", r"\bquiero terminar con todo\b", r"\bquiero acabar con todo\b", r"\bquiero matarme\b", r"\bme voy a suicidar\b", r"\bsuicidarme\b", r"\bquitarme la vida\b", r"\bya no quiero vivir\b", r"\bsería mejor morir\b", r"\bseria mejor morir\b", r"\bestoy pensando en matarme\b", ] PATRONES_RIESGO_MODERADO = [ r"\bnada tiene sentido\b", r"\bno veo salida\b", r"\bya no puedo\b", r"\bquiero desaparecer\b", r"\bme siento mal\b", r"\bestoy muy triste\b", r"\bestoy deprimid[oa]\b", r"\bme siento solo\b", r"\bme siento sola\b", r"\bnadie me entiende\b", r"\btengo miedo\b", r"\bestoy muy estresad[oa]\b", r"\bme siento estresad[oa]\b", r"\btengo muchas tareas\b", r"\bestoy cansad[oa]\b", r"\bme siento abrumad[oa]\b", r"\bno puedo más\b", r"\bno puedo mas\b", r"\bya no aguanto\b", r"\btodo sería mejor sin mí\b", r"\btodo seria mejor sin mi\b", ] PATRONES_DESESPERANZA = [ r"\bnada tiene sentido\b", r"\bno veo salida\b", r"\bya no puedo\b", r"\bno puedo más\b", r"\bno puedo mas\b", r"\bya no aguanto\b", r"\bquisiera desaparecer\b", r"\btodo sería mejor sin mí\b", r"\btodo seria mejor sin mi\b", r"\bmi vida no vale\b", r"\bsoy una carga\b", ] PATRONES_AISLAMIENTO = [ r"\bme siento solo\b", r"\bme siento sola\b", r"\bnadie me entiende\b", r"\bestoy solo\b", r"\bestoy sola\b", r"\bno tengo a nadie\b", r"\bquiero aislarme\b", r"\bno quiero hablar con nadie\b", ] PATRONES_IDEACION_SUICIDA = [ r"\bme quiero morir\b", r"\bquiero morir\b", r"\bno quiero vivir\b", r"\bquiero matarme\b", r"\bme voy a suicidar\b", r"\bsuicidarme\b", r"\bquitarme la vida\b", r"\bestoy pensando en matarme\b", r"\bquisiera dormirme y no despertar\b", ] # ===================================================== # FUNCIÓN PARA CALCULAR NIVEL DE RIESGO # ===================================================== def calcular_nivel_riesgo(texto, label, score): t = str(texto or "").lower().strip() coincidencias_altas = [p for p in PATRONES_RIESGO_ALTO if re.search(p, t)] coincidencias_moderadas = [p for p in PATRONES_RIESGO_MODERADO if re.search(p, t)] if coincidencias_altas: return "alto", "🔴", coincidencias_altas + coincidencias_moderadas if coincidencias_moderadas: return "moderado", "🟠", coincidencias_moderadas if label == "NEG" and score >= 0.90: return "moderado", "🟠", ["modelo_NEG_alto"] return "bajo", "🟢", [] # ===================================================== # DETECTAR TEMA PRINCIPAL DEL MENSAJE # ===================================================== def detectar_tema(texto): t = str(texto or "").lower().strip() if any(p in t for p in ["no puedo dormir", "insomnio", "duermo mal", "no duermo"]): return "sueno" if any(p in t for p in ["triste", "tristeza", "deprimido", "deprimida", "llorar", "vacío", "vacio"]): return "tristeza" if any(p in t for p in ["ansiedad", "ansioso", "ansiosa", "estres", "estrés", "abrumado", "abrumada", "presión", "presion"]): return "ansiedad" if any(p in t for p in ["tarea", "tareas", "examen", "materia", "profe", "profesor", "calificación", "calificacion", "estudiar", "actividad"]): return "academico" if any(p in t for p in ["familia", "mamá", "mama", "papá", "papa", "casa", "hermano", "hermana", "padres"]): return "familia" if any(p in t for p in ["novio", "novia", "pareja", "terminé", "termine", "terminamos", "relación", "relacion"]): return "pareja" if any(p in t for p in ["solo", "sola", "nadie me entiende", "sin amigos", "me siento aislado", "me siento aislada"]): return "soledad" if any(p in t for p in ["horario", "documento", "trámite", "tramite", "grupo", "boleta", "inscripción", "inscripcion"]): return "administrativo" return "general" # ===================================================== # DETECCIÓN DE INDICADORES DE RIESGO SUICIDA # ===================================================== def detectar_indicadores_suicidio(texto): t = str(texto or "").lower().strip() ideacion = any(re.search(p, t) for p in PATRONES_IDEACION_SUICIDA) desesperanza = any(re.search(p, t) for p in PATRONES_DESESPERANZA) aislamiento = any(re.search(p, t) for p in PATRONES_AISLAMIENTO) if ideacion: tipo_riesgo = "riesgo_suicida_alto" alerta_urgente = "si" elif desesperanza or aislamiento: tipo_riesgo = "riesgo_socioemocional_moderado" alerta_urgente = "no" else: tipo_riesgo = "malestar_general_o_sin_indicadores" alerta_urgente = "no" return { "ideacion_suicida": "si" if ideacion else "no", "desesperanza": "si" if desesperanza else "no", "aislamiento": "si" if aislamiento else "no", "tipo_riesgo": tipo_riesgo, "alerta_urgente": alerta_urgente } # ===================================================== # ANALIZAR MENSAJE # ===================================================== def analizar_frase(texto): texto = str(texto or "").strip() if not texto: return None resultado_modelo = modelo_sentimiento(texto)[0] mejor = max(resultado_modelo, key=lambda x: x["score"]) label = mejor["label"] score = float(mejor["score"]) probabilidades = { x["label"]: round(float(x["score"]), 3) for x in resultado_modelo } texto_lower = texto.lower() nivel_riesgo, semaforo, coincidencias_riesgo = calcular_nivel_riesgo( texto_lower, label, score ) if nivel_riesgo == "alto": clasificacion = "alto_riesgo" elif nivel_riesgo == "moderado": clasificacion = "riesgo_moderado" else: clasificacion = "normal" if any(p in texto_lower for p in [ "tarea", "tareas", "profe", "profesor", "materia", "ejercicio", "actividad", "tema", "examen", "calificación", "calificacion", "muchas tareas", "no entiendo", "estudiar" ]): intencion = "academico_emocional" elif any(p in texto_lower for p in [ "horario", "documento", "trámite", "tramite", "grupo", "boleta", "inscripción", "inscripcion" ]): intencion = "administrativo" else: intencion = "emocional" tema = detectar_tema(texto) indicadores = detectar_indicadores_suicidio(texto) return { "clasificacion": clasificacion, "intencion": intencion, "label": label, "score": score, "probabilidades": probabilidades, "nivel_riesgo": nivel_riesgo, "semaforo": semaforo, "tema": tema, "coincidencias_riesgo": coincidencias_riesgo, "tipo_riesgo": indicadores["tipo_riesgo"], "ideacion_suicida": indicadores["ideacion_suicida"], "desesperanza": indicadores["desesperanza"], "aislamiento": indicadores["aislamiento"], "alerta_urgente": indicadores["alerta_urgente"] } # ===================================================== # BLOQUES DE TEXTO VARIABLES # ===================================================== def elegir(lista): return random.choice(lista) def construir_sugerencias_por_tema(tema, intencion): sugerencias_generales = [ "tratar de poner en palabras lo que estás sintiendo para entenderlo mejor", "buscar a una persona de confianza con quien puedas hablar", "hacer una pausa breve, respirar despacio y bajar un poco la tensión del momento", "evitar cargar con todo tú solo o tú sola", ] mapa = { "sueno": [ "evitar usar el celular unos minutos antes de dormir", "hacer respiraciones lentas y profundas antes de acostarte", "anotar las preocupaciones que te están quitando el sueño", "pedir apoyo si el insomnio se está volviendo frecuente" ], "tristeza": [ "expresar lo que sientes con alguien de confianza", "no aislarte por completo en este momento", "realizar una actividad sencilla que te ayude a salir un poco de la carga emocional", "buscar acompañamiento del área de orientación o psicología" ], "ansiedad": [ "hacer pausas cortas durante el día para disminuir la saturación mental", "dividir el problema en partes pequeñas en lugar de querer resolver todo al mismo tiempo", "practicar respiración profunda por unos minutos", "identificar qué situación te está generando más presión" ], "academico": [ "organizar tus tareas por prioridad y fecha de entrega", "empezar por una sola actividad en vez de pensar en todo al mismo tiempo", "pedir que te expliquen nuevamente el tema que no entiendes", "buscar apoyo con tu docente, tutor o un compañero de confianza" ], "familia": [ "hablar con calma con una persona de tu familia que te genere confianza", "buscar apoyo adulto si en casa te sientes sobrepasado o sobrepasada", "tratar de identificar qué situación familiar te está afectando más", "pedir acompañamiento institucional si el conflicto continúa" ], "pareja": [ "tomarte un momento para entender qué fue lo que más te lastimó", "evitar tomar decisiones impulsivas cuando la emoción está muy intensa", "hablar con alguien de confianza para no quedarte solo o sola con esto", "recordar que una situación afectiva difícil también necesita acompañamiento" ], "soledad": [ "acercarte a una persona con quien te sientas mínimamente en confianza", "evitar encerrarte completamente con lo que sientes", "buscar apoyo en orientación o tutoría", "dar un primer paso pequeño para pedir ayuda" ], "administrativo": [ "explicar con más detalle cuál es el trámite o la duda que necesitas resolver", "revisar si tu duda corresponde a horario, grupo, boleta o documento", "acercarte al área del plantel que lleve ese proceso", "tener a la mano tus datos escolares para recibir una orientación más clara" ], "general": sugerencias_generales } sugerencias = mapa.get(tema, sugerencias_generales) if intencion == "administrativo": sugerencias = mapa["administrativo"] elif intencion == "academico_emocional" and tema == "general": sugerencias = mapa["academico"] random.shuffle(sugerencias) return sugerencias[:3] # ===================================================== # RESPUESTA DE CRISIS # ===================================================== def construir_respuesta_crisis(nombre="Estudiante"): return ( f"Hola {nombre}. Gracias por decir lo que estás sintiendo.\n\n" "Lo que acabas de expresar requiere atención inmediata. No estás solo o sola.\n\n" "Por favor, haz esto ahora mismo:\n" "1. Busca a un adulto, familiar, docente, tutor, orientador u otra persona de confianza y dile que necesitas acompañamiento inmediato.\n" "2. No te quedes solo o sola en este momento.\n" "3. Acércate de inmediato al área de orientación, tutoría o psicología de tu plantel.\n" "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" "Tu bienestar importa y pedir ayuda ahora es muy importante.\n\n" "Respóndeme con una sola palabra:\n" "- 'solo' si estás sin compañía\n" "- 'acompañado' si hay alguien contigo" ) # ===================================================== # RESPUESTA AMPLIA DEL AGENTE # ===================================================== def construir_respuesta_amplia(resultado, nombre="Estudiante"): if not resultado: return ( f"Hola {nombre}. Gracias por escribir.\n\n" "Quiero leerte con atención. Cuéntame un poco más sobre lo que estás viviendo " "para poder orientarte mejor." ) clasificacion = resultado.get("clasificacion", "normal") intencion = resultado.get("intencion", "emocional") nivel_riesgo = resultado.get("nivel_riesgo", "bajo") tema = resultado.get("tema", "general") aperturas = [ f"Hola {nombre}. Gracias por confiar en este espacio para expresar lo que estás viviendo.", f"Hola {nombre}. Gracias por compartir lo que sientes; hacerlo ya es un paso importante.", f"Hola {nombre}. Valoro que hayas escrito sobre esto, porque lo que te pasa sí importa." ] empatia_moderada = [ "Lo que estás sintiendo merece atención, acompañamiento y un espacio para hablarlo con calma.", "A veces la presión emocional, personal o escolar puede sentirse muy pesada, y no tienes por qué cargar con todo sin apoyo.", "Cuando varias cosas se juntan al mismo tiempo, es normal sentirse cansado, abrumado o sin mucha claridad." ] preguntas_finales = [ "Si quieres, puedo ayudarte a identificar qué te está afectando más en este momento.", "Si te parece, podemos ir paso a paso para ubicar qué situación te está pesando más.", "Cuéntame un poco más y te ayudo a ordenar lo que estás sintiendo." ] if clasificacion == "alto_riesgo" or nivel_riesgo == "alto": return construir_respuesta_crisis(nombre) if clasificacion == "riesgo_moderado" or nivel_riesgo == "moderado": sugerencias = construir_sugerencias_por_tema(tema, intencion) texto_tema = { "sueno": "El problema para dormir muchas veces aparece cuando hay preocupaciones, tensión acumulada o malestar emocional.", "tristeza": "La tristeza sostenida puede ser una señal de que algo importante necesita atención y apoyo.", "ansiedad": "La ansiedad o el estrés pueden afectar tanto tus pensamientos como tu cuerpo, tu concentración y tu descanso.", "academico": "La carga escolar puede generar mucha presión cuando se acumulan tareas, exámenes o temas difíciles.", "familia": "Los problemas familiares también pueden afectar mucho el ánimo, la concentración y la sensación de estabilidad.", "pareja": "Las situaciones afectivas pueden doler profundamente y alterar el equilibrio emocional.", "soledad": "Sentirse solo o sola puede volver más pesada cualquier situación difícil.", "administrativo": "Cuando una duda escolar o administrativa no se resuelve, también puede generar estrés y frustración.", "general": "A veces cuesta poner en orden lo que sentimos, sobre todo cuando hay varias preocupaciones al mismo tiempo." } return ( f"{elegir(aperturas)}\n\n" f"{elegir(empatia_moderada)} {texto_tema.get(tema, texto_tema['general'])}\n\n" "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" "Estas opciones podrían ayudarte:\n" f"- {sugerencias[0]}\n" f"- {sugerencias[1]}\n" f"- {sugerencias[2]}\n\n" "También es recomendable que busques acompañamiento en tu plantel para no enfrentar esto en soledad. " "Puedes acercarte a tutoría, orientación educativa o psicología.\n\n" f"{elegir(preguntas_finales)} " "Puedes decirme si esto se relaciona más con la escuela, con tus emociones, con tu familia o con algún problema personal." ) if intencion == "academico_emocional": sugerencias = construir_sugerencias_por_tema("academico", intencion) return ( f"{elegir(aperturas)}\n\n" "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" "Eso no significa que no puedas avanzar; muchas veces significa que necesitas apoyo, orden y empezar por una sola parte del problema.\n\n" "Podrías intentar lo siguiente:\n" f"- {sugerencias[0]}\n" f"- {sugerencias[1]}\n" f"- {sugerencias[2]}\n\n" "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" "Cuéntame qué materia o actividad te está preocupando más y avanzamos paso a paso." ) if intencion == "administrativo": sugerencias = construir_sugerencias_por_tema("administrativo", intencion) return ( f"{elegir(aperturas)}\n\n" "Puedo orientarte con dudas escolares o administrativas relacionadas con horario, grupo, documentos, boletas, trámites o indicaciones generales del plantel.\n\n" "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" "Por ejemplo, puedes especificar si tu duda se relaciona con:\n" f"- {sugerencias[0]}\n" f"- {sugerencias[1]}\n" f"- {sugerencias[2]}\n\n" "Entre más claro me expliques tu situación, más precisa podrá ser mi orientación." ) sugerencias = construir_sugerencias_por_tema(tema, intencion) return ( f"{elegir(aperturas)}\n\n" "A veces poner en palabras lo que nos pasa ayuda a entender mejor la situación y a encontrar un primer paso para afrontarla. " "No necesitas resolver todo de una sola vez.\n\n" "Algunas opciones que podrían ayudarte en este momento son:\n" f"- {sugerencias[0]}\n" f"- {sugerencias[1]}\n" f"- {sugerencias[2]}\n\n" "Si quieres, puedo seguir acompañándote para identificar qué es lo que más te preocupa y cómo podrías empezar a manejarlo." ) # ===================================================== # ANÁLISIS TEXTUAL # ===================================================== def construir_analisis_textual(frase, resultado): if not resultado: return "Sin análisis." probs = resultado.get("probabilidades", {}) texto = [] texto.append("ANÁLISIS EMOCIONAL") texto.append(f"Frase: {frase}") texto.append(f"Clasificación: {resultado.get('clasificacion', '')}") texto.append(f"Intención detectada: {resultado.get('intencion', '')}") texto.append(f"Tema detectado: {resultado.get('tema', '')}") texto.append(f"Etiqueta principal: {resultado.get('label', '')}") texto.append(f"Confianza: {float(resultado.get('score', 0.0)):.3f}") texto.append(f"Nivel de riesgo: {resultado.get('nivel_riesgo', '')}") texto.append(f"Semáforo: {resultado.get('semaforo', '')}") texto.append(f"Coincidencias de riesgo: {resultado.get('coincidencias_riesgo', '')}") texto.append(f"Tipo de riesgo: {resultado.get('tipo_riesgo', '')}") texto.append(f"Ideación suicida: {resultado.get('ideacion_suicida', '')}") texto.append(f"Desesperanza: {resultado.get('desesperanza', '')}") texto.append(f"Aislamiento: {resultado.get('aislamiento', '')}") texto.append(f"Alerta urgente: {resultado.get('alerta_urgente', '')}") texto.append("") texto.append("Probabilidades:") texto.append(f"- NEG: {float(probs.get('NEG', 0.0)):.3f}") texto.append(f"- NEU: {float(probs.get('NEU', 0.0)):.3f}") texto.append(f"- POS: {float(probs.get('POS', 0.0)):.3f}") return "\n".join(texto) # ===================================================== # GUARDAR EN GOOGLE SHEETS # ===================================================== def guardar_interaccion( folio_atencion, matricula, nombre, grupo, mensaje, resultado, respuesta, datos_guion=None ): try: print("ENTRANDO A guardar_interaccion...") sheet = conectar_sheet() asegurar_encabezados(sheet) print("Conexión con Google Sheets correcta") resultado = resultado or {} datos_guion = datos_guion or {} fila = [ datetime.now().strftime("%Y-%m-%d %H:%M:%S"), str(folio_atencion or ""), str(matricula or ""), str(nombre or ""), str(grupo or ""), str(mensaje or ""), str(datos_guion.get("codigo", "")), str(datos_guion.get("dimension", "")), str(datos_guion.get("numero", "")), str(datos_guion.get("tipo", "")), str(datos_guion.get("pregunta", "")), str(resultado.get("clasificacion", "")), str(resultado.get("intencion", "")), str(resultado.get("tema", "")), str(resultado.get("label", "")), str(resultado.get("score", "")), str(resultado.get("nivel_riesgo", "")), str(resultado.get("semaforo", "")), str(resultado.get("coincidencias_riesgo", "")), str(resultado.get("probabilidades", "")), str(resultado.get("tipo_riesgo", "")), str(resultado.get("ideacion_suicida", "")), str(resultado.get("desesperanza", "")), str(resultado.get("aislamiento", "")), str(resultado.get("alerta_urgente", "")), str(respuesta or "") ] sheet.append_row( fila, value_input_option="USER_ENTERED", insert_data_option="INSERT_ROWS" ) print("✅ Datos guardados correctamente en Google Sheets") return True, "Guardado correcto" except Exception as e: error = f"❌ Error al guardar en Google Sheets: {repr(e)}" print(error) return False, error # ===================================================== # FORMATO VISUAL DE LA CONVERSACIÓN # ===================================================== def formatear_pregunta(numero, texto, profundizacion=False): texto_seguro = html.escape(str(texto or "")) if profundizacion: return f"""
Bienvenido(a) a SOFIA, Sistema de Orientación con Inteligencia Artificial.
SOFIA brinda orientación inicial en temas emocionales, escolares y socioeducativos. Estás interactuando con un sistema de inteligencia artificial.
Tus mensajes serán analizados para generar orientación inicial y detectar posibles señales de riesgo socioemocional.
Versión del consentimiento: {VERSION_CONSENTIMIENTO}
He leído y comprendido el Consentimiento Informado. Acepto voluntariamente utilizar SOFIA y entiendo que brinda orientación inicial, no sustituye la atención profesional y puedo dejar de utilizarlo en cualquier momento.
✅ Brindarte acompañamiento inicial
✅ Detectar señales de riesgo socioemocional
✅ Orientarte en temas emocionales y escolares
✅ Canalizarte si necesitas ayuda urgente
Este agente no sustituye atención psicológica, médica o de emergencia.
Si existe peligro inmediato, llama al 911 o a la Línea de la Vida: 800 911 2000.
🔒 Hola, este es un espacio seguro para ti.
Puedes hablar conmigo con confianza.
Todo lo que compartas será tratado con seriedad, respeto y
confidencialidad, conforme al aviso de privacidad institucional.
Estoy aquí para apoyarte. Si te sientes en riesgo o necesitas ayuda urgente,
busca a tu tutor, orientador del bienestar o a un adulto de confianza.
No estás solo.
📋 Evaluación rápida
📚 Recursos de apoyo
🧠 Acompañamiento emocional inicial