quito comentarios

This commit is contained in:
jlimolina 2025-11-24 01:40:46 +01:00
parent 68a5528f2f
commit 937da3f90b
8 changed files with 48 additions and 496 deletions

View file

@ -1,16 +0,0 @@
id,nombre
1,Ciencia
2,Cultura
3,Deportes
4,Economía
5,Educación
6,Entretenimiento
7,Internacional
8,Medio Ambiente
9,Moda
10,Opinión
11,Política
12,Salud
13,Sociedad
14,Tecnología
15,Viajes
1 id nombre
2 1 Ciencia
3 2 Cultura
4 3 Deportes
5 4 Economía
6 5 Educación
7 6 Entretenimiento
8 7 Internacional
9 8 Medio Ambiente
10 9 Moda
11 10 Opinión
12 11 Política
13 12 Salud
14 13 Sociedad
15 14 Tecnología
16 15 Viajes

View file

@ -31,30 +31,41 @@ EVENT_BATCH_IDS = int(os.environ.get("EVENT_BATCH_IDS", "200"))
EVENT_SLEEP_IDLE = float(os.environ.get("EVENT_SLEEP_IDLE", "5.0")) EVENT_SLEEP_IDLE = float(os.environ.get("EVENT_SLEEP_IDLE", "5.0"))
EVENT_DIST_THRESHOLD = float(os.environ.get("EVENT_DIST_THRESHOLD", "0.25")) EVENT_DIST_THRESHOLD = float(os.environ.get("EVENT_DIST_THRESHOLD", "0.25"))
EMB_MODEL = os.environ.get(
"EMB_MODEL",
"sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
)
def get_conn(): def get_conn():
return psycopg2.connect(**DB) return psycopg2.connect(**DB)
def ensure_schema(conn): def ensure_schema(conn):
"""
Asegura que la tabla de eventos y las columnas necesarias existen.
Aquí se asume el esquema original de eventos con centroid JSONB.
"""
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute( cur.execute(
""" """
CREATE TABLE IF NOT EXISTS eventos ( CREATE TABLE IF NOT EXISTS eventos (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
creado_en TIMESTAMP NOT NULL DEFAULT NOW(), creado_en TIMESTAMP NOT NULL DEFAULT NOW(),
actualizado_en TIMESTAMP NOT NULL DEFAULT NOW(), actualizado_en TIMESTAMP NOT NULL DEFAULT NOW(),
centroid JSONB NOT NULL, centroid JSONB NOT NULL,
total_traducciones INTEGER NOT NULL DEFAULT 1 total_traducciones INTEGER NOT NULL DEFAULT 1
); );
""" """
) )
cur.execute( cur.execute(
""" """
ALTER TABLE traducciones ALTER TABLE traducciones
ADD COLUMN IF NOT EXISTS evento_id INTEGER REFERENCES eventos(id); ADD COLUMN IF NOT EXISTS evento_id INTEGER REFERENCES eventos(id);
""" """
) )
cur.execute( cur.execute(
""" """
CREATE INDEX IF NOT EXISTS idx_traducciones_evento CREATE INDEX IF NOT EXISTS idx_traducciones_evento
@ -67,6 +78,7 @@ def ensure_schema(conn):
ON traducciones(evento_id, noticia_id); ON traducciones(evento_id, noticia_id);
""" """
) )
cur.execute( cur.execute(
""" """
CREATE OR REPLACE FUNCTION actualizar_evento_modificado() CREATE OR REPLACE FUNCTION actualizar_evento_modificado()
@ -91,43 +103,55 @@ def ensure_schema(conn):
def fetch_pending_traducciones(conn) -> List[int]: def fetch_pending_traducciones(conn) -> List[int]:
"""
Traducciones con status 'done', sin evento asignado
y que ya tienen embedding en traduccion_embeddings para EMB_MODEL.
"""
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute( cur.execute(
""" """
SELECT t.id SELECT t.id
FROM traducciones t FROM traducciones t
JOIN embeddings e ON e.traduccion_id = t.id JOIN traduccion_embeddings e
ON e.traduccion_id = t.id
AND e.model = %s
WHERE t.status = 'done' WHERE t.status = 'done'
AND t.evento_id IS NULL AND t.evento_id IS NULL
AND t.lang_to = ANY(%s) AND t.lang_to = ANY(%s)
ORDER BY t.id DESC ORDER BY t.id DESC
LIMIT %s; LIMIT %s;
""", """,
(EVENT_LANGS, EVENT_BATCH_IDS), (EMB_MODEL, EVENT_LANGS, EVENT_BATCH_IDS),
) )
rows = cur.fetchall() rows = cur.fetchall()
return [r[0] for r in rows] return [r[0] for r in rows]
def fetch_embeddings_for(conn, tr_ids: List[int]) -> Dict[int, np.ndarray]: def fetch_embeddings_for(conn, tr_ids: List[int]) -> Dict[int, np.ndarray]:
"""
Devuelve un diccionario {traduccion_id: vector_numpy}
leyendo de traduccion_embeddings.embedding para el EMB_MODEL.
"""
if not tr_ids: if not tr_ids:
return {} return {}
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute( cur.execute(
""" """
SELECT traduccion_id, vec SELECT traduccion_id, embedding
FROM embeddings FROM traduccion_embeddings
WHERE traduccion_id = ANY(%s); WHERE traduccion_id = ANY(%s)
AND model = %s;
""", """,
(tr_ids,), (tr_ids, EMB_MODEL),
) )
rows = cur.fetchall() rows = cur.fetchall()
out: Dict[int, np.ndarray] = {} out: Dict[int, np.ndarray] = {}
for tr_id, vec in rows: for tr_id, emb in rows:
if not vec: if not emb:
continue continue
arr = np.array([float(x or 0.0) for x in vec], dtype="float32") arr = np.array([float(x or 0.0) for x in emb], dtype="float32")
if arr.size == 0: if arr.size == 0:
continue continue
out[int(tr_id)] = arr out[int(tr_id)] = arr
@ -135,6 +159,9 @@ def fetch_embeddings_for(conn, tr_ids: List[int]) -> Dict[int, np.ndarray]:
def fetch_centroids(conn) -> List[Dict[str, Any]]: def fetch_centroids(conn) -> List[Dict[str, Any]]:
"""
Carga todos los centroides actuales desde eventos.
"""
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur: with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
cur.execute( cur.execute(
""" """
@ -180,6 +207,10 @@ def assign_to_event(
vec: np.ndarray, vec: np.ndarray,
centroids: List[Dict[str, Any]], centroids: List[Dict[str, Any]],
) -> None: ) -> None:
"""
Asigna una traducción a un evento existente (si distancia <= umbral)
o crea un evento nuevo con este vector como centroide.
"""
from psycopg2.extras import Json from psycopg2.extras import Json
if vec is None or vec.size == 0: if vec is None or vec.size == 0:
@ -260,11 +291,12 @@ def assign_to_event(
def main(): def main():
log.info( log.info(
"Iniciando cluster_worker eventos " "Iniciando cluster_worker eventos "
"(EVENT_LANGS=%s, BATCH_IDS=%s, DIST_THRESHOLD=%.3f, SLEEP=%.1fs)", "(EVENT_LANGS=%s, BATCH_IDS=%s, DIST_THRESHOLD=%.3f, SLEEP=%.1fs, EMB_MODEL=%s)",
",".join(EVENT_LANGS), ",".join(EVENT_LANGS),
EVENT_BATCH_IDS, EVENT_BATCH_IDS,
EVENT_DIST_THRESHOLD, EVENT_DIST_THRESHOLD,
EVENT_SLEEP_IDLE, EVENT_SLEEP_IDLE,
EMB_MODEL,
) )
while True: while True:

View file

@ -1,7 +0,0 @@
id,nombre
1,África
2,América
3,Asia
4,Europa
5,Oceanía
6,Antártida
1 id nombre
2 1 África
3 2 América
4 3 Asia
5 4 Europa
6 5 Oceanía
7 6 Antártida

View file

@ -2,7 +2,6 @@ import nltk
import logging import logging
import ssl import ssl
# Soluciona problemas de certificado SSL en algunas configuraciones de sistema al descargar
try: try:
_create_unverified_https_context = ssl._create_unverified_context _create_unverified_https_context = ssl._create_unverified_context
except AttributeError: except AttributeError:
@ -12,31 +11,22 @@ else:
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s') logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
# Lista de paquetes de NLTK que newspaper3k puede necesitar.
# 'punkt' y 'punkt_tab' son para tokenización, 'stopwords' para el resumen.
PACKAGES = ['punkt', 'punkt_tab', 'stopwords'] PACKAGES = ['punkt', 'punkt_tab', 'stopwords']
def download_nltk_data(): def download_nltk_data():
"""
Descarga los paquetes de NLTK necesarios para newspaper3k.
"""
for package in PACKAGES: for package in PACKAGES:
try: try:
logging.info(f"Verificando si el paquete '{package}' de NLTK está disponible...") logging.info(f"Verificando si el paquete '{package}' de NLTK está disponible...")
# Determina la ruta correcta para la verificación
if package.startswith('punkt'): if package.startswith('punkt'):
path = f'tokenizers/{package}' path = f'tokenizers/{package}'
else: else:
path = f'corpora/{package}' path = f'corpora/{package}'
nltk.data.find(path) nltk.data.find(path)
logging.info(f"El paquete '{package}' ya está descargado.") logging.info(f"El paquete '{package}' ya está descargado.")
except LookupError: except LookupError:
logging.info(f"El paquete '{package}' no se encontró. Iniciando descarga...") logging.info(f"El paquete '{package}' no se encontró. Iniciando descarga...")
try: try:
# El parámetro quiet=True evita el diálogo interactivo
nltk.download(package, quiet=True) nltk.download(package, quiet=True)
logging.info(f"Paquete '{package}' descargado con éxito.") logging.info(f"Paquete '{package}' descargado con éxito.")
except Exception as e: except Exception as e:
@ -44,6 +34,7 @@ def download_nltk_data():
import sys import sys
sys.exit(1) sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
download_nltk_data() download_nltk_data()

View file

@ -13,7 +13,6 @@ import torch
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s') logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s')
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# ---------- Configuración DB ----------
DB = dict( DB = dict(
host=os.environ.get("DB_HOST", "localhost"), host=os.environ.get("DB_HOST", "localhost"),
port=int(os.environ.get("DB_PORT", 5432)), port=int(os.environ.get("DB_PORT", 5432)),
@ -22,31 +21,22 @@ DB = dict(
password=os.environ.get("DB_PASS", "x"), password=os.environ.get("DB_PASS", "x"),
) )
# ---------- Parámetros de worker ----------
# Modelo por defecto: multilingüe, bueno para muchas lenguas
EMB_MODEL = os.environ.get( EMB_MODEL = os.environ.get(
"EMB_MODEL", "EMB_MODEL",
"sentence-transformers/paraphrase-multilingual-mpnet-base-v2", "sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
) )
EMB_BATCH = int(os.environ.get("EMB_BATCH", "128")) EMB_BATCH = int(os.environ.get("EMB_BATCH", "128"))
SLEEP_IDLE = float(os.environ.get("EMB_SLEEP_IDLE", "5.0")) SLEEP_IDLE = float(os.environ.get("EMB_SLEEP_IDLE", "5.0"))
# Filtrado por idiomas destino (coma-separado). Por defecto sólo 'es'
EMB_LANGS = [s.strip() for s in os.environ.get("EMB_LANGS", "es").split(",") if s.strip()] EMB_LANGS = [s.strip() for s in os.environ.get("EMB_LANGS", "es").split(",") if s.strip()]
# DEVICE_ENV: 'auto' | 'cpu' | 'cuda'
DEVICE_ENV = os.environ.get("DEVICE", "auto").lower() DEVICE_ENV = os.environ.get("DEVICE", "auto").lower()
# Límite por iteración (para no tragar toda la tabla de golpe)
EMB_LIMIT = int(os.environ.get("EMB_LIMIT", "1000")) EMB_LIMIT = int(os.environ.get("EMB_LIMIT", "1000"))
# ---------- Utilidades ----------
def get_conn(): def get_conn():
return psycopg2.connect(**DB) return psycopg2.connect(**DB)
def ensure_schema(conn): def ensure_schema(conn):
"""Crea la tabla de embeddings para traducciones si no existe."""
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute( cur.execute(
""" """
@ -61,21 +51,12 @@ def ensure_schema(conn):
); );
""" """
) )
# Alineado con init-db/08-embeddings.sql cur.execute("CREATE INDEX IF NOT EXISTS idx_tr_emb_model ON traduccion_embeddings(model);")
cur.execute( cur.execute("CREATE INDEX IF NOT EXISTS idx_tr_emb_traduccion_id ON traduccion_embeddings(traduccion_id);")
"CREATE INDEX IF NOT EXISTS idx_tr_emb_model ON traduccion_embeddings(model);"
)
cur.execute(
"CREATE INDEX IF NOT EXISTS idx_tr_emb_traduccion_id ON traduccion_embeddings(traduccion_id);"
)
conn.commit() conn.commit()
def fetch_batch_pending(conn) -> List[psycopg2.extras.DictRow]: def fetch_batch_pending(conn) -> List[psycopg2.extras.DictRow]:
"""
Devuelve un lote de traducciones 'done' del/los idioma(s) objetivo
que no tienen embedding aún para el EMB_MODEL indicado.
"""
with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur: with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur:
cur.execute( cur.execute(
""" """
@ -96,15 +77,10 @@ def fetch_batch_pending(conn) -> List[psycopg2.extras.DictRow]:
""", """,
(EMB_MODEL, EMB_LANGS, EMB_LIMIT), (EMB_MODEL, EMB_LANGS, EMB_LIMIT),
) )
rows = cur.fetchall() return cur.fetchall()
return rows
def texts_from_rows(rows: List[psycopg2.extras.DictRow]) -> List[str]: def texts_from_rows(rows: List[psycopg2.extras.DictRow]) -> List[str]:
"""
Compone el texto a vectorizar por cada traducción:
'titulo_trad' + '\n' + 'resumen_trad'. Si alguno falta, usa lo disponible.
"""
texts: List[str] = [] texts: List[str] = []
for r in rows: for r in rows:
title = (r["titulo_trad"] or "").strip() title = (r["titulo_trad"] or "").strip()
@ -117,15 +93,11 @@ def texts_from_rows(rows: List[psycopg2.extras.DictRow]) -> List[str]:
def upsert_embeddings(conn, rows, embs: np.ndarray, model_name: str): def upsert_embeddings(conn, rows, embs: np.ndarray, model_name: str):
"""
Inserta/actualiza embeddings por traducción en lote (batch insert).
"""
if embs.size == 0 or not rows: if embs.size == 0 or not rows:
return return
dim = int(embs.shape[1]) dim = int(embs.shape[1])
# Preparamos los datos para execute_values
data = [ data = [
( (
int(r["traduccion_id"]), int(r["traduccion_id"]),
@ -152,7 +124,6 @@ def upsert_embeddings(conn, rows, embs: np.ndarray, model_name: str):
conn.commit() conn.commit()
# ---------- Main loop ----------
def main(): def main():
log.info("Arrancando embeddings_worker para TRADUCCIONES") log.info("Arrancando embeddings_worker para TRADUCCIONES")
log.info( log.info(

View file

@ -1,223 +0,0 @@
#!/bin/bash
set -e
APP_NAME="rss"
DB_NAME="rss"
DB_USER="rss"
APP_USER="x"
APP_DIR=$(pwd)
PYTHON_ENV="$APP_DIR/venv"
WSGI_APP_ENTRY="app:app"
WEB_PORT=8000
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
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."
echo "🟢 Paso 1: Instalando dependencias del sistema..."
apt-get update
apt-get install -y wget ca-certificates postgresql postgresql-contrib python3-venv python3-pip python3-dev libpq-dev gunicorn
echo "🔥 Paso 2: Eliminando y recreando la base de datos y el usuario..."
## CORRECCIÓN: Se exporta PGPASSWORD para evitar exponer la contraseña en la línea de comandos
## y se agrupa con la del paso 4 para mayor eficiencia.
export PGPASSWORD="$DB_PASS"
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."
echo "🐍 Paso 3: Configurando el entorno de la aplicación..."
if ! id "$APP_USER" &>/dev/null; then
echo "👤 Creando usuario del sistema '$APP_USER'..."
useradd -m -s /bin/bash "$APP_USER"
else
echo "✅ Usuario del sistema '$APP_USER' ya existe."
fi
chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
sudo -u "$APP_USER" bash <<EOF
set -e
cd "$APP_DIR"
echo " -> Creando entorno virtual en $PYTHON_ENV"
rm -rf "$PYTHON_ENV"
python3 -m venv "$PYTHON_ENV"
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."
fi
EOF
echo "🧠 Paso 3.5: Descargando modelos de lenguaje para Newspaper3k..."
if [ -f "download_models.py" ]; then
sudo -u "$APP_USER" "$PYTHON_ENV/bin/python" "$APP_DIR/download_models.py"
echo "✅ Modelos NLP verificados/descargados."
else
echo "⚠️ ADVERTENCIA: No se encontró download_models.py. El scraping de URLs puede fallar."
fi
echo "📐 Paso 4: Creando esquema de BD y sembrando datos..."
# La variable PGPASSWORD ya está exportada desde el paso 2
psql -U "$DB_USER" -h localhost -d "$DB_NAME" <<SQL
CREATE TABLE IF NOT EXISTS continentes (id SERIAL PRIMARY KEY, nombre VARCHAR(50) NOT NULL UNIQUE);
CREATE TABLE IF NOT EXISTS categorias (id SERIAL PRIMARY KEY, nombre VARCHAR(100) NOT NULL UNIQUE);
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);
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);
CREATE TABLE IF NOT EXISTS fuentes_url (id SERIAL PRIMARY KEY, nombre VARCHAR(255) NOT NULL, 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) DEFAULT 'es');
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, fuente_nombre VARCHAR(255), categoria_id INTEGER REFERENCES categorias(id) ON DELETE SET NULL, pais_id INTEGER REFERENCES paises(id) ON DELETE SET NULL, tsv tsvector);
ALTER TABLE noticias ADD COLUMN IF NOT EXISTS tsv tsvector;
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;
DROP TRIGGER IF EXISTS tsvectorupdate ON noticias;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON noticias FOR EACH ROW EXECUTE PROCEDURE noticias_tsv_trigger();
CREATE INDEX IF NOT EXISTS noticias_tsv_idx ON noticias USING gin(tsv);
SQL
echo " -> 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"
fi
if [ -f "categorias.sql" ]; then
echo " -> Cargando categorias.sql..."
psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "categorias.sql"
fi
if [ -f "paises.sql" ]; then
echo " -> Cargando paises.sql..."
psql -U "$DB_USER" -h localhost -d "$DB_NAME" -f "paises.sql"
fi
echo " -> Actualizando contadores de secuencias de la base de datos..."
psql -U "$DB_USER" -h localhost -d "$DB_NAME" <<SQL
SELECT setval('categorias_id_seq', (SELECT MAX(id) FROM categorias), true) WHERE (SELECT MAX(id) FROM categorias) IS NOT NULL;
SELECT setval('continentes_id_seq', (SELECT MAX(id) FROM continentes), true) WHERE (SELECT MAX(id) FROM continentes) IS NOT NULL;
SELECT setval('paises_id_seq', (SELECT MAX(id) FROM paises), true) WHERE (SELECT MAX(id) FROM paises) IS NOT NULL;
SQL
unset PGPASSWORD
echo "✅ Esquema de base de datos y datos iniciales configurados."
## CORRECCIÓN: Se elimina el Paso 5, ya no es necesario crear el worker.py dinámicamente.
echo "⚙️ Paso 6: Creando nuevos archivos de servicio systemd..."
cat <<EOF > /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"
ExecStart=$PYTHON_ENV/bin/gunicorn --workers 3 --bind 0.0.0.0:$WEB_PORT --timeout 120 $WSGI_APP_ENTRY
Restart=always
[Install]
WantedBy=multi-user.target
EOF
## CORRECCIÓN: Se elimina la creación de rss-worker.service y rss-worker.timer.
## Se añade el nuevo servicio para el planificador persistente scheduler.py.
cat <<EOF > /etc/systemd/system/$APP_NAME-scheduler.service
[Unit]
Description=$APP_NAME Scheduler Worker
After=postgresql.service
[Service]
Type=simple
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"
ExecStart=$PYTHON_ENV/bin/python $APP_DIR/scheduler.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
echo "✅ Archivos de servicio creados."
echo "🚀 Paso 7: Recargando, habilitando, arrancando servicios y configurando firewall..."
systemctl daemon-reload
systemctl enable $APP_NAME.service
systemctl start $APP_NAME.service
## CORRECCIÓN: Se habilitan e inician el nuevo servicio del planificador.
systemctl enable $APP_NAME-scheduler.service
systemctl start $APP_NAME-scheduler.service
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://<IP_DE_TU_SERVIDOR>:$WEB_PORT"
echo ""
echo "Puedes verificar el estado de los servicios con:"
echo "sudo systemctl status $APP_NAME.service"
## CORRECCIÓN: Se actualiza el mensaje para reflejar el nuevo nombre del servicio del worker.
echo "sudo systemctl status $APP_NAME-scheduler.service"
echo ""
echo "Para ver los logs de la aplicación web:"
echo "sudo journalctl -u $APP_NAME.service -f"
echo "Para ver los logs del planificador de noticias:"
echo "sudo journalctl -u $APP_NAME-scheduler.service -f"

View file

@ -1,196 +0,0 @@
id,nombre,continente_id
1,Afganistán,3
2,Albania,4
3,Alemania,4
4,Andorra,4
5,Angola,1
6,Antigua y Barbuda,2
7,Arabia Saudita,3
8,Argelia,1
9,Argentina,2
10,Armenia,3
11,Australia,5
12,Austria,4
13,Azerbaiyán,3
14,Bahamas,2
15,Bangladés,3
16,Barbados,2
17,Baréin,3
19,Belice,2
20,Benín,1
21,Bielorrusia,4
22,Birmania,3
23,Bolivia,2
24,Bosnia y Herzegovina,4
25,Botsuana,1
26,Brasil,2
27,Brunéi,3
28,Bulgaria,4
29,Burkina Faso,1
30,Burundi,1
31,Bután,3
18,Bélgica,4
32,Cabo Verde,1
33,Camboya,3
34,Camerún,1
35,Canadá,2
36,Catar,3
37,Chad,1
38,Chile,2
39,China,3
40,Chipre,3
41,Colombia,2
42,Comoras,1
43,Corea del Norte,3
44,Corea del Sur,3
46,Costa Rica,2
45,Costa de Marfil,1
47,Croacia,4
48,Cuba,2
49,Dinamarca,4
50,Dominica,2
51,Ecuador,2
52,Egipto,1
53,El Salvador,2
54,Emiratos Árabes Unidos,3
55,Eritrea,1
56,Eslovaquia,4
57,Eslovenia,4
58,España,4
59,Estados Unidos,2
60,Estonia,4
61,Esuatini,1
62,Etiopía,1
63,Filipinas,3
64,Finlandia,4
65,Fiyi,5
66,Francia,4
67,Gabón,1
68,Gambia,1
69,Georgia,3
70,Ghana,1
71,Granada,2
72,Grecia,4
73,Guatemala,2
74,Guinea,1
76,Guinea Ecuatorial,1
75,Guinea-Bisáu,1
77,Guyana,2
78,Haití,2
79,Honduras,2
80,Hungría,4
81,India,3
82,Indonesia,3
83,Irak,3
85,Irlanda,4
84,Irán,3
86,Islandia,4
87,Islas Marshall,5
88,Islas Salomón,5
89,Israel,3
90,Italia,4
91,Jamaica,2
92,Japón,3
93,Jordania,3
94,Kazajistán,3
95,Kenia,1
96,Kirguistán,3
97,Kiribati,5
98,Kuwait,3
99,Laos,3
100,Lesoto,1
101,Letonia,4
103,Liberia,1
104,Libia,1
105,Liechtenstein,4
106,Lituania,4
107,Luxemburgo,4
102,Líbano,3
108,Macedonia del Norte,4
109,Madagascar,1
110,Malasia,3
111,Malaui,1
112,Maldivas,3
114,Malta,4
113,Malí,1
115,Marruecos,1
116,Mauricio,1
117,Mauritania,1
119,Micronesia,5
120,Moldavia,4
122,Mongolia,3
123,Montenegro,4
124,Mozambique,1
118,México,2
121,Mónaco,4
125,Namibia,1
126,Nauru,5
127,Nepal,3
128,Nicaragua,2
130,Nigeria,1
131,Noruega,4
132,Nueva Zelanda,5
129,Níger,1
133,Omán,3
135,Pakistán,3
136,Palaos,5
137,Palestina,3
138,Panamá,2
139,Papúa Nueva Guinea,5
140,Paraguay,2
134,Países Bajos,4
141,Perú,2
142,Polonia,4
143,Portugal,4
144,Reino Unido,4
145,República Centroafricana,1
146,República Checa,4
148,República Democrática del Congo,1
149,República Dominicana,2
147,República del Congo,1
150,Ruanda,1
151,Rumanía,4
152,Rusia,3
153,Samoa,5
154,San Cristóbal y Nieves,2
155,San Marino,4
156,San Vicente y las Granadinas,2
157,Santa Lucía,2
158,Santo Tomé y Príncipe,1
159,Senegal,1
160,Serbia,4
161,Seychelles,1
162,Sierra Leona,1
163,Singapur,3
164,Siria,3
165,Somalia,1
166,Sri Lanka,3
167,Sudáfrica,1
168,Sudán,1
169,Sudán del Sur,1
170,Suecia,4
171,Suiza,4
172,Surinam,2
173,Tailandia,3
174,Tanzania,1
175,Tayikistán,3
176,Timor Oriental,3
177,Togo,1
178,Tonga,5
179,Trinidad y Tobago,2
181,Turkmenistán,3
182,Turquía,3
183,Tuvalu,5
180,Túnez,1
184,Ucrania,4
185,Uganda,1
186,Uruguay,2
187,Uzbekistán,3
188,Vanuatu,5
189,Vaticano,4
190,Venezuela,2
191,Vietnam,3
192,Yemen,3
193,Yibuti,1
194,Zambia,1
195,Zimbabue,1
1 id nombre continente_id
2 1 Afganistán 3
3 2 Albania 4
4 3 Alemania 4
5 4 Andorra 4
6 5 Angola 1
7 6 Antigua y Barbuda 2
8 7 Arabia Saudita 3
9 8 Argelia 1
10 9 Argentina 2
11 10 Armenia 3
12 11 Australia 5
13 12 Austria 4
14 13 Azerbaiyán 3
15 14 Bahamas 2
16 15 Bangladés 3
17 16 Barbados 2
18 17 Baréin 3
19 19 Belice 2
20 20 Benín 1
21 21 Bielorrusia 4
22 22 Birmania 3
23 23 Bolivia 2
24 24 Bosnia y Herzegovina 4
25 25 Botsuana 1
26 26 Brasil 2
27 27 Brunéi 3
28 28 Bulgaria 4
29 29 Burkina Faso 1
30 30 Burundi 1
31 31 Bután 3
32 18 Bélgica 4
33 32 Cabo Verde 1
34 33 Camboya 3
35 34 Camerún 1
36 35 Canadá 2
37 36 Catar 3
38 37 Chad 1
39 38 Chile 2
40 39 China 3
41 40 Chipre 3
42 41 Colombia 2
43 42 Comoras 1
44 43 Corea del Norte 3
45 44 Corea del Sur 3
46 46 Costa Rica 2
47 45 Costa de Marfil 1
48 47 Croacia 4
49 48 Cuba 2
50 49 Dinamarca 4
51 50 Dominica 2
52 51 Ecuador 2
53 52 Egipto 1
54 53 El Salvador 2
55 54 Emiratos Árabes Unidos 3
56 55 Eritrea 1
57 56 Eslovaquia 4
58 57 Eslovenia 4
59 58 España 4
60 59 Estados Unidos 2
61 60 Estonia 4
62 61 Esuatini 1
63 62 Etiopía 1
64 63 Filipinas 3
65 64 Finlandia 4
66 65 Fiyi 5
67 66 Francia 4
68 67 Gabón 1
69 68 Gambia 1
70 69 Georgia 3
71 70 Ghana 1
72 71 Granada 2
73 72 Grecia 4
74 73 Guatemala 2
75 74 Guinea 1
76 76 Guinea Ecuatorial 1
77 75 Guinea-Bisáu 1
78 77 Guyana 2
79 78 Haití 2
80 79 Honduras 2
81 80 Hungría 4
82 81 India 3
83 82 Indonesia 3
84 83 Irak 3
85 85 Irlanda 4
86 84 Irán 3
87 86 Islandia 4
88 87 Islas Marshall 5
89 88 Islas Salomón 5
90 89 Israel 3
91 90 Italia 4
92 91 Jamaica 2
93 92 Japón 3
94 93 Jordania 3
95 94 Kazajistán 3
96 95 Kenia 1
97 96 Kirguistán 3
98 97 Kiribati 5
99 98 Kuwait 3
100 99 Laos 3
101 100 Lesoto 1
102 101 Letonia 4
103 103 Liberia 1
104 104 Libia 1
105 105 Liechtenstein 4
106 106 Lituania 4
107 107 Luxemburgo 4
108 102 Líbano 3
109 108 Macedonia del Norte 4
110 109 Madagascar 1
111 110 Malasia 3
112 111 Malaui 1
113 112 Maldivas 3
114 114 Malta 4
115 113 Malí 1
116 115 Marruecos 1
117 116 Mauricio 1
118 117 Mauritania 1
119 119 Micronesia 5
120 120 Moldavia 4
121 122 Mongolia 3
122 123 Montenegro 4
123 124 Mozambique 1
124 118 México 2
125 121 Mónaco 4
126 125 Namibia 1
127 126 Nauru 5
128 127 Nepal 3
129 128 Nicaragua 2
130 130 Nigeria 1
131 131 Noruega 4
132 132 Nueva Zelanda 5
133 129 Níger 1
134 133 Omán 3
135 135 Pakistán 3
136 136 Palaos 5
137 137 Palestina 3
138 138 Panamá 2
139 139 Papúa Nueva Guinea 5
140 140 Paraguay 2
141 134 Países Bajos 4
142 141 Perú 2
143 142 Polonia 4
144 143 Portugal 4
145 144 Reino Unido 4
146 145 República Centroafricana 1
147 146 República Checa 4
148 148 República Democrática del Congo 1
149 149 República Dominicana 2
150 147 República del Congo 1
151 150 Ruanda 1
152 151 Rumanía 4
153 152 Rusia 3
154 153 Samoa 5
155 154 San Cristóbal y Nieves 2
156 155 San Marino 4
157 156 San Vicente y las Granadinas 2
158 157 Santa Lucía 2
159 158 Santo Tomé y Príncipe 1
160 159 Senegal 1
161 160 Serbia 4
162 161 Seychelles 1
163 162 Sierra Leona 1
164 163 Singapur 3
165 164 Siria 3
166 165 Somalia 1
167 166 Sri Lanka 3
168 167 Sudáfrica 1
169 168 Sudán 1
170 169 Sudán del Sur 1
171 170 Suecia 4
172 171 Suiza 4
173 172 Surinam 2
174 173 Tailandia 3
175 174 Tanzania 1
176 175 Tayikistán 3
177 176 Timor Oriental 3
178 177 Togo 1
179 178 Tonga 5
180 179 Trinidad y Tobago 2
181 181 Turkmenistán 3
182 182 Turquía 3
183 183 Tuvalu 5
184 180 Túnez 1
185 184 Ucrania 4
186 185 Uganda 1
187 186 Uruguay 2
188 187 Uzbekistán 3
189 188 Vanuatu 5
190 189 Vaticano 4
191 190 Venezuela 2
192 191 Vietnam 3
193 192 Yemen 3
194 193 Yibuti 1
195 194 Zambia 1
196 195 Zimbabue 1

View file