refactor: reorganizar estructura de archivos en raiz

Antes la raiz tenia 20+ archivos sueltos. Ahora organizado en:

  docs/       10 archivos .md de documentacion tecnica
  scripts/    3 scripts utilitarios (credentials, migrate, verify)
  config/     entity_config.json (aliases y blacklist NER)
  data/       feeds.csv (feeds precargados)

Eliminados restos de Docker que ya no aplican:
  .dockerignore, .env.example, .env.secure.example, nginx.conf (raiz)

Makefile: eliminados targets docker-build, añadidos install/rebuild/check/poc

Referencias actualizadas en:
  deploy/debian/install.sh  entity_config.json -> config/entity_config.json
  deploy/debian/build.sh    entity_config.json -> config/entity_config.json
  README.md                 links a docs/ y data/ actualizados,
                            arbol de estructura del repo reescrito

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SITO 2026-03-30 22:29:50 +02:00
parent ec839b5b54
commit b3bf3d7a7f
23 changed files with 59 additions and 335 deletions

54
docs/DEPLOY.md Normal file
View file

@ -0,0 +1,54 @@
# Deployment Guide
This guide describes how to deploy the application to a new server.
## Prerequisites
* **Linux Server** (Ubuntu 22.04+ recommended)
* **NVIDIA GPU**: Required for translation, embeddings, and NER services.
* **NVIDIA Container Toolkit**: Must be installed to allow Docker to access the GPU.
* **Docker** & **Docker Compose**: Latest versions.
* **Git**: To clone the repository.
* **External Service**: An instance of [AllTalk](https://github.com/erew123/alltalk_tts) running externally or on the host (port 7851 by default).
## Deployment Steps
1. **Clone the Repository**
```bash
git clone <your-repo-url>
cd <your-repo-name>
```
2. **Configure Environment Variables**
Copy the example configuration file:
```bash
cp .env.example .env
```
Edit `.env` and set secure passwords and configuration:
```bash
nano .env
```
* Change `POSTGRES_PASSWORD` and `DB_PASS` to a strong unique password.
* Change `SECRET_KEY` to a long random string.
* Verify `ALLTALK_URL` points to your AllTalk instance (default assumes host machine access).
3. **Start the Services**
Run the following command to build and start the application:
```bash
docker compose up -d --build
```
4. **Database Initialization**
The database will automatically initialize on the first run using the scripts in `init-db/`. This may take a few minutes. Check logs with:
```bash
docker compose logs -f db
```
5. **Verify Deployment**
Access the application at `http://<your-server-ip>:8001`.
## Important Notes
* **Models**: The application mounts `./models` and `./hf_cache` to persist AI models. On the first run, it will attempt to download necessary models (NLLB, BERT, etc.), which requires significant bandwidth and time.
* **Data Persistence**: Database data is stored in `./pgdata` (mapped in docker-compose). Ensure this directory is backed up.
* **Security**: Ensure port 5432 (Postgres) and 6379 (Redis) are firewall-protected and not exposed to the public internet unless intended (Docker maps them to the host network).

280
docs/DEPLOY_DEBIAN.md Normal file
View file

@ -0,0 +1,280 @@
# COCONEWS · Despliegue en Debian (sin Docker)
Guía completa para instalar y operar COCONEWS en un servidor Debian 12 (Bookworm) o Ubuntu 22.04+, sin Docker ni contenedores.
---
## Requisitos de hardware
| Modo | CPU | RAM | Disco |
|------|-----|-----|-------|
| **Mínimo (solo CPU)** | 4 cores | 8 GB | 40 GB |
| **Recomendado** | 8 cores | 16 GB | 80 GB |
| **Con GPU** | 8 cores + NVIDIA | 16 GB | 80 GB |
> Los modelos de IA (NLLB-200 + MiniLM + spaCy) ocupan ~5 GB en disco una vez descargados.
---
## Servicios que se instalan en el servidor
| Servicio | Tecnología | Gestionado por |
|----------|-----------|----------------|
| Base de datos | PostgreSQL 16 | apt + systemd |
| Caché | Redis 7 | apt + systemd |
| Búsqueda vectorial | Qdrant (binario) | systemd |
| API REST | Go (backend) | systemd |
| Ingestor RSS | Go | systemd |
| Scraper / Discovery / Wiki / Topics / Related / Qdrant-worker | Go | systemd |
| Traducción (NLLB-200) | Python + CTranslate2 | systemd |
| Embeddings | Python + Sentence-Transformers | systemd |
| NER | Python + spaCy | systemd |
| Clustering / Categorización | Python | systemd |
| Frontend | React (estático compilado) | nginx |
| Proxy / Web | nginx | apt + systemd |
---
## Instalación paso a paso
### 1. Clonar el repositorio
```bash
git clone https://gitea.laenre.net/pietre/rss2.git /opt/src/rss2
cd /opt/src/rss2
git checkout coconews
```
### 2. Configurar variables de entorno
```bash
cp deploy/debian/env.example /opt/rss2/.env
nano /opt/rss2/.env
```
Valores que **debes cambiar obligatoriamente**:
```env
POSTGRES_PASSWORD=contraseña_segura_postgres
DB_PASS=contraseña_segura_postgres
REDIS_PASSWORD=contraseña_segura_redis
SECRET_KEY=cadena_aleatoria_minimo_32_caracteres
```
Genera claves seguras con:
```bash
openssl rand -hex 32
```
### 3. Descargar y convertir el modelo de traducción (NLLB-200)
Este paso se hace **una sola vez** y puede tardar 10-30 minutos dependiendo de la conexión.
```bash
# Instalar dependencias Python primero (si aun no se hizo)
python3 -m venv /opt/rss2/venv
/opt/rss2/venv/bin/pip install ctranslate2 transformers sentencepiece
# Convertir modelo NLLB-200 a formato CTranslate2 (tarda 10-30 min)
/opt/rss2/venv/bin/python - <<'EOF'
from ctranslate2.converters import OpusMTConverter
converter = OpusMTConverter("facebook/nllb-200-distilled-600M")
converter.convert("/opt/rss2/models/nllb-ct2", quantization="int8", force=True)
print("Modelo convertido OK en /opt/rss2/models/nllb-ct2")
EOF
```
> El modelo ocupa ~600 MB convertido. Si la descarga de HuggingFace falla, exporta
> `HF_ENDPOINT=https://huggingface.co` o usa un mirror.
### 4. Ejecutar el instalador
```bash
sudo bash /opt/src/rss2/deploy/debian/install.sh
```
El script hace automáticamente:
- Instala PostgreSQL, Redis, nginx, Go, Node.js via apt
- Descarga el binario de Qdrant
- Crea el usuario `rss2` del sistema
- Crea la base de datos y ejecuta las migraciones
- Compila los 8 binarios Go
- Compila el frontend React
- Instala y habilita los 16 servicios systemd
---
## Verificar que funciona
```bash
# Estado de todos los servicios
systemctl status rss2-backend rss2-ingestor rss2-translator rss2-embeddings
# Ver logs en tiempo real
journalctl -u rss2-backend -f
journalctl -u rss2-translator -f
# Comprobar que el API responde
curl http://localhost:8080/api/stats
# Acceder al frontend
# http://IP_DEL_SERVIDOR:8001
```
---
## Gestión de servicios
### Iniciar / parar / reiniciar
```bash
# Un servicio concreto
systemctl start rss2-backend
systemctl stop rss2-translator
systemctl restart rss2-embeddings
# Todos los workers de una vez
systemctl restart rss2-backend rss2-ingestor rss2-scraper rss2-discovery \
rss2-wiki rss2-topics rss2-related rss2-qdrant-worker \
rss2-langdetect rss2-translation-scheduler rss2-translator \
rss2-embeddings rss2-ner rss2-cluster rss2-categorizer
```
### Ver logs
```bash
journalctl -u rss2-backend -f # API Go
journalctl -u rss2-translator -f # Traductor
journalctl -u rss2-embeddings -f # Embeddings
journalctl -u rss2-ner -f # NER entidades
journalctl -u rss2-ingestor -f # Ingestor RSS
```
---
## Actualizar el código
Cuando hay nuevos cambios en el repositorio:
```bash
cd /opt/src/rss2
git pull
sudo bash deploy/debian/build.sh
```
El script `build.sh` recompila los binarios Go, el frontend y sincroniza los workers Python, y reinicia los servicios automáticamente.
---
## Estructura de directorios en el servidor
```
/opt/rss2/
├── .env # Variables de entorno (permisos 600)
├── bin/ # Binarios Go compilados
│ ├── server # API REST
│ ├── ingestor # Ingestor RSS
│ ├── scraper
│ ├── discovery
│ ├── wiki_worker
│ ├── topics
│ ├── related
│ └── qdrant_worker
├── src/
│ └── workers/ # Workers Python
├── venv/ # Virtualenv Python (ML)
├── models/
│ └── nllb-ct2/ # Modelo traduccion CTranslate2
├── hf_cache/ # Cache HuggingFace (embeddings, NER)
├── frontend/
│ └── dist/ # Frontend React compilado (servido por nginx)
├── data/
│ ├── wiki_images/ # Imagenes Wikipedia descargadas
│ └── qdrant_storage/ # Datos vectoriales Qdrant
└── qdrant/
└── qdrant # Binario Qdrant
```
---
## Requisitos de red / firewall
Solo exponer al exterior el puerto **8001** (nginx). El resto deben ser internos:
```bash
# Con ufw:
ufw allow 8001/tcp # COCONEWS web
ufw deny 5432/tcp # PostgreSQL - solo localhost
ufw deny 6379/tcp # Redis - solo localhost
ufw deny 6333/tcp # Qdrant - solo localhost
ufw deny 8080/tcp # API Go - solo localhost (nginx hace proxy)
ufw enable
```
---
## Backup de datos
```bash
# PostgreSQL
sudo -u postgres pg_dump rss > /opt/rss2/backups/rss_$(date +%Y%m%d).sql
# Datos Qdrant
systemctl stop rss2-qdrant
tar -czf /opt/rss2/backups/qdrant_$(date +%Y%m%d).tar.gz /opt/rss2/data/qdrant_storage
systemctl start rss2-qdrant
# Imagenes Wikipedia (opcional, se pueden re-descargar)
tar -czf /opt/rss2/backups/wiki_images_$(date +%Y%m%d).tar.gz /opt/rss2/data/wiki_images
```
---
## Solución de problemas frecuentes
### El traductor no arranca
```bash
journalctl -u rss2-translator -n 50
# Si dice "model not found": el modelo NLLB-200 no está convertido
# Ejecutar el paso 3 de la instalación
```
### PostgreSQL rechaza la conexión
```bash
# Verificar que el .env tiene DB_HOST=127.0.0.1 (no "db")
grep DB_HOST /opt/rss2/.env
# Verificar que el usuario existe
sudo -u postgres psql -c "\du"
```
### nginx devuelve 502 Bad Gateway
```bash
# El backend Go no está corriendo
systemctl status rss2-backend
journalctl -u rss2-backend -n 30
```
### Memoria insuficiente para los modelos Python
Con 8 GB RAM el translator + embeddings + NER pueden coincidir. Si el servidor tiene poca RAM, deshabilitar el translator-gpu y bajar el batch:
```bash
# En /opt/rss2/.env
TRANSLATOR_BATCH=8
EMB_BATCH=32
NER_BATCH=16
systemctl restart rss2-translator rss2-embeddings rss2-ner
```
---
## Primer inicio de sesión
1. Abrir `http://IP:8001` en el navegador
2. Al no haber usuarios, el sistema te redirige al registro
3. El primer usuario registrado se convierte en **administrador**
4. Ir a Configuración → Feeds → Importar el `feeds.csv` del repositorio para empezar con fuentes precargadas

View file

@ -0,0 +1,75 @@
# Descripción de Archivos y Funciones del Proyecto RSS2
Este documento detalla la estructura del proyecto y la función de sus archivos principales.
## 🐳 Infraestructura y Despliegue
| Archivo / Directorio | Descripción |
|----------------------|-------------|
| `docker-compose.yml` | **Orquestador principal**. Define todos los servicios (db, web, workers, redis, qdrant, etc.), redes, volúmenes de persistencia y configuración de recursos. |
| `Dockerfile` | Definición de la imagen base para la aplicación web y la mayoría de los workers en Python. |
| `Dockerfile.llm_worker` | Imagen específica para el worker de LLM, incluye dependencias de CUDA y PyTorch para ExLlamaV2. |
| `Dockerfile.url_worker` | Imagen optimizada para el worker de descubrimiento y procesamiento de URLs. |
| `nginx.conf` | Configuración del servidor web Nginx que actúa como proxy inverso y servidos de archivos estáticos. |
| `.env` | Variables de entorno con credenciales y configuración sensible (NO compartir). |
| `gunicorn_config.py` | Configuración del servidor de aplicaciones WSGI Gunicorn para producción. |
## 🧠 Núcleo de la Aplicación (Python)
| Archivo | Descripción |
|---------|-------------|
| `app.py` | **Punto de entrada**. Inicializa la aplicación Flask, registra blueprints (rutas) y configura extensiones. |
| `config.py` | Carga y valida la configuración desde variables de entorno. Define constantes globales. |
| `db.py` | Gestión de la conexión a la base de datos PostgreSQL (pool de conexiones). |
| `cache.py` | Capa de abstracción para Redis. Maneja caché de respuestas y estados transitorios. |
| `scheduler.py` | Planificador de tareas periódicas (cron jobs internos) para mantenimiento y disparadores. |
| `requirements.txt` | Lista de dependencias de Python necesarias para el proyecto. |
## 👷 Workers (Procesamiento en Segundo Plano)
Ubicados en `workers/`:
| Archivo | Función |
|---------|---------|
| `llm_categorizer_worker.py` | Categoriza noticias usando un LLM local (Mistral/ExLlamaV2). Asigna etiquetas temáticas. |
| `url_worker_daemon.py` | Procesa y valida URLs extraídas, gestionando la cola de descargas. |
| `url_discovery_worker.py` | Busca nuevos feeds RSS a partir de las URLs base. |
| `translation_worker.py` | Traduce contenido usando modelos NLLB (No Language Left Behind). |
| `embeddings_worker.py` | Genera vectores semánticos para búsqueda y clustering. |
| `cluster_worker.py` | Agrupa noticias similares en "historias" o eventos. |
| `ner_worker.py` | Extracción de Entidades Nombradas (personas, organizaciones, lugares). |
| `topics_worker.py` | Identifica y extrae tópicos principales de los textos. |
| `qdrant_worker.py` | Sincroniza los vectores generados con la base de datos vectorial Qdrant. |
## 🌐 API y Rutas
Ubicados en `routers/`:
| Archivo | Descripción |
|---------|-------------|
| `api.py` | Endpoints generales de la API REST. |
| `feeds.py` | Gestión de fuentes RSS (CRUD). |
| `news.py` | Endpoints para listar y filtrar noticias. |
| `dashboard.py` | Rutas para el panel de administración y estadísticas. |
| `auth.py` | Manejo de autenticación y autorización. |
| `search.py` | Endpoints para búsqueda semántica y tradicional. |
## 🛠️ Herramientas y Scripts
| Archivo | Descripción |
|---------|-------------|
| `scripts/download_llm_model.sh` | Script para descargar modelos LLM cuantizados desde HuggingFace de forma segura. |
| `verify_security.sh` | Auditoría de seguridad automatizada (verifica permisos, configuración TLS, etc.). |
| `generate_secure_credentials.sh` | Genera contraseñas seguras y configura el entorno inicial. |
| `rss-ingestor-go/` | Ingestor de RSS de alto rendimiento escrito en Go. |
## 📂 Directorios de Datos
| Directorio | Contenido |
|------------|-----------|
| `models/` | Almacenamiento persistente de modelos de IA (LLMs, Embeddings, Traducción). |
| `pgdata/` | Persistencia de la base de datos PostgreSQL. |
| `qdrant_storage/` | Persistencia de la base de datos vectorial Qdrant. |
| `hf_cache/` | Caché de HuggingFace para modelos y tokenizers. |
| `templates/` | Plantillas HTML (Jinja2) para la interfaz web. |
| `static/` | Archivos CSS, JS e imágenes públicas. |

View file

@ -0,0 +1,401 @@
# 📊 Resumen de Implementación - Sistema LLM Categorizer
**Fecha**: 2026-01-20
**Estado**: ✅ Completado
---
## ✅ Tareas Completadas
### 1. Revisión y Levantamiento de la Aplicación
- ✓ Aplicación RSS2 levantada exitosamente
- ✓ Todos los 22 contenedores funcionando correctamente
- ✓ Web accesible en http://localhost:8001 (HTTP 200)
- ✓ Base de datos operativa con **853,118 noticias**
- ✓ **7,666 feeds** registrados (**1,695 activos**)
### 2. Implementación del Sistema LLM Categorizer
Se ha creado un sistema completo de categorización automática que:
- Toma **10 noticias** del feed simultáneamente
- Las envía a un **LLM local** (ExLlamaV2)
- El LLM **discrimina/categoriza** cada noticia automáticamente
- Actualiza la base de datos con las categorías asignadas
#### Archivos Creados:
```
/home/x/rss2/
├── workers/
│ └── llm_categorizer_worker.py ✓ Worker principal (440 líneas)
├── Dockerfile.llm_worker ✓ Dockerfile con CUDA + ExLlamaV2
├── docker-compose.yml ✓ Actualizado con servicio LLM
├── scripts/
│ ├── download_llm_model.sh ✓ Script de descarga de modelos
│ └── test_llm_categorizer.py ✓ Script de prueba
├── docs/
│ └── LLM_CATEGORIZER.md ✓ Documentación completa
├── QUICKSTART_LLM.md ✓ Guía rápida
└── README.md ✓ Actualizado
```
---
## 🤖 Características del Sistema
### Modelo Recomendado
**Mistral-7B-Instruct-v0.2 (GPTQ 4-bit)**
- Optimizado para RTX 3060 12GB
- Tamaño: ~4.5 GB
- VRAM: ~6-7 GB
- Rendimiento: 120-300 noticias/hora
### Alternativas Disponibles
1. Mistral-7B-Instruct-v0.2 (EXL2 4.0bpw) - Más rápido
2. OpenHermes-2.5-Mistral-7B (GPTQ) - Mejor generalista
3. Neural-Chat-7B (GPTQ) - Bueno para español
### Categorías Predefinidas
El sistema clasifica en **15 categorías**:
- Política
- Economía
- Tecnología
- Ciencia
- Salud
- Deportes
- Entretenimiento
- Internacional
- Nacional
- Sociedad
- Cultura
- Medio Ambiente
- Educación
- Seguridad
- Otros
---
## 🔧 Configuración Técnica
### Servicio Docker
```yaml
llm-categorizer:
container: rss2_llm_categorizer
GPU: NVIDIA (1 GPU asignada)
Memoria: 10GB límite
Modelo: ExLlamaV2
Backend: CUDA 12.1
```
### Variables de Entorno
| Variable | Valor | Descripción |
|----------|-------|-------------|
| `LLM_BATCH_SIZE` | 10 | Noticias por lote |
| `LLM_SLEEP_IDLE` | 30s | Espera entre lotes |
| `LLM_MAX_SEQ_LEN` | 4096 | Longitud máxima de contexto |
| `LLM_CACHE_MODE` | FP16 | Modo de caché (FP16/Q4) |
| `LLM_GPU_SPLIT` | auto | Distribución de GPU |
### Base de Datos
Se añadieron automáticamente 4 columnas nuevas a `noticias`:
| Columna | Tipo | Descripción |
|---------|------|-------------|
| `llm_categoria` | VARCHAR(100) | Categoría asignada |
| `llm_confianza` | FLOAT | Nivel de confianza (0.0-1.0) |
| `llm_processed` | BOOLEAN | Si fue procesada |
| `llm_processed_at` | TIMESTAMP | Fecha de procesamiento |
---
## 📈 Rendimiento Estimado
### Con RTX 3060 12GB
- **VRAM utilizada**: ~6-7 GB
- **Tiempo por noticia**: 2-5 segundos
- **Throughput**: 120-300 noticias/hora
- **Precisión esperada**: 85-90%
### Procesamiento Total
Con **853,118 noticias** en la BD:
- **Tiempo estimado**: 47-118 horas (2-5 días continuos)
- **Modo 24/7**: El worker procesa automáticamente
- **Control**: Puedes detener/reiniciar en cualquier momento
---
## 🚀 Próximos Pasos
### 1. Descargar el Modelo (OBLIGATORIO)
```bash
cd /home/x/rss2
./scripts/download_llm_model.sh
```
Selecciona **opción 1** (Mistral-7B-Instruct GPTQ)
⏱️ Tiempo: 10-30 minutos
💾 Espacio: 4.5 GB
### 2. Probar el Sistema (Recomendado)
```bash
# Instalar dependencias
pip3 install exllamav2 torch
# Ejecutar prueba
python3 scripts/test_llm_categorizer.py
```
Esto prueba el modelo ANTES de levantar Docker.
### 3. Levantar el Servicio
```bash
# Construir y levantar
docker compose up -d --build llm-categorizer
# Ver logs
docker compose logs -f llm-categorizer
```
**Primera carga**: 2-5 minutos cargando modelo en GPU
### 4. Monitorear
```bash
# Ver estado
docker compose ps llm-categorizer
# Ver categorías asignadas
docker exec -it rss2_db psql -U rss -d rss -c \
"SELECT llm_categoria, COUNT(*) FROM noticias WHERE llm_processed = TRUE GROUP BY llm_categoria;"
# Ver progreso
docker exec -it rss2_db psql -U rss -d rss -c \
"SELECT COUNT(*) as procesadas,
(COUNT(*)::float / 853118 * 100)::numeric(5,2) as porcentaje
FROM noticias WHERE llm_processed = TRUE;"
```
---
## 📚 Documentación
### Guías Disponibles
1. **QUICKSTART_LLM.md** - Guía rápida de inicio
2. **docs/LLM_CATEGORIZER.md** - Documentación completa
3. **README.md** - Visión general actualizada
### Comandos Útiles
```bash
# Ver logs en vivo
docker compose logs -f llm-categorizer
# Reiniciar servicio
docker compose restart llm-categorizer
# Detener servicio
docker compose stop llm-categorizer
# Ver uso de GPU
nvidia-smi
# Ver todas las tablas
docker exec -it rss2_db psql -U rss -d rss -c "\dt"
```
---
## 🔍 Consultas SQL Útiles
### Distribución de categorías
```sql
SELECT llm_categoria, COUNT(*) as total,
AVG(llm_confianza) as confianza_media
FROM noticias
WHERE llm_processed = TRUE
GROUP BY llm_categoria
ORDER BY total DESC;
```
### Progreso de procesamiento
```sql
SELECT
COUNT(CASE WHEN llm_processed = TRUE THEN 1 END) as procesadas,
COUNT(CASE WHEN llm_processed = FALSE THEN 1 END) as pendientes,
(COUNT(CASE WHEN llm_processed = TRUE THEN 1 END)::float / COUNT(*) * 100)::numeric(5,2) as porcentaje
FROM noticias;
```
### Noticias por categoría (últimas)
```sql
SELECT titulo, llm_categoria, llm_confianza, fecha
FROM noticias
WHERE llm_categoria = 'Tecnología'
AND llm_processed = TRUE
ORDER BY fecha DESC
LIMIT 10;
```
### Resetear para reprocesar
```sql
-- Resetear últimas 100 noticias
UPDATE noticias
SET llm_processed = FALSE
WHERE id IN (
SELECT id FROM noticias
WHERE llm_processed = TRUE
ORDER BY fecha DESC
LIMIT 100
);
```
---
## ⚠️ Troubleshooting
### Problema: Out of Memory
**Solución**: Reducir batch size y usar cache Q4
```yaml
# En docker-compose.yml
environment:
LLM_BATCH_SIZE: 5
LLM_CACHE_MODE: Q4
```
### Problema: Modelo no encontrado
**Solución**: Verificar descarga
```bash
ls -la /home/x/rss2/models/llm/
# Debe contener: config.json, model.safetensors, etc.
```
### Problema: No procesa noticias
**Solución**: Verificar si hay noticias pendientes
```bash
docker exec -it rss2_db psql -U rss -d rss -c \
"SELECT COUNT(*) FROM noticias WHERE llm_processed = FALSE;"
```
---
## 🎯 Ventajas del Sistema
**100% Local**: Sin envío de datos a APIs externas
**Alta Precisión**: LLM entiende contexto, no solo keywords
**Automático**: Procesamiento continuo en background
**Escalable**: Procesa 10 noticias por lote eficientemente
**Integrado**: Worker nativo del ecosistema RSS2
**Optimizado**: Específico para RTX 3060 12GB
**Extensible**: Fácil añadir nuevas categorías
**Monitoreable**: Logs detallados y métricas en BD
---
## 📊 Estado de Feeds
### Estadísticas Actuales
- **Total de feeds**: 7,666
- **Feeds activos**: 1,695 (22%)
- **Total de noticias**: 853,118
- **Noticias sin categorizar (LLM)**: 853,118 (100%)
### Recomendación
Considera **reevaluar los feeds inactivos**:
```sql
-- Ver feeds inactivos con errores
SELECT nombre, url, fallos, last_error
FROM feeds
WHERE activo = FALSE
ORDER BY fallos DESC
LIMIT 20;
-- Reactivar feeds con pocos fallos
UPDATE feeds
SET activo = TRUE, fallos = 0
WHERE activo = FALSE AND fallos < 5;
```
---
## 🔮 Mejoras Futuras Sugeridas
1. **Subcategorías automáticas** - Categorización más granular
2. **Resúmenes por categoría** - Generar resúmenes diarios
3. **Trending topics** - Detectar temas de moda por categoría
4. **Alertas personalizadas** - Notificar por categorías de interés
5. **Fine-tuning del modelo** - Entrenar con feedback de usuario
6. **API REST** - Endpoint para categorización bajo demanda
7. **Dashboard web** - Visualización de categorías en tiempo real
---
## 📞 Soporte
### Logs
```bash
docker compose logs llm-categorizer
```
### GPU
```bash
nvidia-smi
watch -n 1 nvidia-smi # Monitoreo en vivo
```
### Base de Datos
```bash
docker exec -it rss2_db psql -U rss -d rss
```
---
## ✨ Conclusión
El sistema LLM Categorizer está **completamente implementado y listo para usar**.
Solo necesitas:
1. ✅ Descargar el modelo (~15 min)
2. ✅ Levantar el servicio (1 comando)
3. ✅ Monitorear el progreso
**Resultado**: Categorización automática e inteligente de todas las noticias del sistema.
---
**Implementado por**: Antigravity AI
**Fecha**: 2026-01-20
**Versión**: 1.0
**Estado**: ✅ Producción

View file

@ -0,0 +1,145 @@
# Diseño Periodístico Clásico - El Observador
## 🎨 Transformación Visual Completa
La aplicación ha sido completamente rediseñada con un **estilo periodístico clásico** inspirado en los mejores periódicos del mundo:
- **The New York Times** (estructura y jerarquía)
- **El País** (elementos visuales españoles)
- **The Guardian** (claridad tipográfica)
## ✨ Características Implementadas
### 1. Tipografía Periodística
- **Titulares**: Old Standard TT & Playfair Display (serif clásico)
- **Cuerpo**: Merriweather (serif legible para lectura larga)
- **UI/Navegación**: Lato (sans-serif limpio y moderno)
### 2. Paleta de Colores Clásica
- **Tinta Negra** (#1a1a1a): Texto principal con peso y autoridad
- **Papel Blanco** (#ffffff): Fondo limpio y profesional
- **Papel Crema** (#f9f7f4): Fondo general con calidez
- **Rojo Acento** (#c1121f): Color institucional para destacados
- **Azul Enlaces** (#326891): Enlaces legibles y tradicionales
### 3. Elementos de Diseño Periodístico
- ✅ Cabecera con nombre en mayúsculas y bordes dobles
- ✅ Fecha y hora actualizadas en tiempo real (Estilo Madrid)
- ✅ Navegación sticky negra con bordes rojos
- ✅ Tarjetas de noticias con bordes sutiles
- ✅ Tipografía jerárquica (títulos grandes, meta pequeña)
- ✅ Hover effects suaves y profesionales
- ✅ Badges de categoría en rojo institucional
- ✅ Layout responsive adaptado a móviles
### 4. Página de Artículos
- 📰 Breadcrumbs para navegación
- 📰 Título prominente con tipografía serif
- 📰 Metadata periodística (fuente, fecha, país, categoría)
- 📰 Resumen destacado con borde rojo
- 📰 Sidebar con artículos relacionados
- 📰 Modo lectura inmersivo
- 📰 Botones de compartir, PDF y favoritos
### 5. Funcionalidades
- **Modo Lectura**: Aumenta fuente y elimina distracciones
- **Modo Oscuro**: Tema completamente adaptado
- **Responsive**: Diseño adaptado para móvil, tablet y desktop
- **Animaciones**: Transiciones suaves y profesionales
- **Accesibilidad**: Contraste adecuado y jerarquía clara
## 📱 Responsive Design
- **Desktop (>968px)**: Layout completo con sidebar
- **Tablet (768px-968px)**: Grid adaptado en 2 columnas
- **Mobile (<768px)**: Una columna, menú de hamburguesa
## 🌓 Modo Oscuro
Paleta invertida manteniendo la estética:
- Fondo oscuro (#0f0f0f)
- Texto claro (#e0e0e0)
- Acento rojo más brillante (#ff4444)
- Bordes sutiles (#333)
## 🎯 Mejoras de UX
1. **Historial de Lectura**: Artículos leídos aparecen con opacidad reducida
2. **Favoritos Persistentes**: Sistema de guardado con estrellas
3. **Búsqueda Avanzada**: Filtros por categoría, país, fecha
4. **Búsqueda Semántica IA**: Toggle para búsqueda inteligente
5. **Notificaciones**: Sistema de alertas para nuevas noticias
6. **Paginación Clara**: Navegación entre páginas de noticias
## 📂 Archivos Modificados
- `static/style.css` - CSS completamente reescrito (1300+ líneas)
- `templates/base.html` - Actualizado con nuevas fuentes y nombre
- `templates/noticia_classic.html` - Template de detalle mejorado
- `templates/_noticias_list.html` - Cards de noticias (sin cambios necesarios)
## 🚀 Activación
Los cambios están **activos automáticamente** después de reiniciar nginx:
```bash
docker compose restart nginx
```
## 🎨 Paleta de Colores Completa
```css
--ink-black: #1a1a1a /* Texto principal */
--newspaper-gray: #333333 /* Texto secundario */
--paper-white: #ffffff /* Fondo de tarjetas */
--paper-cream: #f9f7f4 /* Fondo general */
--border-gray: #d1d1d1 /* Bordes */
--accent-red: #c1121f /* Acento principal */
--accent-red-dark: #9a0e1a /* Acento hover */
--link-blue: #326891 /* Enlaces */
--text-gray: #4a4a4a /* Meta información */
--light-gray: #f0f0f0 /* Fondos sutiles */
```
## 💡 Inspiración de Diseño
El diseño sigue los principios de los mejores periódicos:
1. **Jerarquía Clara**: Los títulos dominan visualmente
2. **Espacios en Blanco**: El contenido respira
3. **Legibilidad**: Fuentes serif para lectura larga
4. **Profesionalismo**: Colores sobrios y clásicos
5. **Credibilidad**: Diseño serio y confiable
## 🔄 Migración desde Diseño Anterior
El diseño anterior era moderno con:
- Glassmorphism
- Gradientes animados
- Colores vibrantes (púrpura, rosa, azul)
- Fuentes sans-serif (Poppins, Roboto)
El nuevo diseño es periodístico con:
- Fondos sólidos
- Colores clásicos (blanco, negro, rojo)
- Tipografía serif tradicional
- Bordes definidos
## ✅ Testing Recomendado
1. ✓ Verificar que el header muestra "EL OBSERVADOR"
2. ✓ Comprobar que las fuentes serif se carguen correctamente
3. ✓ Probar el modo oscuro (botón luna/sol)
4. ✓ Verificar responsive en móvil
5. ✓ Probar funcionalidades (favoritos, búsqueda, filtros)
6. ✓ Verificar que las imágenes de noticias se vean bien
7. ✓ Comprobar paginación
8. ✓ Probar modo lectura en artículos
## 👨‍💻 Créditos
Diseño creado siguiendo las mejores prácticas de diseño periodístico digital, manteniendo toda la funcionalidad existente del agregador de noticias RSS.
---
**Fecha de Actualización**: Enero 2026
**Versión**: 2.0 - Diseño Periodístico Clásico

257
docs/QDRANT_SETUP.md Normal file
View file

@ -0,0 +1,257 @@
# ✅ Sistema Qdrant - Búsquedas Semánticas
## 🎯 Arquitectura Actual
El sistema vectoriza **directamente** las noticias traducidas y proporciona búsqueda semántica en tiempo real.
```
Noticias Originales (RSS)
Traducción (translator workers)
PostgreSQL (tabla 'traducciones')
Qdrant Worker (vectorización directa)
Qdrant (búsquedas semánticas)
API de Búsqueda (utils/qdrant_search.py)
Buscador General + Monitor de Conflictos
```
## ✅ Servicios y Componentes
| Componente | Puerto/Ubicación | Descripción |
|----------|--------|-------------|
| **Qdrant** | 6333 | Base de datos vectorial |
| **Qdrant Worker** | - | Vectorización continua |
| **Búsqueda Semántica** | `utils/qdrant_search.py` | API de búsqueda vectorial |
| **Buscador General** | `routers/search.py` | Búsqueda con fallback a PostgreSQL |
| **Monitor de Conflictos** | `routers/conflicts.py` | Búsqueda por keywords con vectores |
### Configuración del Worker
- **Origen**: Tabla `traducciones`
- **Modelo**: `paraphrase-multilingual-MiniLM-L12-v2`
- **Dimensiones**: 384
- **Dispositivo**: CPU (GPU disponible)
- **Velocidad**: ~100+ noticias/minuto
- **Total Vectorizado**: ~507,000 noticias
## 🚀 Integración Completa
### Buscador General (`/api/search`)
La búsqueda ahora usa **Qdrant primero** para mayor velocidad y precisión semántica:
1. **Búsqueda Semántica** (por defecto): Usa vectores de Qdrant
2. **Fallback PostgreSQL**: Si falla o no hay resultados
3. **Enriquecimiento**: Combina datos de ambas fuentes
**Ventajas:**
- ✅ Búsquedas 10-100x más rápidas (sin escaneo de 500k filas)
- ✅ Comprende sinónimos y contexto ("protestas" encuentra "manifestaciones")
- ✅ Multilingüe automático
- ✅ Sin dependencia de palabras exactas
**Parámetros:**
- `q`: Texto de búsqueda
- `limit`: Máximo de resultados (default: 10, max: 50)
- `semantic`: `true/false` (default: `true`)
### Monitor de Conflictos (`/conflicts/<id>`)
Ahora usa búsqueda semántica por keywords:
**Antes (ILIKE con PostgreSQL):**
- ❌ "irán protestas" requería coincidencia exacta de toda la frase
- ❌ Lento con 500k noticias
- ❌ No encontraba variaciones ("manifestación", "protesta")
**Ahora (Qdrant):**
- ✅ "irán protestas" busca por ambas palabras independientemente
- ✅ Rápido (búsqueda vectorial)
- ✅ Encuentra contenido semánticamente similar
## 🔧 Comandos Útiles
### Ver Logs
```bash
docker-compose logs -f qdrant-worker
docker-compose logs -f qdrant
docker-compose logs -f rss2_web # Para ver logs de búsqueda
```
### Estadísticas
```bash
docker exec -it rss2_web python scripts/migrate_to_qdrant.py --stats
```
### Vectorizar Pendientes (Manual)
```bash
docker exec -it rss2_web python scripts/migrate_to_qdrant.py --vectorize --batch-size 200
```
### Reset Completo (⚠️ Destructivo)
```bash
docker exec -it rss2_web python scripts/migrate_to_qdrant.py --reset
```
### Probar Búsqueda Semántica
```bash
# Búsqueda semántica
curl "http://localhost:8001/api/search?q=protestas+en+iran&semantic=true"
# Búsqueda tradicional (fallback)
curl "http://localhost:8001/api/search?q=protestas+en+iran&semantic=false"
```
## 📊 Verificar Estado
### Base de Datos
```sql
-- Progreso de vectorización
SELECT
COUNT(*) as total,
COUNT(*) FILTER (WHERE vectorized = TRUE) as vectorizadas,
COUNT(*) FILTER (WHERE vectorized = FALSE) as pendientes
FROM traducciones
WHERE lang_to = 'es' AND status = 'done';
```
### Qdrant API
```bash
# Estado de la colección
curl http://localhost:6333/collections/news_vectors
# Health check
curl http://localhost:6333/healthz
# Conteo de puntos
curl http://localhost:6333/collections/news_vectors | jq '.result.points_count'
```
## 🔍 Variables de Entorno
```bash
# .env
QDRANT_HOST=qdrant
QDRANT_PORT=6333
QDRANT_COLLECTION_NAME=news_vectors
QDRANT_BATCH_SIZE=100
QDRANT_SLEEP_IDLE=30
EMB_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
```
## 📁 Archivos Relevantes
| Archivo | Función |
|---------|---------|
| `workers/qdrant_worker.py` | Worker de vectorización continua |
| `utils/qdrant_search.py` | **NUEVO**: API de búsqueda semántica |
| `routers/search.py` | **ACTUALIZADO**: Buscador con Qdrant |
| `routers/conflicts.py` | **ACTUALIZADO**: Monitor de conflictos con Qdrant |
| `scripts/migrate_to_qdrant.py` | Migración/estadísticas |
| `docker-compose.yml` | Configuración de servicios + timezone sync |
## ⏰ Sincronización de Hora
**Problema Resuelto:** Todos los contenedores Docker ahora tienen la hora sincronizada (TZ=Europe/Madrid).
**Cambios:**
- ✅ Variable `TZ=Europe/Madrid` en todos los servicios
- ✅ Volúmenes `/etc/timezone` y `/etc/localtime` en servicios clave
- ✅ Logs consistentes entre todos los workers
## 🚀 Despliegue en Nuevas Máquinas
### Requisitos Previos
1. Docker y Docker Compose instalados
2. Al menos 8GB RAM (recomendado 16GB)
3. GPU NVIDIA (opcional, para workers de traducción)
### Pasos de Instalación
```bash
# 1. Clonar repositorio
git clone <repo-url>
cd rss2
# 2. Configurar variables de entorno
cp .env.example .env
# Editar .env con tus credenciales
# 3. Iniciar servicios
docker-compose up -d
# 4. Verificar que Qdrant está funcionando
curl http://localhost:6333/healthz
# 5. Monitorear vectorización
docker-compose logs -f qdrant-worker
```
### Migración de Datos Existentes
Si ya tienes noticias traducidas sin vectorizar:
```bash
# Ver estadísticas
docker exec -it rss2_web python scripts/migrate_to_qdrant.py --stats
# Vectorizar todas las pendientes (puede tardar horas con 500k noticias)
# El worker lo hace automáticamente, pero puedes forzarlo:
docker-compose restart qdrant-worker
```
## 🔍 Troubleshooting
### La búsqueda no usa Qdrant
```bash
# Verificar que Qdrant está corriendo
docker ps | grep qdrant
# Ver logs del worker
docker-compose logs qdrant-worker
# Verificar conexión
curl http://localhost:6333/collections/news_vectors
```
### Búsqueda lenta aún
```bash
# Verificar cuántas noticias están vectorizadas
docker exec -it rss2_web python scripts/migrate_to_qdrant.py --stats
# Si hay muchas pendientes, el worker las procesará automáticamente
# Para acelerar, aumenta el batch size en docker-compose.yml:
# QDRANT_BATCH_SIZE=200
```
### Error "No module named 'qdrant_client'"
```bash
# Reconstruir imagen web
docker-compose build rss2_web
docker-compose restart rss2_web
```
## 📈 Rendimiento
**Antes (PostgreSQL solo):**
- Búsqueda simple: 2-5 segundos (500k filas)
- Búsqueda compleja: 10-30 segundos
- Monitor de conflictos: 5-15 segundos
**Ahora (Qdrant + PostgreSQL):**
- Búsqueda semántica: 50-200ms
- Enriquecimiento PostgreSQL: +50ms
- Monitor de conflictos: 100-300ms
- **Mejora: 10-100x más rápido**
## 🎯 Próximos Pasos
- [ ] Implementar filtros avanzados (fecha, país, categoría)
- [ ] Cachear resultados frecuentes en Redis
- [ ] Agregar búsqueda híbrida (combinar PostgreSQL FTS + Qdrant)
- [ ] Dashboard de métricas de búsqueda

269
docs/QUICKSTART_LLM.md Normal file
View file

@ -0,0 +1,269 @@
# 🚀 Guía Rápida: Sistema LLM Categorizer
## ✅ Estado Actual
- ✓ Aplicación RSS2 levantada y funcionando correctamente
- ✓ Todos los contenedores están operativos
- ✓ Web accesible en http://localhost:8001
- ✓ Nuevo sistema LLM Categorizer creado y configurado
## 📋 Próximos Pasos
### 1. Descargar el Modelo LLM (REQUERIDO)
```bash
cd /home/x/rss2
./scripts/download_llm_model.sh
```
**Selecciona la opción 1** (Mistral-7B-Instruct-v0.2 GPTQ) - Recomendado para RTX 3060 12GB
⏱️ **Tiempo estimado**: 10-30 minutos según tu conexión
💾 **Espacio necesario**: ~4.5 GB
### 2. Probar el Sistema (OPCIONAL pero recomendado)
```bash
# Instalar dependencias para prueba local
pip3 install exllamav2 torch
# Ejecutar prueba
python3 scripts/test_llm_categorizer.py
```
Esto te permite verificar que el modelo funciona ANTES de levantar el contenedor.
### 3. Levantar el Servicio LLM
```bash
# Construir y levantar el contenedor
docker compose up -d --build llm-categorizer
# Monitorear los logs
docker compose logs -f llm-categorizer
```
**Primera ejecución**: El contenedor tardará 2-5 minutos en cargar el modelo en GPU.
### 4. Verificar Funcionamiento
```bash
# Ver estado
docker compose ps llm-categorizer
# Ver últimas categorizaciones
docker exec -it rss2_db psql -U rss -d rss -c \
"SELECT llm_categoria, COUNT(*) FROM noticias WHERE llm_processed = TRUE GROUP BY llm_categoria;"
```
---
## 🔧 Configuración
### Archivos Creados
```
/home/x/rss2/
├── workers/
│ └── llm_categorizer_worker.py # Worker principal
├── Dockerfile.llm_worker # Dockerfile específico
├── scripts/
│ ├── download_llm_model.sh # Descarga del modelo
│ └── test_llm_categorizer.py # Script de prueba
├── docs/
│ └── LLM_CATEGORIZER.md # Documentación completa
└── docker-compose.yml # Actualizado con servicio llm-categorizer
```
### Servicio en docker-compose.yml
```yaml
llm-categorizer:
build:
context: .
dockerfile: Dockerfile.llm_worker
environment:
LLM_BATCH_SIZE: 10 # Noticias por lote
LLM_SLEEP_IDLE: 30 # Segundos entre lotes
LLM_MODEL_PATH: /app/models/llm
deploy:
resources:
limits:
memory: 10G
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [ gpu ]
```
---
## 🎯 Cómo Funciona
1. **Recopilación**: El worker consulta la BD y obtiene 10 noticias sin categorizar
2. **Procesamiento**: Envía cada noticia al LLM local (Mistral-7B)
3. **Categorización**: El LLM determina la categoría más apropiada
4. **Actualización**: Guarda la categoría y confianza en la BD
5. **Loop**: Repite el proceso continuamente
### Categorías Disponibles
- Política
- Economía
- Tecnología
- Ciencia
- Salud
- Deportes
- Entretenimiento
- Internacional
- Nacional
- Sociedad
- Cultura
- Medio Ambiente
- Educación
- Seguridad
- Otros
---
## 📊 Rendimiento Esperado
### Con RTX 3060 12GB + Mistral-7B GPTQ
- **VRAM utilizada**: ~6-7 GB
- **Tiempo por noticia**: 2-5 segundos
- **Throughput**: ~120-300 noticias/hora
- **Precisión**: ~85-90% (depende del contenido)
---
## 🔍 Consultas SQL Útiles
### Ver distribución de categorías
```sql
SELECT llm_categoria, COUNT(*) as total,
AVG(llm_confianza) as confianza_media
FROM noticias
WHERE llm_processed = TRUE
GROUP BY llm_categoria
ORDER BY total DESC;
```
### Ver noticias de una categoría
```sql
SELECT titulo, llm_categoria, llm_confianza, fecha
FROM noticias
WHERE llm_categoria = 'Tecnología'
AND llm_processed = TRUE
ORDER BY fecha DESC
LIMIT 20;
```
### Resetear procesamiento (para reprocesar)
```sql
-- Resetear últimas 100 noticias
UPDATE noticias
SET llm_processed = FALSE
WHERE id IN (
SELECT id FROM noticias
ORDER BY fecha DESC
LIMIT 100
);
```
---
## 🐛 Troubleshooting Rápido
### ❌ "Out of memory"
```yaml
# Reducir batch size en docker-compose.yml
LLM_BATCH_SIZE: 5
LLM_CACHE_MODE: Q4
```
### ❌ "Model not found"
```bash
# Verificar descarga
ls -la models/llm/
# Re-descargar si necesario
./scripts/download_llm_model.sh
```
### ❌ No procesa noticias
```bash
# Verificar cuántas faltan
docker exec -it rss2_db psql -U rss -d rss -c \
"SELECT COUNT(*) FROM noticias WHERE llm_processed = FALSE;"
# Resetear algunas para probar
docker exec -it rss2_db psql -U rss -d rss -c \
"UPDATE noticias SET llm_processed = FALSE LIMIT 20;"
```
---
## 📚 Documentación Completa
Para más detalles, consulta:
```bash
cat docs/LLM_CATEGORIZER.md
```
O abre: `/home/x/rss2/docs/LLM_CATEGORIZER.md`
---
## 🎓 Comandos Útiles
```bash
# Ver todos los servicios
docker compose ps
# Reiniciar solo el LLM
docker compose restart llm-categorizer
# Ver uso de GPU
nvidia-smi
# Ver logs + seguir
docker compose logs -f llm-categorizer
# Detener el LLM
docker compose stop llm-categorizer
# Eliminar completamente (rebuild desde cero)
docker compose down llm-categorizer
docker compose up -d --build llm-categorizer
```
---
## ⚡ Optimizaciones Futuras
Si quieres mejorar el rendimiento:
1. **Usar EXL2 en lugar de GPTQ** (más rápido en ExLlamaV2)
2. **Aumentar batch size** si sobra VRAM
3. **Fine-tune el modelo** con tus propias categorizaciones
4. **Usar vLLM** para servidor de inferencia más eficiente
---
## 🤝 Soporte
Si encuentras problemas:
1. Revisa logs: `docker compose logs llm-categorizer`
2. Consulta documentación: `docs/LLM_CATEGORIZER.md`
3. Verifica GPU: `nvidia-smi`
---
**¡Listo!** El sistema está completamente configurado. Solo falta descargar el modelo y levantarlo. 🚀

201
docs/SECURITY_AUDIT.md Normal file
View file

@ -0,0 +1,201 @@
# 🔒 Auditoría de Seguridad de Red - Resumen Ejecutivo
**Fecha**: 2026-01-12
**Sistema**: RSS2 News Aggregator
**Auditor**: Análisis Automatizado de Seguridad
---
## 📊 RESUMEN EJECUTIVO
Se han identificado **múltiples vulnerabilidades críticas** en la configuración de red de los contenedores Docker. El sistema actual expone servicios internos sin autenticación y utiliza credenciales débiles que comprometen severamente la seguridad de la aplicación.
**Nivel de Riesgo Global**: 🔴 **CRÍTICO**
---
## 🚨 VULNERABILIDADES CRÍTICAS (Prioridad 1)
### 1. Credenciales Comprometidas
- **Severidad**: 🔴 CRÍTICA
- **CVSS Score**: 9.8 (Critical)
- **Descripción**:
- PostgreSQL usa password `x` (1 carácter)
- Flask SECRET_KEY es `secret` (valor por defecto conocido)
- Grafana usa password `admin` (credencial por defecto)
- **Impacto**:
- Acceso completo a la base de datos
- Posible firma de sesiones falsas
- Compromiso total del sistema de autenticación
- **Solución**: Generar credenciales aleatorias de 32+ caracteres
### 2. Exposición de Base de Datos Vectorial (Qdrant)
- **Severidad**: 🔴 CRÍTICA
- **CVSS Score**: 8.6 (High)
- **Puertos Expuestos**: 6333, 6334
- **Descripción**: Qdrant accesible públicamente sin autenticación
- **Impacto**:
- Lectura/modificación de vectores de noticias
- Potencial exfiltración de datos
- Manipulación de búsquedas semánticas
- **Solución**: Eliminar exposición de puertos, usar solo red interna
### 3. Redis Sin Autenticación
- **Severidad**: 🔴 ALTA
- **CVSS Score**: 7.5 (High)
- **Descripción**: Redis accesible sin password
- **Impacto**:
- Acceso no autorizado a caché
- Posible inyección de datos maliciosos
- DoS mediante flush de caché
- **Solución**: Habilitar requirepass en Redis
### 4. Exposición de Prometheus y cAdvisor
- **Severidad**: 🟠 ALTA
- **CVSS Score**: 7.2 (High)
- **Puertos Expuestos**: 9090 (Prometheus), 8081 (cAdvisor)
- **Descripción**: Métricas del sistema accesibles públicamente
- **Impacto**:
- Información sensible sobre arquitectura
- Vectores de ataque (uptime, recursos, vulnerabilidades)
- Reconocimiento de servicios internos
- **Solución**: Internalizar puertos, acceso solo via túnel SSH
---
## ⚠️ VULNERABILIDADES DE RIESGO MEDIO (Prioridad 2)
### 5. Ausencia de Segmentación de Red
- **Severidad**: 🟠 MEDIA
- **Descripción**: Todos los servicios en una única red Docker
- **Impacto**: Movimiento lateral fácil si un contenedor es comprometido
- **Solución**: Implementar 3 redes segmentadas (frontend, backend, monitoring)
### 6. Sin Límites de Recursos
- **Severidad**: 🟡 MEDIA-BAJA
- **Descripción**: Contenedores sin límites de CPU/memoria
- **Impacto**: Posible DoS por consumo excesivo de recursos
- **Solución**: Establecer límites y reservas de recursos
### 7. Montaje de Volúmenes con Permisos Excesivos
- **Severidad**: 🟡 BAJA
- **Descripción**: Código fuente montado en read-write
- **Impacto**: Modificación de código desde contenedor comprometido
- **Solución**: Montar volúmenes críticos en modo read-only
---
## ✅ SOLUCIONES IMPLEMENTADAS
### Archivos Creados
1. **`docker-compose.secure.yml`**
- Redes segmentadas (frontend, backend, monitoring)
- Puertos internalizados
- Autenticación en Redis
- Límites de recursos en todos los servicios
- Volúmenes read-only donde aplica
2. **`.env.secure.example`**
- Template con instrucciones de seguridad
- Placeholders para credenciales fuertes
3. **`generate_secure_credentials.sh`**
- Script automatizado para generar credenciales
- Genera passwords de 32 caracteres
- Crea .env con configuración segura
4. **`SECURITY_GUIDE.md`**
- Guía completa de migración
- Troubleshooting
- Best practices
5. **Código Python actualizado**
- `config.py`: Soporte para REDIS_PASSWORD
- `cache.py`: Autenticación en Redis
---
## 📈 MEJORAS DE SEGURIDAD
| Métrica | ANTES | DESPUÉS | Mejora |
|---------|-------|---------|--------|
| Puertos públicos | 7 | 1 | **-85%** |
| Servicios con autenticación | 1/4 | 4/4 | **+300%** |
| Redes aisladas | 1 | 3 | **+200%** |
| Servicios con límites de recursos | 0% | 100% | **+100%** |
| Fortaleza de passwords (bits) | ~4 bits | ~256 bits | **+6300%** |
---
## 🎯 PLAN DE ACCIÓN RECOMENDADO
### Fase 1: INMEDIATO (Hoy)
1. ✅ Revisar archivos generados
2. ✅ Leer SECURITY_GUIDE.md
3. ⏳ Ejecutar `./generate_secure_credentials.sh`
4. ⏳ Guardar credenciales en gestor de passwords
### Fase 2: CORTO PLAZO (Esta semana)
5. ⏳ Hacer backup completo de datos
6. ⏳ Migrar a `docker-compose.secure.yml`
7. ⏳ Validar funcionamiento en desarrollo
8. ⏳ Configurar acceso SSH a Grafana
### Fase 3: MEDIO PLAZO (Este mes)
9. ⏳ Implementar monitoreo de seguridad
10. ⏳ Configurar backups automáticos encriptados
11. ⏳ Implementar rate limiting en nginx
12. ⏳ Configurar fail2ban
---
## 📋 CHECKLIST DE VALIDACIÓN
Antes de marcar como resuelto, verificar:
- [ ] Todas las credenciales cambiadas y guardadas
- [ ] Solo puerto 8001 expuesto públicamente
- [ ] Qdrant NO accesible desde internet
- [ ] Prometheus NO accesible desde internet
- [ ] cAdvisor NO accesible desde internet
- [ ] Redis requiere autenticación
- [ ] Grafana solo en localhost (127.0.0.1:3001)
- [ ] Web app funciona correctamente
- [ ] Workers se conectan a servicios
- [ ] Búsqueda funciona
- [ ] Backups configurados
- [ ] Firewall del servidor activo
---
## 🔗 REFERENCIAS
- [Docker Security Best Practices](https://docs.docker.com/develop/security-best-practices/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker)
- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
---
## 📞 CONTACTO Y SOPORTE
Para asistencia con la migración:
- Revisar `SECURITY_GUIDE.md` (troubleshooting completo)
- Verificar logs: `docker-compose logs -f`
- Verificar conectividad de redes: `docker network inspect rss2_backend`
---
**Última actualización**: 2026-01-12 18:18 CET
**Próxima revisión recomendada**: 2026-02-12 (mensual)
---
## 🏆 CONCLUSIÓN
La implementación de las soluciones propuestas reducirá el riesgo de seguridad de **CRÍTICO a BAJO**, cerrando todas las vulnerabilidades identificadas y estableciendo una base sólida de seguridad para la aplicación RSS2.
**Tiempo estimado de implementación**: 2-4 horas
**Complejidad**: Media
**ROI de seguridad**: Extremadamente Alto

383
docs/SECURITY_GUIDE.md Normal file
View file

@ -0,0 +1,383 @@
# 🔒 GUÍA DE SEGURIDAD Y MIGRACIÓN - RSS2 Application
## ⚠️ RESUMEN DE VULNERABILIDADES ENCONTRADAS
### CRÍTICAS (Arreglar INMEDIATAMENTE)
1. **Credenciales débiles en .env**
- PostgreSQL password: `x`
- Flask SECRET_KEY: `secret`
- Grafana password: `admin`
2. **Servicios expuestos públicamente sin autenticación**
- Qdrant (puertos 6333, 6334) - Base de datos vectorial
- Prometheus (puerto 9090) - Métricas del sistema
- cAdvisor (puerto 8081) - Estadísticas de contenedores
3. **Redis sin autenticación**
- Accesible por todos los contenedores sin password
### ALTO RIESGO
4. **Ausencia de segmentación de red**
- Todos los servicios en una única red Docker
5. **Ausencia de límites de recursos**
- Contenedores sin límites de CPU/memoria (riesgo de DoS)
6. **Volúmenes con permisos excesivos**
- Código fuente montado con permisos de escritura
---
## 🛠️ SOLUCIONES IMPLEMENTADAS
### 1. Archivo `docker-compose.secure.yml`
**Mejoras de seguridad implementadas:**
#### 🔹 Redes Segmentadas
```yaml
networks:
frontend: # Solo nginx y rss2_web
backend: # BD, workers, redis, qdrant (interna)
monitoring: # Prometheus, Grafana, cAdvisor (interna)
```
#### 🔹 Puertos Internalizados
- ❌ **Eliminados puertos públicos de:**
- Qdrant (6333, 6334) → Solo acceso interno
- Prometheus (9090) → Solo acceso interno
- cAdvisor (8081) → Solo acceso interno
- rss-web-go (8002) → Servicio comentado (duplicado)
- ✅ **Único puerto público:**
- Nginx (8001) → Proxy reverso con seguridad
- ✅ **Puerto localhost únicamente:**
- Grafana (127.0.0.1:3001) → Acceso solo local o via túnel SSH
#### 🔹 Autenticación en Redis
```yaml
redis:
command: >
redis-server
--requirepass ${REDIS_PASSWORD}
```
#### 🔹 Límites de Recursos
Todos los contenedores tienen límites de CPU y memoria:
```yaml
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
memory: 512M
```
#### 🔹 Volúmenes Read-Only
Código fuente montado en modo lectura donde sea posible:
```yaml
volumes:
- ./app.py:/app/app.py:ro
- ./routers:/app/routers:ro
- ./templates:/app/templates:ro
```
---
## 📋 PASOS DE MIGRACIÓN
### Opción A: Migración Gradual (RECOMENDADO para producción)
#### Paso 1: Generar Credenciales Seguras
```bash
# 1. Generar password para PostgreSQL
POSTGRES_PASSWORD=$(openssl rand -base64 32)
echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD"
# 2. Generar password para Redis
REDIS_PASSWORD=$(openssl rand -base64 32)
echo "REDIS_PASSWORD=$REDIS_PASSWORD"
# 3. Generar SECRET_KEY para Flask
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
echo "SECRET_KEY=$SECRET_KEY"
# 4. Generar password para Grafana
GRAFANA_PASSWORD=$(openssl rand -base64 24)
echo "GRAFANA_PASSWORD=$GRAFANA_PASSWORD"
# Guardar estos valores en un lugar seguro (gestor de passwords)
```
#### Paso 2: Copiar y Configurar .env Seguro
```bash
# Copiar el ejemplo seguro
cp .env.secure.example .env
# Editar .env y pegar las contraseñas generadas
nano .env # o usa tu editor preferido
```
#### Paso 3: Backup de Datos
```bash
# Backup de PostgreSQL
docker exec rss2_db pg_dump -U rss rss > backup_$(date +%Y%m%d_%H%M%S).sql
# Backup de Qdrant
tar -czf qdrant_backup_$(date +%Y%m%d_%H%M%S).tar.gz qdrant_storage/
# Backup de Redis (opcional)
docker exec rss2_redis redis-cli --rdb /data/dump.rdb
cp redis-data/dump.rdb redis_backup_$(date +%Y%m%d_%H%M%S).rdb
```
#### Paso 4: Detener Servicios Actuales
```bash
docker-compose down
```
#### Paso 5: Migrar a Configuración Segura
```bash
# Renombrar archivo actual (backup)
mv docker-compose.yml docker-compose.yml.insecure.bak
# Usar la versión segura
cp docker-compose.secure.yml docker-compose.yml
# Verificar configuración
docker-compose config
```
#### Paso 6: Iniciar con Nueva Configuración
```bash
# Iniciar servicios
docker-compose up -d
# Verificar logs
docker-compose logs -f
# Verificar que todos los contenedores están corriendo
docker-compose ps
```
#### Paso 7: Verificar Conectividad
```bash
# Test web app
curl http://localhost:8001
# Test Redis (desde dentro de un contenedor)
docker exec rss2_web bash -c 'python3 -c "import redis; r = redis.Redis(host=\"redis\", port=6379, password=\"$REDIS_PASSWORD\"); print(r.ping())"'
# Verificar logs de workers
docker-compose logs rss2_tasks_py | tail -20
```
---
### Opción B: Migración Directa (Para desarrollo/testing)
```bash
# 1. Backup de datos (como en Opción A, Paso 3)
# 2. Detener todo
docker-compose down -v # CUIDADO: -v elimina volúmenes
# 3. Generar credenciales y configurar .env
cp .env.secure.example .env
# Editar .env con credenciales generadas
# 4. Restaurar datos si es necesario
# (Restaurar dump SQL, qdrant_storage, etc.)
# 5. Iniciar con configuración segura
cp docker-compose.secure.yml docker-compose.yml
docker-compose up -d
```
---
## 🔐 ACCESO A SERVICIOS PROTEGIDOS
### Grafana (Monitoring)
Ahora solo accesible en localhost. Para acceso remoto:
```bash
# Opción 1: Túnel SSH (RECOMENDADO)
ssh -L 3001:localhost:3001 usuario@servidor
# Luego acceder en tu navegador local:
# http://localhost:3001
# Usuario: admin
# Password: El que configuraste en GRAFANA_PASSWORD
```
### Qdrant (Base de Datos Vectorial)
Ya no es accesible públicamente. Para acceso de desarrollo:
```bash
# Opción 1: Temporalmente exponer puerto (SOLO para debug)
# Editar docker-compose.yml y descomentar:
# ports:
# - "127.0.0.1:6333:6333"
# Opción 2: Acceder desde dentro de la red Docker
docker exec -it rss2_qdrant_worker bash
curl http://qdrant:6333/collections
```
### Prometheus (Métricas)
```bash
# Acceso via túnel SSH
ssh -L 9090:localhost:9090 usuario@servidor
# O exponer temporalmente en localhost:
# En docker-compose.yml, prometheus service:
# ports:
# - "127.0.0.1:9090:9090"
```
---
## 🧪 TESTING DE SEGURIDAD
### Verificar que puertos NO son accesibles públicamente:
```bash
# Desde FUERA del servidor (desde tu máquina local)
# Estos NO deberían responder:
curl http://servidor:6333 # Qdrant - debe fallar
curl http://servidor:9090 # Prometheus - debe fallar
curl http://servidor:8081 # cAdvisor - debe fallar
# Este SÍ debe responder:
curl http://servidor:8001 # Nginx - debe funcionar
```
### Verificar segmentación de redes:
```bash
# Los contenedores NO deberían poder acceder a servicios fuera de su red
# Desde un worker backend, NO debe alcanzar nginx:
docker exec rss2_cluster_py curl http://nginx # Debería fallar
# Desde monitoring, NO debe alcanzar db:
docker exec rss2_prometheus curl http://db:5432 # Debería fallar
```
---
## 📊 COMPARATIVA: ANTES vs DESPUÉS
| Aspecto | ANTES (Inseguro) | DESPUÉS (Seguro) |
|---------|------------------|------------------|
| **Puertos expuestos** | 7 puertos públicos | 1 puerto público + 1 localhost |
| **Autenticación Redis** | ❌ Sin password | ✅ Autenticado |
| **PostgreSQL Password** | `x` (débil) | 32+ caracteres aleatorios |
| **Flask SECRET_KEY** | `secret` | 64 caracteres hex |
| **Segmentación de red** | ❌ Una red única | ✅ 3 redes aisladas |
| **Límites de recursos** | ❌ Sin límites | ✅ CPU y RAM limitados |
| **Volúmenes** | Read-Write | Read-Only donde posible |
| **Qdrant público** | ⚠️ Sí (puerto 6333) | ✅ Solo interno |
| **Prometheus público** | ⚠️ Sí (puerto 9090) | ✅ Solo interno |
---
## 🚨 CHECKLIST FINAL DE SEGURIDAD
Antes de poner en producción, verifica:
- [ ] Todas las contraseñas generadas aleatoriamente (min 32 caracteres)
- [ ] Archivo `.env` NO está en el repositorio (revisar .gitignore)
- [ ] Solo puerto 8001 expuesto públicamente
- [ ] Grafana accesible solo en localhost
- [ ] Redis requiere autenticación
- [ ] Todos los workers pueden conectarse a Redis
- [ ] Todos los workers pueden conectarse a PostgreSQL
- [ ] Web app funciona correctamente
- [ ] Búsqueda semántica (Qdrant) funciona
- [ ] Backups automáticos configurados
- [ ] Monitoring (Grafana) accessible via SSH tunnel
- [ ] Firewall del servidor configurado (solo permitir 8001, 22)
---
## 🔧 TROUBLESHOOTING
### Error: Redis authentication failed
```bash
# Verificar que REDIS_PASSWORD está en .env
grep REDIS_PASSWORD .env
# Verificar que los workers tienen la variable
docker exec rss2_web env | grep REDIS
# Reiniciar servicios
docker-compose restart
```
### Error: No puedo acceder a Grafana desde mi máquina
```bash
# Asegurarte de que el túnel SSH está activo
ssh -L 3001:localhost:3001 usuario@servidor
# Verificar que Grafana está corriendo
docker-compose ps | grep grafana
# Logs de Grafana
docker-compose logs grafana
```
### Workers no pueden conectarse a Qdrant
```bash
# Verificar que Qdrant está en la red backend
docker network inspect rss2_backend | grep qdrant
# Verificar logs de Qdrant
docker-compose logs qdrant
# Test de conectividad desde un worker
docker exec rss2_qdrant_worker curl http://qdrant:6333/collections
```
---
## 📚 RECURSOS ADICIONALES
- [Docker Networks Security Best Practices](https://docs.docker.com/network/network-tutorial-standalone/)
- [Redis Security](https://redis.io/docs/management/security/)
- [PostgreSQL Security](https://www.postgresql.org/docs/current/security.html)
- [OWASP Docker Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)
---
## 📝 NOTAS IMPORTANTES
1. **Backup Regular**: Configura backups automáticos ANTES de migrar
2. **Testing**: Prueba en un entorno de desarrollo primero
3. **Downtime**: Planifica una ventana de mantenimiento
4. **Monitoring**: Verifica que Grafana funciona después de migrar
5. **Documentation**: Documenta las contraseñas en un gestor seguro
---
**Última actualización**: 2026-01-12
**Autor**: Auditoría de Seguridad Automatizada

View file

@ -0,0 +1,202 @@
# 🎯 Resumen de Solución - Traducciones Repetitivas
## ✅ Problema Resuelto
### Estado Inicial
- **3,093 traducciones defectuosas** detectadas con patrones repetitivos
- Ejemplos: "la línea de la línea de la línea...", "de Internet de Internet..."
### Soluciones Implementadas
#### 1. ✅ Mejoras en Translation Worker
**Archivo**: `workers/translation_worker.py`
**Cambios aplicados:**
- ✅ `repetition_penalty`: 1.2 → **2.5** (penalización más agresiva)
- ✅ `no_repeat_ngram_size`: 4 → **3** (bloqueo de 3-gramas)
- ✅ Nueva función `_is_repetitive_output()` para validación post-traducción
- ✅ Rechazo automático de outputs repetitivos
**Código clave añadido:**
```python
# Validación automática
if _is_repetitive_output(ttr) or _is_repetitive_output(btr):
LOG.warning(f"Rejecting repetitive translation for tr_id={i['tr_id']}")
errors.append(("Repetitive output detected", i["tr_id"]))
continue
```
#### 2. ✅ Script de Limpieza Automática
**Archivo**: `scripts/clean_repetitive_translations.py`
**Funcionalidad:**
- Escanea todas las traducciones completadas
- Detecta patrones repetitivos mediante regex y análisis de diversidad
- Marca traducciones defectuosas como 'pending' para re-traducción
- Genera reportes detallados
**Uso:**
```bash
docker exec rss2_web python3 scripts/clean_repetitive_translations.py
```
#### 3. ✅ Script de Monitoreo
**Archivo**: `scripts/monitor_translation_quality.py`
**Funcionalidad:**
- Estadísticas en tiempo real de traducciones
- Detección de problemas de calidad
- Modo watch para monitoreo continuo
**Uso:**
```bash
# Reporte único
docker exec rss2_web python3 scripts/monitor_translation_quality.py --hours 24
# Monitoreo continuo
docker exec rss2_web python3 scripts/monitor_translation_quality.py --watch
```
#### 4. ✅ Limpieza de Base de Datos
**Ejecutado:**
```sql
UPDATE traducciones
SET status='pending',
titulo_trad=NULL,
resumen_trad=NULL,
error='Repetitive output - retranslating with improved settings'
WHERE status='done'
AND (resumen_trad LIKE '%la línea de la línea%'
OR resumen_trad LIKE '%de la la %'
OR resumen_trad LIKE '%de Internet de Internet%');
```
**Resultado:** 3,093 traducciones marcadas para re-traducción
#### 5. ✅ Workers Reiniciados
```bash
docker restart rss2_translator_py rss2_translator_py2 rss2_translator_py3
```
**Estado:** ✅ Todos los workers funcionando con nueva configuración
## 📊 Resultados Verificados
### Estado Actual de la Base de Datos
```
Total traducciones: 1,026,356
├─ Completadas (done): 1,022,466
├─ Pendientes: 3,713 (incluye las 3,093 marcadas)
└─ Errores: 49
```
### Verificación de Calidad (últimos 10 minutos)
```
Nuevas traducciones repetitivas: 0 ✅
```
## 🔍 Detección de Patrones Repetitivos
La función `_is_repetitive_output()` detecta:
1. **Palabras repetidas 4+ veces consecutivas**
- Regex: `(\b\w+\b)( \1){3,}`
2. **Frases de 2 palabras repetidas 3+ veces**
- Regex: `(\b\w+ \w+\b)( \1){2,}`
3. **Patrones específicos conocidos:**
- "de la la"
- "la línea de la línea"
- "de Internet de Internet"
- "de la de la"
- "en el en el"
4. **Baja diversidad de vocabulario**
- Threshold: < 25% palabras únicas
## 🚀 Próximos Pasos
### Automático (Ya en marcha)
- ✅ Re-traducción de 3,093 noticias con nueva configuración
- ✅ Validación automática de nuevas traducciones
- ✅ Rechazo inmediato de outputs repetitivos
### Manual (Recomendado)
1. **Monitorear logs del translation worker:**
```bash
docker logs -f rss2_translator_py | grep -E "(Rejecting|WARNING|repetitive)"
```
2. **Ejecutar limpieza periódica (semanal):**
```bash
docker exec rss2_web python3 scripts/clean_repetitive_translations.py
```
3. **Revisar calidad mensualmente:**
```bash
docker exec rss2_web python3 scripts/monitor_translation_quality.py --hours 720
```
## 📈 Métricas de Éxito
### Antes
- ❌ 3,093 traducciones repetitivas detectadas
- ❌ ~0.3% de tasa de error de calidad
- ❌ Sin validación automática
### Después
- ✅ 0 nuevas traducciones repetitivas (verificado)
- ✅ Validación automática en tiempo real
- ✅ Rechazo inmediato de outputs defectuosos
- ✅ Re-traducción automática programada
## 🛠️ Archivos Modificados/Creados
### Modificados
1. `workers/translation_worker.py` - Mejoras en parámetros y validación
### Creados
1. `scripts/clean_repetitive_translations.py` - Limpieza automática
2. `scripts/monitor_translation_quality.py` - Monitoreo de calidad
3. `docs/TRANSLATION_QUALITY_FIX.md` - Documentación completa
## 🎓 Lecciones Aprendidas
### ¿Por qué ocurrió?
1. **Repetition penalty insuficiente** (1.2 era muy bajo)
2. **N-gram blocking inadecuado** (4-gramas permitían repeticiones de 3 palabras)
3. **Sin validación post-traducción**
4. **Textos fuente corruptos** de algunos RSS feeds
### Prevención a futuro
1. ✅ Validación automática implementada
2. ✅ Parámetros optimizados
3. ✅ Scripts de monitoreo disponibles
4. ✅ Documentación completa
## 📞 Soporte
Si detectas nuevas traducciones repetitivas:
1. **Verificar logs:**
```bash
docker logs rss2_translator_py | tail -100
```
2. **Ejecutar limpieza:**
```bash
docker exec rss2_web python3 scripts/clean_repetitive_translations.py
```
3. **Reiniciar workers si es necesario:**
```bash
docker restart rss2_translator_py rss2_translator_py2 rss2_translator_py3
```
---
**Implementado por:** Antigravity AI
**Fecha:** 2026-01-28
**Estado:** ✅ Completado y Verificado
**Impacto:** 3,093 traducciones mejoradas, 0% nuevos errores