Spaces:
				
			
			
	
			
			
		Sleeping
		
	
	
	
			
			
	
	
	
	
		
		
		Sleeping
		
	Upload 3 files
Browse files- app.py +402 -0
 - app_epico.py +393 -0
 - requirements.txt +7 -0
 
    	
        app.py
    ADDED
    
    | 
         @@ -0,0 +1,402 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import gradio as gr
         
     | 
| 2 | 
         
            +
            import pandas as pd
         
     | 
| 3 | 
         
            +
            import spacy
         
     | 
| 4 | 
         
            +
            from textblob import TextBlob
         
     | 
| 5 | 
         
            +
            from transformers import pipeline
         
     | 
| 6 | 
         
            +
            import torch
         
     | 
| 7 | 
         
            +
            from io import BytesIO
         
     | 
| 8 | 
         
            +
            import numpy as np
         
     | 
| 9 | 
         
            +
             
     | 
| 10 | 
         
            +
            # Cargar modelos
         
     | 
| 11 | 
         
            +
            print("⏳ Cargando modelos...")
         
     | 
| 12 | 
         
            +
             
     | 
| 13 | 
         
            +
            # 1. Cargar modelo de spaCy para español
         
     | 
| 14 | 
         
            +
            try:
         
     | 
| 15 | 
         
            +
                nlp = spacy.load("es_core_news_sm")
         
     | 
| 16 | 
         
            +
                print("✅ Modelo spaCy cargado correctamente")
         
     | 
| 17 | 
         
            +
            except OSError:
         
     | 
| 18 | 
         
            +
                print("❌ Modelo spaCy no encontrado. Usando análisis básico...")
         
     | 
| 19 | 
         
            +
                nlp = None
         
     | 
| 20 | 
         
            +
             
     | 
| 21 | 
         
            +
            # 2. Cargar múltiples modelos de sentimiento
         
     | 
| 22 | 
         
            +
            models = {}
         
     | 
| 23 | 
         
            +
             
     | 
| 24 | 
         
            +
            # Modelo multilingüe principal
         
     | 
| 25 | 
         
            +
            try:
         
     | 
| 26 | 
         
            +
                models['multilingual'] = pipeline(
         
     | 
| 27 | 
         
            +
                    "text-classification", 
         
     | 
| 28 | 
         
            +
                    model="tabularisai/multilingual-sentiment-analysis"
         
     | 
| 29 | 
         
            +
                )
         
     | 
| 30 | 
         
            +
                print("✅ Modelo multilingüe cargado")
         
     | 
| 31 | 
         
            +
            except Exception as e:
         
     | 
| 32 | 
         
            +
                print(f"❌ Error con modelo multilingüe: {e}")
         
     | 
| 33 | 
         
            +
                models['multilingual'] = None
         
     | 
| 34 | 
         
            +
             
     | 
| 35 | 
         
            +
            # Modelo BERT para sentimiento detallado
         
     | 
| 36 | 
         
            +
            try:
         
     | 
| 37 | 
         
            +
                models['bert'] = pipeline(
         
     | 
| 38 | 
         
            +
                    "sentiment-analysis",
         
     | 
| 39 | 
         
            +
                    model="nlptown/bert-base-multilingual-uncased-sentiment",
         
     | 
| 40 | 
         
            +
                    tokenizer="nlptown/bert-base-multilingual-uncased-sentiment"
         
     | 
| 41 | 
         
            +
                )
         
     | 
| 42 | 
         
            +
                print("✅ Modelo BERT cargado")
         
     | 
| 43 | 
         
            +
            except Exception as e:
         
     | 
| 44 | 
         
            +
                print(f"❌ Error con modelo BERT: {e}")
         
     | 
| 45 | 
         
            +
                models['bert'] = None
         
     | 
| 46 | 
         
            +
             
     | 
| 47 | 
         
            +
            # Diccionario léxico mejorado para español
         
     | 
| 48 | 
         
            +
            palabras_positivas = {
         
     | 
| 49 | 
         
            +
                'bueno', 'excelente', 'fantástico', 'maravilloso', 'perfecto', 'genial',
         
     | 
| 50 | 
         
            +
                'increíble', 'amo', 'encanta', 'feliz', 'contento', 'satisfecho', 'agradable',
         
     | 
| 51 | 
         
            +
                'recomiendo', 'magnífico', 'extraordinario', 'asombroso', 'increíble', 'estupendo',
         
     | 
| 52 | 
         
            +
                'fabril', 'perfectamente', 'óptimo', 'superior', 'mejor', 'inmejorable', 'ideal'
         
     | 
| 53 | 
         
            +
            }
         
     | 
| 54 | 
         
            +
             
     | 
| 55 | 
         
            +
            palabras_negativas = {
         
     | 
| 56 | 
         
            +
                'malo', 'terrible', 'horrible', 'pésimo', 'odio', 'decepcionado', 'fatal',
         
     | 
| 57 | 
         
            +
                'triste', 'enojado', 'frustrado', 'pobre', 'deficiente', 'desastroso',
         
     | 
| 58 | 
         
            +
                'insatisfecho', 'decepcionante', 'horroroso', 'desastroso', 'pésimo', 'malísimo',
         
     | 
| 59 | 
         
            +
                'inútil', 'defectuoso', 'deplorable', 'lamentable', 'desagradable', 'terrible'
         
     | 
| 60 | 
         
            +
            }
         
     | 
| 61 | 
         
            +
             
     | 
| 62 | 
         
            +
            def analizar_sentimiento_multimodelo(texto):
         
     | 
| 63 | 
         
            +
                """Combina múltiples métodos para análisis más preciso"""
         
     | 
| 64 | 
         
            +
                resultados = {}
         
     | 
| 65 | 
         
            +
                
         
     | 
| 66 | 
         
            +
                # Método 1: Modelo multilingüe principal
         
     | 
| 67 | 
         
            +
                if models['multilingual']:
         
     | 
| 68 | 
         
            +
                    try:
         
     | 
| 69 | 
         
            +
                        resultado = models['multilingual'](texto)[0]
         
     | 
| 70 | 
         
            +
                        resultados['multilingual'] = {
         
     | 
| 71 | 
         
            +
                            'label': resultado['label'],
         
     | 
| 72 | 
         
            +
                            'score': resultado['score'],
         
     | 
| 73 | 
         
            +
                            'normalized_score': resultado['score'] if resultado['label'] == 'POSITIVE' else -resultado['score']
         
     | 
| 74 | 
         
            +
                        }
         
     | 
| 75 | 
         
            +
                    except Exception as e:
         
     | 
| 76 | 
         
            +
                        resultados['multilingual'] = {'error': str(e)}
         
     | 
| 77 | 
         
            +
                
         
     | 
| 78 | 
         
            +
                # Método 2: Modelo BERT con estrellas
         
     | 
| 79 | 
         
            +
                if models['bert']:
         
     | 
| 80 | 
         
            +
                    try:
         
     | 
| 81 | 
         
            +
                        resultado = models['bert'](texto)[0]
         
     | 
| 82 | 
         
            +
                        # Convertir estrellas a puntuación -1 a 1
         
     | 
| 83 | 
         
            +
                        star_mapping = {
         
     | 
| 84 | 
         
            +
                            '1 star': -1.0, '2 stars': -0.5, '3 stars': 0.0,
         
     | 
| 85 | 
         
            +
                            '4 stars': 0.5, '5 stars': 1.0
         
     | 
| 86 | 
         
            +
                        }
         
     | 
| 87 | 
         
            +
                        normalized_score = star_mapping.get(resultado['label'], 0.0)
         
     | 
| 88 | 
         
            +
                        resultados['bert'] = {
         
     | 
| 89 | 
         
            +
                            'label': resultado['label'],
         
     | 
| 90 | 
         
            +
                            'score': resultado['score'],
         
     | 
| 91 | 
         
            +
                            'normalized_score': normalized_score
         
     | 
| 92 | 
         
            +
                        }
         
     | 
| 93 | 
         
            +
                    except Exception as e:
         
     | 
| 94 | 
         
            +
                        resultados['bert'] = {'error': str(e)}
         
     | 
| 95 | 
         
            +
                
         
     | 
| 96 | 
         
            +
                # Método 3: Análisis léxico mejorado
         
     | 
| 97 | 
         
            +
                if nlp:
         
     | 
| 98 | 
         
            +
                    try:
         
     | 
| 99 | 
         
            +
                        doc = nlp(texto.lower())
         
     | 
| 100 | 
         
            +
                        palabras = [token.lemma_ for token in doc if token.is_alpha and len(token.text) > 2]
         
     | 
| 101 | 
         
            +
                        
         
     | 
| 102 | 
         
            +
                        positivas = sum(1 for palabra in palabras if palabra in palabras_positivas)
         
     | 
| 103 | 
         
            +
                        negativas = sum(1 for palabra in palabras if palabra in palabras_negativas)
         
     | 
| 104 | 
         
            +
                        
         
     | 
| 105 | 
         
            +
                        total_relevantes = len(palabras)
         
     | 
| 106 | 
         
            +
                        if total_relevantes > 0:
         
     | 
| 107 | 
         
            +
                            polaridad = (positivas - negativas) / total_relevantes
         
     | 
| 108 | 
         
            +
                            # Normalizar a -1 a 1
         
     | 
| 109 | 
         
            +
                            polaridad = max(-1.0, min(1.0, polaridad * 5))
         
     | 
| 110 | 
         
            +
                        else:
         
     | 
| 111 | 
         
            +
                            polaridad = 0.0
         
     | 
| 112 | 
         
            +
                        
         
     | 
| 113 | 
         
            +
                        resultados['lexico'] = {
         
     | 
| 114 | 
         
            +
                            'positivas': positivas,
         
     | 
| 115 | 
         
            +
                            'negativas': negativas,
         
     | 
| 116 | 
         
            +
                            'total_palabras': total_relevantes,
         
     | 
| 117 | 
         
            +
                            'normalized_score': polaridad
         
     | 
| 118 | 
         
            +
                        }
         
     | 
| 119 | 
         
            +
                    except Exception as e:
         
     | 
| 120 | 
         
            +
                        resultados['lexico'] = {'error': str(e)}
         
     | 
| 121 | 
         
            +
                
         
     | 
| 122 | 
         
            +
                # Método 4: TextBlob (para inglés y español básico)
         
     | 
| 123 | 
         
            +
                try:
         
     | 
| 124 | 
         
            +
                    blob = TextBlob(texto)
         
     | 
| 125 | 
         
            +
                    polaridad = blob.sentiment.polarity
         
     | 
| 126 | 
         
            +
                    resultados['textblob'] = {
         
     | 
| 127 | 
         
            +
                        'polarity': polaridad,
         
     | 
| 128 | 
         
            +
                        'subjectivity': blob.sentiment.subjectivity,
         
     | 
| 129 | 
         
            +
                        'normalized_score': polaridad
         
     | 
| 130 | 
         
            +
                    }
         
     | 
| 131 | 
         
            +
                except Exception as e:
         
     | 
| 132 | 
         
            +
                    resultados['textblob'] = {'error': str(e)}
         
     | 
| 133 | 
         
            +
                
         
     | 
| 134 | 
         
            +
                return resultados
         
     | 
| 135 | 
         
            +
             
     | 
| 136 | 
         
            +
            def determinar_sentimiento_final(resultados):
         
     | 
| 137 | 
         
            +
                """Combina resultados de todos los métodos"""
         
     | 
| 138 | 
         
            +
                scores = []
         
     | 
| 139 | 
         
            +
                pesos = {'multilingual': 0.4, 'bert': 0.3, 'lexico': 0.2, 'textblob': 0.1}
         
     | 
| 140 | 
         
            +
                
         
     | 
| 141 | 
         
            +
                for metodo, peso in pesos.items():
         
     | 
| 142 | 
         
            +
                    if metodo in resultados and 'normalized_score' in resultados[metodo]:
         
     | 
| 143 | 
         
            +
                        scores.append(resultados[metodo]['normalized_score'] * peso)
         
     | 
| 144 | 
         
            +
                
         
     | 
| 145 | 
         
            +
                if scores:
         
     | 
| 146 | 
         
            +
                    score_final = sum(scores)
         
     | 
| 147 | 
         
            +
                    
         
     | 
| 148 | 
         
            +
                    if score_final > 0.2:
         
     | 
| 149 | 
         
            +
                        return "😊 POSITIVO", score_final, "green"
         
     | 
| 150 | 
         
            +
                    elif score_final < -0.2:
         
     | 
| 151 | 
         
            +
                        return "😠 NEGATIVO", score_final, "red"
         
     | 
| 152 | 
         
            +
                    else:
         
     | 
| 153 | 
         
            +
                        return "😐 NEUTRO", score_final, "gray"
         
     | 
| 154 | 
         
            +
                else:
         
     | 
| 155 | 
         
            +
                    return "❓ INDETERMINADO", 0.0, "orange"
         
     | 
| 156 | 
         
            +
             
     | 
| 157 | 
         
            +
            def analizar_lingüistica(texto):
         
     | 
| 158 | 
         
            +
                """Análisis lingüístico con spaCy"""
         
     | 
| 159 | 
         
            +
                if not nlp:
         
     | 
| 160 | 
         
            +
                    return {"error": "Modelo spaCy no disponible"}
         
     | 
| 161 | 
         
            +
                
         
     | 
| 162 | 
         
            +
                doc = nlp(texto)
         
     | 
| 163 | 
         
            +
                
         
     | 
| 164 | 
         
            +
                analisis = {
         
     | 
| 165 | 
         
            +
                    'estadisticas': {
         
     | 
| 166 | 
         
            +
                        'total_tokens': len(doc),
         
     | 
| 167 | 
         
            +
                        'total_palabras': len([token for token in doc if token.is_alpha]),
         
     | 
| 168 | 
         
            +
                        'total_oraciones': len(list(doc.sents)),
         
     | 
| 169 | 
         
            +
                        'total_entidades': len(doc.ents)
         
     | 
| 170 | 
         
            +
                    },
         
     | 
| 171 | 
         
            +
                    'tokens': [],
         
     | 
| 172 | 
         
            +
                    'entidades': [],
         
     | 
| 173 | 
         
            +
                    'oraciones': [sent.text for sent in doc.sents]
         
     | 
| 174 | 
         
            +
                }
         
     | 
| 175 | 
         
            +
                
         
     | 
| 176 | 
         
            +
                # Análisis de tokens
         
     | 
| 177 | 
         
            +
                for token in doc[:20]:  # Mostrar solo primeros 20 tokens
         
     | 
| 178 | 
         
            +
                    analisis['tokens'].append({
         
     | 
| 179 | 
         
            +
                        'texto': token.text,
         
     | 
| 180 | 
         
            +
                        'lemma': token.lemma_,
         
     | 
| 181 | 
         
            +
                        'POS': token.pos_,
         
     | 
| 182 | 
         
            +
                        'explicacion': spacy.explain(token.pos_) or ''
         
     | 
| 183 | 
         
            +
                    })
         
     | 
| 184 | 
         
            +
                
         
     | 
| 185 | 
         
            +
                # Entidades nombradas
         
     | 
| 186 | 
         
            +
                for ent in doc.ents[:10]:  # Mostrar solo primeras 10 entidades
         
     | 
| 187 | 
         
            +
                    analisis['entidades'].append({
         
     | 
| 188 | 
         
            +
                        'texto': ent.text,
         
     | 
| 189 | 
         
            +
                        'tipo': ent.label_,
         
     | 
| 190 | 
         
            +
                        'explicacion': spacy.explain(ent.label_) or ''
         
     | 
| 191 | 
         
            +
                    })
         
     | 
| 192 | 
         
            +
                
         
     | 
| 193 | 
         
            +
                return analisis
         
     | 
| 194 | 
         
            +
             
     | 
| 195 | 
         
            +
            def analizar_texto_completo(texto):
         
     | 
| 196 | 
         
            +
                """Función principal que combina todo el análisis"""
         
     | 
| 197 | 
         
            +
                if not texto.strip():
         
     | 
| 198 | 
         
            +
                    return "❌ Por favor ingresa un texto para analizar", "", ""
         
     | 
| 199 | 
         
            +
                
         
     | 
| 200 | 
         
            +
                # Análisis de sentimiento
         
     | 
| 201 | 
         
            +
                resultados = analizar_sentimiento_multimodelo(texto)
         
     | 
| 202 | 
         
            +
                sentimiento_final, score_final, color = determinar_sentimiento_final(resultados)
         
     | 
| 203 | 
         
            +
                
         
     | 
| 204 | 
         
            +
                # Análisis lingüístico
         
     | 
| 205 | 
         
            +
                analisis_ling = analizar_lingüistica(texto)
         
     | 
| 206 | 
         
            +
                
         
     | 
| 207 | 
         
            +
                # Crear resumen de resultados
         
     | 
| 208 | 
         
            +
                resumen_html = f"""
         
     | 
| 209 | 
         
            +
                <div style='background-color: {color}20; padding: 20px; border-radius: 10px; border-left: 5px solid {color};'>
         
     | 
| 210 | 
         
            +
                    <h2 style='margin: 0; color: {color};'>{sentimiento_final}</h2>
         
     | 
| 211 | 
         
            +
                    <p style='margin: 5px 0;'><strong>Puntuación final:</strong> {score_final:.3f}</p>
         
     | 
| 212 | 
         
            +
                    <p style='margin: 5px 0;'><strong>Longitud del texto:</strong> {len(texto)} caracteres</p>
         
     | 
| 213 | 
         
            +
                </div>
         
     | 
| 214 | 
         
            +
                """
         
     | 
| 215 | 
         
            +
                
         
     | 
| 216 | 
         
            +
                # Detalles por método
         
     | 
| 217 | 
         
            +
                detalles_html = "<h3>📊 Resultados por Método:</h3>"
         
     | 
| 218 | 
         
            +
                for metodo, resultado in resultados.items():
         
     | 
| 219 | 
         
            +
                    detalles_html += f"<div style='margin-bottom: 15px;'>"
         
     | 
| 220 | 
         
            +
                    detalles_html += f"<strong>{metodo.upper()}:</strong><br>"
         
     | 
| 221 | 
         
            +
                    
         
     | 
| 222 | 
         
            +
                    if 'error' in resultado:
         
     | 
| 223 | 
         
            +
                        detalles_html += f"<span style='color: red;'>Error: {resultado['error']}</span>"
         
     | 
| 224 | 
         
            +
                    else:
         
     | 
| 225 | 
         
            +
                        if 'label' in resultado:
         
     | 
| 226 | 
         
            +
                            detalles_html += f"Etiqueta: {resultado['label']}<br>"
         
     | 
| 227 | 
         
            +
                        if 'score' in resultado:
         
     | 
| 228 | 
         
            +
                            detalles_html += f"Confianza: {resultado['score']:.3f}<br>"
         
     | 
| 229 | 
         
            +
                        if 'normalized_score' in resultado:
         
     | 
| 230 | 
         
            +
                            detalles_html += f"Puntuación: {resultado['normalized_score']:.3f}<br>"
         
     | 
| 231 | 
         
            +
                        if 'positivas' in resultado:
         
     | 
| 232 | 
         
            +
                            detalles_html += f"Palabras positivas: {resultado['positivas']}<br>"
         
     | 
| 233 | 
         
            +
                            detalles_html += f"Palabras negativas: {resultado['negativas']}<br>"
         
     | 
| 234 | 
         
            +
                    
         
     | 
| 235 | 
         
            +
                    detalles_html += "</div>"
         
     | 
| 236 | 
         
            +
                
         
     | 
| 237 | 
         
            +
                # Análisis lingüístico
         
     | 
| 238 | 
         
            +
                ling_html = "<h3>📝 Análisis Lingüístico:</h3>"
         
     | 
| 239 | 
         
            +
                if 'error' in analisis_ling:
         
     | 
| 240 | 
         
            +
                    ling_html += f"<p style='color: red;'>{analisis_ling['error']}</p>"
         
     | 
| 241 | 
         
            +
                else:
         
     | 
| 242 | 
         
            +
                    stats = analisis_ling['estadisticas']
         
     | 
| 243 | 
         
            +
                    ling_html += f"""
         
     | 
| 244 | 
         
            +
                    <p><strong>Estadísticas:</strong></p>
         
     | 
| 245 | 
         
            +
                    <ul>
         
     | 
| 246 | 
         
            +
                        <li>Total de tokens: {stats['total_tokens']}</li>
         
     | 
| 247 | 
         
            +
                        <li>Palabras: {stats['total_palabras']}</li>
         
     | 
| 248 | 
         
            +
                        <li>Oraciones: {stats['total_oraciones']}</li>
         
     | 
| 249 | 
         
            +
                        <li>Entidades: {stats['total_entidades']}</li>
         
     | 
| 250 | 
         
            +
                    </ul>
         
     | 
| 251 | 
         
            +
                    """
         
     | 
| 252 | 
         
            +
                    
         
     | 
| 253 | 
         
            +
                    if analisis_ling['entidades']:
         
     | 
| 254 | 
         
            +
                        ling_html += "<p><strong>Entidades detectadas:</strong></p>"
         
     | 
| 255 | 
         
            +
                        for ent in analisis_ling['entidades']:
         
     | 
| 256 | 
         
            +
                            ling_html += f"• {ent['texto']} ({ent['tipo']})<br>"
         
     | 
| 257 | 
         
            +
                
         
     | 
| 258 | 
         
            +
                return resumen_html, detalles_html, ling_html
         
     | 
| 259 | 
         
            +
             
     | 
| 260 | 
         
            +
            def analizar_archivo_excel(archivo):
         
     | 
| 261 | 
         
            +
                """Analizar archivo Excel con textos"""
         
     | 
| 262 | 
         
            +
                try:
         
     | 
| 263 | 
         
            +
                    df = pd.read_excel(archivo)
         
     | 
| 264 | 
         
            +
                    
         
     | 
| 265 | 
         
            +
                    # Buscar columnas con texto
         
     | 
| 266 | 
         
            +
                    columnas_texto = []
         
     | 
| 267 | 
         
            +
                    for col in df.columns:
         
     | 
| 268 | 
         
            +
                        if df[col].dtype == 'object':
         
     | 
| 269 | 
         
            +
                            # Verificar si contiene texto
         
     | 
| 270 | 
         
            +
                            muestras = df[col].dropna()[:5]
         
     | 
| 271 | 
         
            +
                            if any(isinstance(x, str) and len(str(x).strip()) > 10 for x in muestras):
         
     | 
| 272 | 
         
            +
                                columnas_texto.append(col)
         
     | 
| 273 | 
         
            +
                    
         
     | 
| 274 | 
         
            +
                    if not columnas_texto:
         
     | 
| 275 | 
         
            +
                        return pd.DataFrame({"Resultado": ["❌ No se encontraron columnas con texto suficiente para analizar"]})
         
     | 
| 276 | 
         
            +
                    
         
     | 
| 277 | 
         
            +
                    resultados = []
         
     | 
| 278 | 
         
            +
                    for col in columnas_texto[:2]:  # Analizar máximo 2 columnas
         
     | 
| 279 | 
         
            +
                        for idx, texto in enumerate(df[col].dropna()[:50]):  # Máximo 50 filas por columna
         
     | 
| 280 | 
         
            +
                            if isinstance(texto, str) and len(texto.strip()) > 5:
         
     | 
| 281 | 
         
            +
                                resultado_sentimiento = analizar_sentimiento_multimodelo(texto)
         
     | 
| 282 | 
         
            +
                                sentimiento, score, _ = determinar_sentimiento_final(resultado_sentimiento)
         
     | 
| 283 | 
         
            +
                                
         
     | 
| 284 | 
         
            +
                                resultados.append({
         
     | 
| 285 | 
         
            +
                                    'Columna': col,
         
     | 
| 286 | 
         
            +
                                    'Fila': idx + 1,
         
     | 
| 287 | 
         
            +
                                    'Texto': texto[:100] + '...' if len(texto) > 100 else texto,
         
     | 
| 288 | 
         
            +
                                    'Sentimiento': sentimiento,
         
     | 
| 289 | 
         
            +
                                    'Puntuación': f"{score:.3f}",
         
     | 
| 290 | 
         
            +
                                    'Longitud': len(texto)
         
     | 
| 291 | 
         
            +
                                })
         
     | 
| 292 | 
         
            +
                    
         
     | 
| 293 | 
         
            +
                    if not resultados:
         
     | 
| 294 | 
         
            +
                        return pd.DataFrame({"Resultado": ["❌ No se pudieron analizar los textos del archivo"]})
         
     | 
| 295 | 
         
            +
                    
         
     | 
| 296 | 
         
            +
                    df_resultados = pd.DataFrame(resultados)
         
     | 
| 297 | 
         
            +
                    return df_resultados
         
     | 
| 298 | 
         
            +
                    
         
     | 
| 299 | 
         
            +
                except Exception as e:
         
     | 
| 300 | 
         
            +
                    return pd.DataFrame({"Error": [f"❌ Error al procesar el archivo: {str(e)}"]})
         
     | 
| 301 | 
         
            +
             
     | 
| 302 | 
         
            +
            # Interfaz Gradio simplificada
         
     | 
| 303 | 
         
            +
            with gr.Blocks(theme="soft", title="Análisis Completo de Texto") as demo:
         
     | 
| 304 | 
         
            +
                gr.Markdown("""
         
     | 
| 305 | 
         
            +
                # 🎯 Análisis Completo de Texto - Multimodelo
         
     | 
| 306 | 
         
            +
                
         
     | 
| 307 | 
         
            +
                **Combina análisis lingüístico + múltiples métodos de sentimiento para máxima precisión**
         
     | 
| 308 | 
         
            +
                """)
         
     | 
| 309 | 
         
            +
                
         
     | 
| 310 | 
         
            +
                with gr.Tab("📝 Análisis de Texto Individual"):
         
     | 
| 311 | 
         
            +
                    with gr.Row():
         
     | 
| 312 | 
         
            +
                        with gr.Column():
         
     | 
| 313 | 
         
            +
                            texto_input = gr.Textbox(
         
     | 
| 314 | 
         
            +
                                label="Ingresa tu texto",
         
     | 
| 315 | 
         
            +
                                placeholder="Escribe aquí tu texto en español, inglés, francés o portugués...",
         
     | 
| 316 | 
         
            +
                                lines=5
         
     | 
| 317 | 
         
            +
                            )
         
     | 
| 318 | 
         
            +
                            analizar_btn = gr.Button("🔍 Analizar Texto", variant="primary")
         
     | 
| 319 | 
         
            +
                            
         
     | 
| 320 | 
         
            +
                            gr.Markdown("### Ejemplos rápidos:")
         
     | 
| 321 | 
         
            +
                            ejemplos = gr.Examples(
         
     | 
| 322 | 
         
            +
                                examples=[
         
     | 
| 323 | 
         
            +
                                    ["Me encanta este producto! Es excelente y superó mis expectativas completamente."],
         
     | 
| 324 | 
         
            +
                                    ["No estoy para nada satisfecho, la calidad es pésima y el servicio terrible."],
         
     | 
| 325 | 
         
            +
                                    ["El producto cumple su función básica, pero podría mejorar en algunos aspectos."],
         
     | 
| 326 | 
         
            +
                                    ["I love this amazing product! It works perfectly and the quality is outstanding."],
         
     | 
| 327 | 
         
            +
                                    ["C'est un produit fantastique, je le recommande vivement à tous mes amis."],
         
     | 
| 328 | 
         
            +
                                    ["Este serviço é muito bom, atendimento excelente e qualidade superior."]
         
     | 
| 329 | 
         
            +
                                ],
         
     | 
| 330 | 
         
            +
                                inputs=texto_input
         
     | 
| 331 | 
         
            +
                            )
         
     | 
| 332 | 
         
            +
                        
         
     | 
| 333 | 
         
            +
                        with gr.Column():
         
     | 
| 334 | 
         
            +
                            resultado_resumen = gr.HTML(label="🎯 Resultado Principal")
         
     | 
| 335 | 
         
            +
                            resultado_detalles = gr.HTML(label="📊 Detalles por Método")
         
     | 
| 336 | 
         
            +
                            resultado_linguistica = gr.HTML(label="📝 Análisis Lingüístico")
         
     | 
| 337 | 
         
            +
                
         
     | 
| 338 | 
         
            +
                with gr.Tab("📊 Análisis de Archivos Excel"):
         
     | 
| 339 | 
         
            +
                    with gr.Row():
         
     | 
| 340 | 
         
            +
                        with gr.Column():
         
     | 
| 341 | 
         
            +
                            archivo_input = gr.File(
         
     | 
| 342 | 
         
            +
                                label="Sube tu archivo Excel",
         
     | 
| 343 | 
         
            +
                                file_types=[".xlsx", ".xls"]
         
     | 
| 344 | 
         
            +
                            )
         
     | 
| 345 | 
         
            +
                            analizar_excel_btn = gr.Button("📈 Analizar Archivo", variant="primary")
         
     | 
| 346 | 
         
            +
                            
         
     | 
| 347 | 
         
            +
                            gr.Markdown("""
         
     | 
| 348 | 
         
            +
                            **Formato esperado:**
         
     | 
| 349 | 
         
            +
                            - Archivo Excel (.xlsx o .xls)
         
     | 
| 350 | 
         
            +
                            - Columnas con texto (reseñas, comentarios, etc.)
         
     | 
| 351 | 
         
            +
                            - Mínimo 5-10 palabras por texto para mejor análisis
         
     | 
| 352 | 
         
            +
                            """)
         
     | 
| 353 | 
         
            +
                        
         
     | 
| 354 | 
         
            +
                        with gr.Column():
         
     | 
| 355 | 
         
            +
                            resultado_excel = gr.Dataframe(label="Resultados del Análisis")
         
     | 
| 356 | 
         
            +
                
         
     | 
| 357 | 
         
            +
                with gr.Tab("ℹ️ Información del Sistema"):
         
     | 
| 358 | 
         
            +
                    gr.Markdown("""
         
     | 
| 359 | 
         
            +
                    ## 🔧 Métodos de Análisis Utilizados
         
     | 
| 360 | 
         
            +
                    
         
     | 
| 361 | 
         
            +
                    ### 🤖 Modelos de Machine Learning
         
     | 
| 362 | 
         
            +
                    - **Multilingual Sentiment**: Modelo especializado en múltiples idiomas
         
     | 
| 363 | 
         
            +
                    - **BERT Multilingual**: Modelo transformer con clasificación por estrellas (1-5)
         
     | 
| 364 | 
         
            +
                    
         
     | 
| 365 | 
         
            +
                    ### 📚 Análisis Léxico
         
     | 
| 366 | 
         
            +
                    - Diccionario de palabras positivas/negativas en español
         
     | 
| 367 | 
         
            +
                    - Lematización y análisis morfológico
         
     | 
| 368 | 
         
            +
                    
         
     | 
| 369 | 
         
            +
                    ### 📊 TextBlob
         
     | 
| 370 | 
         
            +
                    - Análisis tradicional de sentimientos
         
     | 
| 371 | 
         
            +
                    - Compatible con múltiples idiomas
         
     | 
| 372 | 
         
            +
                    
         
     | 
| 373 | 
         
            +
                    ### 📝 Análisis Lingüístico (spaCy)
         
     | 
| 374 | 
         
            +
                    - Tokenización y POS tagging
         
     | 
| 375 | 
         
            +
                    - Reconocimiento de entidades
         
     | 
| 376 | 
         
            +
                    - Análisis de dependencias
         
     | 
| 377 | 
         
            +
                    
         
     | 
| 378 | 
         
            +
                    ## 🎯 Combinación Inteligente
         
     | 
| 379 | 
         
            +
                    Los resultados se combinan usando pesos ponderados para mayor precisión
         
     | 
| 380 | 
         
            +
                    """)
         
     | 
| 381 | 
         
            +
                
         
     | 
| 382 | 
         
            +
                # Conectar eventos
         
     | 
| 383 | 
         
            +
                analizar_btn.click(
         
     | 
| 384 | 
         
            +
                    fn=analizar_texto_completo,
         
     | 
| 385 | 
         
            +
                    inputs=texto_input,
         
     | 
| 386 | 
         
            +
                    outputs=[resultado_resumen, resultado_detalles, resultado_linguistica]
         
     | 
| 387 | 
         
            +
                )
         
     | 
| 388 | 
         
            +
                
         
     | 
| 389 | 
         
            +
                texto_input.submit(
         
     | 
| 390 | 
         
            +
                    fn=analizar_texto_completo,
         
     | 
| 391 | 
         
            +
                    inputs=texto_input,
         
     | 
| 392 | 
         
            +
                    outputs=[resultado_resumen, resultado_detalles, resultado_linguistica]
         
     | 
| 393 | 
         
            +
                )
         
     | 
| 394 | 
         
            +
                
         
     | 
| 395 | 
         
            +
                analizar_excel_btn.click(
         
     | 
| 396 | 
         
            +
                    fn=analizar_archivo_excel,
         
     | 
| 397 | 
         
            +
                    inputs=archivo_input,
         
     | 
| 398 | 
         
            +
                    outputs=resultado_excel
         
     | 
| 399 | 
         
            +
                )
         
     | 
| 400 | 
         
            +
             
     | 
| 401 | 
         
            +
            if __name__ == "__main__":
         
     | 
| 402 | 
         
            +
                demo.launch()
         
     | 
    	
        app_epico.py
    ADDED
    
    | 
         @@ -0,0 +1,393 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
             
     | 
| 2 | 
         
            +
            import gradio as gr
         
     | 
| 3 | 
         
            +
            import pandas as pd
         
     | 
| 4 | 
         
            +
            import numpy as np
         
     | 
| 5 | 
         
            +
            import spacy
         
     | 
| 6 | 
         
            +
            from textblob import TextBlob
         
     | 
| 7 | 
         
            +
            from transformers import pipeline
         
     | 
| 8 | 
         
            +
            from langdetect import detect, DetectorFactory
         
     | 
| 9 | 
         
            +
            from functools import lru_cache
         
     | 
| 10 | 
         
            +
             
     | 
| 11 | 
         
            +
            DetectorFactory.seed = 0  # deterministic langdetect
         
     | 
| 12 | 
         
            +
             
     | 
| 13 | 
         
            +
            APP_TITLE = "🚀 Análisis Épico de Sentimientos (Multimodelo + Lingüística)"
         
     | 
| 14 | 
         
            +
             
     | 
| 15 | 
         
            +
            # ==============================
         
     | 
| 16 | 
         
            +
            # Carga perezosa (lazy) de modelos
         
     | 
| 17 | 
         
            +
            # ==============================
         
     | 
| 18 | 
         
            +
             
     | 
| 19 | 
         
            +
            @lru_cache(maxsize=1)
         
     | 
| 20 | 
         
            +
            def load_spacy():
         
     | 
| 21 | 
         
            +
                try:
         
     | 
| 22 | 
         
            +
                    nlp = spacy.load("es_core_news_sm")
         
     | 
| 23 | 
         
            +
                    return nlp, "✅ spaCy (es_core_news_sm)"
         
     | 
| 24 | 
         
            +
                except Exception as e:
         
     | 
| 25 | 
         
            +
                    return None, f"❌ spaCy no disponible: {e}"
         
     | 
| 26 | 
         
            +
             
     | 
| 27 | 
         
            +
            @lru_cache(maxsize=1)
         
     | 
| 28 | 
         
            +
            def load_multilingual_sentiment():
         
     | 
| 29 | 
         
            +
                try:
         
     | 
| 30 | 
         
            +
                    clf = pipeline("text-classification", model="tabularisai/multilingual-sentiment-analysis")
         
     | 
| 31 | 
         
            +
                    return clf, "✅ Multilingual Sentiment cargado"
         
     | 
| 32 | 
         
            +
                except Exception as e:
         
     | 
| 33 | 
         
            +
                    return None, f"❌ Multilingual Sentiment no disponible: {e}"
         
     | 
| 34 | 
         
            +
             
     | 
| 35 | 
         
            +
            @lru_cache(maxsize=1)
         
     | 
| 36 | 
         
            +
            def load_multilingual_bert():
         
     | 
| 37 | 
         
            +
                try:
         
     | 
| 38 | 
         
            +
                    clf = pipeline("sentiment-analysis",
         
     | 
| 39 | 
         
            +
                                   model="nlptown/bert-base-multilingual-uncased-sentiment",
         
     | 
| 40 | 
         
            +
                                   tokenizer="nlptown/bert-base-multilingual-uncased-sentiment")
         
     | 
| 41 | 
         
            +
                    return clf, "✅ BERT Multilingual (estrellas) cargado"
         
     | 
| 42 | 
         
            +
                except Exception as e:
         
     | 
| 43 | 
         
            +
                    return None, f"❌ BERT Multilingual no disponible: {e}"
         
     | 
| 44 | 
         
            +
             
     | 
| 45 | 
         
            +
            # ==============================
         
     | 
| 46 | 
         
            +
            # Léxico sencillo ES + resaltado
         
     | 
| 47 | 
         
            +
            # ==============================
         
     | 
| 48 | 
         
            +
             
     | 
| 49 | 
         
            +
            PAL_POS = {
         
     | 
| 50 | 
         
            +
                'bueno','excelente','fantástico','maravilloso','perfecto','genial',
         
     | 
| 51 | 
         
            +
                'increíble','amo','encanta','feliz','contento','satisfecho','agradable',
         
     | 
| 52 | 
         
            +
                'recomiendo','magnífico','extraordinario','asombroso','estupendo',
         
     | 
| 53 | 
         
            +
                'óptimo','superior','inmejorable','ideal','brutal','espectacular'
         
     | 
| 54 | 
         
            +
            }
         
     | 
| 55 | 
         
            +
             
     | 
| 56 | 
         
            +
            PAL_NEG = {
         
     | 
| 57 | 
         
            +
                'malo','terrible','horrible','pésimo','odio','decepcionado','fatal',
         
     | 
| 58 | 
         
            +
                'triste','enojado','frustrado','pobre','deficiente','desastroso',
         
     | 
| 59 | 
         
            +
                'insatisfecho','decepcionante','horroroso','malísimo','inútil',
         
     | 
| 60 | 
         
            +
                'defectuoso','deplorable','lamentable','desagradable'
         
     | 
| 61 | 
         
            +
            }
         
     | 
| 62 | 
         
            +
             
     | 
| 63 | 
         
            +
            def lexical_score(text, nlp):
         
     | 
| 64 | 
         
            +
                text_low = text.lower().strip()
         
     | 
| 65 | 
         
            +
                if not nlp:
         
     | 
| 66 | 
         
            +
                    # fallback básico sin lematizar
         
     | 
| 67 | 
         
            +
                    tokens = [t for t in ''.join([c if c.isalpha() or c.isspace() else ' ' for c in text_low]).split() if len(t)>2]
         
     | 
| 68 | 
         
            +
                    lemmas = tokens
         
     | 
| 69 | 
         
            +
                else:
         
     | 
| 70 | 
         
            +
                    doc = nlp(text_low)
         
     | 
| 71 | 
         
            +
                    lemmas = [t.lemma_ for t in doc if t.is_alpha and len(t) > 2]
         
     | 
| 72 | 
         
            +
             
     | 
| 73 | 
         
            +
                pos = sum(1 for w in lemmas if w in PAL_POS)
         
     | 
| 74 | 
         
            +
                neg = sum(1 for w in lemmas if w in PAL_NEG)
         
     | 
| 75 | 
         
            +
                total = max(1, len(lemmas))
         
     | 
| 76 | 
         
            +
                raw = (pos - neg) / total
         
     | 
| 77 | 
         
            +
                norm = max(-1.0, min(1.0, raw * 5))
         
     | 
| 78 | 
         
            +
                return {"positivas": pos, "negativas": neg, "total": total, "normalized_score": norm, "lemmas": lemmas}
         
     | 
| 79 | 
         
            +
             
     | 
| 80 | 
         
            +
            def highlight_words(text, nlp):
         
     | 
| 81 | 
         
            +
                # Resalta palabras del léxico en el texto original
         
     | 
| 82 | 
         
            +
                if not text:
         
     | 
| 83 | 
         
            +
                    return ""
         
     | 
| 84 | 
         
            +
                original = text
         
     | 
| 85 | 
         
            +
                if nlp:
         
     | 
| 86 | 
         
            +
                    doc = nlp(original)
         
     | 
| 87 | 
         
            +
                    tokens = [t.text for t in doc]
         
     | 
| 88 | 
         
            +
                else:
         
     | 
| 89 | 
         
            +
                    tokens = original.split()
         
     | 
| 90 | 
         
            +
             
     | 
| 91 | 
         
            +
                def wrap(tok):
         
     | 
| 92 | 
         
            +
                    low = tok.lower()
         
     | 
| 93 | 
         
            +
                    if low in PAL_POS:
         
     | 
| 94 | 
         
            +
                        return f"<mark style='background:#D1FAE5; padding:2px 4px; border-radius:4px'>+{tok}</mark>"
         
     | 
| 95 | 
         
            +
                    if low in PAL_NEG:
         
     | 
| 96 | 
         
            +
                        return f"<mark style='background:#FEE2E2; padding:2px 4px; border-radius:4px'>-{tok}</mark>"
         
     | 
| 97 | 
         
            +
                    return tok
         
     | 
| 98 | 
         
            +
             
     | 
| 99 | 
         
            +
                return " ".join(wrap(t) for t in tokens)
         
     | 
| 100 | 
         
            +
             
     | 
| 101 | 
         
            +
            # ==============================
         
     | 
| 102 | 
         
            +
            # Sentimiento por modelos
         
     | 
| 103 | 
         
            +
            # ==============================
         
     | 
| 104 | 
         
            +
             
     | 
| 105 | 
         
            +
            STAR_MAP = {'1 star': -1.0, '2 stars': -0.5, '3 stars': 0.0, '4 stars': 0.5, '5 stars': 1.0}
         
     | 
| 106 | 
         
            +
             
     | 
| 107 | 
         
            +
            def model_scores(text):
         
     | 
| 108 | 
         
            +
                out = {}
         
     | 
| 109 | 
         
            +
                clf1, status1 = load_multilingual_sentiment()
         
     | 
| 110 | 
         
            +
                clf2, status2 = load_multilingual_bert()
         
     | 
| 111 | 
         
            +
                nlp, _ = load_spacy()
         
     | 
| 112 | 
         
            +
             
     | 
| 113 | 
         
            +
                # Multilingual Sentiment
         
     | 
| 114 | 
         
            +
                if clf1:
         
     | 
| 115 | 
         
            +
                    try:
         
     | 
| 116 | 
         
            +
                        r = clf1(text)[0]
         
     | 
| 117 | 
         
            +
                        out['multilingual'] = {
         
     | 
| 118 | 
         
            +
                            "label": r['label'], "score": float(r['score']),
         
     | 
| 119 | 
         
            +
                            "normalized_score": float(r['score']) if r['label']=='POSITIVE' else -float(r['score'])
         
     | 
| 120 | 
         
            +
                        }
         
     | 
| 121 | 
         
            +
                    except Exception as e:
         
     | 
| 122 | 
         
            +
                        out['multilingual'] = {"error": str(e)}
         
     | 
| 123 | 
         
            +
                else:
         
     | 
| 124 | 
         
            +
                    out['multilingual'] = {"error": status1}
         
     | 
| 125 | 
         
            +
             
     | 
| 126 | 
         
            +
                # BERT estrellas
         
     | 
| 127 | 
         
            +
                if clf2:
         
     | 
| 128 | 
         
            +
                    try:
         
     | 
| 129 | 
         
            +
                        r = clf2(text)[0]
         
     | 
| 130 | 
         
            +
                        out['bert'] = {
         
     | 
| 131 | 
         
            +
                            "label": r['label'], "score": float(r.get('score', 0.0)),
         
     | 
| 132 | 
         
            +
                            "normalized_score": float(STAR_MAP.get(r['label'], 0.0))
         
     | 
| 133 | 
         
            +
                        }
         
     | 
| 134 | 
         
            +
                    except Exception as e:
         
     | 
| 135 | 
         
            +
                        out['bert'] = {"error": str(e)}
         
     | 
| 136 | 
         
            +
                else:
         
     | 
| 137 | 
         
            +
                    out['bert'] = {"error": status2}
         
     | 
| 138 | 
         
            +
             
     | 
| 139 | 
         
            +
                # Léxico
         
     | 
| 140 | 
         
            +
                try:
         
     | 
| 141 | 
         
            +
                    out['lexico'] = lexical_score(text, nlp)
         
     | 
| 142 | 
         
            +
                except Exception as e:
         
     | 
| 143 | 
         
            +
                    out['lexico'] = {"error": str(e)}
         
     | 
| 144 | 
         
            +
             
     | 
| 145 | 
         
            +
                # TextBlob
         
     | 
| 146 | 
         
            +
                try:
         
     | 
| 147 | 
         
            +
                    blob = TextBlob(text)
         
     | 
| 148 | 
         
            +
                    out['textblob'] = {
         
     | 
| 149 | 
         
            +
                        "polarity": float(blob.sentiment.polarity),
         
     | 
| 150 | 
         
            +
                        "subjectivity": float(blob.sentiment.subjectivity),
         
     | 
| 151 | 
         
            +
                        "normalized_score": float(blob.sentiment.polarity)
         
     | 
| 152 | 
         
            +
                    }
         
     | 
| 153 | 
         
            +
                except Exception as e:
         
     | 
| 154 | 
         
            +
                    out['textblob'] = {"error": str(e)}
         
     | 
| 155 | 
         
            +
             
     | 
| 156 | 
         
            +
                return out
         
     | 
| 157 | 
         
            +
             
     | 
| 158 | 
         
            +
            def fuse_scores(results, w_multi=0.4, w_bert=0.3, w_lex=0.2, w_tb=0.1, thr=0.2):
         
     | 
| 159 | 
         
            +
                scores = []
         
     | 
| 160 | 
         
            +
                if 'normalized_score' in results.get('multilingual', {}):
         
     | 
| 161 | 
         
            +
                    scores.append(results['multilingual']['normalized_score'] * w_multi)
         
     | 
| 162 | 
         
            +
                if 'normalized_score' in results.get('bert', {}):
         
     | 
| 163 | 
         
            +
                    scores.append(results['bert']['normalized_score'] * w_bert)
         
     | 
| 164 | 
         
            +
                if 'normalized_score' in results.get('lexico', {}):
         
     | 
| 165 | 
         
            +
                    scores.append(results['lexico']['normalized_score'] * w_lex)
         
     | 
| 166 | 
         
            +
                if 'normalized_score' in results.get('textblob', {}):
         
     | 
| 167 | 
         
            +
                    scores.append(results['textblob']['normalized_score'] * w_tb)
         
     | 
| 168 | 
         
            +
             
     | 
| 169 | 
         
            +
                if not scores:
         
     | 
| 170 | 
         
            +
                    return "❓ INDETERMINADO", 0.0, "#FB923C"
         
     | 
| 171 | 
         
            +
             
     | 
| 172 | 
         
            +
                s = float(np.sum(scores))
         
     | 
| 173 | 
         
            +
             
     | 
| 174 | 
         
            +
                if s > thr:
         
     | 
| 175 | 
         
            +
                    return "😊 POSITIVO", s, "#10B981"
         
     | 
| 176 | 
         
            +
                elif s < -thr:
         
     | 
| 177 | 
         
            +
                    return "😠 NEGATIVO", s, "#EF4444"
         
     | 
| 178 | 
         
            +
                else:
         
     | 
| 179 | 
         
            +
                    return "😐 NEUTRO", s, "#6B7280"
         
     | 
| 180 | 
         
            +
             
     | 
| 181 | 
         
            +
            def detect_lang(text):
         
     | 
| 182 | 
         
            +
                try:
         
     | 
| 183 | 
         
            +
                    return detect(text)
         
     | 
| 184 | 
         
            +
                except Exception:
         
     | 
| 185 | 
         
            +
                    return "unknown"
         
     | 
| 186 | 
         
            +
             
     | 
| 187 | 
         
            +
            # ==============================
         
     | 
| 188 | 
         
            +
            # Análisis de texto (UI)
         
     | 
| 189 | 
         
            +
            # ==============================
         
     | 
| 190 | 
         
            +
             
     | 
| 191 | 
         
            +
            def analyze_text(text, w_multi, w_bert, w_lex, w_tb, thr):
         
     | 
| 192 | 
         
            +
                text = (text or "").strip()
         
     | 
| 193 | 
         
            +
                if not text:
         
     | 
| 194 | 
         
            +
                    return "❌ Ingresa un texto", "", "", ""
         
     | 
| 195 | 
         
            +
             
     | 
| 196 | 
         
            +
                lang = detect_lang(text)
         
     | 
| 197 | 
         
            +
                models = model_scores(text)
         
     | 
| 198 | 
         
            +
                label, final, color = fuse_scores(models, w_multi, w_bert, w_lex, w_tb, thr)
         
     | 
| 199 | 
         
            +
             
     | 
| 200 | 
         
            +
                nlp, _ = load_spacy()
         
     | 
| 201 | 
         
            +
             
     | 
| 202 | 
         
            +
                header = f"""
         
     | 
| 203 | 
         
            +
                <div style='background:{color}22; border-left:6px solid {color}; padding:16px; border-radius:10px'>
         
     | 
| 204 | 
         
            +
                  <div style='display:flex; justify-content:space-between; align-items:center'>
         
     | 
| 205 | 
         
            +
                    <h2 style='margin:0; color:{color}'>{label}</h2>
         
     | 
| 206 | 
         
            +
                    <code style='opacity:0.8'>Idioma detectado: {lang}</code>
         
     | 
| 207 | 
         
            +
                  </div>
         
     | 
| 208 | 
         
            +
                  <p style='margin:4px 0'><b>Puntuación combinada:</b> {final:.3f}</p>
         
     | 
| 209 | 
         
            +
                  <p style='margin:4px 0'><b>Longitud:</b> {len(text)} caracteres</p>
         
     | 
| 210 | 
         
            +
                </div>
         
     | 
| 211 | 
         
            +
                """
         
     | 
| 212 | 
         
            +
             
     | 
| 213 | 
         
            +
                # Detalles por modelo
         
     | 
| 214 | 
         
            +
                def block(name, d):
         
     | 
| 215 | 
         
            +
                    if 'error' in d:
         
     | 
| 216 | 
         
            +
                        return f"<div><b>{name}</b><br><span style='color:#EF4444'>Error: {d['error']}</span></div>"
         
     | 
| 217 | 
         
            +
                    rows = []
         
     | 
| 218 | 
         
            +
                    for k,v in d.items():
         
     | 
| 219 | 
         
            +
                        if isinstance(v, float):
         
     | 
| 220 | 
         
            +
                            rows.append(f"{k}: {v:.3f}")
         
     | 
| 221 | 
         
            +
                        else:
         
     | 
| 222 | 
         
            +
                            rows.append(f"{k}: {v}")
         
     | 
| 223 | 
         
            +
                    return f"<div style='padding:8px; border:1px solid #e5e7eb; border-radius:8px'><b>{name}</b><br>" + "<br>".join(rows) + "</div>"
         
     | 
| 224 | 
         
            +
             
     | 
| 225 | 
         
            +
                details = "<h3>📊 Resultados por método</h3>" +               "<div style='display:grid; gap:10px; grid-template-columns: repeat(auto-fit,minmax(240px,1fr))'>" +               block("Multilingual", models.get('multilingual', {})) +               block("BERT (estrellas)", models.get('bert', {})) +               block("Léxico (ES)", models.get('lexico', {})) +               block("TextBlob", models.get('textblob', {})) +               "</div>"
         
     | 
| 226 | 
         
            +
             
     | 
| 227 | 
         
            +
                # Resaltado léxico
         
     | 
| 228 | 
         
            +
                highlighted = highlight_words(text, nlp)
         
     | 
| 229 | 
         
            +
                highlight_html = f"""
         
     | 
| 230 | 
         
            +
                <h3>🔎 Palabras clave detectadas</h3>
         
     | 
| 231 | 
         
            +
                <div style='padding:12px; border:1px dashed #d1d5db; border-radius:10px'>{highlighted}</div>
         
     | 
| 232 | 
         
            +
                """
         
     | 
| 233 | 
         
            +
             
     | 
| 234 | 
         
            +
                # Lingüística resumida
         
     | 
| 235 | 
         
            +
                if nlp:
         
     | 
| 236 | 
         
            +
                    doc = nlp(text)
         
     | 
| 237 | 
         
            +
                    ents = "<br>".join([f"• {e.text} ({e.label_})" for e in list(doc.ents)[:8]]) or "—"
         
     | 
| 238 | 
         
            +
                    ling = f"""
         
     | 
| 239 | 
         
            +
                    <h3>📝 Análisis lingüístico (spaCy)</h3>
         
     | 
| 240 | 
         
            +
                    <ul>
         
     | 
| 241 | 
         
            +
                      <li>Tokens: {len(doc)}</li>
         
     | 
| 242 | 
         
            +
                      <li>Palabras: {len([t for t in doc if t.is_alpha])}</li>
         
     | 
| 243 | 
         
            +
                      <li>Oraciones: {len(list(doc.sents))}</li>
         
     | 
| 244 | 
         
            +
                      <li>Entidades: {len(doc.ents)}</li>
         
     | 
| 245 | 
         
            +
                    </ul>
         
     | 
| 246 | 
         
            +
                    <p><b>Entidades detectadas:</b><br>{ents}</p>
         
     | 
| 247 | 
         
            +
                    """
         
     | 
| 248 | 
         
            +
                else:
         
     | 
| 249 | 
         
            +
                    ling = "<p style='color:#EF4444'>spaCy no disponible (modelo es_core_news_sm no instalado)</p>"
         
     | 
| 250 | 
         
            +
             
     | 
| 251 | 
         
            +
                return header, details, highlight_html, ling
         
     | 
| 252 | 
         
            +
             
     | 
| 253 | 
         
            +
            # ==============================
         
     | 
| 254 | 
         
            +
            # Excel/CSV
         
     | 
| 255 | 
         
            +
            # ==============================
         
     | 
| 256 | 
         
            +
             
     | 
| 257 | 
         
            +
            def analyze_file(file, max_rows, text_cols_manual, w_multi, w_bert, w_lex, w_tb, thr):
         
     | 
| 258 | 
         
            +
                if file is None:
         
     | 
| 259 | 
         
            +
                    return pd.DataFrame([{"Resultado":"❌ Sube un archivo .xlsx o .csv"}])
         
     | 
| 260 | 
         
            +
             
     | 
| 261 | 
         
            +
                name = getattr(file, "name", "archivo")
         
     | 
| 262 | 
         
            +
                try:
         
     | 
| 263 | 
         
            +
                    if name.lower().endswith(".csv"):
         
     | 
| 264 | 
         
            +
                        df = pd.read_csv(file)
         
     | 
| 265 | 
         
            +
                    else:
         
     | 
| 266 | 
         
            +
                        df = pd.read_excel(file)
         
     | 
| 267 | 
         
            +
                except Exception as e:
         
     | 
| 268 | 
         
            +
                    return pd.DataFrame([{"Error": f"❌ No pude leer el archivo: {e}"}])
         
     | 
| 269 | 
         
            +
             
     | 
| 270 | 
         
            +
                # Detectar columnas de texto si no se especifican
         
     | 
| 271 | 
         
            +
                if text_cols_manual:
         
     | 
| 272 | 
         
            +
                    cols = [c.strip() for c in text_cols_manual.split(",") if c.strip() in df.columns]
         
     | 
| 273 | 
         
            +
                else:
         
     | 
| 274 | 
         
            +
                    cols = []
         
     | 
| 275 | 
         
            +
                    for c in df.columns:
         
     | 
| 276 | 
         
            +
                        if df[c].dtype == "object":
         
     | 
| 277 | 
         
            +
                            sample = df[c].dropna().astype(str).head(5).tolist()
         
     | 
| 278 | 
         
            +
                            if any(len(s.split()) >= 5 for s in sample):
         
     | 
| 279 | 
         
            +
                                cols.append(c)
         
     | 
| 280 | 
         
            +
                    cols = cols[:2]  # máximo 2 columnas por defecto
         
     | 
| 281 | 
         
            +
             
     | 
| 282 | 
         
            +
                if not cols:
         
     | 
| 283 | 
         
            +
                    return pd.DataFrame([{"Resultado":"❌ No encontré columnas de texto (o especifica manualmente)"}])
         
     | 
| 284 | 
         
            +
             
     | 
| 285 | 
         
            +
                records = []
         
     | 
| 286 | 
         
            +
                for c in cols:
         
     | 
| 287 | 
         
            +
                    for i, text in enumerate(df[c].dropna().astype(str).head(max_rows), start=1):
         
     | 
| 288 | 
         
            +
                        models = model_scores(text)
         
     | 
| 289 | 
         
            +
                        label, s, _ = fuse_scores(models, w_multi, w_bert, w_lex, w_tb, thr)
         
     | 
| 290 | 
         
            +
                        records.append({
         
     | 
| 291 | 
         
            +
                            "Columna": c,
         
     | 
| 292 | 
         
            +
                            "Fila": i,
         
     | 
| 293 | 
         
            +
                            "Texto": (text[:140] + "...") if len(text) > 140 else text,
         
     | 
| 294 | 
         
            +
                            "Sentimiento": label.replace("😊 ","").replace("😠 ","").replace("😐 ",""),
         
     | 
| 295 | 
         
            +
                            "Score": round(s,3),
         
     | 
| 296 | 
         
            +
                            "Len": len(text)
         
     | 
| 297 | 
         
            +
                        })
         
     | 
| 298 | 
         
            +
             
     | 
| 299 | 
         
            +
                return pd.DataFrame.from_records(records)
         
     | 
| 300 | 
         
            +
             
     | 
| 301 | 
         
            +
            # ==============================
         
     | 
| 302 | 
         
            +
            # UI
         
     | 
| 303 | 
         
            +
            # ==============================
         
     | 
| 304 | 
         
            +
             
     | 
| 305 | 
         
            +
            with gr.Blocks(theme="soft", title=APP_TITLE, css="""
         
     | 
| 306 | 
         
            +
            #component-0 .hover\:bg-red-500:hover{ background: none }
         
     | 
| 307 | 
         
            +
            .markdown-body h1, .markdown-body h2 { margin-top:0 }
         
     | 
| 308 | 
         
            +
            """) as demo:
         
     | 
| 309 | 
         
            +
                gr.Markdown(f"""
         
     | 
| 310 | 
         
            +
                # {APP_TITLE}
         
     | 
| 311 | 
         
            +
                **Combina múltiples modelos, léxico y análisis lingüístico. Ajusta pesos y genera insights épicos.**
         
     | 
| 312 | 
         
            +
                """)
         
     | 
| 313 | 
         
            +
             
     | 
| 314 | 
         
            +
                with gr.Tab("📝 Texto individual"):
         
     | 
| 315 | 
         
            +
                    with gr.Row():
         
     | 
| 316 | 
         
            +
                        with gr.Column(scale=5):
         
     | 
| 317 | 
         
            +
                            text_in = gr.Textbox(label="Texto", lines=6, placeholder="Escribe aquí en ES/EN/FR/PT...")
         
     | 
| 318 | 
         
            +
                            with gr.Accordion("⚙️ Pesos y umbral", open=False):
         
     | 
| 319 | 
         
            +
                                w_multi = gr.Slider(0,1,value=0.4,step=0.05,label="Peso Multilingual")
         
     | 
| 320 | 
         
            +
                                w_bert  = gr.Slider(0,1,value=0.3,step=0.05,label="Peso BERT")
         
     | 
| 321 | 
         
            +
                                w_lex   = gr.Slider(0,1,value=0.2,step=0.05,label="Peso Léxico")
         
     | 
| 322 | 
         
            +
                                w_tb    = gr.Slider(0,1,value=0.1,step=0.05,label="Peso TextBlob")
         
     | 
| 323 | 
         
            +
                                thr     = gr.Slider(0,1,value=0.2,step=0.01,label="Umbral de neutro (|score| ≤ umbral)")
         
     | 
| 324 | 
         
            +
                            btn = gr.Button("🔍 Analizar", variant="primary")
         
     | 
| 325 | 
         
            +
                            gr.Examples(
         
     | 
| 326 | 
         
            +
                                examples=[
         
     | 
| 327 | 
         
            +
                                    ["Me encanta este producto, superó mis expectativas y lo recomiendo."],
         
     | 
| 328 | 
         
            +
                                    ["Pésimo servicio, llegó tarde y defectuoso. Muy decepcionado."],
         
     | 
| 329 | 
         
            +
                                    ["El producto cumple, pero no destaca. Está bien por el precio."],
         
     | 
| 330 | 
         
            +
                                    ["I absolutely love it! Great quality and fast delivery."],
         
     | 
| 331 | 
         
            +
                                    ["C'est un service horrible, je ne le recommande à personne."],
         
     | 
| 332 | 
         
            +
                                    ["O atendimento foi excelente e o produto é ótimo."]
         
     | 
| 333 | 
         
            +
                                ],
         
     | 
| 334 | 
         
            +
                                inputs=[text_in]
         
     | 
| 335 | 
         
            +
                            )
         
     | 
| 336 | 
         
            +
                        with gr.Column(scale=5):
         
     | 
| 337 | 
         
            +
                            head = gr.HTML(label="🎯 Resultado")
         
     | 
| 338 | 
         
            +
                            methods = gr.HTML(label="📊 Detalles por modelo")
         
     | 
| 339 | 
         
            +
                            highlights = gr.HTML(label="🔎 Palabras clave")
         
     | 
| 340 | 
         
            +
                            ling = gr.HTML(label="📝 Lingüística")
         
     | 
| 341 | 
         
            +
             
     | 
| 342 | 
         
            +
                    btn.click(analyze_text, [text_in, w_multi, w_bert, w_lex, w_tb, thr], [head, methods, highlights, ling])
         
     | 
| 343 | 
         
            +
                    text_in.submit(analyze_text, [text_in, w_multi, w_bert, w_lex, w_tb, thr], [head, methods, highlights, ling])
         
     | 
| 344 | 
         
            +
             
     | 
| 345 | 
         
            +
                with gr.Tab("📈 Lote (Excel/CSV)"):
         
     | 
| 346 | 
         
            +
                    with gr.Row():
         
     | 
| 347 | 
         
            +
                        with gr.Column(scale=5):
         
     | 
| 348 | 
         
            +
                            f = gr.File(label="Sube .xlsx o .csv")
         
     | 
| 349 | 
         
            +
                            max_rows = gr.Slider(5, 500, value=100, step=5, label="Filas máximas por columna")
         
     | 
| 350 | 
         
            +
                            text_cols_manual = gr.Textbox(label="Columnas de texto (opcional, separadas por coma)")
         
     | 
| 351 | 
         
            +
                            with gr.Accordion("⚙️ Pesos y umbral", open=False):
         
     | 
| 352 | 
         
            +
                                w_multi2 = gr.Slider(0,1,value=0.4,step=0.05,label="Peso Multilingual")
         
     | 
| 353 | 
         
            +
                                w_bert2  = gr.Slider(0,1,value=0.3,step=0.05,label="Peso BERT")
         
     | 
| 354 | 
         
            +
                                w_lex2   = gr.Slider(0,1,value=0.2,step=0.05,label="Peso Léxico")
         
     | 
| 355 | 
         
            +
                                w_tb2    = gr.Slider(0,1,value=0.1,step=0.05,label="Peso TextBlob")
         
     | 
| 356 | 
         
            +
                                thr2     = gr.Slider(0,1,value=0.2,step=0.01,label="Umbral de neutro")
         
     | 
| 357 | 
         
            +
                            btn2 = gr.Button("🚀 Analizar archivo", variant="primary")
         
     | 
| 358 | 
         
            +
                        with gr.Column(scale=5):
         
     | 
| 359 | 
         
            +
                            df_out = gr.Dataframe(wrap=True, label="Resultados")
         
     | 
| 360 | 
         
            +
                            dl = gr.DownloadButton(label="⬇️ Descargar CSV", value=None)
         
     | 
| 361 | 
         
            +
             
     | 
| 362 | 
         
            +
                    def _pipe(file, max_rows, text_cols_manual, w1,w2,w3,w4,thr):
         
     | 
| 363 | 
         
            +
                        df = analyze_file(file, int(max_rows), text_cols_manual, w1,w2,w3,w4,thr)
         
     | 
| 364 | 
         
            +
                        # generar CSV temporal
         
     | 
| 365 | 
         
            +
                        try:
         
     | 
| 366 | 
         
            +
                            csv = df.to_csv(index=False).encode("utf-8")
         
     | 
| 367 | 
         
            +
                            return df, csv
         
     | 
| 368 | 
         
            +
                        except Exception:
         
     | 
| 369 | 
         
            +
                            return df, None
         
     | 
| 370 | 
         
            +
             
     | 
| 371 | 
         
            +
                    btn2.click(_pipe,
         
     | 
| 372 | 
         
            +
                               [f, max_rows, text_cols_manual, w_multi2, w_bert2, w_lex2, w_tb2, thr2],
         
     | 
| 373 | 
         
            +
                               [df_out, dl])
         
     | 
| 374 | 
         
            +
             
     | 
| 375 | 
         
            +
                with gr.Tab("ℹ️ Sistema & Modelos"):
         
     | 
| 376 | 
         
            +
                    spacy_status = load_spacy()[1]
         
     | 
| 377 | 
         
            +
                    m1_status = load_multilingual_sentiment()[1]
         
     | 
| 378 | 
         
            +
                    m2_status = load_multilingual_bert()[1]
         
     | 
| 379 | 
         
            +
                    gr.Markdown(f"""
         
     | 
| 380 | 
         
            +
                    ### Estado de modelos
         
     | 
| 381 | 
         
            +
                    - {spacy_status}
         
     | 
| 382 | 
         
            +
                    - {m1_status}
         
     | 
| 383 | 
         
            +
                    - {m2_status}
         
     | 
| 384 | 
         
            +
             
     | 
| 385 | 
         
            +
                    ### Cómo mejorar precisión
         
     | 
| 386 | 
         
            +
                    - Ajusta pesos según tu dominio (por ejemplo, más peso al léxico para español coloquial).
         
     | 
| 387 | 
         
            +
                    - Entrena un diccionario propio con palabras frecuentes de tus clientes.
         
     | 
| 388 | 
         
            +
                    - Limpia el texto (remueve spam, URLs, firmas) antes de analizar.
         
     | 
| 389 | 
         
            +
                    - Para grandes volúmenes, considera un modelo fine-tuned con tus datos.
         
     | 
| 390 | 
         
            +
                    """)
         
     | 
| 391 | 
         
            +
             
     | 
| 392 | 
         
            +
            if __name__ == "__main__":
         
     | 
| 393 | 
         
            +
                demo.launch()
         
     | 
    	
        requirements.txt
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            gradio>=4.0.0
         
     | 
| 2 | 
         
            +
            transformers>=4.20.0
         
     | 
| 3 | 
         
            +
            torch>=1.9.0
         
     | 
| 4 | 
         
            +
            spacy>=3.0.0
         
     | 
| 5 | 
         
            +
            textblob>=0.17.1
         
     | 
| 6 | 
         
            +
            pandas>=1.3.0
         
     | 
| 7 | 
         
            +
            numpy>=1.21.0
         
     |