Una hemeroteca digital para una colección privada: los números de periódico en PDF se convierten en un archivo donde se encuentra cualquier artículo por palabra, por significado o haciendo una pregunta, siempre con la fuente citada.
El problema
El cliente tenía una colección de números de periódico en archivos PDF. Encontrar un artículo significaba abrir los archivos uno a uno, y muchas páginas eran escaneos sin texto. Además, el archivo tenía que ser privado: solo podían entrar las personas invitadas por el administrador.
Lo que construí: una hemeroteca digital
Construí un archivo web privado en el que cada número se guarda con la cabecera del periódico, la fecha y el PDF, y cada página se puede buscar.
- Extracción de texto con OCR de respaldo: una página conserva su propia capa de texto cuando la tiene. Las páginas con menos de 40 caracteres se renderizan a 220 DPI y se leen con Tesseract. Así también los escaneos antiguos se pueden buscar.
- Búsqueda de texto completo en italiano: la búsqueda usa stemming italiano e ignora los acentos, de modo que «citta» encuentra «città». Las frases exactas aparecen primero, con fragmentos resaltados.
- Búsqueda por significado: el texto de cada página se divide en bloques solapados y se convierte en vectores de 768 dimensiones con un modelo multilingüe. Los lectores encuentran artículos aunque no conozcan las palabras exactas.
- Asistente con citas: el lector hace una pregunta en lenguaje natural. El sistema recupera hasta 8 pasajes, por significado y por palabra clave, y Claude responde solo a partir de esos pasajes, citando cabecera, fecha y página. Se activa desde el panel de administración con la propia clave de Anthropic del cliente.
- Carga masiva desde una carpeta: el administrador selecciona una carpeta de PDF y la fecha se lee del nombre de cada archivo. Los duplicados se omiten por cabecera y fecha o por huella SHA-256, así que una carga se puede repetir sin riesgo.
- Acceso solo por invitación: dos roles, administrador y lector. El panel gestiona usuarios, cabeceras, la clave de IA y las copias de seguridad.
Las cifras
- Límite de memoria de 8 GB en el servicio de embeddings, con un reinicio diario. Antes del límite había llegado a unos 15 GB y bloqueó la cola durante una semana.
- Copia de seguridad nocturna a las 03:30: un volcado comprimido de la base de datos, del que se conservan los 14 últimos, más una réplica incremental de los PDF.
- Recuperación automática de tareas atascadas: una tarea que lleva más de 20 minutos en ejecución vuelve a la cola, con un máximo de 6 intentos.
Con qué está hecho
Python 3.11 y FastAPI para la aplicación web. PostgreSQL 16 con pgvector, unaccent y pg_trgm para los datos, la búsqueda de texto y los vectores. PyMuPDF y Tesseract para el texto y el OCR. Sentence-transformers con el modelo multilingual-e5-base para los embeddings, ejecutado como un servicio local independiente. La API de Anthropic para el asistente. Todo funciona como servicios systemd detrás de un proxy inverso Apache.
Por qué se hizo así
Mantuve la cola de tareas dentro de PostgreSQL en lugar de añadir Redis. Los workers reclaman las tareas con FOR UPDATE SKIP LOCKED, de modo que varios workers trabajan en paralelo sin procesar nunca dos veces el mismo número. El servidor aloja también otros sitios, así que tener un servicio menos que mantener y proteger fue una ventaja real.


