import gradio as gr import PyPDF2 import os import json import pandas as pd import re from datetime import datetime from huggingface_hub import InferenceClient from reportlab.lib.pagesizes import letter, A4 from reportlab.lib import colors from reportlab.lib.units import inch from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT import time import numpy as np import wave # Para TTS emocional try: from gtts import gTTS GTTS_AVAILABLE = True except ImportError: GTTS_AVAILABLE = False print("⚠️ gTTS no disponible. Instala con: pip install gtts") # ============= EXTRAER TEXTO DEL PDF ============= def extraer_texto_pdf(pdf_file): try: pdf_reader = PyPDF2.PdfReader(pdf_file) texto = "" for pagina in pdf_reader.pages: texto += pagina.extract_text() + "\n" return texto except Exception as e: return f"Error: {str(e)}" # ============= GENERAR AUDIO CON EMOCIÓN MEJORADO ============= # ============= GENERAR AUDIO CON EMOCIÓN MEJORADO ============= # ============= GENERAR AUDIO CON EMOCIÓN MEJORADO ============= # ============= GENERAR AUDIO CON EMOCIÓN Y ANÁLISIS DE SENTIMIENTO ============= # ============= GENERAR AUDIO CON EMOCIÓN - VERSIÓN CORREGIDA ============= def generar_audio_respuesta(texto, client): """TTS emocional FUNCIONAL con gTTS (Google Text-to-Speech) - Diciembre 2024""" try: # Limpiar y preparar texto texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip() oraciones = re.split(r'[.!?]+', texto_limpio) oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10] texto_audio = ". ".join(oraciones[:5]) + "." if len(oraciones) > 5 else ". ".join(oraciones) + "." if len(texto_audio) > 500: texto_audio = texto_audio[:497] + "..." print(f"🎤 Generando audio para: '{texto_audio[:100]}...'") # PASO 1: Análisis emocional emocion_detectada = "neutral" confianza = 0.5 try: print("🧠 Analizando emoción...") emotion_response = client.text_classification( text=texto_audio[:512], model="finiteautomata/beto-sentiment-analysis" ) if emotion_response and len(emotion_response) > 0: label = emotion_response[0]['label'].lower() sentiment_to_emotion = { 'pos': 'joy', 'positive': 'joy', 'neu': 'neutral', 'neutral': 'neutral', 'neg': 'sadness', 'negative': 'sadness' } emocion_detectada = sentiment_to_emotion.get(label, 'neutral') confianza = emotion_response[0]['score'] print(f"😊 Emoción: {emocion_detectada} (confianza: {confianza:.2%})") except Exception as e: print(f"⚠️ Error en análisis emocional: {str(e)[:100]}") # PASO 2: Generar audio con gTTS print("🔊 Generando audio con Google TTS...") if GTTS_AVAILABLE: tts = gTTS(text=texto_audio, lang='es', slow=False) timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') audio_path = f"audio_emocional_{emocion_detectada}_{timestamp}.mp3" tts.save(audio_path) if os.path.exists(audio_path) and os.path.getsize(audio_path) > 1000: print(f"✅ Audio generado: {audio_path} ({os.path.getsize(audio_path)} bytes)") return audio_path print("⚠️ Intentando método alternativo...") return generar_audio_alternativo(texto, client) except Exception as e: print(f"❌ Error general: {str(e)}") return None, "neutral", 0.5 def generar_audio_alternativo(texto, client): """Método alternativo usando HuggingFace TTS""" emocion_detectada = "neutral" confianza = 0.5 texto_limpio = texto.replace("*", "").replace("#", "").replace("`", "").replace("€", " euros").strip() oraciones = re.split(r'[.!?]+', texto_limpio) oraciones = [o.strip() for o in oraciones if o.strip() and len(o.strip()) > 10] texto_audio = ". ".join(oraciones[:3]) + "." if len(texto_audio) > 400: texto_audio = texto_audio[:397] + "..." modelos_tts = ["facebook/mms-tts-spa"] for modelo in modelos_tts: try: print(f"🔊 Probando: {modelo}") audio_data = client.text_to_speech(text=texto_audio, model=modelo) timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') audio_path = f"audio_{timestamp}.wav" with open(audio_path, "wb") as f: if isinstance(audio_data, bytes): f.write(audio_data) elif hasattr(audio_data, 'read'): f.write(audio_data.read()) else: for chunk in audio_data: if chunk: f.write(chunk if isinstance(chunk, bytes) else bytes(chunk)) if os.path.exists(audio_path) and os.path.getsize(audio_path) > 1000: print(f"✅ Audio generado con {modelo}") return audio_path else: if os.path.exists(audio_path): os.remove(audio_path) except Exception as e: print(f"❌ Error con {modelo}: {str(e)[:100]}") return None, emocion_detectada, confianza # ============= ASISTENTE IA CONVERSACIONAL ============= def asistente_ia_factura(texto, pregunta_usuario): """Asistente IA que explica conceptos, responde preguntas y da consejos sobre facturas""" token = os.getenv("aa") if not token: return "❌ Error: Falta configurar HF_TOKEN en Settings → Secrets", None texto_limpio = texto[:6000] prompt = f"""Eres un asistente experto en facturas y finanzas que ayuda a entender documentos comerciales. TEXTO DE LA FACTURA: {texto_limpio} PREGUNTA DEL USUARIO: {pregunta_usuario} INSTRUCCIONES: 1. Responde de forma clara, amigable y profesional en español 2. Si te preguntan sobre conceptos (IVA, base imponible, etc.), explícalos de manera sencilla 3. Si te preguntan datos específicos, extráelos del texto de la factura 4. Da consejos útiles cuando sea relevante (gestión, pagos, fiscalidad básica) 5. Si no encuentras información específica en la factura, indícalo claramente 6. Usa un lenguaje accesible para personas sin conocimientos técnicos 7. Sé conciso pero completo (máximo 200 palabras) 8. IMPORTANTE: Tu respuesta será convertida a audio, así que: - Usa frases cortas y claras - Evita símbolos especiales como *, #, € - Usa "euros" en lugar de "€" - Habla en tono conversacional y natural Responde ahora:""" modelos = [ "Qwen/Qwen2.5-72B-Instruct", "meta-llama/Llama-3.2-3B-Instruct", "mistralai/Mistral-Nemo-Instruct-2407" ] for modelo in modelos: try: print(f"\n🤖 Consultando con: {modelo}") client = InferenceClient(token=token) response = client.chat.completions.create( model=modelo, messages=[ {"role": "system", "content": "Eres un asistente experto en facturas, finanzas y contabilidad básica. Ayudas a las personas a entender sus documentos comerciales de forma clara y amigable. Respondes en un estilo conversacional perfecto para convertir a audio."}, {"role": "user", "content": prompt} ], max_tokens=600, temperature=0.7 ) respuesta = response.choices[0].message.content print(f"✅ Respuesta obtenida con {modelo}") # Generar audio de la respuesta # Generar audio emocional de la respuesta print("🎵 Iniciando generación de audio emocional...") audio_path = generar_audio_respuesta(respuesta, client) # Crear transcripción con información emocional timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') transcripcion_path = f"transcripcion_{timestamp}.txt" with open(transcripcion_path, "w", encoding="utf-8") as f: f.write("=" * 60 + "\n") f.write("TRANSCRIPCIÓN DE AUDIO - ASISTENTE IA\n") f.write("=" * 60 + "\n\n") f.write(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n") f.write(f"\n" + "-" * 60 + "\n\n") f.write("TEXTO COMPLETO:\n\n") f.write(respuesta) f.write(f"\n\n" + "-" * 60 + "\n") f.write(f"\nArchivo de audio: {audio_path if audio_path else 'No generado'}\n") f.write("=" * 60 + "\n") if audio_path and os.path.exists(audio_path): print(f"✅ Audio generado correctamente: {audio_path}") return respuesta, audio_path, transcripcion_path else: print("⚠️ No se pudo generar el audio, pero la respuesta está disponible") timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') audio_vacio = f"audio_no_disponible_{timestamp}.mp3" with open(audio_vacio, "w") as f: f.write("") return respuesta, audio_vacio, transcripcion_path except Exception as e: print(f"❌ Error con {modelo}: {str(e)}") continue return "❌ No se pudo obtener respuesta del asistente IA", None, None, "neutral", 0.0 # ============= ANÁLISIS DE SENTIMIENTO DE FACTURA ============= def analizar_sentimiento_factura(texto, client): """Analiza si la factura tiene alertas, urgencias o problemas""" prompt = f"""Analiza esta factura y determina si hay algo preocupante o urgente. TEXTO: {texto[:3000]} Responde en formato JSON: {{ "sentimiento": "positivo/neutral/alerta", "urgencia": "alta/media/baja", "razon": "explicación breve", "recomendacion": "qué hacer" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=300, temperature=0.3 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"sentimiento": "neutral", "urgencia": "baja", "razon": "Análisis no disponible", "recomendacion": "Revisar manualmente"} # ============= SUGERENCIAS INTELIGENTES ============= def generar_sugerencias_ia(datos_json, client): """Genera sugerencias personalizadas basadas en la factura""" prompt = f"""Basándote en esta factura, da 3 sugerencias útiles y prácticas: DATOS: {json.dumps(datos_json, indent=2)} Responde en español con: 1. Sugerencia sobre organización 2. Sugerencia sobre pagos o plazos 3. Sugerencia sobre optimización o ahorro Sé breve (máximo 150 palabras total):""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.7 ) return response.choices[0].message.content except: return "💡 Sugerencias: Mantén tus facturas organizadas por fecha, verifica los plazos de pago, y considera digitalizar todos tus documentos." # ============= EXTRACTOR DE CATEGORÍAS ============= def extraer_categorias_gasto(datos_json, client): """Categoriza automáticamente el tipo de gasto""" productos = datos_json.get('productos', []) texto_productos = " ".join([p.get('descripcion', '') for p in productos[:5]]) prompt = f"""Clasifica esta factura en UNA categoría de gasto: Productos/Servicios: {texto_productos} Total: {datos_json.get('totales', {}).get('total', 0)}€ Categorías posibles: - Oficina y suministros - Tecnología e IT - Servicios profesionales - Marketing y publicidad - Viajes y transporte - Alimentación y hostelería - Mantenimiento y reparaciones - Otros gastos Responde solo con el nombre de la categoría:""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=50, temperature=0.3 ) categoria = response.choices[0].message.content.strip() return f" **Categoría:** {categoria}" except: return " **Categoría:** No clasificada" # ============= TRADUCTOR MULTIIDIOMA CON CSV TABULAR ============= def traducir_factura_con_csv(datos_json, texto, idioma_destino, client): """Traduce la factura y genera tanto texto como CSV tabular""" idiomas = { "Inglés": "English", "Francés": "Français", "Alemán": "Deutsch", "Italiano": "Italiano", "Portugués": "Português" } idioma = idiomas.get(idioma_destino, "English") # 1. Traducir el texto completo prompt_texto = f"""Traduce este resumen de factura al {idioma}. Mantén el formato y estructura: {texto[:2000]} Traducción:""" try: response_texto = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt_texto}], max_tokens=1000, temperature=0.3 ) texto_traducido = response_texto.choices[0].message.content except: texto_traducido = "❌ Error en la traducción del texto" # 2. Crear DataFrame traducido if not datos_json: return texto_traducido, None, None # Traducir etiquetas según el idioma traducciones = { "Inglés": { "seccion": "Section", "campo": "Field", "valor": "Value", "tipo": "Type", "info_general": "GENERAL INFORMATION", "numero_factura": "Invoice Number", "fecha": "Date", "identificador": "Identifier", "emisor": "ISSUER", "nombre": "Name", "nif": "Tax ID", "direccion": "Address", "cliente": "CLIENT", "productos": "PRODUCTS", "producto": "Product", "cantidad": "Quantity", "precio_unitario": "Unit Price", "total_producto": "Total", "descripcion": "Description", "numerico": "Numeric", "monetario": "Monetary", "totales": "TOTALS", "base_imponible": "Taxable Base", "iva": "VAT", "total": "TOTAL", "informacion": "Information" }, "Francés": { "seccion": "Section", "campo": "Champ", "valor": "Valeur", "tipo": "Type", "info_general": "INFORMATIONS GÉNÉRALES", "numero_factura": "Numéro de Facture", "fecha": "Date", "identificador": "Identifiant", "emisor": "ÉMETTEUR", "nombre": "Nom", "nif": "NIF", "direccion": "Adresse", "cliente": "CLIENT", "productos": "PRODUITS", "producto": "Produit", "cantidad": "Quantité", "precio_unitario": "Prix Unitaire", "total_producto": "Total", "descripcion": "Description", "numerico": "Numérique", "monetario": "Monétaire", "totales": "TOTAUX", "base_imponible": "Base Imposable", "iva": "TVA", "total": "TOTAL", "informacion": "Information" }, "Alemán": { "seccion": "Abschnitt", "campo": "Feld", "valor": "Wert", "tipo": "Typ", "info_general": "ALLGEMEINE INFORMATIONEN", "numero_factura": "Rechnungsnummer", "fecha": "Datum", "identificador": "Kennung", "emisor": "AUSSTELLER", "nombre": "Name", "nif": "Steuernummer", "direccion": "Adresse", "cliente": "KUNDE", "productos": "PRODUKTE", "producto": "Produkt", "cantidad": "Menge", "precio_unitario": "Stückpreis", "total_producto": "Gesamt", "descripcion": "Beschreibung", "numerico": "Numerisch", "monetario": "Monetär", "totales": "SUMMEN", "base_imponible": "Steuerbemessungsgrundlage", "iva": "MwSt", "total": "GESAMT", "informacion": "Information" }, "Italiano": { "seccion": "Sezione", "campo": "Campo", "valor": "Valore", "tipo": "Tipo", "info_general": "INFORMAZIONI GENERALI", "numero_factura": "Numero Fattura", "fecha": "Data", "identificador": "Identificatore", "emisor": "EMITTENTE", "nombre": "Nome", "nif": "Partita IVA", "direccion": "Indirizzo", "cliente": "CLIENTE", "productos": "PRODOTTI", "producto": "Prodotto", "cantidad": "Quantità", "precio_unitario": "Prezzo Unitario", "total_producto": "Totale", "descripcion": "Descrizione", "numerico": "Numerico", "monetario": "Monetario", "totales": "TOTALI", "base_imponible": "Imponibile", "iva": "IVA", "total": "TOTALE", "informacion": "Informazione" }, "Portugués": { "seccion": "Seção", "campo": "Campo", "valor": "Valor", "tipo": "Tipo", "info_general": "INFORMAÇÃO GERAL", "numero_factura": "Número da Fatura", "fecha": "Data", "identificador": "Identificador", "emisor": "EMISSOR", "nombre": "Nome", "nif": "NIF", "direccion": "Endereço", "cliente": "CLIENTE", "productos": "PRODUTOS", "producto": "Produto", "cantidad": "Quantidade", "precio_unitario": "Preço Unitário", "total_producto": "Total", "descripcion": "Descrição", "numerico": "Numérico", "monetario": "Monetário", "totales": "TOTAIS", "base_imponible": "Base Tributável", "iva": "IVA", "total": "TOTAL", "informacion": "Informação" } } t = traducciones.get(idioma_destino, traducciones["Inglés"]) filas = [] # Información general filas.append({ t["seccion"]: t["info_general"], t["campo"]: t["numero_factura"], t["valor"]: datos_json.get('numero_factura', 'N/A'), t["tipo"]: t["identificador"] }) filas.append({ t["seccion"]: t["info_general"], t["campo"]: t["fecha"], t["valor"]: datos_json.get('fecha', 'N/A'), t["tipo"]: t["fecha"] }) # Emisor if 'emisor' in datos_json: emisor = datos_json['emisor'] if isinstance(emisor, dict): for key, value in emisor.items(): campo_traducido = t.get(key, key.replace('_', ' ').title()) filas.append({ t["seccion"]: t["emisor"], t["campo"]: campo_traducido, t["valor"]: str(value), t["tipo"]: t["informacion"] }) # Cliente if 'cliente' in datos_json: cliente = datos_json['cliente'] if isinstance(cliente, dict): for key, value in cliente.items(): campo_traducido = t.get(key, key.replace('_', ' ').title()) filas.append({ t["seccion"]: t["cliente"], t["campo"]: campo_traducido, t["valor"]: str(value), t["tipo"]: t["informacion"] }) # Productos productos = datos_json.get('productos', datos_json.get('conceptos', datos_json.get('items', []))) if productos and len(productos) > 0: for i, prod in enumerate(productos, 1): filas.append({ t["seccion"]: t["productos"], t["campo"]: f'{t["producto"]} {i}', t["valor"]: prod.get('descripcion', 'N/A'), t["tipo"]: t["descripcion"] }) filas.append({ t["seccion"]: t["productos"], t["campo"]: f'{t["cantidad"]} P{i}', t["valor"]: str(prod.get('cantidad', '')), t["tipo"]: t["numerico"] }) filas.append({ t["seccion"]: t["productos"], t["campo"]: f'{t["precio_unitario"]} P{i}', t["valor"]: f"{prod.get('precio_unitario', 0)}", t["tipo"]: t["monetario"] }) filas.append({ t["seccion"]: t["productos"], t["campo"]: f'{t["total_producto"]} P{i}', t["valor"]: f"{prod.get('total', 0)}", t["tipo"]: t["monetario"] }) # Totales totales = datos_json.get('totales', {}) if totales or 'base_imponible' in datos_json or 'total' in datos_json: base = totales.get('base_imponible', datos_json.get('base_imponible', 0)) iva = totales.get('iva', datos_json.get('iva', 0)) porcentaje_iva = totales.get('porcentaje_iva', datos_json.get('porcentaje_iva', 0)) total = totales.get('total', datos_json.get('total', 0)) filas.append({ t["seccion"]: t["totales"], t["campo"]: t["base_imponible"], t["valor"]: f"{base}", t["tipo"]: t["monetario"] }) filas.append({ t["seccion"]: t["totales"], t["campo"]: f'{t["iva"]} ({porcentaje_iva}%)', t["valor"]: f"{iva}", t["tipo"]: t["monetario"] }) filas.append({ t["seccion"]: t["totales"], t["campo"]: t["total"], t["valor"]: f"{total}", t["tipo"]: t["monetario"] }) df_traducido = pd.DataFrame(filas) # Guardar CSV timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') csv_filename = f"factura_traducida_{idioma_destino}_{timestamp}.csv" df_traducido.to_csv(csv_filename, index=False, encoding='utf-8-sig', sep=',') return texto_traducido, df_traducido, csv_filename # ============= DETECTOR DE FRAUDE ============= def detectar_fraude_factura(datos_json, texto, client): """Analiza la factura en busca de señales de fraude o irregularidades""" prompt = f"""Analiza esta factura y detecta posibles señales de fraude o irregularidades: DATOS JSON: {json.dumps(datos_json, indent=2)} TEXTO: {texto[:2000]} Busca: - Números de factura duplicados o sospechosos - Importes inusuales - Datos inconsistentes - Falta de información obligatoria - Patrones irregulares Responde en formato JSON: {{ "nivel_riesgo": "bajo/medio/alto", "alertas": ["alerta1", "alerta2"], "recomendacion": "texto" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.2 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"nivel_riesgo": "bajo", "alertas": [], "recomendacion": "No se detectaron irregularidades evidentes"} # ============= PREDICCIÓN DE FECHA DE PAGO ============= def predecir_fecha_pago(datos_json, client): """Predice la mejor fecha de pago basándose en condiciones de la factura""" prompt = f"""Basándote en esta factura, sugiere la fecha óptima de pago: DATOS: {json.dumps(datos_json, indent=2)} Considera: - Fecha de emisión - Plazos habituales (30, 60, 90 días) - Descuentos por pronto pago - Recargos por mora Responde en JSON: {{ "fecha_sugerida": "DD/MM/YYYY", "razon": "explicación breve", "ahorro_posible": "cantidad o N/A" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=300, temperature=0.3 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"fecha_sugerida": "N/A", "razon": "No se pudo calcular", "ahorro_posible": "N/A"} # ============= GENERADOR DE RESUMEN EJECUTIVO ============= def generar_resumen_ejecutivo(datos_json, client): """Genera un resumen ejecutivo tipo dashboard para gerencia""" prompt = f"""Crea un resumen ejecutivo profesional de esta factura: DATOS: {json.dumps(datos_json, indent=2)} Incluye: - Resumen en 2-3 líneas - Puntos clave financieros - Impacto en presupuesto - Acción requerida Formato profesional y conciso:""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.4 ) return response.choices[0].message.content except: return "No se pudo generar el resumen ejecutivo" # ============= ANÁLISIS DE DUPLICADOS ============= def detectar_facturas_duplicadas(datos_json, client): """Analiza si esta factura puede ser un duplicado""" prompt = f"""Analiza esta factura y determina indicadores de duplicación: DATOS: {json.dumps(datos_json, indent=2)} Busca: - Patrones de números de factura sospechosos - Fechas anómalas - Importes repetitivos Responde en JSON: {{ "posible_duplicado": true/false, "nivel_confianza": "bajo/medio/alto", "indicadores": ["indicador1", "indicador2"], "recomendacion": "texto" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=300, temperature=0.2 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"posible_duplicado": False, "nivel_confianza": "bajo", "indicadores": [], "recomendacion": "No se detectaron patrones duplicados"} # ============= CALCULADORA DE IMPACTO PRESUPUESTARIO ============= def calcular_impacto_presupuesto(datos_json, client): """Calcula el impacto de esta factura en un presupuesto mensual promedio""" total = datos_json.get('totales', {}).get('total', datos_json.get('total', 0)) prompt = f"""Analiza el impacto presupuestario de esta factura: Total: {total}€ Datos: {json.dumps(datos_json, indent=2)} Calcula: - Porcentaje sobre presupuesto promedio PYME (10.000€/mes) - Nivel de impacto - Recomendaciones de planificación Responde en JSON: {{ "impacto_porcentaje": number, "nivel_impacto": "bajo/medio/alto/crítico", "analisis": "texto", "recomendacion_financiera": "texto" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.3 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"impacto_porcentaje": 0, "nivel_impacto": "bajo", "analisis": "No disponible", "recomendacion_financiera": "Consulte con su contador"} # ============= GENERADOR DE RECORDATORIOS ============= def generar_recordatorios_pago(datos_json, client): """Genera recordatorios inteligentes de pago""" prompt = f"""Basándote en esta factura, genera un plan de recordatorios de pago: DATOS: {json.dumps(datos_json, indent=2)} Crea: - 3 recordatorios (inicial, intermedio, urgente) - Fechas sugeridas - Mensajes personalizados Responde en JSON: {{ "recordatorios": [ {{"tipo": "inicial", "dias_antes": number, "mensaje": "texto"}}, {{"tipo": "intermedio", "dias_antes": number, "mensaje": "texto"}}, {{"tipo": "urgente", "dias_antes": number, "mensaje": "texto"}} ] }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=500, temperature=0.4 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"recordatorios": []} # ============= ANÁLISIS DE CONDICIONES DE PAGO ============= def analizar_condiciones_pago(datos_json, texto, client): """Analiza las condiciones de pago y sugiere negociaciones""" prompt = f"""Analiza las condiciones de pago de esta factura: DATOS: {json.dumps(datos_json, indent=2)} TEXTO: {texto[:2000]} Identifica: - Plazo de pago actual - Condiciones especiales - Oportunidades de negociación - Descuentos por pronto pago Responde en JSON: {{ "plazo_actual": "texto", "condiciones_especiales": ["condicion1", "condicion2"], "oportunidades_negociacion": "texto", "sugerencias_mejora": "texto" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.3 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"plazo_actual": "N/A", "condiciones_especiales": [], "oportunidades_negociacion": "No detectadas", "sugerencias_mejora": "Revisar manualmente"} # ============= COMPARADOR CON MERCADO ============= def comparar_precios_mercado(datos_json, client): """Compara los precios de la factura con precios de mercado promedio""" productos = datos_json.get('productos', []) if not productos: return {"analisis": "No hay productos para comparar"} productos_texto = "\n".join([f"- {p.get('descripcion', 'N/A')}: {p.get('precio_unitario', 0)}€" for p in productos[:5]]) prompt = f"""Analiza si estos precios son razonables comparados con el mercado: PRODUCTOS Y PRECIOS: {productos_texto} Determina: - ¿Los precios son competitivos? - ¿Hay precios excesivamente altos? - Recomendaciones Responde en JSON: {{ "evaluacion_general": "competitivo/normal/elevado", "productos_caros": ["producto1", "producto2"], "ahorro_potencial": number, "recomendacion": "texto" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.3 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"evaluacion_general": "normal", "productos_caros": [], "ahorro_potencial": 0, "recomendacion": "Precios dentro del rango esperado"} # ============= VALIDADOR DE DATOS FISCALES ============= def validar_datos_fiscales(datos_json, client): """Valida que los datos fiscales sean correctos y completos""" prompt = f"""Valida los datos fiscales de esta factura: DATOS: {json.dumps(datos_json, indent=2)} Verifica: - NIF/CIF válido (formato español) - IVA correcto (21%, 10%, 4%) - Datos obligatorios presentes - Formato de factura legal Responde en JSON: {{ "es_valida": true/false, "errores": ["error1", "error2"], "advertencias": ["advertencia1"], "nivel_cumplimiento": "completo/parcial/insuficiente" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.2 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"es_valida": True, "errores": [], "advertencias": [], "nivel_cumplimiento": "completo"} def extraer_gastos_deducibles(datos_json, texto, client): """Identifica qué parte de la factura es deducible fiscalmente""" prompt = f"""Analiza esta factura e identifica los gastos deducibles fiscalmente en España: DATOS: {json.dumps(datos_json, indent=2)} TEXTO: {texto[:2000]} Responde en JSON: {{ "porcentaje_deducible": number, "importe_deducible": number, "tipo_deduccion": "texto", "explicacion": "texto breve" }}""" try: response = client.chat.completions.create( model="Qwen/Qwen2.5-72B-Instruct", messages=[{"role": "user", "content": prompt}], max_tokens=300, temperature=0.3 ) resultado = response.choices[0].message.content resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado).strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: return json.loads(match.group(0)) except: pass return {"porcentaje_deducible": 0, "importe_deducible": 0, "tipo_deduccion": "N/A", "explicacion": "Consulta con un asesor fiscal"} # ============= ANALIZAR CON LLM Y CONVERTIR A JSON ============= def analizar_y_convertir_json(texto): """El LLM lee la factura y devuelve JSON estructurado""" token = os.getenv("aa") if not token: return None, None, "Error: Falta configurar HF_TOKEN en Settings → Secrets" texto_limpio = texto[:8000] prompt = f"""Eres un experto en análisis de facturas. Lee esta factura y conviértela a JSON. TEXTO DE LA FACTURA: {texto_limpio} INSTRUCCIONES: 1. Analiza el texto y decide qué información es importante extraer 2. Crea un JSON estructurado con TODOS los datos que encuentres 3. Incluye: número de factura, fecha, emisor, cliente, productos/servicios, importes 4. Para los números: usa formato numérico puro (ejemplo: 250 no "250€") 5. Si hay tabla de productos, extrae CADA producto con cantidad, precio y total FORMATO JSON (ajusta según lo que encuentres): {{ "numero_factura": "string", "fecha": "DD/MM/YYYY", "emisor": {{ "nombre": "string", "nif": "string", "direccion": "string" }}, "cliente": {{ "nombre": "string", "nif": "string" }}, "productos": [ {{ "descripcion": "string", "cantidad": number, "precio_unitario": number, "total": number }} ], "totales": {{ "base_imponible": number, "iva": number, "porcentaje_iva": number, "total": number }} }} Responde SOLO con el JSON válido (sin explicaciones, sin markdown):""" modelos = [ "Qwen/Qwen2.5-72B-Instruct", "meta-llama/Llama-3.2-3B-Instruct", "mistralai/Mistral-Nemo-Instruct-2407" ] for modelo in modelos: try: print(f"\nProbando: {modelo}") client = InferenceClient(token=token) response = client.chat.completions.create( model=modelo, messages=[{"role": "user", "content": prompt}], max_tokens=2000, temperature=0.1 ) resultado = response.choices[0].message.content resultado = resultado.strip() resultado = re.sub(r'```json\s*', '', resultado) resultado = re.sub(r'```\s*', '', resultado) resultado = resultado.strip() match = re.search(r'\{.*\}', resultado, re.DOTALL) if match: json_str = match.group(0) try: datos_json = json.loads(json_str) print(f"JSON válido extraído con {modelo}") resumen_util = generar_resumen_util(texto_limpio, modelo, client) return datos_json, resumen_util, f"Procesado con {modelo}" except json.JSONDecodeError as e: print(f"JSON inválido: {str(e)[:50]}") continue except Exception as e: print(f"{modelo} falló: {str(e)[:100]}") continue return None, None, "Ningún modelo LLM pudo extraer el JSON. Verifica tu HF_TOKEN." # ============= GENERAR RESUMEN ÚTIL ============= def generar_resumen_util(texto, modelo, client): """Genera un resumen con información útil para administrativos""" prompt_resumen = f"""Analiza esta factura y proporciona información útil para un administrativo o usuario medio. TEXTO DE LA FACTURA: {texto[:6000]} Genera un resumen estructurado con: 1. ESTADO DE PAGO: ¿Está pagada? ¿Fecha de vencimiento? 2. INFORMACIÓN CLAVE: Datos importantes que destacar 3. ALERTAS: Cualquier aspecto que requiera atención (vencimientos, importes altos, etc.) 4. RESUMEN EJECUTIVO: Descripción breve y clara de la factura Responde en español de forma clara y profesional:""" try: response = client.chat.completions.create( model=modelo, messages=[{"role": "user", "content": prompt_resumen}], max_tokens=800, temperature=0.4 ) return response.choices[0].message.content except: return "No se pudo generar el resumen de información útil." # ============= CONVERTIR JSON A CSV TABULAR ============= def json_a_csv(datos_json): """Convierte el JSON en un DataFrame CSV con formato tabular usando comas""" if not datos_json: return None filas = [] # Información general filas.append({ 'Sección': 'INFORMACIÓN GENERAL', 'Campo': 'Número de Factura', 'Valor': datos_json.get('numero_factura', 'N/A'), 'Tipo': 'Identificador' }) filas.append({ 'Sección': 'INFORMACIÓN GENERAL', 'Campo': 'Fecha', 'Valor': datos_json.get('fecha', 'N/A'), 'Tipo': 'Fecha' }) # Emisor if 'emisor' in datos_json: emisor = datos_json['emisor'] if isinstance(emisor, dict): for key, value in emisor.items(): filas.append({ 'Sección': 'EMISOR', 'Campo': key.replace('_', ' ').title(), 'Valor': str(value), 'Tipo': 'Información' }) # Cliente if 'cliente' in datos_json: cliente = datos_json['cliente'] if isinstance(cliente, dict): for key, value in cliente.items(): filas.append({ 'Sección': 'CLIENTE', 'Campo': key.replace('_', ' ').title(), 'Valor': str(value), 'Tipo': 'Información' }) # Productos productos = datos_json.get('productos', datos_json.get('conceptos', datos_json.get('items', []))) if productos and len(productos) > 0: for i, prod in enumerate(productos, 1): filas.append({ 'Sección': 'PRODUCTOS', 'Campo': f'Producto {i}', 'Valor': prod.get('descripcion', 'N/A'), 'Tipo': 'Descripción' }) filas.append({ 'Sección': 'PRODUCTOS', 'Campo': f'Cantidad P{i}', 'Valor': str(prod.get('cantidad', '')), 'Tipo': 'Numérico' }) filas.append({ 'Sección': 'PRODUCTOS', 'Campo': f'Precio Unitario P{i}', 'Valor': f"{prod.get('precio_unitario', 0)}", 'Tipo': 'Monetario' }) filas.append({ 'Sección': 'PRODUCTOS', 'Campo': f'Total P{i}', 'Valor': f"{prod.get('total', 0)}", 'Tipo': 'Monetario' }) # Totales totales = datos_json.get('totales', {}) if totales or 'base_imponible' in datos_json or 'total' in datos_json: base = totales.get('base_imponible', datos_json.get('base_imponible', 0)) iva = totales.get('iva', datos_json.get('iva', 0)) porcentaje_iva = totales.get('porcentaje_iva', datos_json.get('porcentaje_iva', 0)) total = totales.get('total', datos_json.get('total', 0)) filas.append({ 'Sección': 'TOTALES', 'Campo': 'Base Imponible', 'Valor': f"{base}", 'Tipo': 'Monetario' }) filas.append({ 'Sección': 'TOTALES', 'Campo': f'IVA ({porcentaje_iva}%)', 'Valor': f"{iva}", 'Tipo': 'Monetario' }) filas.append({ 'Sección': 'TOTALES', 'Campo': 'TOTAL', 'Valor': f"{total}", 'Tipo': 'Monetario' }) return pd.DataFrame(filas) # ============= GENERAR PDF TEMPLATES ============= def generar_pdf_clasico(csv_file, datos_json): timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') pdf_filename = f"factura_clasica_{timestamp}.pdf" doc = SimpleDocTemplate(pdf_filename, pagesize=A4) story = [] styles = getSampleStyleSheet() titulo_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontSize=24, textColor=colors.HexColor('#1a1a1a'), spaceAfter=30, alignment=TA_CENTER) story.append(Paragraph("FACTURA", titulo_style)) story.append(Spacer(1, 0.3*inch)) info_data = [['Número de Factura:', datos_json.get('numero_factura', 'N/A')], ['Fecha:', datos_json.get('fecha', 'N/A')]] info_table = Table(info_data, colWidths=[2*inch, 4*inch]) info_table.setStyle(TableStyle([('FONTNAME', (0, 0), (-1, -1), 'Helvetica'), ('FONTSIZE', (0, 0), (-1, -1), 11)])) story.append(info_table) doc.build(story) return pdf_filename def generar_pdf_moderno(csv_file, datos_json): timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') pdf_filename = f"factura_moderna_{timestamp}.pdf" doc = SimpleDocTemplate(pdf_filename, pagesize=A4) story = [] styles = getSampleStyleSheet() titulo_style = ParagraphStyle('ModernTitle', parent=styles['Heading1'], fontSize=32, textColor=colors.HexColor('#2196F3'), spaceAfter=10, alignment=TA_LEFT, fontName='Helvetica-Bold') story.append(Paragraph("FACTURA", titulo_style)) doc.build(story) return pdf_filename def generar_pdf_elegante(csv_file, datos_json): timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') pdf_filename = f"factura_elegante_{timestamp}.pdf" doc = SimpleDocTemplate(pdf_filename, pagesize=A4) story = [] styles = getSampleStyleSheet() header_style = ParagraphStyle('ElegantHeader', parent=styles['Heading1'], fontSize=28, textColor=colors.HexColor('#1a237e'), spaceAfter=5, alignment=TA_CENTER, fontName='Helvetica-Bold') story.append(Paragraph("F A C T U R A", header_style)) doc.build(story) return pdf_filename # ============= FUNCIÓN PRINCIPAL ============= def procesar_factura(pdf_file): if pdf_file is None: return "", None, None, "", "", None, None, pdf_file print("\n--- Extrayendo texto del PDF...") texto = extraer_texto_pdf(pdf_file) if texto.startswith("Error"): return "", None, None, "", f"Error: {texto}", None, None, None texto_preview = f"{texto[:1500]}..." if len(texto) > 1500 else texto print("--- El LLM está analizando la factura y creando el JSON...") datos_json, resumen_util, mensaje = analizar_y_convertir_json(texto) if not datos_json: return texto_preview, None, None, "", mensaje, None, None, pdf_file print("--- Convirtiendo JSON a CSV tabular...") df = json_a_csv(datos_json) timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') numero = datos_json.get('numero_factura', 'factura') numero = re.sub(r'[^\w\-]', '_', str(numero)) csv_filename = f"{numero}_{timestamp}.csv" # Guardar CSV con comas como separador df.to_csv(csv_filename, index=False, encoding='utf-8-sig', sep=',') resumen_tecnico = f"""## Factura Procesada Exitosamente **Consulta más información abajo** --- ### Estructura JSON Generada ```json {json.dumps(datos_json, indent=2, ensure_ascii=False)} ``` --- ### Información del Archivo CSV **Nombre del archivo:** `{csv_filename}` **Total de filas:** {len(df)} **Formato:** UTF-8 con BOM, separador: coma (,) --- ### Datos Principales Extraídos **Número de factura:** {datos_json.get('numero_factura', 'N/A')} **Fecha de emisión:** {datos_json.get('fecha', 'N/A')} **Productos/Servicios:** {len(datos_json.get('productos', datos_json.get('conceptos', [])))} items **Importe total:** {datos_json.get('totales', {}).get('total', datos_json.get('total', 'N/A'))} EUR """ print(f"--- CSV guardado: {csv_filename}") return texto_preview, df, csv_filename, resumen_tecnico, resumen_util, datos_json, csv_filename, pdf_file # ============= GENERAR PDF CON TEMPLATE SELECCIONADO ============= def generar_pdf_con_template(template, csv_file, datos_json): if not datos_json: return None, "Error: Primero debes procesar una factura" try: if template == "Clásico": pdf_file = generar_pdf_clasico(csv_file, datos_json) elif template == "Moderno": pdf_file = generar_pdf_moderno(csv_file, datos_json) elif template == "Elegante": pdf_file = generar_pdf_elegante(csv_file, datos_json) else: return None, "Template no válido" return pdf_file, f"PDF generado exitosamente: {pdf_file}" except Exception as e: return None, f"Error al generar PDF: {str(e)}" # ============= INTERFAZ GRADIO ============= with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo: datos_json_state = gr.State() csv_file_state = gr.State() pdf_path_state = gr.State() texto_state = gr.State() gr.Markdown(""" # FACTULAB ### Extrae datos de facturas PDF con IA, rápido y sin complicaciones. """) gr.Markdown("---") with gr.Tabs(): # ============= TAB 1: EXTRACCIÓN AUTOMÁTICA ============= with gr.Tab("Extracción Automática"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Subir Factura PDF") pdf_input = gr.File(label="Seleccionar factura PDF", file_types=[".pdf"], type="filepath") btn_extraer = gr.Button(" Extraer Datos de la Factura", variant="primary", size="lg") # Indicador de carga silencioso loading_extraccion = gr.HTML(visible=False, value="""

Procesando tu factura...

""") gr.Markdown("---") gr.Markdown("### Descarga tu factura en formato CSV") csv_output = gr.File(label="CSV Tabular (separado por comas)") gr.Markdown("---") gr.Markdown("### Rediseña tu PDF con un template") template_selector = gr.Radio( choices=["Clásico", "Moderno", "Elegante"], value="Moderno", label="Estilo de factura" ) btn_generar_pdf = gr.Button("Generar Factura PDF", variant="secondary", size="lg") pdf_output = gr.File(label="Descargar PDF generado") pdf_status = gr.Textbox(label="Estado", interactive=False, lines=2) with gr.Column(scale=2): gr.Markdown("### ") info_util = gr.Markdown(value="*Aquí aparecerá información una vez procesada la factura*") gr.Markdown("---") with gr.Tabs(): with gr.Tab("Vista previa CSV"): tabla_preview = gr.DataFrame(label="Datos estructurados en formato tabular", wrap=True) with gr.Tab("Texto procesado de tu PDF"): texto_extraido = gr.Textbox(label="Texto extraído del PDF", lines=18) with gr.Tab("Más información"): resumen_tecnico = gr.Markdown(label="Estructura de datos y metadatos") # ============= TAB 2: ASISTENTE IA CON VOZ Y AVATAR ============= # ============= TAB 2: ASISTENTE IA CON ANÁLISIS EMOCIONAL ============= with gr.Tab(" Pregunta a la IA sobre tu factura"): gr.Markdown(""" # Modelo base IA ### Pregúntale cualquier cosa sobre tu factura """) with gr.Row(): with gr.Column(scale=1): pregunta_ia = gr.Textbox( label="Tu pregunta ", placeholder="Ejemplo: ¿Cuál es el total de esta factura?", value="¿Cuál es el total de esta factura y cuándo debería pagarla?", lines=4 ) btn_consulta_ia = gr.Button("Consultar", variant="primary", size="lg") # Indicador de carga loading_ia = gr.HTML(visible=False, value="""

El asistente está analizando...

""") gr.Markdown("---") gr.Markdown("#### Ejemplos de preguntas:") gr.Markdown(""" - ¿Cuál es el total de la factura? - ¿Qué es la base imponible? - ¿Cuándo debo pagar esta factura? - ¿Hay algún descuento aplicado? - ¿Quién emitió esta factura? """) # Indicador de emoción with gr.Column(scale=2): gr.Markdown("### ") gr.Markdown("### Respuesta a tu consulta") resultado_ia = gr.Markdown( value="*Haz una pregunta y el asistente te responderá aquí...*" ) gr.Markdown("---") gr.Markdown("### Lectura automática de la respuesta") with gr.Row(): with gr.Column(): audio_respuesta = gr.Audio( label=" Reproducir respuesta en audio", type="filepath", visible=True, autoplay=True ) with gr.Column(): transcripcion_output = gr.File( label=" Descargar Transcripción (TXT)" ) # ============= TAB 3: HERRAMIENTAS IA AVANZADAS ============= with gr.Tab("Consulta el analisis inteligente de tu factura"): gr.Markdown(""" #### Verifica la información sensible """) with gr.Tabs(): # Sub-tab 1: Análisis Financiero with gr.Tab(" Análisis IA"): with gr.Row(): with gr.Column(): gr.Markdown("### ") btn_sentimiento = gr.Button(" Riesgos", variant="primary") resultado_sentimiento = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_deducibles = gr.Button(" Calcular Deducciones", variant="primary") resultado_deducibles = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_impacto = gr.Button(" Impacto presupuestario", variant="primary") resultado_impacto = gr.Markdown() with gr.Column(): gr.Markdown("### ") btn_prediccion = gr.Button(" Predicción de pago", variant="primary") resultado_prediccion = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_sugerencias = gr.Button(" Generar Recomendaciones", variant="primary") resultado_sugerencias = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_categoria = gr.Button(" Clasificar Gasto", variant="primary") resultado_categoria = gr.Markdown() # Sub-tab 2: Seguridad y Validación with gr.Tab(" Análisis de riesgos"): with gr.Row(): with gr.Column(): gr.Markdown("### ") btn_fraude = gr.Button("Irregularidades", variant="primary") resultado_fraude = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_validador = gr.Button(" Datos Fiscales", variant="primary") resultado_validador = gr.Markdown() with gr.Column(): gr.Markdown("### ") btn_condiciones = gr.Button(" Analizar Condiciones", variant="primary") resultado_condiciones = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_recordatorios = gr.Button(" Generar Recordatorios de pago", variant="primary") resultado_recordatorios = gr.Markdown() gr.Markdown("---") gr.Markdown("### ") btn_ejecutivo = gr.Button(" Dashboard Básico", variant="primary") resultado_ejecutivo = gr.Markdown() # Sub-tab 3: Comparación y Mercado with gr.Tab(" Análisis IA de Mercado"): gr.Markdown("### Comparador de Precios con Mercado") btn_mercado = gr.Button(" Analizar", variant="primary", size="lg") resultado_mercado = gr.Markdown() # ============= TAB 4: TRADUCCIÓN MULTIIDIOMA CON TABLA ============= with gr.Tab(" Traduce tu factura a otro idioma"): gr.Markdown(""" # ### Traduce tu factura a 5 idiomas con vista tabular y exporta a CSV """) with gr.Row(): with gr.Column(): gr.Markdown("### Seleccionar Idioma") idioma_selector = gr.Dropdown( choices=["Inglés", "Francés", "Alemán", "Italiano", "Portugués"], value="Inglés", label=" Selecciona un idioma" ) btn_traducir = gr.Button(" Traducir Factura", variant="primary", size="lg") gr.Markdown("---") gr.Markdown("### Exportar Traducción") csv_traduccion_output = gr.File(label=" Descargar CSV Tabular Traducido") with gr.Column(): gr.Markdown("### Vista Tabular Traducida") tabla_traduccion = gr.DataFrame( label="Factura traducida en formato tabular", wrap=True ) gr.Markdown("---") gr.Markdown("### Texto Traducido") resultado_traduccion = gr.Textbox( label="Resumen en texto", lines=10, placeholder="La traducción aparecerá aquí..." ) gr.Markdown("---") # ============= CONECTAR EVENTOS ============= # Extracción automática def procesar_con_loading(pdf_file): if pdf_file is None: return "", None, None, "", "", None, None, None, gr.update(visible=False) yield "", None, None, "", "", None, None, None, gr.update(visible=True) time.sleep(0.5) resultado = procesar_factura(pdf_file) yield (*resultado, gr.update(visible=False)) btn_extraer.click( fn=procesar_con_loading, inputs=[pdf_input], outputs=[texto_extraido, tabla_preview, csv_output, resumen_tecnico, info_util, datos_json_state, csv_file_state, pdf_path_state, loading_extraccion] ) # Generar PDF btn_generar_pdf.click( fn=generar_pdf_con_template, inputs=[template_selector, csv_file_state, datos_json_state], outputs=[pdf_output, pdf_status] ) def consultar_ia_con_loading(texto, pregunta): if not texto: return ("❌ Por favor, procesa una factura primero", None, None, gr.update(visible=False)) yield ("🔄 El asistente está analizando tu pregunta...", None, None, gr.update(visible=True)) time.sleep(0.3) try: respuesta, audio, transcripcion = asistente_ia_factura(texto, pregunta) audio_final = audio if (audio and os.path.exists(audio) and os.path.getsize(audio) > 100) else None if audio_final: print(f"✅ Audio disponible: {audio_final}") else: print("⚠️ Audio no disponible") emocion_info += "\n\n⚠️ *El audio no pudo generarse, pero la respuesta está en texto.*" yield (respuesta, audio_final, transcripcion, gr.update(visible=False)) except Exception as e: error_msg = f"❌ Error: {str(e)[:200]}" print(f"Error completo: {str(e)}") yield (error_msg, None, None, gr.update(visible=False)) btn_consulta_ia.click( fn=consultar_ia_con_loading, inputs=[texto_extraido, pregunta_ia], outputs=[resultado_ia, audio_respuesta, transcripcion_output, loading_ia] ) # Funciones de análisis avanzado def ejecutar_sentimiento(texto): if not texto: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = analizar_sentimiento_factura(texto, client) emoji_sentimiento = {"positivo": "✅", "neutral": "⚪", "alerta": "⚠️"} emoji_urgencia = {"alta": "🔴", "media": "🟡", "baja": "🟢"} return f"""### {emoji_sentimiento.get(resultado['sentimiento'], '⚪')} Análisis de Sentimiento **Estado:** {resultado['sentimiento'].upper()} **Urgencia:** {emoji_urgencia.get(resultado['urgencia'], '⚪')} {resultado['urgencia'].upper()} **Razón:** {resultado['razon']} **Recomendación:** {resultado['recomendacion']}""" def ejecutar_fraude(datos_json, texto): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = detectar_fraude_factura(datos_json, texto, client) nivel_color = {"bajo": "🟢", "medio": "🟡", "alto": "🔴"} alertas_texto = "\n".join([f"- {alerta}" for alerta in resultado.get('alertas', [])]) return f"""### {nivel_color.get(resultado['nivel_riesgo'], '⚪')} Detección de Fraude **Nivel de Riesgo:** {resultado['nivel_riesgo'].upper()} **Alertas Detectadas:** {alertas_texto if alertas_texto else "- No se detectaron alertas"} **Recomendación:** {resultado['recomendacion']}""" def ejecutar_deducibles(datos_json, texto): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = extraer_gastos_deducibles(datos_json, texto, client) return f"""### 💰 Análisis de Gastos Deducibles **Porcentaje Deducible:** {resultado['porcentaje_deducible']}% **Importe Deducible:** {resultado['importe_deducible']}€ **Tipo de Deducción:** {resultado['tipo_deduccion']} **Explicación:** {resultado['explicacion']} ⚠️ *Nota: Esta es una estimación. Consulta con tu asesor fiscal.*""" def ejecutar_sugerencias(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) return f"### 💡 Sugerencias Personalizadas\n\n{generar_sugerencias_ia(datos_json, client)}" def ejecutar_categoria(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) return f"### 🏷️ Categorización Automática\n\n{extraer_categorias_gasto(datos_json, client)}" def ejecutar_prediccion(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = predecir_fecha_pago(datos_json, client) return f"""### 📅 Predicción de Fecha de Pago Óptima **Fecha Sugerida:** {resultado['fecha_sugerida']} **Razón:** {resultado['razon']} **Ahorro Posible:** {resultado['ahorro_posible']} 💡 Pagar en la fecha sugerida puede optimizar tu flujo de caja.""" def ejecutar_ejecutivo(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) return f"### Resumen Ejecutivo - Dashboard\n\n{generar_resumen_ejecutivo(datos_json, client)}" # Traducción completa con tabla def ejecutar_traduccion_completa(texto, datos_json, idioma): if not texto: return "❌ Procesa una factura primero", None, None token = os.getenv("aa") if not token: return "❌ Error de configuración", None, None client = InferenceClient(token=token) texto_traducido, df_traducido, csv_filename = traducir_factura_con_csv(datos_json, texto, idioma, client) return texto_traducido, df_traducido, csv_filename def ejecutar_duplicados(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = detectar_facturas_duplicadas(datos_json, client) return f"""### 🔄 Análisis de Duplicados **¿Es posible duplicado?** {'✅ SÍ' if resultado['posible_duplicado'] else '❌ NO'} **Nivel de confianza:** {resultado['nivel_confianza'].upper()} **Indicadores:** {chr(10).join([f"- {ind}" for ind in resultado.get('indicadores', [])]) if resultado.get('indicadores') else '- No se detectaron indicadores'} **Recomendación:** {resultado['recomendacion']}""" def ejecutar_impacto(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = calcular_impacto_presupuesto(datos_json, client) nivel_emoji = {"bajo": "🟢", "medio": "🟡", "alto": "🟠", "crítico": "🔴"} return f"""### 📊 Impacto Presupuestario **Porcentaje del presupuesto:** {resultado['impacto_porcentaje']}% **Nivel de impacto:** {nivel_emoji.get(resultado['nivel_impacto'], '⚪')} {resultado['nivel_impacto'].upper()} **Análisis:** {resultado['analisis']} **Recomendación Financiera:** {resultado['recomendacion_financiera']}""" def ejecutar_recordatorios(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = generar_recordatorios_pago(datos_json, client) recordatorios = resultado.get('recordatorios', []) texto = "### Plan de Recordatorios de Pago\n\n" for r in recordatorios: texto += f"**{r.get('tipo', '').upper()}** ({r.get('dias_antes', 0)} días antes):\n" texto += f"{r.get('mensaje', '')}\n\n" return texto if recordatorios else "No se pudieron generar recordatorios" def ejecutar_condiciones(datos_json, texto): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = analizar_condiciones_pago(datos_json, texto, client) return f"""### Análisis de Condiciones de Pago **Plazo Actual:** {resultado['plazo_actual']} **Condiciones Especiales:** {chr(10).join([f"- {c}" for c in resultado.get('condiciones_especiales', [])]) if resultado.get('condiciones_especiales') else '- No detectadas'} **Oportunidades de Negociación:** {resultado['oportunidades_negociacion']} **Sugerencias de Mejora:** {resultado['sugerencias_mejora']}""" def ejecutar_mercado(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = comparar_precios_mercado(datos_json, client) eval_emoji = {"competitivo": "✅", "normal": "⚪", "elevado": "⚠️"} return f"""### 💲 Comparación con Precios de Mercado **Evaluación General:** {eval_emoji.get(resultado['evaluacion_general'], '⚪')} {resultado['evaluacion_general'].upper()} **Productos con Precios Elevados:** {chr(10).join([f"- {p}" for p in resultado.get('productos_caros', [])]) if resultado.get('productos_caros') else '- Todos los precios son razonables'} **Ahorro Potencial:** {resultado['ahorro_potencial']}€ **Recomendación:** {resultado['recomendacion']}""" def ejecutar_validador(datos_json): if not datos_json: return "❌ Procesa una factura primero" token = os.getenv("aa") if not token: return "❌ Error de configuración" client = InferenceClient(token=token) resultado = validar_datos_fiscales(datos_json, client) return f"""### Validación de Datos Fiscales **¿Es válida?** {'✅ SÍ' if resultado['es_valida'] else '❌ NO'} **Nivel de Cumplimiento:** {resultado['nivel_cumplimiento'].upper()} **Errores Detectados:** {chr(10).join([f"- ❌ {e}" for e in resultado.get('errores', [])]) if resultado.get('errores') else '- No se detectaron errores'} **Advertencias:** {chr(10).join([f"- ⚠️ {a}" for a in resultado.get('advertencias', [])]) if resultado.get('advertencias') else '- No hay advertencias'}""" # Conectar funcionalidades btn_impacto.click(fn=ejecutar_impacto, inputs=[datos_json_state], outputs=[resultado_impacto]) btn_recordatorios.click(fn=ejecutar_recordatorios, inputs=[datos_json_state], outputs=[resultado_recordatorios]) btn_condiciones.click(fn=ejecutar_condiciones, inputs=[datos_json_state, texto_extraido], outputs=[resultado_condiciones]) btn_mercado.click(fn=ejecutar_mercado, inputs=[datos_json_state], outputs=[resultado_mercado]) btn_validador.click(fn=ejecutar_validador, inputs=[datos_json_state], outputs=[resultado_validador]) btn_sentimiento.click(fn=ejecutar_sentimiento, inputs=[texto_extraido], outputs=[resultado_sentimiento]) btn_fraude.click(fn=ejecutar_fraude, inputs=[datos_json_state, texto_extraido], outputs=[resultado_fraude]) btn_deducibles.click(fn=ejecutar_deducibles, inputs=[datos_json_state, texto_extraido], outputs=[resultado_deducibles]) btn_sugerencias.click(fn=ejecutar_sugerencias, inputs=[datos_json_state], outputs=[resultado_sugerencias]) btn_categoria.click(fn=ejecutar_categoria, inputs=[datos_json_state], outputs=[resultado_categoria]) btn_prediccion.click(fn=ejecutar_prediccion, inputs=[datos_json_state], outputs=[resultado_prediccion]) btn_ejecutivo.click(fn=ejecutar_ejecutivo, inputs=[datos_json_state], outputs=[resultado_ejecutivo]) # Traducción con tabla btn_traducir.click( fn=ejecutar_traduccion_completa, inputs=[texto_extraido, datos_json_state, idioma_selector], outputs=[resultado_traduccion, tabla_traduccion, csv_traduccion_output] ) if __name__ == "__main__": demo.launch()