2026-09-22 — Surtido → un solo engine (remoto): evaluación de fusión

Veredicto: fusionar SOLO la capa de captura (retrieval + almacén + dedup + búsqueda) en el engine del Linode. La capa editorial (terna, boletín, web, radio) y lo privado (likes/X, corpus paywall) se quedan donde están. Surtido deja de scrapear y pasa a consumir: feed.generar() se vuelve adaptador del export del engine. Con la fusión, el que scrapea es el remoto; el Mac deja de scrapear.

Evidencia medida hoy (batería del engine)

datovalorfuente
solape surtido ↔ engine100% (0 items solo-surtido en la ventana)2026-09-22-bateria-engine-resultados.md (unfetch)
profundidad (t3)titular 50% → texto 90% (grafo); perfil 0% → 70%ídem
fuentes del engine122 (65 RSS + 51 X + 6 TG) ya derivadas de feed.py + channels-todo.jsonnef-silo/recursos.json
capturacada 30 min en el Linode, con backoff/topes/salud (mejora 7)crontab Linode + silo.log
contenido en el engineaún NO (F1 pendiente: guardar content:encoded/summary)batería, decisiones F1/F2.5

→ El engine ya cubre el scraping de surtido. Mantener los dos es doble costo, dedup inconsistente y dos puntos de falla.

Gaps a cerrar antes de fusionar

  1. 3 fuentes de prensa sin cubrir: Página 12, Periodistas Unidos, Revista Espejo. (Chiapas Paralelo, Río Doce, Contralínea y Rompeviento sí están en recursos.json.)
  2. Temas: el engine usa 4 (Tech/IA 80 · Mundo 22 · Mexico 15 · Seguridad 5); el home de surtido usa 7 (+Economia, Ciencia, Cultura). Opción A: añadir los 3 temas al engine; opción B: home_feed.py re-clasifica (ya lo hace por palabras del titular).
  3. Campos del export: exportar() hoy manda solo titulo/url/tema/medio/publicado. Faltan lang, region, thumbnail y el flag en que usan la terna (candidatas_desde_feed) y el feed tech.
  4. Contenido (F1): sin texto, la terna del boletín sigue a ciegas; la batería mide el lift (50%→90%).

Destino por componente

Componente de surtidoDestino
feed.py (RSS/X/YT/TG/HN)FUSIONAR → engine; queda como adaptador delgado
scripts/prensa_noche.pyFUSIONAR (añadir sus 3 fuentes); muere el cron local y su fragilidad de DNS
papers_cn.py, pulso.py, ingest.py, nlp.py, sentimiento.py, traducciones.pyEVALUAR por caso: si alimentan la capa editorial, consumen del engine
hn.py (home) + scripts/home_feed.pyADAPTAR: leer el export del engine
asgi.py (Vercel: proponer/cron/subscribe/leer)QUEDA (producto)
proponer.py, mailing.py, cartel.py, imagen.py, miniaturas.pyQUEDA (producto)
radio (radio_push.sh + /opt/radio)YA VIVE en el remoto (precedente de fusión)
likes_*, fomo_release.py, pool-likes.jsonLOCAL-ONLY (privado; no al engine)
corpus news-silo (NYT/WaPo/Atlantic)LOCAL-ONLY (tiers T2/T3, decisión F2.5)

Interfaz engine → surtido

Opción 1 (recomendada): export JSON estático servido por nginx del Linode (p. ej. /silo/export-surtido.json), regenerado en cada capturar/todo. Público (solo metadatos), sin secretos nuevos, Vercel lo lee server-side. Añadir TLS (certbot) si molesta el HTTP.

Opción 2: artefacto Supabase (feed_engine) — surtido ya usa ese bus (propuesta_edicion, envio_ultimo); el engine necesitaría una key de escritura.

En ambos casos el adapter conserva la firma feed.generar(limite=40) para no tocar los 2 call sites de asgi.py (líneas 819 y 962).

Shape propuesto (compatible con proponer.candidatas_desde_feed):

{"generado": "ISO", "items": [{"url": "", "titulo": "", "medio": "", "tema": "",
  "publicado": "", "capturado": "", "lang": "", "region": "", "en": true,
  "thumbnail": null}]}

Fases (cada una termina en artefacto verificable)

  1. Acordar con la sesión nef-silo: F1 (contenido) + export enriquecido + fuentes/temas faltantes. → esta evaluación + lo que decida esa sesión.
  2. Añadir las 3 fuentes de prensa a recursos.json → capturar en el Linode → n_items > 0 por fuente.
  3. Export servido: engine escribe export-surtido.json + nginx → curl desde la Mac devuelve N items.
  4. feed.py --desde-engine: arma el doc desde el export; comparación 1:1 contra feed.generar() (mismo total/categorías, diff de títulos).
  5. /api/proponer y /api/cron con FEED_SOURCE=engine (flag) → una edición completa de prueba.
  6. Retirar el cron local de prensa_noche.py y congelar el scraping de feed.py (no borrar).

Riesgos / lista de NO

  • NO mover likes/X ni el corpus paywall al server (privacidad).
  • NO entregar el export por rsync local: reintroduce el punto de falla que tumbó el 17-18 sep (DNS del Mac a las 03:00).
  • NO duplicar captura (dos scrapers = doble costo, dedup inconsistente).
  • NO tocar asgi.py hasta que el adapter dé el mismo shape (2 call sites).
  • Un solo engine = punto único de falla → mitigación ya existente: salud/alertas del engine (mejora 7) + failsafe de >2 días en _resolver_edicion de asgi.py.
  • Rate limits RSSHub: radio + silo + surtido comparten cuota; el engine ya corre con topes por fuente.

Coordinación

La sesión nef-silo es dueña del engine (batería 2026-09-22-bateria-engine-resultados.md, F1/F2.5). Esta evaluación es el lado surtido: fuentes faltantes, export enriquecido y el adapter. Punto de contacto único: el shape del export.