feat: prerequisites, POC local y README reescrito

deploy/debian/prerequisites.sh:
- Instalador de dependencias del sistema para Debian/Ubuntu
- Detecta OS, instala PostgreSQL 16 (repo oficial), Redis, nginx,
  Go 1.25, Node.js 20 LTS, Qdrant binario, Python venv
- Crea usuario rss2 y estructura /opt/rss2
- Pregunta interactivamente si instalar modelos ML pesados
  (ctranslate2, transformers, spaCy es_core_news_lg, NLLB-200)
- Separado de install.sh para poder ejecutarlo independientemente

poc/poc.sh:
- POC local en ~2 minutos sin Docker, sin workers ML
- Crea BD temporal coconews_poc con schema completo
- Carga 10 noticias de muestra en español listas para ver
- Compila backend Go y frontend React en /tmp/coconews-poc
- Lanza Redis en puerto alternativo (6380) sin interferir
- Sirve frontend con npx serve en http://127.0.0.1:18001
- Limpieza automatica al Ctrl+C

poc/seed.sql:
- 10 noticias de muestra en español (no requieren traduccion)
- Categorias, continentes y paises basicos
- 5 feeds de ejemplo (El Pais, BBC Mundo, etc.)

README.md:
- Reescrito completamente sin referencias Docker
- Diagrama ASCII de arquitectura
- Inicio rapido con poc.sh (2 minutos)
- Instrucciones de install en Debian con prerequisites.sh
- Tabla de requisitos hardware por modo
- Mapa completo del repositorio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SITO 2026-03-30 21:17:11 +02:00
parent ab3b0b53c5
commit e3c682a36f
4 changed files with 766 additions and 101 deletions

255
README.md
View file

@ -1,124 +1,177 @@
# RSS2 - AI-Powered News Intelligence Platform
# COCONEWS
RSS2 es una plataforma avanzada de agregación, traducción, análisis y vectorización de noticias, diseñada para transformar flujos masivos de información en inteligencia accionable. Utiliza una arquitectura híbrida de microservicios (Go + Python) integrada con modelos de inteligencia artificial de última generación para ofrecer búsqueda semántica, clasificación inteligente y automatización de contenidos.
Plataforma de inteligencia de noticias. Agrega feeds RSS de cualquier idioma, los traduce automáticamente al español, extrae entidades, los agrupa por eventos y los hace buscables semánticamente.
---
## 🚀 Capacidades Principales
## Qué hace
* **Enriquecimiento con Wikipedia**: Sistema automatizado que detecta personas y organizaciones, descarga sus biografías e imágenes oficiales de Wikipedia para mostrarlas en tooltips interactivos con avatares circulares.
* **Categorización Inteligente (LLM)**: Clasificación de noticias mediante una instancia local de Mistral-7B / Llama-3 (vía Ollama), procesando contenido en tiempo real.
* **Búsqueda Semántica**: Motor vectorial Qdrant para descubrir noticias por contexto y significado, yendo más allá de las palabras clave tradicionales.
* **Traducción Neuronal de Alta Calidad**: Integración de NLLB-200 (vía CTranslate2) para traducir noticias de múltiples idiomas al español con precisión profesional.
* **Inteligencia de Entidades (NER)**: Extracción y normalización automática de Personas, Organizaciones y Lugares para análisis de tendencias y mapeo de relaciones.
* **Búsqueda de Noticias Relacionadas**: Algoritmos de similitud que agrupan noticias sobre el mismo tema automáticamente.
- **Ingesta** feeds RSS/Atom de cualquier idioma de forma continua
- **Traduce** al español con NLLB-200 (200 idiomas soportados)
- **Extrae entidades** (personas, organizaciones, lugares) con spaCy y las enriquece con Wikipedia
- **Genera embeddings** para búsqueda por significado, no solo por palabras clave
- **Agrupa noticias** del mismo evento automáticamente
- **Categoriza** contenido con reglas y modelos de lenguaje
- Interfaz web React con búsqueda semántica, filtros y tooltips de Wikipedia
---
## 🏗️ Arquitectura de Servicios (Docker)
## Arquitectura
El sistema se orquestra mediante Docker Compose y se divide en capas especializadas:
### Capa de Acceso y API
| Servicio | Tecnología | Descripción |
|---------|------------|-------------|
| **`nginx`** | Nginx Alpine | Gateway y Proxy Inverso (Puerto **8001**). |
| **`rss2_frontend`** | React + Vite | Interfaz web de usuario moderna y responsiva. |
| **`backend-go`** | Go + Gin | API REST principal y gestión de lógica de negocio. |
### Ingesta y Descubrimiento (Go)
| Servicio | Tecnología | Descripción |
|---------|------------|-------------|
| **`rss-ingestor-go`** | Go | Crawler de alto rendimiento para feeds RSS. |
| **`scraper`** | Go | Scraper profundo con sanitización de HTML y extracción de texto. |
| **`discovery`** | Go | Agente autónomo para descubrir nuevos feeds a partir de URLs. |
### Procesamiento de Datos e IA (Go & Python)
| Servicio | Tecnología | Descripción |
|---------|------------|-------------|
| **`translator`** | NLLB-200 (CPU) | Traducción neuronal optimizada con CTranslate2. |
| **`translator-gpu`**| NLLB-200 (GPU) | Traducción acelerada por hardware (CUDA). |
| **`wiki-worker`** | Go | **[NUEVO]** Integración con Wikipedia y gestión de imágenes locales. |
| **`embeddings`** | S-Transformers | Generación de vectores para búsqueda semántica. |
| **`ner`** | Spacy / BERT | Reconocimiento de entidades nombradas (NER). |
| **`llm-categorizer`**| Ollama / Mistral | Clasificación avanzada mediante modelos de lenguaje. |
| **`topics`** | Go | Matcher automático de países y temas predefinidos. |
| **`related`** | Go | Motor de detección de noticias relacionadas. |
### Capa de Almacenamiento
| Servicio | Tecnología | Descripción |
|---------|------------|-------------|
| **`db`** | PostgreSQL 18 | Base de datos relacional principal. |
| **`qdrant`** | Qdrant | Base de datos vectorial para búsqueda por similitud. |
| **`redis`** | Redis 7 | Colas de mensajes y caché de alto desempeño. |
---
## ⚙️ Guía de Configuración
### 1. Requisitos de Hardware
* **Modo Básico (CPU)**: 4+ Cores CPU, 8GB RAM.
* **Modo Avanzado (IA)**: NVIDIA GPU con 8GB+ VRAM (mínimo recomendado para LLM y Traducción GPU).
### 2. Instalación Rápida
```bash
git clone <repo_url>
cd rss2
cp .env.example .env
# Edita .env con tus credenciales
docker compose up -d
```
Internet (RSS/Atom)
rss-ingestor-go ──→ PostgreSQL ──→ langdetect
│ │ │
scraper/discovery │ translator (NLLB-200)
│ │
│ embeddings (MiniLM)
│ │
│ ner (spaCy)
│ │
│ cluster / related
│ │
│ qdrant-worker ──→ Qdrant
backend-go (API REST :8080)
nginx (:8001)
Frontend React
```
### 3. Escalado de Workers (¡Importante!)
Para aumentar la velocidad de procesamiento (especialmente la traducción), puedes escalar los workers:
**Stack:**
- Go 1.25 — API REST (Gin), ingestor RSS, scraper, workers
- Python 3 — Workers ML (NLLB-200, MiniLM, spaCy, CTranslate2)
- PostgreSQL 16 — datos relacionales + full-text search
- Qdrant — búsqueda vectorial semántica
- Redis 7 — caché de consultas
- React 18 + TypeScript + Tailwind — frontend
- nginx — proxy inverso + archivos estáticos
---
## Inicio rápido (POC local)
Prueba COCONEWS en tu máquina en ~2 minutos con datos de muestra, sin instalar los modelos ML.
**Requisitos mínimos para el POC:**
- Go 1.25+
- Node.js 18+
- PostgreSQL (corriendo)
- Redis (corriendo)
```bash
# Ejecutar 4 traductores en paralelo
docker compose up -d --scale translator=4
git clone https://gitea.laenre.net/pietre/rss2.git coconews
cd coconews
git checkout coconews
# Si usas GPU y tienes capacidad
docker compose up -d --scale translator-gpu=2
bash poc/poc.sh
```
Abre `http://127.0.0.1:18001` en el navegador.
El primer usuario que se registre será administrador.
---
## Instalación en servidor Debian
Para un despliegue completo con todos los workers ML en producción:
### 1. Instalar prerequisites
```bash
sudo bash deploy/debian/prerequisites.sh
```
Instala: PostgreSQL 16, Redis, nginx, Go 1.25, Node.js 20, Qdrant, Python 3 venv.
Pregunta si instalar los modelos ML pesados ahora o después.
### 2. Configurar entorno
```bash
cp deploy/debian/env.example /opt/rss2/.env
nano /opt/rss2/.env # edita contraseñas y SECRET_KEY
```
### 3. Instalar y arrancar
```bash
sudo bash deploy/debian/install.sh
```
Compila los binarios Go, el frontend React, crea los servicios systemd y arranca todo.
### Acceder
```
http://IP_DEL_SERVIDOR:8001
```
Guía completa: [DEPLOY_DEBIAN.md](DEPLOY_DEBIAN.md)
---
## Gestión de servicios
```bash
# Estado general
systemctl status rss2-backend rss2-ingestor rss2-translator
# Logs en tiempo real
journalctl -u rss2-backend -f
journalctl -u rss2-translator -f
# Reiniciar tras actualizar código
git pull
sudo bash deploy/debian/build.sh
```
---
## 🛡️ Administración y Mantenimiento
## Actualizar el código
### Copias de Seguridad (Backups)
Desde el panel de Administración (`/admin/settings`), puedes realizar:
* **Backup Completo**: Volcado SQL de toda la base de datos.
* **Backup de Noticias (ZIP)**: **[NUEVO]** Genera un archivo comprimido que incluye las tablas de noticias, traducciones y todas sus etiquetas. Ideal para migraciones de contenido.
### Variables de Entorno Clave (`.env`)
| Variable | Descripción |
|----------|-------------|
| `WIKI_SLEEP` | Tiempo de espera entre peticiones a Wikipedia (evita bloqueos). |
| `SCHEDULER_BATCH`| Cantidad de noticias a enviar a traducir por ciclo. |
| `TARGET_LANGS` | Idiomas destino (ej: `es`). |
| `OLLAMA_HOST` | Dirección del servidor Ollama para categorización. |
---
## 📖 Documentación de la API (Campos Wikipedia)
Las respuestas de noticias ahora incluyen el objeto `entities` enriquecido:
```json
{
"id": 67449,
"titulo": "...",
"entities": [
{
"valor": "Apple",
"tipo": "organizacion",
"wiki_summary": "Apple Inc. es una empresa estadounidense...",
"wiki_url": "https://es.wikipedia.org/wiki/Apple",
"image_path": "/api/wiki-images/wiki_5723.png"
}
]
}
```bash
cd /ruta/al/repo
git pull
sudo bash deploy/debian/build.sh
```
`build.sh` recompila los binarios Go, el frontend y sincroniza los workers Python, y reinicia los servicios automáticamente.
---
**RSS2** - *Transformando noticias en inteligencia con IA localizada.*
## Requisitos de hardware
| Modo | CPU | RAM | Disco |
|------|-----|-----|-------|
| POC local | 2 cores | 4 GB | 10 GB |
| Producción CPU | 4+ cores | 8 GB | 40 GB |
| Producción recomendado | 8 cores | 16 GB | 80 GB |
---
## Estructura del repositorio
```
├── backend/ Go — API REST + workers (scraper, discovery, wiki, topics, related, qdrant)
├── rss-ingestor-go/ Go — Ingestor de feeds RSS
├── frontend/ React + TypeScript + Tailwind
├── workers/ Python — ML workers (traducción, embeddings, NER, cluster, categorización)
├── init-db/ SQL — Schema y datos iniciales
├── migrations/ SQL — Migraciones incrementales
├── deploy/debian/ Scripts de despliegue para Debian sin Docker
│ ├── prerequisites.sh Instala todas las dependencias del sistema
│ ├── install.sh Instalación completa
│ ├── build.sh Recompila y reinicia tras actualizar código
│ ├── env.example Plantilla de variables de entorno
│ ├── nginx.conf Configuración nginx para despliegue nativo
│ └── systemd/ Ficheros de servicio systemd (16 servicios)
├── poc/
│ ├── poc.sh POC local con datos de prueba (sin Docker, sin ML)
│ └── seed.sql Datos de muestra para el POC
├── feeds.csv Feeds RSS precargados para importar desde el admin
├── entity_config.json Aliases y blacklist para normalización de entidades NER
└── DEPLOY_DEBIAN.md Guía detallada de despliegue en Debian
```

305
deploy/debian/prerequisites.sh Executable file
View file

@ -0,0 +1,305 @@
#!/usr/bin/env bash
# =============================================================================
# COCONEWS - Instalacion de prerequisites en Debian 12 / Ubuntu 22.04+
# Ejecutar ANTES de install.sh
# Uso: sudo bash prerequisites.sh
# =============================================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info() { echo -e "${GREEN}[OK]${NC} $*"; }
step() { echo -e "${BLUE}[-->]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
[[ "$EUID" -ne 0 ]] && error "Ejecutar como root: sudo bash prerequisites.sh"
# Detectar OS
if [[ -f /etc/os-release ]]; then
. /etc/os-release
OS_ID="$ID"
OS_VER="$VERSION_ID"
else
error "No se puede detectar el sistema operativo"
fi
[[ "$OS_ID" == "debian" || "$OS_ID" == "ubuntu" ]] || \
error "Solo soportado en Debian/Ubuntu. Detectado: $OS_ID"
echo ""
echo "================================================="
echo " COCONEWS - Instalador de Prerequisites"
echo " OS: $PRETTY_NAME"
echo "================================================="
echo ""
# =============================================================================
# 1. PAQUETES APT BASE
# =============================================================================
step "Actualizando repositorios apt..."
apt-get update -qq
step "Instalando paquetes del sistema..."
apt-get install -y --no-install-recommends \
curl wget git build-essential \
ca-certificates gnupg lsb-release \
software-properties-common apt-transport-https \
tzdata locales \
rsync \
openssl \
libpq-dev \
libssl-dev \
libffi-dev \
libbz2-dev \
libreadline-dev \
libsqlite3-dev \
zlib1g-dev
info "Paquetes base instalados"
# =============================================================================
# 2. POSTGRESQL 16
# =============================================================================
step "Instalando PostgreSQL 16..."
if ! command -v psql &>/dev/null; then
# Repositorio oficial de PostgreSQL
install -d /usr/share/postgresql-common/pgdg
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| gpg --dearmor -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg] \
https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list
apt-get update -qq
apt-get install -y postgresql-16 postgresql-client-16
fi
info "PostgreSQL: $(psql --version)"
# =============================================================================
# 3. REDIS
# =============================================================================
step "Instalando Redis..."
if ! command -v redis-server &>/dev/null; then
apt-get install -y redis-server
fi
info "Redis: $(redis-server --version | cut -d' ' -f3)"
# =============================================================================
# 4. NGINX
# =============================================================================
step "Instalando Nginx..."
if ! command -v nginx &>/dev/null; then
apt-get install -y nginx
fi
info "Nginx: $(nginx -v 2>&1 | cut -d'/' -f2)"
# =============================================================================
# 5. PYTHON 3 + pip + venv
# =============================================================================
step "Instalando Python 3..."
apt-get install -y python3 python3-pip python3-venv python3-dev
info "Python: $(python3 --version)"
# =============================================================================
# 6. NODE.JS 20 LTS
# =============================================================================
step "Instalando Node.js 20 LTS..."
if ! command -v node &>/dev/null || [[ "$(node -v | tr -d 'v' | cut -d. -f1)" -lt 18 ]]; then
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
fi
info "Node.js: $(node --version) | npm: $(npm --version)"
# =============================================================================
# 7. GO 1.25
# =============================================================================
step "Instalando Go 1.25..."
GO_VERSION="1.25.0"
INSTALLED_GO=$(go version 2>/dev/null | awk '{print $3}' | tr -d 'go' || echo "0")
# Comparar version instalada
needs_go=false
if ! command -v go &>/dev/null; then
needs_go=true
else
IFS='.' read -ra INS <<< "$INSTALLED_GO"
IFS='.' read -ra REQ <<< "$GO_VERSION"
if [[ "${INS[0]}" -lt "${REQ[0]}" ]] || \
([[ "${INS[0]}" == "${REQ[0]}" ]] && [[ "${INS[1]:-0}" -lt "${REQ[1]:-0}" ]]); then
needs_go=true
fi
fi
if [[ "$needs_go" == "true" ]]; then
ARCH=$(dpkg --print-architecture)
case "$ARCH" in
amd64) GO_ARCH="amd64" ;;
arm64) GO_ARCH="arm64" ;;
*) error "Arquitectura no soportada para Go: $ARCH" ;;
esac
step " Descargando Go ${GO_VERSION} (${GO_ARCH})..."
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" -o /tmp/go.tar.gz
rm -rf /usr/local/go
tar -C /usr/local -xzf /tmp/go.tar.gz
rm /tmp/go.tar.gz
# Perfil global
cat > /etc/profile.d/golang.sh << 'GOEOF'
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
GOEOF
chmod +x /etc/profile.d/golang.sh
export PATH=$PATH:/usr/local/go/bin
info "Go instalado: $(go version)"
else
export PATH=$PATH:/usr/local/go/bin
info "Go ya instalado: $(go version)"
fi
# =============================================================================
# 8. QDRANT (binario oficial)
# =============================================================================
step "Instalando Qdrant..."
QDRANT_VERSION="v1.12.1"
QDRANT_INSTALL_DIR="/opt/rss2/qdrant"
mkdir -p "$QDRANT_INSTALL_DIR"
if [[ ! -f "$QDRANT_INSTALL_DIR/qdrant" ]]; then
ARCH=$(dpkg --print-architecture)
case "$ARCH" in
amd64) QDRANT_ARCH="x86_64-unknown-linux-musl" ;;
arm64) QDRANT_ARCH="aarch64-unknown-linux-musl" ;;
*) error "Arquitectura no soportada para Qdrant: $ARCH" ;;
esac
step " Descargando Qdrant ${QDRANT_VERSION}..."
curl -fsSL \
"https://github.com/qdrant/qdrant/releases/download/${QDRANT_VERSION}/qdrant-${QDRANT_ARCH}.tar.gz" \
-o /tmp/qdrant.tar.gz
tar -C "$QDRANT_INSTALL_DIR" -xzf /tmp/qdrant.tar.gz
chmod +x "$QDRANT_INSTALL_DIR/qdrant"
rm /tmp/qdrant.tar.gz
info "Qdrant ${QDRANT_VERSION} instalado en ${QDRANT_INSTALL_DIR}"
else
info "Qdrant ya instalado en ${QDRANT_INSTALL_DIR}"
fi
# =============================================================================
# 9. USUARIO DEL SISTEMA rss2
# =============================================================================
step "Creando usuario del sistema 'rss2'..."
if ! id rss2 &>/dev/null; then
useradd -r -m -d /opt/rss2 -s /bin/bash rss2
info "Usuario 'rss2' creado"
else
info "Usuario 'rss2' ya existe"
fi
# Crear estructura de directorios
mkdir -p \
/opt/rss2/bin \
/opt/rss2/src \
/opt/rss2/data/wiki_images \
/opt/rss2/data/qdrant_storage \
/opt/rss2/hf_cache \
/opt/rss2/models \
/opt/rss2/frontend/dist \
/opt/rss2/logs \
/opt/rss2/backups
chown -R rss2:rss2 /opt/rss2
info "Directorios /opt/rss2 creados"
# =============================================================================
# 10. PYTHON VIRTUALENV + DEPENDENCIAS BASE
# =============================================================================
step "Creando virtualenv Python en /opt/rss2/venv..."
if [[ ! -d /opt/rss2/venv ]]; then
python3 -m venv /opt/rss2/venv
fi
/opt/rss2/venv/bin/pip install --upgrade pip setuptools wheel -q
info "Virtualenv listo"
# Dependencias base (sin los modelos pesados de ML)
step "Instalando dependencias Python base..."
/opt/rss2/venv/bin/pip install -q \
psycopg2-binary \
langdetect \
python-dotenv \
requests \
beautifulsoup4 \
lxml \
redis \
qdrant-client \
numpy \
scikit-learn \
tqdm
info "Dependencias Python base instaladas"
# =============================================================================
# 11. DEPENDENCIAS ML (pesadas - opcional en este paso)
# =============================================================================
echo ""
echo -e "${YELLOW}[?]${NC} Instalar dependencias ML pesadas ahora?"
echo " (ctranslate2, transformers, sentence-transformers, spaCy)"
echo " Puede tardar 20-40 minutos y usar ~5 GB de disco."
echo -n " [s/N]: "
read -r install_ml
if [[ "${install_ml,,}" == "s" || "${install_ml,,}" == "si" || "${install_ml,,}" == "y" ]]; then
step "Instalando dependencias ML (esto tarda)..."
/opt/rss2/venv/bin/pip install -q \
ctranslate2>=4.0.0 \
transformers==4.43.3 \
sentencepiece \
sacremoses \
accelerate \
sentence-transformers==3.0.1 \
"spacy>=3.7,<4.0" \
torch --index-url https://download.pytorch.org/whl/cpu
info "Dependencias ML instaladas"
step "Descargando modelo spaCy en español..."
/opt/rss2/venv/bin/python -m spacy download es_core_news_lg
info "Modelo spaCy es_core_news_lg listo"
step "Convirtiendo modelo NLLB-200 a CTranslate2..."
warn "Esto puede tardar 10-30 minutos y requiere ~2 GB de RAM"
mkdir -p /opt/rss2/models
/opt/rss2/venv/bin/python - <<'EOF'
import os, sys
os.makedirs("/opt/rss2/models/nllb-ct2", exist_ok=True)
os.environ["HF_HOME"] = "/opt/rss2/hf_cache"
try:
from ctranslate2.converters import OpusMTConverter
converter = OpusMTConverter("facebook/nllb-200-distilled-600M")
converter.convert("/opt/rss2/models/nllb-ct2", quantization="int8", force=True)
print("[OK] Modelo NLLB-200 convertido en /opt/rss2/models/nllb-ct2")
except Exception as e:
print(f"[ERROR] {e}")
print("Convierte manualmente despues con: deploy/debian/convert_model.sh")
sys.exit(0)
EOF
chown -R rss2:rss2 /opt/rss2/models /opt/rss2/hf_cache
else
warn "ML omitido. Ejecuta 'deploy/debian/install.sh' para instalarlas junto con el resto."
warn "Sin ML: la traduccion y los embeddings no funcionaran."
fi
chown -R rss2:rss2 /opt/rss2
# =============================================================================
# RESUMEN
# =============================================================================
echo ""
echo "================================================="
echo -e " ${GREEN}Prerequisites instalados correctamente${NC}"
echo "================================================="
echo ""
echo " Sistema: $PRETTY_NAME"
echo " Go: $(go version 2>/dev/null | awk '{print $3}')"
echo " Python: $(python3 --version)"
echo " Node.js: $(node --version)"
echo " PostgreSQL: $(psql --version | awk '{print $3}')"
echo " Redis: $(redis-server --version | awk '{print $3}' | tr -d ',')"
echo " Nginx: $(nginx -v 2>&1 | cut -d'/' -f2)"
echo ""
echo " Siguiente paso:"
echo " sudo bash deploy/debian/install.sh"
echo ""

213
poc/poc.sh Executable file
View file

@ -0,0 +1,213 @@
#!/usr/bin/env bash
# =============================================================================
# COCONEWS - POC local (sin Docker, sin ML workers)
# Levanta backend + frontend con datos de prueba en ~2 minutos
#
# Requisitos mínimos: Go 1.25, Node.js 18+, PostgreSQL, Redis
# Uso: bash poc/poc.sh
# =============================================================================
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
POC_DIR="$REPO_ROOT/poc"
TMP_DIR="/tmp/coconews-poc"
PID_FILE="$TMP_DIR/pids"
export PATH=$PATH:/usr/local/go/bin
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $*"; }
step() { echo -e "${BLUE}[→]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
error() { echo -e "${RED}[✗]${NC} $*"; exit 1; }
# Manejar Ctrl+C: para todos los procesos del POC
cleanup() {
echo ""
warn "Deteniendo POC..."
if [[ -f "$PID_FILE" ]]; then
while IFS= read -r pid; do
kill "$pid" 2>/dev/null || true
done < "$PID_FILE"
rm -f "$PID_FILE"
fi
echo -e "${YELLOW}POC detenido.${NC}"
exit 0
}
trap cleanup INT TERM
mkdir -p "$TMP_DIR"
> "$PID_FILE"
echo ""
echo -e "${BOLD}=================================================${NC}"
echo -e "${BOLD} COCONEWS · POC Local${NC}"
echo -e "${BOLD}=================================================${NC}"
echo ""
# =============================================================================
# 1. VERIFICAR PREREQUISITOS
# =============================================================================
step "Verificando prerequisitos..."
command -v go &>/dev/null || error "Go no encontrado. Instala Go 1.25+."
command -v psql &>/dev/null || error "PostgreSQL no encontrado. Instala postgresql."
command -v redis-cli &>/dev/null || error "Redis no encontrado. Instala redis-server."
command -v node &>/dev/null || error "Node.js no encontrado. Instala Node.js 18+."
info "Prerequisitos OK (Go: $(go version | awk '{print $3}'), Node: $(node -v))"
# =============================================================================
# 2. CONFIGURACION POC (sin contrasenas fuertes, solo local)
# =============================================================================
POC_DB_NAME="coconews_poc"
POC_DB_USER="coconews_poc"
POC_DB_PASS="poc_password_local"
POC_REDIS_PORT="6380" # Puerto alternativo para no interferir con Redis principal
POC_API_PORT="18080"
POC_FRONTEND_PORT="18001"
export DATABASE_URL="postgres://${POC_DB_USER}:${POC_DB_PASS}@127.0.0.1:5432/${POC_DB_NAME}?sslmode=disable"
export REDIS_URL="redis://127.0.0.1:${POC_REDIS_PORT}"
export SECRET_KEY="poc_secret_key_solo_para_desarrollo_local"
export SERVER_PORT="$POC_API_PORT"
export GIN_MODE="release"
# =============================================================================
# 3. POSTGRESQL - crear DB de prueba
# =============================================================================
step "Preparando base de datos POC (${POC_DB_NAME})..."
# Verificar que PostgreSQL está corriendo
pg_isready -q || {
warn "PostgreSQL no está corriendo. Intentando iniciar..."
sudo systemctl start postgresql 2>/dev/null || \
sudo service postgresql start 2>/dev/null || \
error "No se puede iniciar PostgreSQL. Inícialo manualmente."
}
# Crear usuario y BD de prueba
sudo -u postgres psql -q -tc "SELECT 1 FROM pg_roles WHERE rolname='${POC_DB_USER}'" \
| grep -q 1 || \
sudo -u postgres psql -q -c "CREATE USER ${POC_DB_USER} WITH PASSWORD '${POC_DB_PASS}';" 2>/dev/null
sudo -u postgres psql -q -tc "SELECT 1 FROM pg_database WHERE datname='${POC_DB_NAME}'" \
| grep -q 1 || \
sudo -u postgres createdb -O "${POC_DB_USER}" "${POC_DB_NAME}" 2>/dev/null
# Aplicar schema completo
sudo -u postgres psql -q -d "${POC_DB_NAME}" \
-f "$REPO_ROOT/init-db/00-complete-schema.sql" 2>/dev/null || true
# Otorgar permisos al usuario POC
sudo -u postgres psql -q -d "${POC_DB_NAME}" \
-c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${POC_DB_USER};
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${POC_DB_USER};" \
2>/dev/null || true
# Cargar datos de muestra
sudo -u postgres psql -q -d "${POC_DB_NAME}" -f "$POC_DIR/seed.sql" 2>/dev/null || true
NEWS_COUNT=$(sudo -u postgres psql -tq -d "${POC_DB_NAME}" -c "SELECT COUNT(*) FROM noticias;" 2>/dev/null | tr -d ' ')
info "BD lista: ${NEWS_COUNT} noticias de prueba cargadas"
# =============================================================================
# 4. REDIS (instancia temporal en puerto alternativo)
# =============================================================================
step "Iniciando Redis en puerto ${POC_REDIS_PORT}..."
redis-server --port "$POC_REDIS_PORT" --daemonize yes \
--logfile "$TMP_DIR/redis-poc.log" \
--pidfile "$TMP_DIR/redis-poc.pid" \
--maxmemory 128mb --maxmemory-policy allkeys-lru \
2>/dev/null || warn "Redis ya corriendo en ${POC_REDIS_PORT}"
sleep 1
redis-cli -p "$POC_REDIS_PORT" ping &>/dev/null && info "Redis OK (puerto ${POC_REDIS_PORT})"
cat "$TMP_DIR/redis-poc.pid" 2>/dev/null >> "$PID_FILE" || true
# =============================================================================
# 5. COMPILAR BACKEND GO
# =============================================================================
step "Compilando backend Go..."
mkdir -p "$TMP_DIR/bin"
(cd "$REPO_ROOT/backend" && \
CGO_ENABLED=0 go build -buildvcs=false -o "$TMP_DIR/bin/server" ./cmd/server \
2>"$TMP_DIR/build-backend.log") || {
cat "$TMP_DIR/build-backend.log"
error "Fallo al compilar backend. Ver log arriba."
}
info "Backend compilado OK"
# =============================================================================
# 6. ARRANCAR BACKEND API
# =============================================================================
step "Arrancando API en puerto ${POC_API_PORT}..."
"$TMP_DIR/bin/server" > "$TMP_DIR/backend.log" 2>&1 &
BACKEND_PID=$!
echo "$BACKEND_PID" >> "$PID_FILE"
# Esperar a que el backend responda
for i in {1..15}; do
sleep 1
if curl -sf "http://127.0.0.1:${POC_API_PORT}/api/stats" &>/dev/null; then
info "API respondiendo en http://127.0.0.1:${POC_API_PORT}"
break
fi
if [[ $i -eq 15 ]]; then
cat "$TMP_DIR/backend.log"
error "El backend no responde. Ver log arriba."
fi
done
# =============================================================================
# 7. FRONTEND REACT
# =============================================================================
step "Preparando frontend..."
cd "$REPO_ROOT/frontend"
if [[ ! -d node_modules ]]; then
step " Instalando dependencias npm (primera vez)..."
npm install --silent
fi
# Compilar apuntando al API local del POC
VITE_API_URL="http://127.0.0.1:${POC_API_PORT}" \
npm run build -- --outDir "$TMP_DIR/frontend-dist" 2>"$TMP_DIR/build-frontend.log" || {
cat "$TMP_DIR/build-frontend.log"
error "Fallo al compilar frontend. Ver log arriba."
}
info "Frontend compilado OK"
# Servir frontend con npx serve (simple, sin nginx)
step "Sirviendo frontend en puerto ${POC_FRONTEND_PORT}..."
npx --yes serve "$TMP_DIR/frontend-dist" -l "$POC_FRONTEND_PORT" \
> "$TMP_DIR/frontend.log" 2>&1 &
FRONTEND_PID=$!
echo "$FRONTEND_PID" >> "$PID_FILE"
sleep 2
cd "$REPO_ROOT"
# =============================================================================
# LISTO
# =============================================================================
echo ""
echo -e "${BOLD}${GREEN}=================================================${NC}"
echo -e "${BOLD}${GREEN} COCONEWS POC corriendo${NC}"
echo -e "${BOLD}${GREEN}=================================================${NC}"
echo ""
echo -e " ${BOLD}Frontend:${NC} http://127.0.0.1:${POC_FRONTEND_PORT}"
echo -e " ${BOLD}API:${NC} http://127.0.0.1:${POC_API_PORT}/api/stats"
echo ""
echo -e " ${BOLD}Login:${NC} Registra el primer usuario en la UI"
echo -e " (será admin automáticamente)"
echo ""
echo -e " ${BOLD}Noticias:${NC} ${NEWS_COUNT} artículos de prueba en español"
echo -e " ${YELLOW}Nota:${NC} Sin workers ML activos."
echo -e " Noticias no tendrán traducción ni entidades."
echo ""
echo -e " ${BLUE}Logs:${NC} $TMP_DIR/*.log"
echo ""
echo -e " Pulsa ${BOLD}Ctrl+C${NC} para detener el POC."
echo ""
# Mantener el script corriendo
wait

94
poc/seed.sql Normal file
View file

@ -0,0 +1,94 @@
-- =============================================================================
-- COCONEWS POC - Datos de prueba mínimos
-- Carga rápida para ver la interfaz funcionando sin workers ML
-- =============================================================================
-- Taxonomía base
INSERT INTO continentes (id, nombre) VALUES
(1, 'África'), (2, 'América'), (3, 'Asia'),
(4, 'Europa'), (5, 'Oceanía')
ON CONFLICT (id) DO NOTHING;
INSERT INTO categorias (nombre) VALUES
('Ciencia'), ('Cultura'), ('Deportes'), ('Economía'),
('Internacional'), ('Política'), ('Salud'), ('Tecnología'), ('Sociedad')
ON CONFLICT DO NOTHING;
INSERT INTO paises (nombre, continente_id) VALUES
('España', 4),
('Argentina', 2),
('México', 2),
('Francia', 4),
('Estados Unidos', 2)
ON CONFLICT DO NOTHING;
-- Config básica
INSERT INTO config (key, value) VALUES
('translator_type', 'cpu'),
('translator_workers', '1'),
('translator_status', 'stopped')
ON CONFLICT (key) DO NOTHING;
-- Feeds de muestra (en español, no necesitan traducción)
INSERT INTO feeds (nombre, descripcion, url, idioma, activo, fallos)
VALUES
('El País', 'Noticias de España y el mundo', 'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/portada', 'es', true, 0),
('El Mundo', 'Diario de información general', 'https://e00-elmundo.uecdn.es/elmundo/rss/portada.xml', 'es', true, 0),
('La Vanguardia','Noticias de España y Cataluña', 'https://www.lavanguardia.com/mvc/feed/rss/home', 'es', true, 0),
('BBC Mundo', 'Noticias en español de la BBC', 'https://feeds.bbci.co.uk/mundo/rss.xml', 'es', true, 0),
('RT Español', 'Russia Today en español', 'https://actualidad.rt.com/rss', 'es', true, 0)
ON CONFLICT (url) DO NOTHING;
-- Noticias de muestra (en español, listas para mostrarse sin traducción)
INSERT INTO noticias (id, titulo, resumen, url, fecha, fuente_nombre, categoria_id, lang, topics_processed)
VALUES
(md5('poc-001'), 'La inteligencia artificial transforma el mercado laboral global',
'Los modelos de lenguaje de gran escala están redefiniendo sectores enteros de la economía, desde el servicio al cliente hasta el desarrollo de software. Empresas de todo el mundo aceleran su adopción mientras sindicatos y gobiernos debaten marcos regulatorios.',
'https://example.com/ia-mercado-laboral', NOW() - INTERVAL '2 hours',
'El País', 8, 'es', false),
(md5('poc-002'), 'Cumbre climática de la ONU aprueba fondo de 100.000 millones para países vulnerables',
'Los representantes de 196 países alcanzaron un acuerdo histórico en la última jornada de negociaciones. El fondo estará operativo en 2026 y priorizará adaptación en África subsahariana y pequeñas islas del Pacífico.',
'https://example.com/cumbre-climatica-onu', NOW() - INTERVAL '4 hours',
'BBC Mundo', 5, 'es', false),
(md5('poc-003'), 'España registra el mayor crecimiento económico de la eurozona en el primer trimestre',
'El PIB español creció un 3,2% interanual en los primeros tres meses del año, impulsado por el turismo, las exportaciones y el consumo interno. El Banco de España revisa al alza sus previsiones para el conjunto del ejercicio.',
'https://example.com/economia-espana-pib', NOW() - INTERVAL '5 hours',
'El Mundo', 4, 'es', false),
(md5('poc-004'), 'La selección española de fútbol golea en la fase de clasificación',
'La Roja aplastó por 4-0 al combinado rival con dos goles de Yamal y otros tantos de Morata en un partido que dejó pocas dudas sobre el potencial del equipo de Luis de la Fuente de cara al próximo gran torneo.',
'https://example.com/seleccion-espana-futbol', NOW() - INTERVAL '6 hours',
'La Vanguardia', 3, 'es', false),
(md5('poc-005'), 'Descubrimiento arqueológico en Extremadura revela ciudad romana inédita',
'Un equipo de la Universidad de Extremadura ha localizado los restos de un asentamiento romano del siglo II d.C. con teatro, termas y foro en perfecto estado de conservación bajo un olivar de la comarca de La Serena.',
'https://example.com/arqueologia-extremadura', NOW() - INTERVAL '8 hours',
'El País', 2, 'es', false),
(md5('poc-006'), 'Nuevo fármaco contra el Alzheimer obtiene aprobación de la EMA',
'La Agencia Europea del Medicamento ha dado luz verde al primer tratamiento que demuestra ralentizar significativamente el deterioro cognitivo en fases tempranas. El medicamento llegará a las farmacias europeas antes de final de año.',
'https://example.com/farmaco-alzheimer-ema', NOW() - INTERVAL '10 hours',
'BBC Mundo', 7, 'es', false),
(md5('poc-007'), 'México anuncia plan de inversión en energías renovables por 50.000 millones',
'El gobierno mexicano presentó su Estrategia Nacional de Transición Energética que contempla duplicar la capacidad solar y eólica instalada antes de 2030, con fuerte participación de capital privado nacional e internacional.',
'https://example.com/mexico-renovables', NOW() - INTERVAL '12 hours',
'RT Español', 5, 'es', false),
(md5('poc-008'), 'OpenAI lanza modelo multimodal capaz de generar video fotorrealista en tiempo real',
'La empresa californiana presentó Sora 2, capaz de producir secuencias de vídeo de alta definición en menos de 30 segundos. Investigadores advierten sobre los riesgos de desinformación mientras la compañía promete mecanismos de marca de agua.',
'https://example.com/openai-sora2', NOW() - INTERVAL '14 hours',
'El Mundo', 8, 'es', false),
(md5('poc-009'), 'Argentina cierra acuerdo comercial con la Unión Europea tras 25 años de negociaciones',
'El tratado de libre comercio Mercosur-UE entra en vigor de forma provisional tras superar los últimos obstáculos relacionados con protección ambiental y acceso al mercado agrícola europeo para los productos del cono sur.',
'https://example.com/argentina-acuerdo-ue', NOW() - INTERVAL '18 hours',
'BBC Mundo', 4, 'es', false),
(md5('poc-010'), 'Telescopio James Webb detecta atmósfera en exoplaneta a 40 años luz de la Tierra',
'Astrónomos del Instituto de Tecnología de California confirmaron la presencia de dióxido de carbono y vapor de agua en la atmósfera del exoplaneta K2-18b, abriendo nuevas posibilidades en la búsqueda de condiciones habitables fuera del sistema solar.',
'https://example.com/james-webb-exoplaneta', NOW() - INTERVAL '22 hours',
'El País', 1, 'es', false)
ON CONFLICT (id) DO NOTHING;