From e3c682a36fcfbc781352caa7a3fa051427812999 Mon Sep 17 00:00:00 2001 From: SITO Date: Mon, 30 Mar 2026 21:17:11 +0200 Subject: [PATCH] feat: prerequisites, POC local y README reescrito MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 255 ++++++++++++++++----------- deploy/debian/prerequisites.sh | 305 +++++++++++++++++++++++++++++++++ poc/poc.sh | 213 +++++++++++++++++++++++ poc/seed.sql | 94 ++++++++++ 4 files changed, 766 insertions(+), 101 deletions(-) create mode 100755 deploy/debian/prerequisites.sh create mode 100755 poc/poc.sh create mode 100644 poc/seed.sql diff --git a/README.md b/README.md index 4019aaa..d70ea43 100644 --- a/README.md +++ b/README.md @@ -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 -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 +``` diff --git a/deploy/debian/prerequisites.sh b/deploy/debian/prerequisites.sh new file mode 100755 index 0000000..156b9c6 --- /dev/null +++ b/deploy/debian/prerequisites.sh @@ -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 "" diff --git a/poc/poc.sh b/poc/poc.sh new file mode 100755 index 0000000..b51552a --- /dev/null +++ b/poc/poc.sh @@ -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 diff --git a/poc/seed.sql b/poc/seed.sql new file mode 100644 index 0000000..44772d7 --- /dev/null +++ b/poc/seed.sql @@ -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;