Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -16,21 +16,6 @@ import time
|
|
| 16 |
import numpy as np
|
| 17 |
import wave
|
| 18 |
|
| 19 |
-
# Para TTS emocional con manipulación de audio
|
| 20 |
-
try:
|
| 21 |
-
from gtts import gTTS
|
| 22 |
-
from pydub import AudioSegment
|
| 23 |
-
from pydub.effects import speedup, normalize
|
| 24 |
-
import soundfile as sf
|
| 25 |
-
import librosa
|
| 26 |
-
GTTS_AVAILABLE = True
|
| 27 |
-
AUDIO_MANIPULATION = True
|
| 28 |
-
except ImportError as e:
|
| 29 |
-
GTTS_AVAILABLE = False
|
| 30 |
-
AUDIO_MANIPULATION = False
|
| 31 |
-
print(f"⚠️ Bibliotecas de audio no disponibles: {e}")
|
| 32 |
-
print("Instala con: pip install gtts pydub soundfile librosa")
|
| 33 |
-
|
| 34 |
# ============= EXTRAER TEXTO DEL PDF =============
|
| 35 |
def extraer_texto_pdf(pdf_file):
|
| 36 |
try:
|
|
@@ -46,258 +31,90 @@ def extraer_texto_pdf(pdf_file):
|
|
| 46 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 47 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 48 |
# ============= GENERAR AUDIO CON EMOCIÓN Y ANÁLISIS DE SENTIMIENTO =============
|
| 49 |
-
# ============= GENERAR AUDIO CON EMOCIÓN - VERSIÓN CORREGIDA =============
|
| 50 |
-
# ============= APLICAR EMOCIÓN AL AUDIO =============
|
| 51 |
-
def aplicar_emocion_audio(audio_path, emocion, confianza):
|
| 52 |
-
"""
|
| 53 |
-
Modifica el audio según la emoción detectada
|
| 54 |
-
|
| 55 |
-
Emociones y sus efectos:
|
| 56 |
-
- joy/excitement: Más rápido, tono más alto, más energía
|
| 57 |
-
- sadness: Más lento, tono más bajo, menos energía
|
| 58 |
-
- anger: Más rápido, más volumen, más intensidad
|
| 59 |
-
- fear: Ligeramente más rápido, tono variable
|
| 60 |
-
- neutral: Sin modificaciones
|
| 61 |
-
"""
|
| 62 |
-
|
| 63 |
-
if not AUDIO_MANIPULATION:
|
| 64 |
-
print("⚠️ Manipulación de audio no disponible")
|
| 65 |
-
return audio_path
|
| 66 |
-
|
| 67 |
-
try:
|
| 68 |
-
print(f"🎭 Aplicando emoción '{emocion}' al audio...")
|
| 69 |
-
|
| 70 |
-
# Cargar audio
|
| 71 |
-
audio = AudioSegment.from_mp3(audio_path)
|
| 72 |
-
|
| 73 |
-
# Configuración de efectos según emoción
|
| 74 |
-
efectos = {
|
| 75 |
-
'joy': {
|
| 76 |
-
'speed': 1.15, # 15% más rápido
|
| 77 |
-
'pitch': 1.5, # Tono más alto
|
| 78 |
-
'volume': 2.0, # Más volumen
|
| 79 |
-
'descripcion': 'alegre y enérgico'
|
| 80 |
-
},
|
| 81 |
-
'excitement': {
|
| 82 |
-
'speed': 1.20, # 20% más rápido
|
| 83 |
-
'pitch': 2.0, # Tono mucho más alto
|
| 84 |
-
'volume': 3.0, # Bastante más volumen
|
| 85 |
-
'descripcion': 'emocionado y entusiasta'
|
| 86 |
-
},
|
| 87 |
-
'sadness': {
|
| 88 |
-
'speed': 0.85, # 15% más lento
|
| 89 |
-
'pitch': -1.5, # Tono más bajo
|
| 90 |
-
'volume': -2.0, # Menos volumen
|
| 91 |
-
'descripcion': 'triste y melancólico'
|
| 92 |
-
},
|
| 93 |
-
'anger': {
|
| 94 |
-
'speed': 1.10, # 10% más rápido
|
| 95 |
-
'pitch': 0.5, # Tono ligeramente alto
|
| 96 |
-
'volume': 4.0, # Más volumen
|
| 97 |
-
'descripcion': 'enfadado e intenso'
|
| 98 |
-
},
|
| 99 |
-
'fear': {
|
| 100 |
-
'speed': 1.12, # 12% más rápido
|
| 101 |
-
'pitch': 1.0, # Tono normal-alto
|
| 102 |
-
'volume': 1.0, # Volumen normal
|
| 103 |
-
'descripcion': 'nervioso y temeroso'
|
| 104 |
-
},
|
| 105 |
-
'surprise': {
|
| 106 |
-
'speed': 1.18, # 18% más rápido
|
| 107 |
-
'pitch': 2.5, # Tono muy alto
|
| 108 |
-
'volume': 2.5, # Más volumen
|
| 109 |
-
'descripcion': 'sorprendido'
|
| 110 |
-
},
|
| 111 |
-
'neutral': {
|
| 112 |
-
'speed': 1.0, # Sin cambios
|
| 113 |
-
'pitch': 0, # Sin cambios
|
| 114 |
-
'volume': 0, # Sin cambios
|
| 115 |
-
'descripcion': 'neutral y calmado'
|
| 116 |
-
}
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
# Obtener configuración de la emoción
|
| 120 |
-
config = efectos.get(emocion, efectos['neutral'])
|
| 121 |
-
|
| 122 |
-
# Ajustar efectos según nivel de confianza
|
| 123 |
-
intensidad = confianza # 0.0 a 1.0
|
| 124 |
-
speed_final = 1.0 + (config['speed'] - 1.0) * intensidad
|
| 125 |
-
pitch_final = config['pitch'] * intensidad
|
| 126 |
-
volume_final = config['volume'] * intensidad
|
| 127 |
-
|
| 128 |
-
print(f" • Velocidad: {speed_final:.2f}x")
|
| 129 |
-
print(f" • Tono: {pitch_final:+.1f} semitonos")
|
| 130 |
-
print(f" • Volumen: {volume_final:+.1f} dB")
|
| 131 |
-
print(f" • Estilo: {config['descripcion']}")
|
| 132 |
-
|
| 133 |
-
# 1. Ajustar velocidad
|
| 134 |
-
if speed_final != 1.0:
|
| 135 |
-
audio = audio.speedup(playback_speed=speed_final)
|
| 136 |
-
|
| 137 |
-
# 2. Ajustar volumen
|
| 138 |
-
if volume_final != 0:
|
| 139 |
-
audio = audio + volume_final
|
| 140 |
-
|
| 141 |
-
# 3. Ajustar tono (pitch shifting)
|
| 142 |
-
if pitch_final != 0:
|
| 143 |
-
# Convertir a numpy array para procesamiento
|
| 144 |
-
samples = audio.get_array_of_samples()
|
| 145 |
-
audio_array = np.array(samples).astype(np.float32)
|
| 146 |
-
|
| 147 |
-
# Normalizar
|
| 148 |
-
if audio_array.max() > 0:
|
| 149 |
-
audio_array = audio_array / np.abs(audio_array).max()
|
| 150 |
-
|
| 151 |
-
# Pitch shifting con librosa
|
| 152 |
-
sample_rate = audio.frame_rate
|
| 153 |
-
n_steps = pitch_final # Semitonos
|
| 154 |
-
|
| 155 |
-
audio_shifted = librosa.effects.pitch_shift(
|
| 156 |
-
audio_array,
|
| 157 |
-
sr=sample_rate,
|
| 158 |
-
n_steps=n_steps
|
| 159 |
-
)
|
| 160 |
-
|
| 161 |
-
# Convertir de vuelta a AudioSegment
|
| 162 |
-
audio_shifted = (audio_shifted * 32767).astype(np.int16)
|
| 163 |
-
audio = AudioSegment(
|
| 164 |
-
audio_shifted.tobytes(),
|
| 165 |
-
frame_rate=sample_rate,
|
| 166 |
-
sample_width=2,
|
| 167 |
-
channels=1
|
| 168 |
-
)
|
| 169 |
-
|
| 170 |
-
# 4. Normalizar audio final
|
| 171 |
-
audio = normalize(audio)
|
| 172 |
-
|
| 173 |
-
# Guardar audio modificado
|
| 174 |
-
output_path = audio_path.replace('.mp3', f'_emocional_{emocion}.mp3')
|
| 175 |
-
audio.export(output_path, format="mp3", bitrate="128k")
|
| 176 |
-
|
| 177 |
-
# Verificar archivo
|
| 178 |
-
if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
|
| 179 |
-
print(f"✅ Audio emocional creado: {output_path}")
|
| 180 |
-
# Eliminar archivo original
|
| 181 |
-
try:
|
| 182 |
-
os.remove(audio_path)
|
| 183 |
-
except:
|
| 184 |
-
pass
|
| 185 |
-
return output_path
|
| 186 |
-
else:
|
| 187 |
-
print("⚠️ Error al crear audio emocional, usando original")
|
| 188 |
-
return audio_path
|
| 189 |
-
|
| 190 |
-
except Exception as e:
|
| 191 |
-
print(f"❌ Error aplicando emoción: {str(e)[:200]}")
|
| 192 |
-
print(" Usando audio sin modificar")
|
| 193 |
-
return audio_path
|
| 194 |
-
|
| 195 |
def generar_audio_respuesta(texto, client):
|
| 196 |
-
"""TTS emocional FUNCIONAL
|
| 197 |
-
|
| 198 |
-
try:
|
| 199 |
-
# Limpiar y preparar texto
|
| 200 |
-
texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip()
|
| 201 |
-
oraciones = re.split(r'[.!?]+', texto_limpio)
|
| 202 |
-
oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10]
|
| 203 |
-
texto_audio = ". ".join(oraciones[:5]) + "." if len(oraciones) > 5 else ". ".join(oraciones) + "."
|
| 204 |
-
|
| 205 |
-
if len(texto_audio) > 500:
|
| 206 |
-
texto_audio = texto_audio[:497] + "..."
|
| 207 |
-
|
| 208 |
-
print(f"🎤 Generando audio para: '{texto_audio[:100]}...'")
|
| 209 |
-
|
| 210 |
-
# PASO 1: Análisis emocional
|
| 211 |
-
emocion_detectada = "neutral"
|
| 212 |
-
confianza = 0.5
|
| 213 |
-
|
| 214 |
-
try:
|
| 215 |
-
print("🧠 Analizando emoción...")
|
| 216 |
-
emotion_response = client.text_classification(
|
| 217 |
-
text=texto_audio[:512],
|
| 218 |
-
model="finiteautomata/beto-sentiment-analysis"
|
| 219 |
-
)
|
| 220 |
-
if emotion_response and len(emotion_response) > 0:
|
| 221 |
-
label = emotion_response[0]['label'].lower()
|
| 222 |
-
sentiment_to_emotion = {
|
| 223 |
-
'pos': 'joy',
|
| 224 |
-
'positive': 'joy',
|
| 225 |
-
'neu': 'neutral',
|
| 226 |
-
'neutral': 'neutral',
|
| 227 |
-
'neg': 'sadness',
|
| 228 |
-
'negative': 'sadness'
|
| 229 |
-
}
|
| 230 |
-
emocion_detectada = sentiment_to_emotion.get(label, 'neutral')
|
| 231 |
-
confianza = emotion_response[0]['score']
|
| 232 |
-
print(f"😊 Emoción: {emocion_detectada} (confianza: {confianza:.2%})")
|
| 233 |
-
except Exception as e:
|
| 234 |
-
print(f"⚠️ Error en análisis emocional: {str(e)[:100]}")
|
| 235 |
-
|
| 236 |
-
# PASO 2: Generar audio con gTTS
|
| 237 |
-
print("🔊 Generando audio con Google TTS...")
|
| 238 |
-
|
| 239 |
-
if GTTS_AVAILABLE:
|
| 240 |
-
tts = gTTS(text=texto_audio, lang='es', slow=False)
|
| 241 |
-
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 242 |
-
audio_path_temp = f"audio_temp_{timestamp}.mp3"
|
| 243 |
-
tts.save(audio_path_temp)
|
| 244 |
-
|
| 245 |
-
if os.path.exists(audio_path_temp) and os.path.getsize(audio_path_temp) > 1000:
|
| 246 |
-
print(f"✅ Audio base generado: {audio_path_temp}")
|
| 247 |
-
|
| 248 |
-
# PASO 3: Aplicar efectos emocionales
|
| 249 |
-
audio_path_final = aplicar_emocion_audio(audio_path_temp, emocion_detectada, confianza)
|
| 250 |
-
|
| 251 |
-
print(f"🎭 Audio final con emoción: {audio_path_final}")
|
| 252 |
-
return audio_path_final, emocion_detectada, confianza
|
| 253 |
-
|
| 254 |
-
except Exception as e:
|
| 255 |
-
print(f"❌ Error general: {str(e)}")
|
| 256 |
-
return None, "neutral", 0.5
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
def generar_audio_alternativo(texto, client):
|
| 260 |
-
"""Método alternativo usando HuggingFace TTS"""
|
| 261 |
-
emocion_detectada = "neutral"
|
| 262 |
-
confianza = 0.5
|
| 263 |
|
|
|
|
| 264 |
texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip()
|
| 265 |
oraciones = re.split(r'[.!?]+', texto_limpio)
|
| 266 |
oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10]
|
| 267 |
-
texto_audio = ". ".join(oraciones[:3]) + "."
|
| 268 |
-
|
| 269 |
if len(texto_audio) > 400:
|
| 270 |
texto_audio = texto_audio[:397] + "..."
|
| 271 |
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
for modelo in modelos_tts:
|
| 275 |
try:
|
| 276 |
print(f"🔊 Probando: {modelo}")
|
| 277 |
-
audio_data = client.text_to_speech(text=texto_audio, model=modelo)
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 280 |
-
audio_path = f"
|
| 281 |
|
| 282 |
with open(audio_path, "wb") as f:
|
| 283 |
if isinstance(audio_data, bytes):
|
| 284 |
f.write(audio_data)
|
| 285 |
elif hasattr(audio_data, 'read'):
|
| 286 |
f.write(audio_data.read())
|
|
|
|
|
|
|
| 287 |
else:
|
|
|
|
| 288 |
for chunk in audio_data:
|
| 289 |
if chunk:
|
| 290 |
f.write(chunk if isinstance(chunk, bytes) else bytes(chunk))
|
| 291 |
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
os.remove(audio_path)
|
|
|
|
| 298 |
except Exception as e:
|
| 299 |
-
|
|
|
|
|
|
|
| 300 |
|
|
|
|
| 301 |
return None, emocion_detectada, confianza
|
| 302 |
|
| 303 |
# ============= ASISTENTE IA CONVERSACIONAL =============
|
|
@@ -379,16 +196,12 @@ Responde ahora:"""
|
|
| 379 |
f.write(f"\nArchivo de audio: {audio_path if audio_path else 'No generado'}\n")
|
| 380 |
f.write("=" * 60 + "\n")
|
| 381 |
|
| 382 |
-
if audio_path
|
| 383 |
print(f"✅ Audio generado correctamente: {audio_path}")
|
| 384 |
-
return respuesta, audio_path, transcripcion_path, emocion, confianza
|
| 385 |
else:
|
| 386 |
print("⚠️ No se pudo generar el audio, pero la respuesta está disponible")
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
with open(audio_vacio, "w") as f:
|
| 390 |
-
f.write("")
|
| 391 |
-
return respuesta, audio_vacio, transcripcion_path, emocion, confianza
|
| 392 |
|
| 393 |
except Exception as e:
|
| 394 |
print(f"❌ Error con {modelo}: {str(e)}")
|
|
@@ -1540,305 +1353,41 @@ def generar_pdf_con_template(template, csv_file, datos_json):
|
|
| 1540 |
return None, f"Error al generar PDF: {str(e)}"
|
| 1541 |
|
| 1542 |
# ============= INTERFAZ GRADIO =============
|
| 1543 |
-
|
| 1544 |
-
custom_css = """
|
| 1545 |
-
/* Contenedor principal */
|
| 1546 |
-
.gradio-container {
|
| 1547 |
-
max-width: 100% !important;
|
| 1548 |
-
padding: 0 !important;
|
| 1549 |
-
}
|
| 1550 |
-
|
| 1551 |
-
/* Header fijo con logo */
|
| 1552 |
-
#header-container {
|
| 1553 |
-
position: fixed;
|
| 1554 |
-
top: 0;
|
| 1555 |
-
left: 0;
|
| 1556 |
-
right: 0;
|
| 1557 |
-
height: 70px;
|
| 1558 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 1559 |
-
z-index: 1000;
|
| 1560 |
-
display: flex;
|
| 1561 |
-
align-items: center;
|
| 1562 |
-
padding: 0 20px;
|
| 1563 |
-
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
| 1564 |
-
}
|
| 1565 |
-
|
| 1566 |
-
#app-title {
|
| 1567 |
-
color: white;
|
| 1568 |
-
font-size: 28px;
|
| 1569 |
-
font-weight: bold;
|
| 1570 |
-
margin-left: 280px;
|
| 1571 |
-
transition: margin-left 0.3s ease;
|
| 1572 |
-
}
|
| 1573 |
-
|
| 1574 |
-
#app-title.menu-collapsed {
|
| 1575 |
-
margin-left: 80px;
|
| 1576 |
-
}
|
| 1577 |
-
|
| 1578 |
-
/* Menú lateral */
|
| 1579 |
-
#sidebar {
|
| 1580 |
-
position: fixed;
|
| 1581 |
-
left: 0;
|
| 1582 |
-
top: 70px;
|
| 1583 |
-
bottom: 0;
|
| 1584 |
-
width: 260px;
|
| 1585 |
-
background: linear-gradient(180deg, #2c3e50 0%, #34495e 100%);
|
| 1586 |
-
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
|
| 1587 |
-
transition: width 0.3s ease;
|
| 1588 |
-
overflow-y: auto;
|
| 1589 |
-
overflow-x: hidden;
|
| 1590 |
-
z-index: 999;
|
| 1591 |
-
}
|
| 1592 |
-
|
| 1593 |
-
#sidebar.collapsed {
|
| 1594 |
-
width: 60px;
|
| 1595 |
-
}
|
| 1596 |
-
|
| 1597 |
-
/* Botón toggle menú */
|
| 1598 |
-
#toggle-menu-btn {
|
| 1599 |
-
position: absolute;
|
| 1600 |
-
top: 15px;
|
| 1601 |
-
right: 10px;
|
| 1602 |
-
background: rgba(255,255,255,0.1);
|
| 1603 |
-
border: none;
|
| 1604 |
-
color: white;
|
| 1605 |
-
width: 40px;
|
| 1606 |
-
height: 40px;
|
| 1607 |
-
border-radius: 8px;
|
| 1608 |
-
cursor: pointer;
|
| 1609 |
-
font-size: 20px;
|
| 1610 |
-
transition: all 0.3s ease;
|
| 1611 |
-
}
|
| 1612 |
-
|
| 1613 |
-
#toggle-menu-btn:hover {
|
| 1614 |
-
background: rgba(255,255,255,0.2);
|
| 1615 |
-
transform: scale(1.1);
|
| 1616 |
-
}
|
| 1617 |
-
|
| 1618 |
-
/* Items del menú */
|
| 1619 |
-
.menu-item {
|
| 1620 |
-
padding: 15px 20px;
|
| 1621 |
-
color: #ecf0f1;
|
| 1622 |
-
cursor: pointer;
|
| 1623 |
-
display: flex;
|
| 1624 |
-
align-items: center;
|
| 1625 |
-
gap: 15px;
|
| 1626 |
-
transition: all 0.3s ease;
|
| 1627 |
-
border-left: 3px solid transparent;
|
| 1628 |
-
}
|
| 1629 |
-
|
| 1630 |
-
.menu-item:hover {
|
| 1631 |
-
background: rgba(255,255,255,0.1);
|
| 1632 |
-
border-left-color: #667eea;
|
| 1633 |
-
transform: translateX(5px);
|
| 1634 |
-
}
|
| 1635 |
-
|
| 1636 |
-
.menu-item.active {
|
| 1637 |
-
background: rgba(102, 126, 234, 0.2);
|
| 1638 |
-
border-left-color: #667eea;
|
| 1639 |
-
}
|
| 1640 |
-
|
| 1641 |
-
.menu-item-icon {
|
| 1642 |
-
font-size: 24px;
|
| 1643 |
-
min-width: 30px;
|
| 1644 |
-
text-align: center;
|
| 1645 |
-
}
|
| 1646 |
-
|
| 1647 |
-
.menu-item-text {
|
| 1648 |
-
font-size: 15px;
|
| 1649 |
-
font-weight: 500;
|
| 1650 |
-
white-space: nowrap;
|
| 1651 |
-
transition: opacity 0.3s ease;
|
| 1652 |
-
}
|
| 1653 |
-
|
| 1654 |
-
#sidebar.collapsed .menu-item-text {
|
| 1655 |
-
opacity: 0;
|
| 1656 |
-
width: 0;
|
| 1657 |
-
}
|
| 1658 |
-
|
| 1659 |
-
#sidebar.collapsed .menu-item {
|
| 1660 |
-
justify-content: center;
|
| 1661 |
-
padding: 15px 10px;
|
| 1662 |
-
}
|
| 1663 |
-
|
| 1664 |
-
/* Contenido principal */
|
| 1665 |
-
#main-content {
|
| 1666 |
-
margin-left: 260px;
|
| 1667 |
-
margin-top: 70px;
|
| 1668 |
-
padding: 30px;
|
| 1669 |
-
transition: margin-left 0.3s ease;
|
| 1670 |
-
min-height: calc(100vh - 70px);
|
| 1671 |
-
}
|
| 1672 |
-
|
| 1673 |
-
#main-content.menu-collapsed {
|
| 1674 |
-
margin-left: 60px;
|
| 1675 |
-
}
|
| 1676 |
-
|
| 1677 |
-
/* Secciones */
|
| 1678 |
-
.section-container {
|
| 1679 |
-
display: none;
|
| 1680 |
-
animation: fadeIn 0.4s ease;
|
| 1681 |
-
}
|
| 1682 |
-
|
| 1683 |
-
.section-container.active {
|
| 1684 |
-
display: block;
|
| 1685 |
-
}
|
| 1686 |
-
|
| 1687 |
-
@keyframes fadeIn {
|
| 1688 |
-
from {
|
| 1689 |
-
opacity: 0;
|
| 1690 |
-
transform: translateY(20px);
|
| 1691 |
-
}
|
| 1692 |
-
to {
|
| 1693 |
-
opacity: 1;
|
| 1694 |
-
transform: translateY(0);
|
| 1695 |
-
}
|
| 1696 |
-
}
|
| 1697 |
-
|
| 1698 |
-
/* Separador del menú */
|
| 1699 |
-
.menu-separator {
|
| 1700 |
-
height: 1px;
|
| 1701 |
-
background: rgba(255,255,255,0.1);
|
| 1702 |
-
margin: 10px 20px;
|
| 1703 |
-
}
|
| 1704 |
-
|
| 1705 |
-
/* Versión Beta badge */
|
| 1706 |
-
.beta-badge {
|
| 1707 |
-
background: rgba(255, 193, 7, 0.2);
|
| 1708 |
-
color: #ffc107;
|
| 1709 |
-
padding: 2px 8px;
|
| 1710 |
-
border-radius: 4px;
|
| 1711 |
-
font-size: 12px;
|
| 1712 |
-
font-weight: bold;
|
| 1713 |
-
margin-left: 8px;
|
| 1714 |
-
}
|
| 1715 |
-
|
| 1716 |
-
/* Scrollbar personalizado */
|
| 1717 |
-
#sidebar::-webkit-scrollbar {
|
| 1718 |
-
width: 6px;
|
| 1719 |
-
}
|
| 1720 |
-
|
| 1721 |
-
#sidebar::-webkit-scrollbar-track {
|
| 1722 |
-
background: rgba(0,0,0,0.1);
|
| 1723 |
-
}
|
| 1724 |
-
|
| 1725 |
-
#sidebar::-webkit-scrollbar-thumb {
|
| 1726 |
-
background: rgba(255,255,255,0.2);
|
| 1727 |
-
border-radius: 3px;
|
| 1728 |
-
}
|
| 1729 |
-
|
| 1730 |
-
#sidebar::-webkit-scrollbar-thumb:hover {
|
| 1731 |
-
background: rgba(255,255,255,0.3);
|
| 1732 |
-
}
|
| 1733 |
-
|
| 1734 |
-
/* Responsivo */
|
| 1735 |
-
@media (max-width: 768px) {
|
| 1736 |
-
#sidebar {
|
| 1737 |
-
width: 60px;
|
| 1738 |
-
}
|
| 1739 |
-
|
| 1740 |
-
#main-content {
|
| 1741 |
-
margin-left: 60px;
|
| 1742 |
-
}
|
| 1743 |
-
|
| 1744 |
-
#app-title {
|
| 1745 |
-
margin-left: 80px;
|
| 1746 |
-
font-size: 20px;
|
| 1747 |
-
}
|
| 1748 |
|
| 1749 |
-
.menu-item-text {
|
| 1750 |
-
display: none;
|
| 1751 |
-
}
|
| 1752 |
-
}
|
| 1753 |
-
"""
|
| 1754 |
-
|
| 1755 |
-
with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
| 1756 |
-
|
| 1757 |
-
# Inyectar CSS personalizado
|
| 1758 |
-
gr.HTML(f"<style>{custom_css}</style>")
|
| 1759 |
-
# Estados
|
| 1760 |
-
datos_json_state = gr.State()
|
| 1761 |
-
csv_file_state = gr.State()
|
| 1762 |
-
|
| 1763 |
-
# Estados
|
| 1764 |
datos_json_state = gr.State()
|
| 1765 |
csv_file_state = gr.State()
|
| 1766 |
pdf_path_state = gr.State()
|
| 1767 |
texto_state = gr.State()
|
| 1768 |
-
seccion_actual = gr.State("extraccion") # Estado para controlar sección visible
|
| 1769 |
-
|
| 1770 |
-
# ============= HEADER FIJO =============
|
| 1771 |
-
with gr.Row(elem_id="header-container"):
|
| 1772 |
-
gr.HTML("""
|
| 1773 |
-
<div id="app-title">
|
| 1774 |
-
DeepBill<span class="beta-badge">Beta</span>
|
| 1775 |
-
</div>
|
| 1776 |
-
""")
|
| 1777 |
-
|
| 1778 |
-
# ============= MENÚ LATERAL =============
|
| 1779 |
-
with gr.Column(elem_id="sidebar", scale=0):
|
| 1780 |
-
gr.HTML("""
|
| 1781 |
-
<button id="toggle-menu-btn" onclick="toggleMenu()">☰</button>
|
| 1782 |
-
""")
|
| 1783 |
-
|
| 1784 |
-
# Items del menú
|
| 1785 |
-
btn_menu_extraccion = gr.Button(
|
| 1786 |
-
value="📄 Extracción",
|
| 1787 |
-
elem_classes="menu-item active",
|
| 1788 |
-
elem_id="menu-extraccion"
|
| 1789 |
-
)
|
| 1790 |
-
|
| 1791 |
-
btn_menu_asistente = gr.Button(
|
| 1792 |
-
value="🤖 Asistente IA",
|
| 1793 |
-
elem_classes="menu-item",
|
| 1794 |
-
elem_id="menu-asistente"
|
| 1795 |
-
)
|
| 1796 |
-
|
| 1797 |
-
btn_menu_analisis = gr.Button(
|
| 1798 |
-
value="🔬 Análisis Avanzado",
|
| 1799 |
-
elem_classes="menu-item",
|
| 1800 |
-
elem_id="menu-analisis"
|
| 1801 |
-
)
|
| 1802 |
-
|
| 1803 |
-
btn_menu_traduccion = gr.Button(
|
| 1804 |
-
value="🌍 Traducción",
|
| 1805 |
-
elem_classes="menu-item",
|
| 1806 |
-
elem_id="menu-traduccion"
|
| 1807 |
-
)
|
| 1808 |
-
|
| 1809 |
-
gr.HTML('<div class="menu-separator"></div>')
|
| 1810 |
-
|
| 1811 |
-
btn_menu_ayuda = gr.Button(
|
| 1812 |
-
value="❓ Ayuda",
|
| 1813 |
-
elem_classes="menu-item",
|
| 1814 |
-
elem_id="menu-ayuda"
|
| 1815 |
-
)
|
| 1816 |
|
| 1817 |
-
|
| 1818 |
-
|
| 1819 |
-
|
| 1820 |
-
|
| 1821 |
-
|
| 1822 |
-
|
| 1823 |
-
|
| 1824 |
-
|
|
|
|
|
|
|
| 1825 |
with gr.Row():
|
| 1826 |
with gr.Column(scale=1):
|
| 1827 |
gr.Markdown("### 📤 Subir Factura PDF")
|
| 1828 |
pdf_input = gr.File(label="Seleccionar factura PDF", file_types=[".pdf"], type="filepath")
|
| 1829 |
btn_extraer = gr.Button("🚀 Extraer Datos de la Factura", variant="primary", size="lg")
|
| 1830 |
|
|
|
|
| 1831 |
loading_extraccion = gr.HTML(visible=False, value="""
|
| 1832 |
<div style="text-align: center; padding: 20px;">
|
| 1833 |
<div class="spinner"></div>
|
| 1834 |
-
<p style="margin-top: 10px; color: #
|
| 1835 |
🔄 Procesando tu factura...
|
| 1836 |
</p>
|
| 1837 |
</div>
|
| 1838 |
<style>
|
| 1839 |
.spinner {
|
| 1840 |
border: 3px solid #f3f3f3;
|
| 1841 |
-
border-top: 3px solid #
|
| 1842 |
border-radius: 50%;
|
| 1843 |
width: 35px;
|
| 1844 |
height: 35px;
|
|
@@ -1854,7 +1403,7 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1854 |
|
| 1855 |
gr.Markdown("---")
|
| 1856 |
gr.Markdown("### 📥 Descargar Archivos")
|
| 1857 |
-
csv_output = gr.File(label="📊 CSV Tabular")
|
| 1858 |
|
| 1859 |
gr.Markdown("---")
|
| 1860 |
gr.Markdown("### 🎨 Rediseñar PDF")
|
|
@@ -1873,16 +1422,19 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1873 |
gr.Markdown("---")
|
| 1874 |
with gr.Tabs():
|
| 1875 |
with gr.Tab("📋 Vista Previa CSV"):
|
| 1876 |
-
tabla_preview = gr.DataFrame(label="Datos estructurados", wrap=True)
|
| 1877 |
with gr.Tab("📝 Texto Original"):
|
| 1878 |
texto_extraido = gr.Textbox(label="Texto extraído del PDF", lines=18)
|
| 1879 |
with gr.Tab("🔍 Información Técnica"):
|
| 1880 |
-
resumen_tecnico = gr.Markdown(label="Estructura de datos")
|
| 1881 |
|
| 1882 |
-
#
|
| 1883 |
-
|
| 1884 |
-
|
| 1885 |
-
gr.Markdown("
|
|
|
|
|
|
|
|
|
|
| 1886 |
|
| 1887 |
with gr.Row():
|
| 1888 |
with gr.Column(scale=1):
|
|
@@ -1895,6 +1447,7 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1895 |
|
| 1896 |
btn_consulta_ia = gr.Button("🎤 Consultar Asistente IA", variant="primary", size="lg")
|
| 1897 |
|
|
|
|
| 1898 |
loading_ia = gr.HTML(visible=False, value="""
|
| 1899 |
<div style="text-align: center; padding: 20px;">
|
| 1900 |
<div class="spinner-ia"></div>
|
|
@@ -1922,22 +1475,29 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1922 |
- ¿Qué es la base imponible?
|
| 1923 |
- ¿Cuándo debo pagar esta factura?
|
| 1924 |
- ¿Hay algún descuento aplicado?
|
|
|
|
| 1925 |
""")
|
| 1926 |
|
|
|
|
| 1927 |
emocion_detectada = gr.Markdown(value="", visible=True)
|
| 1928 |
|
| 1929 |
with gr.Column(scale=2):
|
| 1930 |
-
gr.Markdown("### 🤖 Avatar Virtual")
|
| 1931 |
|
|
|
|
| 1932 |
avatar_html = gr.HTML(value="""
|
| 1933 |
-
<div style="text-align: center; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px;">
|
| 1934 |
-
<div class="robot-avatar">
|
| 1935 |
<svg width="120" height="120" viewBox="0 0 120 120">
|
|
|
|
| 1936 |
<rect x="30" y="35" width="60" height="50" rx="10" fill="#ffffff" stroke="#667eea" stroke-width="3"/>
|
|
|
|
| 1937 |
<line x1="60" y1="35" x2="60" y2="20" stroke="#667eea" stroke-width="3"/>
|
| 1938 |
<circle cx="60" cy="15" r="5" fill="#764ba2"/>
|
|
|
|
| 1939 |
<circle cx="45" cy="55" r="6" fill="#667eea" class="eye-blink"/>
|
| 1940 |
<circle cx="75" cy="55" r="6" fill="#667eea" class="eye-blink"/>
|
|
|
|
| 1941 |
<rect x="40" y="70" width="40" height="8" rx="4" fill="#667eea"/>
|
| 1942 |
</svg>
|
| 1943 |
</div>
|
|
@@ -1946,7 +1506,9 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1946 |
</p>
|
| 1947 |
</div>
|
| 1948 |
<style>
|
| 1949 |
-
.eye-blink {
|
|
|
|
|
|
|
| 1950 |
@keyframes blink {
|
| 1951 |
0%, 49%, 51%, 100% { opacity: 1; }
|
| 1952 |
50% { opacity: 0.3; }
|
|
@@ -1955,21 +1517,44 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1955 |
""")
|
| 1956 |
|
| 1957 |
gr.Markdown("### 📝 Respuesta del Asistente")
|
| 1958 |
-
resultado_ia = gr.Markdown(
|
|
|
|
|
|
|
| 1959 |
|
| 1960 |
gr.Markdown("---")
|
| 1961 |
-
gr.Markdown("### 🔊 Audio y Transcripción")
|
| 1962 |
|
| 1963 |
with gr.Row():
|
| 1964 |
-
|
| 1965 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1966 |
|
| 1967 |
-
#
|
| 1968 |
-
with gr.
|
| 1969 |
-
gr.Markdown("
|
| 1970 |
-
|
|
|
|
|
|
|
| 1971 |
|
| 1972 |
with gr.Tabs():
|
|
|
|
| 1973 |
with gr.Tab("💰 Análisis Financiero"):
|
| 1974 |
with gr.Row():
|
| 1975 |
with gr.Column():
|
|
@@ -1977,12 +1562,14 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1977 |
btn_sentimiento = gr.Button("🔍 Analizar Riesgos", variant="primary")
|
| 1978 |
resultado_sentimiento = gr.Markdown()
|
| 1979 |
|
|
|
|
| 1980 |
gr.Markdown("### 💰 Gastos Deducibles")
|
| 1981 |
-
btn_deducibles = gr.Button("💸 Calcular", variant="primary")
|
| 1982 |
resultado_deducibles = gr.Markdown()
|
| 1983 |
|
|
|
|
| 1984 |
gr.Markdown("### 💵 Impacto Presupuestario")
|
| 1985 |
-
btn_impacto = gr.Button("📊 Calcular", variant="primary")
|
| 1986 |
resultado_impacto = gr.Markdown()
|
| 1987 |
|
| 1988 |
with gr.Column():
|
|
@@ -1990,187 +1577,156 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 1990 |
btn_prediccion = gr.Button("🔮 Fecha Óptima", variant="primary")
|
| 1991 |
resultado_prediccion = gr.Markdown()
|
| 1992 |
|
|
|
|
| 1993 |
gr.Markdown("### 💡 Sugerencias IA")
|
| 1994 |
-
btn_sugerencias = gr.Button("✨ Generar", variant="primary")
|
| 1995 |
resultado_sugerencias = gr.Markdown()
|
| 1996 |
|
|
|
|
| 1997 |
gr.Markdown("### 📁 Categorización")
|
| 1998 |
-
btn_categoria = gr.Button("🏷️ Clasificar", variant="primary")
|
| 1999 |
resultado_categoria = gr.Markdown()
|
| 2000 |
|
| 2001 |
-
|
|
|
|
| 2002 |
with gr.Row():
|
| 2003 |
with gr.Column():
|
| 2004 |
gr.Markdown("### 🚨 Detector de Fraude")
|
| 2005 |
-
btn_fraude = gr.Button("🛡️ Detectar", variant="primary")
|
| 2006 |
resultado_fraude = gr.Markdown()
|
| 2007 |
|
| 2008 |
-
gr.Markdown("
|
| 2009 |
-
|
|
|
|
| 2010 |
resultado_duplicados = gr.Markdown()
|
| 2011 |
|
|
|
|
| 2012 |
gr.Markdown("### ✅ Validador Fiscal")
|
| 2013 |
-
btn_validador = gr.Button("📋 Validar", variant="primary")
|
| 2014 |
resultado_validador = gr.Markdown()
|
| 2015 |
|
| 2016 |
with gr.Column():
|
| 2017 |
-
gr.Markdown("### 📝 Condiciones Pago")
|
| 2018 |
-
btn_condiciones = gr.Button("📄 Analizar", variant="primary")
|
| 2019 |
resultado_condiciones = gr.Markdown()
|
| 2020 |
|
| 2021 |
-
gr.Markdown("
|
| 2022 |
-
|
|
|
|
| 2023 |
resultado_recordatorios = gr.Markdown()
|
| 2024 |
|
|
|
|
| 2025 |
gr.Markdown("### 📊 Resumen Ejecutivo")
|
| 2026 |
-
btn_ejecutivo = gr.Button("📈 Dashboard", variant="primary")
|
| 2027 |
resultado_ejecutivo = gr.Markdown()
|
| 2028 |
|
| 2029 |
-
|
| 2030 |
-
|
| 2031 |
-
|
|
|
|
| 2032 |
resultado_mercado = gr.Markdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2033 |
|
| 2034 |
-
#
|
| 2035 |
-
with gr.
|
| 2036 |
-
gr.Markdown("
|
| 2037 |
-
|
|
|
|
|
|
|
| 2038 |
|
| 2039 |
with gr.Row():
|
| 2040 |
with gr.Column():
|
|
|
|
| 2041 |
idioma_selector = gr.Dropdown(
|
| 2042 |
choices=["Inglés", "Francés", "Alemán", "Italiano", "Portugués"],
|
| 2043 |
value="Inglés",
|
| 2044 |
label="🗣️ Idioma de destino"
|
| 2045 |
)
|
| 2046 |
btn_traducir = gr.Button("🌐 Traducir Factura", variant="primary", size="lg")
|
| 2047 |
-
|
|
|
|
|
|
|
|
|
|
| 2048 |
|
| 2049 |
with gr.Column():
|
| 2050 |
gr.Markdown("### 📊 Vista Tabular Traducida")
|
| 2051 |
-
tabla_traduccion = gr.DataFrame(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2052 |
gr.Markdown("### 📝 Texto Traducido")
|
| 2053 |
-
resultado_traduccion = gr.Textbox(
|
| 2054 |
-
|
| 2055 |
-
|
| 2056 |
-
|
| 2057 |
-
|
| 2058 |
-
|
| 2059 |
-
|
| 2060 |
-
|
| 2061 |
-
|
| 2062 |
-
|
| 2063 |
-
|
| 2064 |
-
|
| 2065 |
-
|
| 2066 |
-
## 🎯 Funcionalidades Principales
|
| 2067 |
-
|
| 2068 |
-
### 💰 Análisis Financiero
|
| 2069 |
-
- Análisis de riesgos y urgencias
|
| 2070 |
-
- Cálculo de gastos deducibles
|
| 2071 |
-
- Impacto presupuestario
|
| 2072 |
-
- Predicción de fechas de pago
|
| 2073 |
-
- Sugerencias personalizadas
|
| 2074 |
-
- Categorización automática
|
| 2075 |
-
|
| 2076 |
-
### 🛡️ Seguridad y Validación
|
| 2077 |
-
- Detección de fraude
|
| 2078 |
-
- Búsqueda de duplicados
|
| 2079 |
-
- Validación fiscal
|
| 2080 |
-
- Análisis de condiciones
|
| 2081 |
-
- Recordatorios inteligentes
|
| 2082 |
-
|
| 2083 |
-
### 📊 Formatos
|
| 2084 |
-
- CSV tabular con comas
|
| 2085 |
-
- PDF rediseñado
|
| 2086 |
-
- Traducciones multiidioma
|
| 2087 |
-
|
| 2088 |
-
## 💡 Consejos
|
| 2089 |
-
|
| 2090 |
-
- Usa PDFs de buena calidad
|
| 2091 |
-
- Revisa la transcripción del audio
|
| 2092 |
-
- Aprovecha las sugerencias IA
|
| 2093 |
-
- Exporta y guarda los análisis
|
| 2094 |
-
""")
|
| 2095 |
|
| 2096 |
-
|
| 2097 |
-
gr.
|
| 2098 |
-
|
| 2099 |
-
|
| 2100 |
-
|
| 2101 |
-
|
| 2102 |
-
|
| 2103 |
-
|
| 2104 |
-
|
| 2105 |
-
|
| 2106 |
-
|
| 2107 |
-
|
| 2108 |
-
|
| 2109 |
-
|
| 2110 |
-
|
| 2111 |
-
|
| 2112 |
-
|
| 2113 |
-
|
| 2114 |
-
|
| 2115 |
-
|
| 2116 |
-
|
| 2117 |
-
|
| 2118 |
-
|
| 2119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2120 |
""")
|
| 2121 |
-
# ============= FUNCIONES PARA NAVEGACIÓN =============
|
| 2122 |
-
def mostrar_seccion(seccion_nombre):
|
| 2123 |
-
"""Cambia la sección visible"""
|
| 2124 |
-
secciones = {
|
| 2125 |
-
"extraccion": gr.update(visible=True),
|
| 2126 |
-
"asistente": gr.update(visible=False),
|
| 2127 |
-
"analisis": gr.update(visible=False),
|
| 2128 |
-
"traduccion": gr.update(visible=False),
|
| 2129 |
-
"ayuda": gr.update(visible=False)
|
| 2130 |
-
}
|
| 2131 |
-
|
| 2132 |
-
# Actualizar visibilidad
|
| 2133 |
-
updates = {}
|
| 2134 |
-
for nombre in secciones:
|
| 2135 |
-
updates[nombre] = gr.update(visible=(nombre == seccion_nombre))
|
| 2136 |
-
|
| 2137 |
-
return [updates[k] for k in ["extraccion", "asistente", "analisis", "traduccion", "ayuda"]]
|
| 2138 |
-
# ============= CONECTAR EVENTOS =============
|
| 2139 |
-
# ============= CONECTAR EVENTOS =============
|
| 2140 |
-
|
| 2141 |
-
# Navegación del menú
|
| 2142 |
-
# ============= CONECTAR EVENTOS =============
|
| 2143 |
-
|
| 2144 |
-
# Navegación del menú - usando los componentes directamente
|
| 2145 |
-
btn_menu_extraccion.click(
|
| 2146 |
-
fn=lambda: (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
|
| 2147 |
-
gr.update(visible=False), gr.update(visible=False)),
|
| 2148 |
-
outputs=[loading_extraccion, seccion_asistente, seccion_analisis, seccion_traduccion, seccion_ayuda]
|
| 2149 |
-
)
|
| 2150 |
-
|
| 2151 |
-
btn_menu_asistente.click(
|
| 2152 |
-
fn=lambda: (gr.update(visible=False), gr.update(visible=True), gr.update(visible=False),
|
| 2153 |
-
gr.update(visible=False), gr.update(visible=False)),
|
| 2154 |
-
outputs=[seccion_extraccion, seccion_asistente, seccion_analisis, seccion_traduccion, seccion_ayuda]
|
| 2155 |
-
)
|
| 2156 |
-
|
| 2157 |
-
btn_menu_analisis.click(
|
| 2158 |
-
fn=lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
|
| 2159 |
-
gr.update(visible=False), gr.update(visible=False)),
|
| 2160 |
-
outputs=[seccion_extraccion, seccion_asistente, seccion_analisis, seccion_traduccion, seccion_ayuda]
|
| 2161 |
-
)
|
| 2162 |
|
| 2163 |
-
|
| 2164 |
-
fn=lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
| 2165 |
-
gr.update(visible=True), gr.update(visible=False)),
|
| 2166 |
-
outputs=[seccion_extraccion, seccion_asistente, seccion_analisis, seccion_traduccion, seccion_ayuda]
|
| 2167 |
-
)
|
| 2168 |
|
| 2169 |
-
btn_menu_ayuda.click(
|
| 2170 |
-
fn=lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(visible=False),
|
| 2171 |
-
gr.update(visible=False), gr.update(visible=True)),
|
| 2172 |
-
outputs=[seccion_extraccion, seccion_asistente, seccion_analisis, seccion_traduccion, seccion_ayuda]
|
| 2173 |
-
)
|
| 2174 |
# Extracción automática
|
| 2175 |
def procesar_con_loading(pdf_file):
|
| 2176 |
if pdf_file is None:
|
|
@@ -2202,23 +1758,22 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 2202 |
|
| 2203 |
yield ("🔄 El asistente está analizando tu pregunta...", None, None, "", gr.update(visible=True))
|
| 2204 |
time.sleep(0.3)
|
|
|
|
| 2205 |
|
| 2206 |
-
|
| 2207 |
-
|
| 2208 |
-
|
| 2209 |
-
|
| 2210 |
-
|
| 2211 |
-
|
| 2212 |
-
|
| 2213 |
-
|
| 2214 |
-
|
| 2215 |
-
|
| 2216 |
-
|
| 2217 |
-
|
| 2218 |
-
|
| 2219 |
-
|
| 2220 |
-
|
| 2221 |
-
emocion_info = f"""
|
| 2222 |
### 🎭 Análisis Emocional
|
| 2223 |
|
| 2224 |
<div style="background: linear-gradient(135deg, {color}22 0%, {color}44 100%); padding: 15px; border-radius: 10px; border-left: 4px solid {color};">
|
|
@@ -2230,21 +1785,8 @@ with gr.Blocks(title="DeepBill ~ Extractor Inteligente") as demo:
|
|
| 2230 |
</p>
|
| 2231 |
</div>
|
| 2232 |
"""
|
| 2233 |
-
|
| 2234 |
-
|
| 2235 |
-
|
| 2236 |
-
if audio_final:
|
| 2237 |
-
print(f"✅ Audio disponible: {audio_final}")
|
| 2238 |
-
else:
|
| 2239 |
-
print("⚠️ Audio no disponible")
|
| 2240 |
-
emocion_info += "\n\n⚠️ *El audio no pudo generarse, pero la respuesta está en texto.*"
|
| 2241 |
-
|
| 2242 |
-
yield (respuesta, audio_final, transcripcion, emocion_info, gr.update(visible=False))
|
| 2243 |
-
|
| 2244 |
-
except Exception as e:
|
| 2245 |
-
error_msg = f"❌ Error: {str(e)[:200]}"
|
| 2246 |
-
print(f"Error completo: {str(e)}")
|
| 2247 |
-
yield (error_msg, None, None, "", gr.update(visible=False))
|
| 2248 |
|
| 2249 |
btn_consulta_ia.click(
|
| 2250 |
fn=consultar_ia_con_loading,
|
|
|
|
| 16 |
import numpy as np
|
| 17 |
import wave
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
# ============= EXTRAER TEXTO DEL PDF =============
|
| 20 |
def extraer_texto_pdf(pdf_file):
|
| 21 |
try:
|
|
|
|
| 31 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 32 |
# ============= GENERAR AUDIO CON EMOCIÓN MEJORADO =============
|
| 33 |
# ============= GENERAR AUDIO CON EMOCIÓN Y ANÁLISIS DE SENTIMIENTO =============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
def generar_audio_respuesta(texto, client):
|
| 35 |
+
"""TTS emocional FUNCIONAL para español - Actualizado diciembre 2025"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
# Limpiar y preparar texto (mismo que antes)
|
| 38 |
texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip()
|
| 39 |
oraciones = re.split(r'[.!?]+', texto_limpio)
|
| 40 |
oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10]
|
| 41 |
+
texto_audio = ". ".join(oraciones[:3]) + "." if len(oraciones) > 3 else ". ".join(oraciones) + "."
|
|
|
|
| 42 |
if len(texto_audio) > 400:
|
| 43 |
texto_audio = texto_audio[:397] + "..."
|
| 44 |
|
| 45 |
+
print(f"🎤 Generando audio para: '{texto_audio[:80]}...'")
|
| 46 |
+
|
| 47 |
+
# PASO 1: Análisis emocional (modelo español que SÍ funciona)
|
| 48 |
+
try:
|
| 49 |
+
print("🧠 Analizando emoción...")
|
| 50 |
+
emotion_response = client.text_classification(
|
| 51 |
+
text=texto_audio,
|
| 52 |
+
model="dariolopez/roberta-base-bne-finetuned-EmotionAnalysisSpanish" # Español nativo
|
| 53 |
+
)
|
| 54 |
+
if emotion_response and len(emotion_response) > 0:
|
| 55 |
+
emocion_detectada = emotion_response[0]['label']
|
| 56 |
+
confianza = emotion_response[0]['score']
|
| 57 |
+
print(f"😊 Emoción: {emocion_detectada} (confianza: {confianza:.2%})")
|
| 58 |
+
else:
|
| 59 |
+
emocion_detectada = "neutral"
|
| 60 |
+
confianza = 0.5
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print(f"⚠️ Error emocional: {str(e)[:100]}. Usando neutral.")
|
| 63 |
+
emocion_detectada = "neutral"
|
| 64 |
+
confianza = 0.5
|
| 65 |
+
|
| 66 |
+
# PASO 2: Modelos TTS que SÍ funcionan en 2025 (español prioritario)
|
| 67 |
+
modelos_tts = [
|
| 68 |
+
"facebook/mms-tts-spa", # Español oficial de Meta - Siempre funciona
|
| 69 |
+
"myshell-ai/MeloTTS-Spanish", # Alta calidad, multi-idioma
|
| 70 |
+
"coqui/XTTS-v2" # Fallback versátil (soporta español)
|
| 71 |
+
]
|
| 72 |
|
| 73 |
for modelo in modelos_tts:
|
| 74 |
try:
|
| 75 |
print(f"🔊 Probando: {modelo}")
|
|
|
|
| 76 |
|
| 77 |
+
# Generar audio
|
| 78 |
+
audio_data = client.text_to_speech(
|
| 79 |
+
text=texto_audio,
|
| 80 |
+
model=modelo
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
# Guardar archivo (mejorado para streams/bytes)
|
| 84 |
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 85 |
+
audio_path = f"audio_emocional_{emocion_detectada}_{timestamp}.wav"
|
| 86 |
|
| 87 |
with open(audio_path, "wb") as f:
|
| 88 |
if isinstance(audio_data, bytes):
|
| 89 |
f.write(audio_data)
|
| 90 |
elif hasattr(audio_data, 'read'):
|
| 91 |
f.write(audio_data.read())
|
| 92 |
+
elif hasattr(audio_data, 'content'):
|
| 93 |
+
f.write(audio_data.content)
|
| 94 |
else:
|
| 95 |
+
# Para iteradores/chunks
|
| 96 |
for chunk in audio_data:
|
| 97 |
if chunk:
|
| 98 |
f.write(chunk if isinstance(chunk, bytes) else bytes(chunk))
|
| 99 |
|
| 100 |
+
# Verificar
|
| 101 |
+
if os.path.exists(audio_path):
|
| 102 |
+
size = os.path.getsize(audio_path)
|
| 103 |
+
print(f"📁 Creado: {audio_path} ({size} bytes)")
|
| 104 |
+
|
| 105 |
+
if size > 2000: # Umbral más bajo para MMS
|
| 106 |
+
print(f"✅ ¡AUDIO GENERADO EXITOSAMENTE!")
|
| 107 |
+
return audio_path, emocion_detectada, confianza
|
| 108 |
+
else:
|
| 109 |
+
print(f"⚠️ Archivo pequeño ({size} bytes), borrando...")
|
| 110 |
os.remove(audio_path)
|
| 111 |
+
|
| 112 |
except Exception as e:
|
| 113 |
+
error_msg = str(e)
|
| 114 |
+
print(f"❌ Error con {modelo}: {error_msg[:100]}")
|
| 115 |
+
continue
|
| 116 |
|
| 117 |
+
print("⚠️ No se generó audio. Verifica límites de API o conexión.")
|
| 118 |
return None, emocion_detectada, confianza
|
| 119 |
|
| 120 |
# ============= ASISTENTE IA CONVERSACIONAL =============
|
|
|
|
| 196 |
f.write(f"\nArchivo de audio: {audio_path if audio_path else 'No generado'}\n")
|
| 197 |
f.write("=" * 60 + "\n")
|
| 198 |
|
| 199 |
+
if audio_path:
|
| 200 |
print(f"✅ Audio generado correctamente: {audio_path}")
|
|
|
|
| 201 |
else:
|
| 202 |
print("⚠️ No se pudo generar el audio, pero la respuesta está disponible")
|
| 203 |
+
|
| 204 |
+
return respuesta, audio_path, transcripcion_path, emocion, confianza
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
except Exception as e:
|
| 207 |
print(f"❌ Error con {modelo}: {str(e)}")
|
|
|
|
| 1353 |
return None, f"Error al generar PDF: {str(e)}"
|
| 1354 |
|
| 1355 |
# ============= INTERFAZ GRADIO =============
|
| 1356 |
+
with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1358 |
datos_json_state = gr.State()
|
| 1359 |
csv_file_state = gr.State()
|
| 1360 |
pdf_path_state = gr.State()
|
| 1361 |
texto_state = gr.State()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1362 |
|
| 1363 |
+
gr.Markdown("""
|
| 1364 |
+
# 🤖 Extractor Inteligente de Facturas con IA
|
| 1365 |
+
### Sistema avanzado de análisis documental con múltiples modelos de IA
|
| 1366 |
+
""")
|
| 1367 |
+
|
| 1368 |
+
gr.Markdown("---")
|
| 1369 |
+
|
| 1370 |
+
with gr.Tabs():
|
| 1371 |
+
# ============= TAB 1: EXTRACCIÓN AUTOMÁTICA =============
|
| 1372 |
+
with gr.Tab("📄 Extracción Automática"):
|
| 1373 |
with gr.Row():
|
| 1374 |
with gr.Column(scale=1):
|
| 1375 |
gr.Markdown("### 📤 Subir Factura PDF")
|
| 1376 |
pdf_input = gr.File(label="Seleccionar factura PDF", file_types=[".pdf"], type="filepath")
|
| 1377 |
btn_extraer = gr.Button("🚀 Extraer Datos de la Factura", variant="primary", size="lg")
|
| 1378 |
|
| 1379 |
+
# Indicador de carga silencioso
|
| 1380 |
loading_extraccion = gr.HTML(visible=False, value="""
|
| 1381 |
<div style="text-align: center; padding: 20px;">
|
| 1382 |
<div class="spinner"></div>
|
| 1383 |
+
<p style="margin-top: 10px; color: #2196F3; font-weight: bold;">
|
| 1384 |
🔄 Procesando tu factura...
|
| 1385 |
</p>
|
| 1386 |
</div>
|
| 1387 |
<style>
|
| 1388 |
.spinner {
|
| 1389 |
border: 3px solid #f3f3f3;
|
| 1390 |
+
border-top: 3px solid #2196F3;
|
| 1391 |
border-radius: 50%;
|
| 1392 |
width: 35px;
|
| 1393 |
height: 35px;
|
|
|
|
| 1403 |
|
| 1404 |
gr.Markdown("---")
|
| 1405 |
gr.Markdown("### 📥 Descargar Archivos")
|
| 1406 |
+
csv_output = gr.File(label="📊 CSV Tabular (separado por comas)")
|
| 1407 |
|
| 1408 |
gr.Markdown("---")
|
| 1409 |
gr.Markdown("### 🎨 Rediseñar PDF")
|
|
|
|
| 1422 |
gr.Markdown("---")
|
| 1423 |
with gr.Tabs():
|
| 1424 |
with gr.Tab("📋 Vista Previa CSV"):
|
| 1425 |
+
tabla_preview = gr.DataFrame(label="Datos estructurados en formato tabular", wrap=True)
|
| 1426 |
with gr.Tab("📝 Texto Original"):
|
| 1427 |
texto_extraido = gr.Textbox(label="Texto extraído del PDF", lines=18)
|
| 1428 |
with gr.Tab("🔍 Información Técnica"):
|
| 1429 |
+
resumen_tecnico = gr.Markdown(label="Estructura de datos y metadatos")
|
| 1430 |
|
| 1431 |
+
# ============= TAB 2: ASISTENTE IA CON VOZ Y AVATAR =============
|
| 1432 |
+
# ============= TAB 2: ASISTENTE IA CON ANÁLISIS EMOCIONAL =============
|
| 1433 |
+
with gr.Tab("🤖 Asistente IA con Análisis Emocional"):
|
| 1434 |
+
gr.Markdown("""
|
| 1435 |
+
# 💬 Asistente Virtual con Análisis de Emociones
|
| 1436 |
+
### Pregúntale cualquier cosa sobre tu factura y recibe respuestas con análisis emocional
|
| 1437 |
+
""")
|
| 1438 |
|
| 1439 |
with gr.Row():
|
| 1440 |
with gr.Column(scale=1):
|
|
|
|
| 1447 |
|
| 1448 |
btn_consulta_ia = gr.Button("🎤 Consultar Asistente IA", variant="primary", size="lg")
|
| 1449 |
|
| 1450 |
+
# Indicador de carga
|
| 1451 |
loading_ia = gr.HTML(visible=False, value="""
|
| 1452 |
<div style="text-align: center; padding: 20px;">
|
| 1453 |
<div class="spinner-ia"></div>
|
|
|
|
| 1475 |
- ¿Qué es la base imponible?
|
| 1476 |
- ¿Cuándo debo pagar esta factura?
|
| 1477 |
- ¿Hay algún descuento aplicado?
|
| 1478 |
+
- ¿Quién emitió esta factura?
|
| 1479 |
""")
|
| 1480 |
|
| 1481 |
+
# Indicador de emoción
|
| 1482 |
emocion_detectada = gr.Markdown(value="", visible=True)
|
| 1483 |
|
| 1484 |
with gr.Column(scale=2):
|
| 1485 |
+
gr.Markdown("### 🤖 Avatar Virtual del Asistente")
|
| 1486 |
|
| 1487 |
+
# Avatar robótico visual
|
| 1488 |
avatar_html = gr.HTML(value="""
|
| 1489 |
+
<div style="text-align: center; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; margin-bottom: 20px;">
|
| 1490 |
+
<div class="robot-avatar" style="margin: 0 auto;">
|
| 1491 |
<svg width="120" height="120" viewBox="0 0 120 120">
|
| 1492 |
+
<!-- Cabeza del robot -->
|
| 1493 |
<rect x="30" y="35" width="60" height="50" rx="10" fill="#ffffff" stroke="#667eea" stroke-width="3"/>
|
| 1494 |
+
<!-- Antena -->
|
| 1495 |
<line x1="60" y1="35" x2="60" y2="20" stroke="#667eea" stroke-width="3"/>
|
| 1496 |
<circle cx="60" cy="15" r="5" fill="#764ba2"/>
|
| 1497 |
+
<!-- Ojos -->
|
| 1498 |
<circle cx="45" cy="55" r="6" fill="#667eea" class="eye-blink"/>
|
| 1499 |
<circle cx="75" cy="55" r="6" fill="#667eea" class="eye-blink"/>
|
| 1500 |
+
<!-- Boca -->
|
| 1501 |
<rect x="40" y="70" width="40" height="8" rx="4" fill="#667eea"/>
|
| 1502 |
</svg>
|
| 1503 |
</div>
|
|
|
|
| 1506 |
</p>
|
| 1507 |
</div>
|
| 1508 |
<style>
|
| 1509 |
+
.eye-blink {
|
| 1510 |
+
animation: blink 3s infinite;
|
| 1511 |
+
}
|
| 1512 |
@keyframes blink {
|
| 1513 |
0%, 49%, 51%, 100% { opacity: 1; }
|
| 1514 |
50% { opacity: 0.3; }
|
|
|
|
| 1517 |
""")
|
| 1518 |
|
| 1519 |
gr.Markdown("### 📝 Respuesta del Asistente")
|
| 1520 |
+
resultado_ia = gr.Markdown(
|
| 1521 |
+
value="*Haz una pregunta y el asistente te responderá aquí...*"
|
| 1522 |
+
)
|
| 1523 |
|
| 1524 |
gr.Markdown("---")
|
| 1525 |
+
gr.Markdown("### 🔊 Audio Generado y Transcripción")
|
| 1526 |
|
| 1527 |
with gr.Row():
|
| 1528 |
+
with gr.Column():
|
| 1529 |
+
audio_respuesta = gr.Audio(
|
| 1530 |
+
label="🎧 Reproducir respuesta en audio",
|
| 1531 |
+
type="filepath",
|
| 1532 |
+
visible=True,
|
| 1533 |
+
autoplay=False
|
| 1534 |
+
)
|
| 1535 |
+
with gr.Column():
|
| 1536 |
+
transcripcion_output = gr.File(
|
| 1537 |
+
label="📄 Descargar Transcripción (TXT)"
|
| 1538 |
+
)
|
| 1539 |
+
|
| 1540 |
+
gr.Markdown("""
|
| 1541 |
+
💡 **Características mejoradas:**
|
| 1542 |
+
- 🎭 Análisis emocional del texto
|
| 1543 |
+
- 🎤 Voz sintetizada de alta calidad
|
| 1544 |
+
- 📝 Transcripción descargable en TXT
|
| 1545 |
+
- 🤖 Avatar interactivo que responde visualmente
|
| 1546 |
+
- 📊 Indicador de emoción detectada
|
| 1547 |
+
""")
|
| 1548 |
|
| 1549 |
+
# ============= TAB 3: HERRAMIENTAS IA AVANZADAS =============
|
| 1550 |
+
with gr.Tab("🔬 Análisis Avanzado IA"):
|
| 1551 |
+
gr.Markdown("""
|
| 1552 |
+
# 🎯 Suite Completa de Herramientas IA
|
| 1553 |
+
### 12 herramientas profesionales de análisis automático
|
| 1554 |
+
""")
|
| 1555 |
|
| 1556 |
with gr.Tabs():
|
| 1557 |
+
# Sub-tab 1: Análisis Financiero
|
| 1558 |
with gr.Tab("💰 Análisis Financiero"):
|
| 1559 |
with gr.Row():
|
| 1560 |
with gr.Column():
|
|
|
|
| 1562 |
btn_sentimiento = gr.Button("🔍 Analizar Riesgos", variant="primary")
|
| 1563 |
resultado_sentimiento = gr.Markdown()
|
| 1564 |
|
| 1565 |
+
gr.Markdown("---")
|
| 1566 |
gr.Markdown("### 💰 Gastos Deducibles")
|
| 1567 |
+
btn_deducibles = gr.Button("💸 Calcular Deducciones", variant="primary")
|
| 1568 |
resultado_deducibles = gr.Markdown()
|
| 1569 |
|
| 1570 |
+
gr.Markdown("---")
|
| 1571 |
gr.Markdown("### 💵 Impacto Presupuestario")
|
| 1572 |
+
btn_impacto = gr.Button("📊 Calcular Impacto", variant="primary")
|
| 1573 |
resultado_impacto = gr.Markdown()
|
| 1574 |
|
| 1575 |
with gr.Column():
|
|
|
|
| 1577 |
btn_prediccion = gr.Button("🔮 Fecha Óptima", variant="primary")
|
| 1578 |
resultado_prediccion = gr.Markdown()
|
| 1579 |
|
| 1580 |
+
gr.Markdown("---")
|
| 1581 |
gr.Markdown("### 💡 Sugerencias IA")
|
| 1582 |
+
btn_sugerencias = gr.Button("✨ Generar Recomendaciones", variant="primary")
|
| 1583 |
resultado_sugerencias = gr.Markdown()
|
| 1584 |
|
| 1585 |
+
gr.Markdown("---")
|
| 1586 |
gr.Markdown("### 📁 Categorización")
|
| 1587 |
+
btn_categoria = gr.Button("🏷️ Clasificar Gasto", variant="primary")
|
| 1588 |
resultado_categoria = gr.Markdown()
|
| 1589 |
|
| 1590 |
+
# Sub-tab 2: Seguridad y Validación
|
| 1591 |
+
with gr.Tab("🛡️ Seguridad y Validación"):
|
| 1592 |
with gr.Row():
|
| 1593 |
with gr.Column():
|
| 1594 |
gr.Markdown("### 🚨 Detector de Fraude")
|
| 1595 |
+
btn_fraude = gr.Button("🛡️ Detectar Irregularidades", variant="primary")
|
| 1596 |
resultado_fraude = gr.Markdown()
|
| 1597 |
|
| 1598 |
+
gr.Markdown("---")
|
| 1599 |
+
gr.Markdown("### 🔍 Detector de Duplicados")
|
| 1600 |
+
btn_duplicados = gr.Button("🔄 Buscar Duplicados", variant="primary")
|
| 1601 |
resultado_duplicados = gr.Markdown()
|
| 1602 |
|
| 1603 |
+
gr.Markdown("---")
|
| 1604 |
gr.Markdown("### ✅ Validador Fiscal")
|
| 1605 |
+
btn_validador = gr.Button("📋 Validar Datos Fiscales", variant="primary")
|
| 1606 |
resultado_validador = gr.Markdown()
|
| 1607 |
|
| 1608 |
with gr.Column():
|
| 1609 |
+
gr.Markdown("### 📝 Condiciones de Pago")
|
| 1610 |
+
btn_condiciones = gr.Button("📄 Analizar Condiciones", variant="primary")
|
| 1611 |
resultado_condiciones = gr.Markdown()
|
| 1612 |
|
| 1613 |
+
gr.Markdown("---")
|
| 1614 |
+
gr.Markdown("### 🔔 Recordatorios de Pago")
|
| 1615 |
+
btn_recordatorios = gr.Button("⏰ Generar Recordatorios", variant="primary")
|
| 1616 |
resultado_recordatorios = gr.Markdown()
|
| 1617 |
|
| 1618 |
+
gr.Markdown("---")
|
| 1619 |
gr.Markdown("### 📊 Resumen Ejecutivo")
|
| 1620 |
+
btn_ejecutivo = gr.Button("📈 Dashboard Gerencial", variant="primary")
|
| 1621 |
resultado_ejecutivo = gr.Markdown()
|
| 1622 |
|
| 1623 |
+
# Sub-tab 3: Comparación y Mercado
|
| 1624 |
+
with gr.Tab("📈 Análisis de Mercado"):
|
| 1625 |
+
gr.Markdown("### 💲 Comparador de Precios con Mercado")
|
| 1626 |
+
btn_mercado = gr.Button("🏪 Comparar con Mercado", variant="primary", size="lg")
|
| 1627 |
resultado_mercado = gr.Markdown()
|
| 1628 |
+
|
| 1629 |
+
gr.Markdown("""
|
| 1630 |
+
---
|
| 1631 |
+
**Esta herramienta analiza:**
|
| 1632 |
+
- ✅ Precios competitivos vs mercado
|
| 1633 |
+
- ✅ Productos con precios elevados
|
| 1634 |
+
- ✅ Ahorro potencial estimado
|
| 1635 |
+
- ✅ Recomendaciones de negociación
|
| 1636 |
+
""")
|
| 1637 |
|
| 1638 |
+
# ============= TAB 4: TRADUCCIÓN MULTIIDIOMA CON TABLA =============
|
| 1639 |
+
with gr.Tab("🌍 Traducción Internacional"):
|
| 1640 |
+
gr.Markdown("""
|
| 1641 |
+
# 🌐 Traductor Profesional de Facturas
|
| 1642 |
+
### Traduce tu factura a 5 idiomas con vista tabular y exporta a CSV
|
| 1643 |
+
""")
|
| 1644 |
|
| 1645 |
with gr.Row():
|
| 1646 |
with gr.Column():
|
| 1647 |
+
gr.Markdown("### Seleccionar Idioma")
|
| 1648 |
idioma_selector = gr.Dropdown(
|
| 1649 |
choices=["Inglés", "Francés", "Alemán", "Italiano", "Portugués"],
|
| 1650 |
value="Inglés",
|
| 1651 |
label="🗣️ Idioma de destino"
|
| 1652 |
)
|
| 1653 |
btn_traducir = gr.Button("🌐 Traducir Factura", variant="primary", size="lg")
|
| 1654 |
+
|
| 1655 |
+
gr.Markdown("---")
|
| 1656 |
+
gr.Markdown("### Exportar Traducción")
|
| 1657 |
+
csv_traduccion_output = gr.File(label="📊 Descargar CSV Tabular Traducido")
|
| 1658 |
|
| 1659 |
with gr.Column():
|
| 1660 |
gr.Markdown("### 📊 Vista Tabular Traducida")
|
| 1661 |
+
tabla_traduccion = gr.DataFrame(
|
| 1662 |
+
label="Factura traducida en formato tabular",
|
| 1663 |
+
wrap=True
|
| 1664 |
+
)
|
| 1665 |
+
|
| 1666 |
+
gr.Markdown("---")
|
| 1667 |
gr.Markdown("### 📝 Texto Traducido")
|
| 1668 |
+
resultado_traduccion = gr.Textbox(
|
| 1669 |
+
label="Resumen en texto",
|
| 1670 |
+
lines=10,
|
| 1671 |
+
placeholder="La traducción aparecerá aquí..."
|
| 1672 |
+
)
|
| 1673 |
+
|
| 1674 |
+
gr.Markdown("""
|
| 1675 |
+
💡 **Características:**
|
| 1676 |
+
- 🌍 Traducción profesional automática
|
| 1677 |
+
- 📊 Vista tabular igual que la factura original
|
| 1678 |
+
- 📥 Exporta a CSV con el mismo formato
|
| 1679 |
+
- ✅ Mantiene la estructura original
|
| 1680 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1681 |
|
| 1682 |
+
gr.Markdown("---")
|
| 1683 |
+
gr.Markdown("""
|
| 1684 |
+
### 📚 Guía Completa de Uso
|
| 1685 |
+
|
| 1686 |
+
1. **📄 Extracción Automática:** Sube tu PDF y obtén datos estructurados en CSV tabular (separado por comas)
|
| 1687 |
+
2. **🤖 Asistente IA con Avatar:** Pregunta cualquier duda y escucha la respuesta con voz robótica
|
| 1688 |
+
3. **🔬 Análisis Avanzado:** 12 herramientas profesionales de análisis
|
| 1689 |
+
4. **🌍 Traducción:** Traduce y exporta a CSV en 5 idiomas
|
| 1690 |
+
|
| 1691 |
+
---
|
| 1692 |
+
|
| 1693 |
+
### 🎯 12 Funcionalidades IA Profesionales:
|
| 1694 |
+
|
| 1695 |
+
**💰 Análisis Financiero:**
|
| 1696 |
+
- 🔍 **Análisis de Riesgos:** Detecta urgencias y alertas
|
| 1697 |
+
- 💸 **Gastos Deducibles:** Calcula deducciones fiscales
|
| 1698 |
+
- 📊 **Impacto Presupuestario:** Analiza impacto en presupuesto
|
| 1699 |
+
- 🔮 **Predicción de Pagos:** Fecha óptima de pago
|
| 1700 |
+
- ✨ **Sugerencias IA:** Recomendaciones personalizadas
|
| 1701 |
+
- 🏷️ **Categorización:** Clasifica gastos automáticamente
|
| 1702 |
+
|
| 1703 |
+
**🛡️ Seguridad y Validación:**
|
| 1704 |
+
- 🚨 **Detector de Fraude:** Identifica irregularidades
|
| 1705 |
+
- 🔄 **Detector de Duplicados:** Busca facturas duplicadas
|
| 1706 |
+
- ✅ **Validador Fiscal:** Verifica datos fiscales
|
| 1707 |
+
- 📄 **Análisis de Condiciones:** Evalúa términos de pago
|
| 1708 |
+
- ⏰ **Recordatorios:** Plan de recordatorios inteligente
|
| 1709 |
+
- 📈 **Dashboard Ejecutivo:** Resumen para gerencia
|
| 1710 |
+
|
| 1711 |
+
**📈 Análisis de Mercado:**
|
| 1712 |
+
- 💲 **Comparador de Precios:** Compara con mercado
|
| 1713 |
+
|
| 1714 |
+
**📊 Formatos Mejorados:**
|
| 1715 |
+
- CSV tabular con comas como separador
|
| 1716 |
+
- Estructura clara: Sección, Campo, Valor, Tipo
|
| 1717 |
+
- Compatible con Excel, Google Sheets, etc.
|
| 1718 |
+
|
| 1719 |
+
**🌐 Traducción Avanzada:**
|
| 1720 |
+
- Traduce a 5 idiomas
|
| 1721 |
+
- Vista tabular traducida
|
| 1722 |
+
- Exporta traducciones a CSV
|
| 1723 |
+
- Mantiene formato profesional
|
| 1724 |
+
|
| 1725 |
+
💡 **¡Empieza subiendo tu factura en la primera pestaña!**
|
| 1726 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1727 |
|
| 1728 |
+
# ============= CONECTAR EVENTOS =============
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1729 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1730 |
# Extracción automática
|
| 1731 |
def procesar_con_loading(pdf_file):
|
| 1732 |
if pdf_file is None:
|
|
|
|
| 1758 |
|
| 1759 |
yield ("🔄 El asistente está analizando tu pregunta...", None, None, "", gr.update(visible=True))
|
| 1760 |
time.sleep(0.3)
|
| 1761 |
+
respuesta, audio, transcripcion, emocion, confianza = asistente_ia_factura(texto, pregunta)
|
| 1762 |
|
| 1763 |
+
# Mapeo de emociones a emojis y colores
|
| 1764 |
+
emotion_map = {
|
| 1765 |
+
"joy": ("😊", "#4CAF50", "Alegría"),
|
| 1766 |
+
"excitement": ("🎉", "#FF9800", "Emoción"),
|
| 1767 |
+
"anger": ("😠", "#F44336", "Enfado"),
|
| 1768 |
+
"sadness": ("😢", "#2196F3", "Tristeza"),
|
| 1769 |
+
"fear": ("😰", "#9C27B0", "Miedo"),
|
| 1770 |
+
"surprise": ("😮", "#FF5722", "Sorpresa"),
|
| 1771 |
+
"neutral": ("😐", "#607D8B", "Neutral")
|
| 1772 |
+
}
|
| 1773 |
+
|
| 1774 |
+
emoji, color, nombre = emotion_map.get(emocion, ("😐", "#607D8B", "Neutral"))
|
| 1775 |
+
|
| 1776 |
+
emocion_info = f"""
|
|
|
|
|
|
|
| 1777 |
### 🎭 Análisis Emocional
|
| 1778 |
|
| 1779 |
<div style="background: linear-gradient(135deg, {color}22 0%, {color}44 100%); padding: 15px; border-radius: 10px; border-left: 4px solid {color};">
|
|
|
|
| 1785 |
</p>
|
| 1786 |
</div>
|
| 1787 |
"""
|
| 1788 |
+
|
| 1789 |
+
yield (respuesta, audio, transcripcion, emocion_info, gr.update(visible=False))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1790 |
|
| 1791 |
btn_consulta_ia.click(
|
| 1792 |
fn=consultar_ia_con_loading,
|