diff --git a/install.sh b/install.sh
index f9db300..a312ba5 100644
--- a/install.sh
+++ b/install.sh
@@ -1,83 +1,274 @@
#!/bin/bash
-# --- SCRIPT DE INSTALACIÓN FINAL (Arquitectura Web + Worker) ---
+# ==============================================================================
+# SCRIPT DE REINSTALACIÓN PARA APLICACIÓN RSS (MODO ACCESO WEB DIRECTO)
+#
+# CARGA LOS DATOS INICIALES DESDE LOS ARCHIVOS .sql EN EL DIRECTORIO.
+# SIRVE LA APLICACIÓN DIRECTAMENTE EN EL PUERTO 8000 USANDO GUNICORN.
+#
+# ACCIONES DESTRUCTIVAS:
+# - DETIENE y ELIMINA todos los servicios systemd que empiecen por "rss".
+# - ELIMINA (DROP) la base de datos y el usuario de la base de datos.
+#
+# USO:
+# 1. Clona tu repositorio y entra en su directorio.
+# 2. Asegúrate de tener los archivos .sql (categorias.sql, etc.) en la raíz.
+# 3. Dale permisos de ejecución a este script: chmod +x install.sh
+# 4. Ejecútalo con sudo: sudo ./install.sh
+# ==============================================================================
-set -e
+set -e # Termina el script si un comando falla
# ========= CONFIGURACIÓN =========
APP_NAME="rss"
DB_NAME="rss"
DB_USER="rss"
-APP_USER="x"
-APP_DIR="/home/$APP_USER/$APP_NAME"
+APP_USER="x" # El usuario del sistema que ejecutará la aplicación
+APP_DIR=$(pwd) # Asume que el directorio de la app es el directorio actual
PYTHON_ENV="$APP_DIR/venv"
WSGI_APP_ENTRY="app:app"
+WEB_PORT=8000 # Puerto en el que la aplicación será accesible
-# ========= 0. COMPROBACIONES INICIALES Y CREACIÓN DE USUARIO =========
-echo "🟢 Paso 0: Verificando usuario y pidiendo contraseña de la BD..."
+# ========= 0. COMPROBACIONES Y CONFIRMACIÓN DE SEGURIDAD =========
+echo "🟢 Paso 0: Verificaciones y confirmación de seguridad"
+
+if [[ $EUID -ne 0 ]]; then
+ echo "❌ Este script debe ser ejecutado como root (usa sudo)."
+ exit 1
+fi
+
+echo "------------------------------------------------------------------"
+echo "⚠️ ADVERTENCIA: Este script realizará las siguientes acciones DESTRUCTIVAS:"
+echo " - Eliminará TODOS los servicios systemd que empiecen por '$APP_NAME'."
+echo " - Eliminará PERMANENTEMENTE la base de datos '$DB_NAME'."
+echo " - Eliminará PERMANENTEMENTE el usuario de base de datos '$DB_USER'."
+echo "------------------------------------------------------------------"
+read -p "Estás seguro de que quieres continuar? (escribe 'si' para confirmar): " CONFIRM
+if [ "$CONFIRM" != "si" ]; then
+ echo "Operación cancelada por el usuario."
+ exit 0
+fi
+
+read -sp "🔑 Introduce la contraseña para el usuario de la base de datos '$DB_USER' (se creará de nuevo): " DB_PASS
+echo
+if [ -z "$DB_PASS" ]; then
+ echo "❌ La contraseña no puede estar vacía. Abortando."
+ exit 1
+fi
+
+# ========= 0.5: LIMPIEZA DE LA INSTALACIÓN ANTERIOR =========
+echo "🧹 Paso 0.5: Limpiando instalación anterior..."
+echo " -> Buscando y eliminando servicios systemd antiguos..."
+for service in $(systemctl list-unit-files | grep "^$APP_NAME" | cut -d' ' -f1); do
+ echo " -> Deteniendo y deshabilitando $service"
+ systemctl stop "$service" || true
+ systemctl disable "$service" || true
+done
+rm -f /etc/systemd/system/$APP_NAME*
+systemctl daemon-reload
+echo " -> Servicios systemd limpiados."
+
+# ========= 1. INSTALAR DEPENDENCIAS DEL SISTEMA =========
+echo "🟢 Paso 1: Instalando dependencias del sistema (PostgreSQL, Python, Gunicorn...)"
+apt-get update
+apt-get install -y wget ca-certificates postgresql postgresql-contrib python3-venv python3-pip python3-dev libpq-dev gunicorn
+
+# ========= 2. RECREAR LA BASE DE DATOS Y EL USUARIO =========
+echo "🔥 Paso 2: Eliminando y recreando la base de datos y el usuario..."
+sudo -u postgres psql -c "DROP DATABASE IF EXISTS $DB_NAME;"
+sudo -u postgres psql -c "DROP USER IF EXISTS $DB_USER;"
+echo " -> Entidades de BD anteriores eliminadas."
+sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';"
+sudo -u postgres psql -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;"
+echo "✅ Base de datos y usuario recreados con éxito."
+
+# ========= 3. PREPARAR ENTORNO DE LA APP =========
+echo "🐍 Paso 3: Configurando el entorno de la aplicación..."
if ! id "$APP_USER" &>/dev/null; then
echo "👤 Creando usuario del sistema '$APP_USER'..."
sudo useradd -m -s /bin/bash "$APP_USER"
+ echo "✅ Usuario '$APP_USER' creado."
else
echo "✅ Usuario del sistema '$APP_USER' ya existe."
fi
-
-if [ -z "$DB_PASS" ]; then
- read -sp "🔑 Introduce la contraseña para el usuario de la base de datos '$DB_USER': " DB_PASS
- echo
- if [ -z "DB\_PASS" \]; then
-echo "❌ La contraseña no puede estar vacía\. Abortando\."
-exit 1
-fi
-fi
-\# \=\=\=\=\=\=\=\=\= 1\. INSTALAR DEPENDENCIAS DEL SISTEMA \=\=\=\=\=\=\=\=\=
-echo "🟢 Paso 1\: Instalando dependencias del sistema\.\.\."
-sudo apt\-get update
-sudo apt\-get install \-y wget ca\-certificates postgresql postgresql\-contrib python3\-venv python3\-pip python3\-dev libpq\-dev
-\# \=\=\=\=\=\=\=\=\= 2\. CONFIGURAR POSTGRESQL \=\=\=\=\=\=\=\=\=
-echo "🛠️ Paso 2\: Configurando PostgreSQL\.\.\."
-sudo \-u postgres psql \-c <7\>"DO \\$\\ BEGIN IF NOT EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = '$DB_USER') THEN CREATE USER $DB_USER WITH PASSWORD '$DB_PASS'; ELSE ALTER USER $DB_USER WITH PASSWORD 'DB\_PASS'; END IF; END \\$\\;"
-if ! sudo -u postgres psql -lqt | cut -d \| -f 1 | grep -qw "$DB_NAME"; then
- sudo -u postgres psql -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;"
- echo "Base de datos '$DB_NAME' creada."
-else
- echo "✅ Base de datos '$DB_NAME' ya existe."
-fi
-
-# ========= 3. PREPARAR DIRECTORIO Y ENTORNO DE LA APP =========
-echo "🐍 Paso 3: Configurando el directorio de la aplicación y el entorno virtual..."
-sudo mkdir -p "$APP_DIR"
-sudo chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
+chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
sudo -u "$APP_USER" bash < Creando entorno virtual en $PYTHON_ENV"
+rm -rf "$PYTHON_ENV"
python3 -m venv "$PYTHON_ENV"
-echo "📦 Instalando dependencias desde requirements.txt..."
+echo " -> Instalando dependencias desde requirements.txt..."
"$PYTHON_ENV/bin/python" -m pip install --upgrade pip
if [ -f "requirements.txt" ]; then
"$PYTHON_ENV/bin/python" -m pip install -r "requirements.txt"
else
- echo "⚠️ ADVERTENCIA: No se encontró requirements.txt."
+ echo "⚠️ ADVERTENCIA: No se encontró requirements.txt. La aplicación podría no funcionar."
fi
EOF
+echo "✅ Entorno de Python configurado."
-# ========= 4. CREAR TABLAS Y CONFIGURAR BÚSQUEDA DE TEXTO COMPLETO =========
-echo "📐 Paso 4: Creando/verificando tablas y configurando Full-Text Search..."
+# ========= 4. CREAR ESQUEMA Y SEMBRAR DATOS DESDE ARCHIVOS SQL =========
+echo "📐 Paso 4: Creando esquema de BD, configurando FTS y sembrando datos desde archivos .sql..."
export PGPASSWORD="$DB_PASS"
-# --- Creación de tablas base completas ---
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE TABLE IF NOT EXISTS continentes (id SERIAL PRIMARY KEY, nombre VARCHAR(50) NOT NULL UNIQUE);"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE TABLE IF NOT EXISTS categorias (id SERIAL PRIMARY KEY, nombre VARCHAR(100) NOT NULL UNIQUE);"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE TABLE IF NOT EXISTS paises (id SERIAL PRIMARY KEY, nombre VARCHAR(100) NOT NULL UNIQUE, continente_id INTEGER REFERENCES continentes(id) ON DELETE SET NULL);"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE TABLE IF NOT EXISTS feeds (id SERIAL PRIMARY KEY, nombre VARCHAR(255), descripcion TEXT, url TEXT NOT NULL UNIQUE, categoria_id INTEGER REFERENCES categorias(id) ON DELETE SET NULL, pais_id INTEGER REFERENCES paises(id) ON DELETE SET NULL, idioma CHAR(2), activo BOOLEAN DEFAULT TRUE, fallos INTEGER DEFAULT 0, last_etag TEXT, last_modified TEXT);"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE TABLE IF NOT EXISTS noticias (id VARCHAR(32) PRIMARY KEY, titulo TEXT, resumen TEXT, url TEXT NOT NULL UNIQUE, fecha TIMESTAMP, imagen_url TEXT, categoria_id INTEGER REFERENCES categorias(id) ON DELETE SET NULL, pais_id INTEGER REFERENCES paises(id) ON DELETE SET NULL, tsv tsvector);"
-# --- Configuración de FTS ---
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "ALTER TABLE noticias ADD COLUMN IF NOT EXISTS tsv tsvector;"
-psql -U "$DB_USER" -h localhost -d "DB\_NAME" \-c "CREATE OR REPLACE FUNCTION noticias\_tsv\_trigger\(\) RETURNS trigger AS \\$\\$ begin new\.tsv \:\= setweight\(to\_tsvector\('spanish', coalesce\(new\.titulo,''\)\), 'A'\) \|\| setweight\(to\_tsvector\('spanish', coalesce\(new\.resumen,''\)\), 'B'\); return new; end \\$\\ LANGUAGE plpgsql;"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "DROP TRIGGER IF EXISTS tsvectorupdate ON noticias;"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE TRIGGER tsvectorupdate BEFORE INSERT ON noticias FOR EACH ROW EXECUTE PROCEDURE noticias_tsv_trigger();"
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "CREATE INDEX IF NOT EXISTS noticias_tsv_idx ON noticias USING gin(tsv);"
-echo "🔄 Actualizando índice de búsqueda para noticias existentes (puede tardar)..."
-psql -U "$DB_USER" -h localhost -d "$DB_NAME" -c "UPDATE noticias SET tsv = setweight(to_tsvector('spanish', coalesce(titulo,'')), 'A') || setweight(to_tsvector('spanish', coalesce(resumen,'')), 'B') WHERE tsv IS NULL;"
-# --- Cargar datos iniciales ---
-for sql_file in continentes.sql paises.sql
+
+# Crear las tablas primero
+psql -U "$DB_USER" -h localhost -d "$DB_NAME" < Buscando archivos .sql para sembrar datos..."
+if [ -f "continentes.sql" ]; then
+ echo " -> Cargando continentes.sql..."
+ psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "continentes.sql"
+else
+ echo " -> ADVERTENCIA: No se encontró continentes.sql"
+fi
+
+if [ -f "categorias.sql" ]; then
+ echo " -> Cargando categorias.sql..."
+ psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "categorias.sql"
+else
+ echo " -> ADVERTENCIA: No se encontró categorias.sql"
+fi
+
+if [ -f "paises.sql" ]; then
+ echo " -> Cargando paises.sql..."
+ psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "paises.sql"
+else
+ echo " -> ADVERTENCIA: No se encontró paises.sql"
+fi
+
+# Reiniciar las secuencias para que los nuevos INSERTs no colisionen
+echo " -> Actualizando contadores de secuencias de la base de datos..."
+psql -U "$DB_USER" -h localhost -d "$DB_NAME" < "$APP_DIR/worker.py"
+import sys
+import os
+import logging
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+try:
+ from app import app, fetch_and_store
+except ImportError as e:
+ logging.basicConfig()
+ logging.critical(f"No se pudo importar la aplicación Flask. Error: {e}")
+ sys.exit(1)
+if __name__ == "__main__":
+ with app.app_context():
+ fetch_and_store()
+EOF
+chown "$APP_USER":"$APP_USER" "$APP_DIR/worker.py"
+echo "✅ Script del worker creado/actualizado."
+
+# ========= 6. CREAR SERVICIOS SYSTEMD =========
+echo "⚙️ Paso 6: Creando nuevos archivos de servicio systemd..."
+
+# --- Servicio para la aplicación web (Gunicorn) ---
+cat < /etc/systemd/system/$APP_NAME.service
+[Unit]
+Description=Gunicorn instance to serve $APP_NAME
+After=network.target
+
+[Service]
+User=$APP_USER
+Group=$APP_USER
+WorkingDirectory=$APP_DIR
+Environment="PATH=$PYTHON_ENV/bin"
+Environment="SECRET_KEY=$(python3 -c 'import os; print(os.urandom(24).hex())')"
+Environment="DB_HOST=localhost"
+Environment="DB_PORT=5432"
+Environment="DB_NAME=$DB_NAME"
+Environment="DB_USER=$DB_USER"
+Environment="DB_PASS=$DB_PASS"
+
+# --- LÍNEA CLAVE ---
+# Gunicorn escucha en todas las IPs (0.0.0.0) en el puerto especificado
+ExecStart=$PYTHON_ENV/bin/gunicorn --workers 3 --bind 0.0.0.0:$WEB_PORT $WSGI_APP_ENTRY
+
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+# --- Servicio para el worker ---
+cat < /etc/systemd/system/$APP_NAME-worker.service
+[Unit]
+Description=$APP_NAME Feed Fetcher Worker
+After=postgresql.service
+[Service]
+Type=oneshot
+User=$APP_USER
+WorkingDirectory=$APP_DIR
+Environment="SECRET_KEY=$(python3 -c 'import os; print(os.urandom(24).hex())')"
+Environment="DB_HOST=localhost"
+Environment="DB_PORT=5432"
+Environment="DB_NAME=$DB_NAME"
+Environment="DB_USER=$DB_USER"
+Environment="DB_PASS=$DB_PASS"
+ExecStart=$PYTHON_ENV/bin/python $APP_DIR/worker.py
+EOF
+
+# --- Timer para el worker ---
+cat < /etc/systemd/system/$APP_NAME-worker.timer
+[Unit]
+Description=Run $APP_NAME worker every 15 minutes
+[Timer]
+OnBootSec=5min
+OnUnitActiveSec=15min
+Unit=$APP_NAME-worker.service
+[Install]
+WantedBy=timers.target
+EOF
+echo "✅ Archivos de servicio y timer creados."
+
+# ========= 7. HABILITAR, ARRANCAR SERVICIOS Y ABRIR FIREWALL =========
+echo "🚀 Paso 7: Recargando, habilitando, arrancando servicios y configurando firewall..."
+systemctl daemon-reload
+systemctl enable $APP_NAME.service
+systemctl start $APP_NAME.service
+systemctl enable $APP_NAME-worker.timer
+systemctl start $APP_NAME-worker.timer
+
+# Abre el puerto en el firewall (UFW), si está activo
+if command -v ufw &> /dev/null && ufw status | grep -q 'Status: active'; then
+ echo " -> Firewall UFW detectado. Abriendo puerto $WEB_PORT..."
+ ufw allow $WEB_PORT/tcp
+ ufw reload
+fi
+
+echo ""
+echo "🎉 ¡REINSTALACIÓN COMPLETADA!"
+echo "--------------------------------"
+echo ""
+echo "✅ La aplicación web está ahora accesible en:"
+echo " http://:$WEB_PORT"
+echo ""
+echo "Puedes verificar el estado de los servicios con:"
+echo "sudo systemctl status $APP_NAME.service"
+echo "sudo systemctl status $APP_NAME-worker.timer"
+echo ""
+echo "Para ver los logs de la aplicación web:"
+echo "sudo journalctl -u $APP_NAME.service -f"
+
diff --git a/worker.py b/worker.py
new file mode 100644
index 0000000..4e183c2
--- /dev/null
+++ b/worker.py
@@ -0,0 +1,13 @@
+import sys
+import os
+import logging
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+try:
+ from app import app, fetch_and_store
+except ImportError as e:
+ logging.basicConfig()
+ logging.critical(f"No se pudo importar la aplicación Flask. Error: {e}")
+ sys.exit(1)
+if __name__ == "__main__":
+ with app.app_context():
+ fetch_and_store()