#!/usr/bin/env python3 """ PDF Resumidor - Resumen de documentos con IA --------------------------------------------- Extrae texto de PDFs y genera resúmenes usando Ollama (local, sin internet). Requisitos: 1. pip install PyMuPDF 2. Tener Ollama instalado (https://ollama.com) 3. ollama pull llama3.2:3b Uso: python3 pdf_resumidor.py documento.pdf python3 pdf_resumidor.py documento.pdf --idioma ingles python3 pdf_resumidor.py documento.pdf --formato bullet """ import argparse import subprocess import sys import os import re def verificar_ollama(): try: subprocess.run(["ollama", "--version"], capture_output=True, check=True, timeout=5) return True except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): return False def extraer_texto_pdf(ruta_pdf): """Extrae texto de un PDF usando PyMuPDF.""" try: import fitz except ImportError: print("❌ Necesitas instalar PyMuPDF: pip install PyMuPDF") sys.exit(1) if not os.path.exists(ruta_pdf): print(f"❌ El archivo {ruta_pdf} no existe") sys.exit(1) print(f"📄 Leyendo PDF: {ruta_pdf}...") doc = fitz.open(ruta_pdf) texto_completo = "" for i, pagina in enumerate(doc): texto = pagina.get_text() texto_completo += f"\n--- Página {i+1} ---\n{texto}" doc.close() print(f"✅ PDF leído ({len(doc)} páginas, {len(texto_completo)} caracteres)") return texto_completo def chunk_text(texto, max_chars=4000): """Divide el texto en fragmentos para no saturar el contexto del modelo.""" parrafos = texto.split('\n') chunks = [] chunk_actual = "" for parrafo in parrafos: if len(chunk_actual) + len(parrafo) < max_chars: chunk_actual += parrafo + "\n" else: if chunk_actual.strip(): chunks.append(chunk_actual.strip()) chunk_actual = parrafo + "\n" if chunk_actual.strip(): chunks.append(chunk_actual.strip()) return chunks def resumir_con_ollama(texto, idioma="español", formato="texto"): """Envía el texto a Ollama y obtiene un resumen.""" if len(texto) > 4000: chunks = chunk_text(texto) print(f"📑 Documento largo, dividiendo en {len(chunks)} partes...") resumenes_parciales = [] for i, chunk in enumerate(chunks): print(f" Resumiendo parte {i+1}/{len(chunks)}...") prompt = f"Resume el siguiente texto en {idioma}. Formato: {formato}.\n\nTexto:\n{chunk}" try: result = subprocess.run( ["ollama", "run", "llama3.2:3b", prompt], capture_output=True, text=True, timeout=120 ) if result.returncode == 0: resumenes_parciales.append(result.stdout.strip()) except subprocess.TimeoutExpired: print(f" ⚠️ Tiempo agotado en parte {i+1}") continue # Resumen final combinando los parciales if len(resumenes_parciales) > 1: print("🔄 Generando resumen final combinado...") texto_combinado = "\n\n".join(resumenes_parciales) prompt_final = f"Combina los siguientes resúmenes parciales en un resumen coherente en {idioma}. Formato: {formato}.\n\n{texto_combinado}" result = subprocess.run( ["ollama", "run", "llama3.2:3b", prompt_final], capture_output=True, text=True, timeout=120 ) if result.returncode == 0: return result.stdout.strip() return "\n\n".join(resumenes_parciales) else: prompt = f"Resume el siguiente texto en {idioma}. Incluye: idea principal, puntos clave y conclusiones.\n\nFormato: {formato}\n\nTexto:\n{texto}" print("🤖 Generando resumen...") result = subprocess.run( ["ollama", "run", "llama3.2:3b", prompt], capture_output=True, text=True, timeout=120 ) if result.returncode == 0: return result.stdout.strip() else: return f"Error al generar resumen: {result.stderr}" def guardar_resumen(resumen, archivo_original): """Guarda el resumen en un archivo .txt junto al PDF original.""" base = os.path.splitext(archivo_original)[0] archivo_salida = f"{base}_resumen.txt" with open(archivo_salida, "w", encoding="utf-8") as f: f.write("=" * 60 + "\n") f.write(f"RESUMEN GENERADO POR IA\n") f.write(f"Archivo original: {os.path.basename(archivo_original)}\n") f.write(f"Modelo: llama3.2:3b\n") f.write("=" * 60 + "\n\n") f.write(resumen) print(f"✅ Resumen guardado en: {archivo_salida}") return archivo_salida def main(): parser = argparse.ArgumentParser( description="PDF Resumidor - Resume documentos PDF usando IA local", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Ejemplos: python3 pdf_resumidor.py documento.pdf python3 pdf_resumidor.py articulo.pdf --idioma ingles python3 pdf_resumidor.py tesis.pdf --formato bullet python3 pdf_resumidor.py contrato.pdf --idioma español --formato parrafo """ ) parser.add_argument("pdf", help="Ruta al archivo PDF") parser.add_argument("--idioma", default="español", help="Idioma del resumen (español, ingles)") parser.add_argument("--formato", default="texto", choices=["texto", "bullet", "esquema"], help="Formato del resumen") parser.add_argument("--salida", help="Archivo de salida (opcional)") args = parser.parse_args() print("🤖 PDF RESUMIDOR - AI Distillery") print("=" * 40) if not verificar_ollama(): print("❌ Ollama no está instalado.") print("Descárgalo desde: https://ollama.com") sys.exit(1) texto = extraer_texto_pdf(args.pdf) if not texto.strip(): print("⚠️ No se pudo extraer texto del PDF (puede ser un documento escaneado)") print("Prueba con un OCR o con un PDF con texto seleccionable.") sys.exit(1) formato_map = { "texto": "párrafos coherentes con introducción, desarrollo y conclusión", "bullet": "lista de puntos clave con viñetas", "esquema": "esquema jerárquico con niveles" } resumen = resumir_con_ollama(texto, args.idioma, formato_map[args.formato]) if resumen: print("\n" + "=" * 40) print("📝 RESUMEN:") print("=" * 40) print(resumen) print("=" * 40) archivo_guardado = guardar_resumen(resumen, args.pdf) print(f"\n💾 También disponible en: {archivo_guardado}") else: print("❌ No se pudo generar el resumen") if __name__ == "__main__": main()