← Todos los posts

Automaticé un medio de noticias con n8n: de 20 feeds RSS a borradores en WordPress

Cómo construí en n8n un pipeline que lee 20 feeds RSS, deduplica con Postgres, cura y reescribe con IA por niveles, y deja el artículo como borrador en WordPress con imagen y aviso por Telegram.

Ilustración de un pipeline de noticias: varios feeds RSS convergen en un flujo que filtra, reescribe con IA y publica un borrador

Monté en n8n un pipeline que vigila una veintena de feeds RSS, descarta lo que ya vi, puntúa cada noticia con un modelo barato, reescribe solo las que valen la pena con un modelo más capaz y deja el resultado como borrador en WordPress —con imagen de portada y un aviso por Telegram. El objetivo no era “publicar solo”, sino quitar el trabajo mecánico: leer decenas de fuentes, decidir qué merece cobertura y dejar un primer borrador listo para revisar. Todo el flujo corre cada tres horas sin que yo toque nada, y lo importante no son los nodos sino las tres o cuatro decisiones de diseño que evitan que se llene de contenido irrelevante o que dispare el gasto en tokens.

Resumen para perezosos
  • Deduplica antes de gastar: hashea el link y deja que Postgres rechace lo repetido con ON CONFLICT DO NOTHING. Así no scrapeas ni reescribes dos veces la misma noticia.
  • Usa un modelo barato para puntuar la relevancia de 0 a 100 antes de reescribir: solo lo que supera el umbral llega al modelo caro. El gasto premium es la excepción, no la regla.
  • Deja todo en borrador, no en publicado. La automatización propone; tú apruebas. Y avísate por Telegram para revisar a tiempo.

En este artículo:

Qué hace el pipeline y por qué lo automaticé

Lo monté para un amigo que lleva un medio de noticias en WordPress. No digo cuál es —es su proyecto, no el mío—, pero el patrón se traslada a cualquier medio con el mismo problema: el cuello de botella no era escribir, sino el trabajo previo. Abrir veinte fuentes, leer titulares, decidir qué tiene interés para la audiencia y armar un primer borrador consume más tiempo que pulir el texto final. Ese trabajo es repetitivo y tiene reglas claras, así que es justo el tipo de tarea que conviene automatizar.

El pipeline hace exactamente ese recorrido. Cada tres horas lee los feeds, se queda con lo nuevo, descarta lo irrelevante con un filtro barato, reescribe lo que merece cobertura en un artículo original optimizado para SEO, le pone una imagen y lo deja como borrador. El editor solo entra a revisar lo que ya está casi listo. No es un generador de spam: es un asistente de redacción que hace la parte mecánica y deja la decisión editorial a una persona.

La clave es que cada paso está pensado para gastar lo mínimo. No todo lo que entra por un feed merece una llamada a un modelo caro, ni siquiera merece descargarse. El flujo es, en esencia, una secuencia de filtros ordenados por costo: los baratos van primero y descartan la mayoría, para que los caros solo procesen lo que llega al final.

El flujo de principio a fin

Antes de entrar en cada pieza, esta es la forma completa del pipeline. Se lee de izquierda a derecha y cada flecha es una salida de un nodo de n8n:

Schedule (cada 3h)


Lista de feeds ──► Split ──► RSS Read ──► Cap 3 por feed (Code)


                                       Hash del link (SHA256)


                                   Postgres: insert + dedup

                                       ¿es nuevo?

                                   ┌──────────┴───── No ──► descartar
                                   │ Sí

                              Scrape (Firecrawl)


                        LLM barato: score 0-100

                              ¿score ≥ 70?

                        ┌──────────┴───── No ──► descartar
                        │ Sí

              LLM por niveles: reescribe
              (Claude si score ≥ 85, si no Gemini)


        Imagen (Unsplash) ──► WordPress (borrador) ──► portada


        Postgres: marcar publicado ──► Telegram

Son tres bloques: conseguir candidatos (schedule, feeds, RSS), filtrarlos barato (dedup y score) y producir el borrador (reescritura, imagen, WordPress, aviso). El orden no es casual: cada filtro está colocado para descartar lo antes posible, cuando descartar todavía es gratis.

Deduplicar antes de gastar: hash + Postgres

El primer problema de cualquier pipeline de RSS es obvio en cuanto lo enciendes: los feeds repiten. La misma noticia aparece en varias fuentes, y el mismo artículo sigue en el feed durante horas o días. Si no deduplicas, cada corrida vuelve a scrapear, puntuar y reescribir lo mismo, y eso es gasto puro: ancho de banda, llamadas a la API de scraping y, sobre todo, tokens.

La defensa es deduplicar antes de gastar nada. Hasheo el enlace del artículo con SHA256 y lo uso como clave única en una tabla de Postgres (uso Neon, pero da igual el proveedor). El truco está en dejar que la base de datos haga el trabajo de decidir si algo es nuevo, con un INSERT ... ON CONFLICT DO NOTHING:

-- La tabla que recuerda lo que ya vi.
CREATE TABLE seen_articles (
  id          BIGSERIAL PRIMARY KEY,
  url_hash    TEXT UNIQUE NOT NULL,   -- SHA256 del link; la clave de dedup
  source_url  TEXT NOT NULL,
  title       TEXT,
  published   BOOLEAN DEFAULT FALSE,  -- ¿llegó a borrador en WordPress?
  wp_post_id  BIGINT,                 -- id del post, para enlazarlo de vuelta
  created_at  TIMESTAMPTZ DEFAULT now()
);
-- En el nodo de Postgres: intenta insertar; si el hash ya existe, no hagas nada.
INSERT INTO seen_articles (url_hash, source_url, title)
VALUES ($1, $2, $3)
ON CONFLICT (url_hash) DO NOTHING
RETURNING id, source_url;

El detalle que hace que esto funcione es el RETURNING. Cuando el insert es nuevo, Postgres devuelve una fila con el id; cuando el hash ya existía, el DO NOTHING no inserta y no devuelve fila. Así que el nodo siguiente solo tiene que preguntar una cosa: ¿vino un id? Si vino, es noticia nueva y continúa; si no, es un duplicado y se descarta.

Postgres devuelve fila (id)  ──►  noticia nueva, continúa
Postgres no devuelve nada    ──►  duplicado, se descarta

Esto me gustó por dos razones. La primera es que la deduplicación es atómica: aunque dos corridas se solapen, el índice único garantiza que solo una gana el insert, sin condiciones de carrera. La segunda es que el “estado de lo ya visto” vive en un solo lugar consultable, no esparcido en memoria del workflow. Esa misma tabla la cierro al final marcando published = true y guardando el wp_post_id, así sé no solo qué vi, sino qué llegó realmente a WordPress.

Un apunte que ahorra dolores: hashea el campo más estable que tengas. El enlace canónico suele servir, pero si tus feeds le cuelgan parámetros de tracking (?utm_source=...), normalízalo antes de hashear o el mismo artículo con dos UTM distintos contará como dos.

Curaduría barata antes de reescribir caro

Esta es la decisión de diseño que más reduce el costo. Que una noticia sea nueva no significa que merezca cobertura. Antes de gastar un modelo caro en reescribirla, la evalúo con un modelo barato cuyo único trabajo es asignar una puntuación de relevancia: “¿qué tan relevante es esta noticia para el sitio, en una escala de 0 a 100?”.

El prompt es deliberadamente mínimo y la salida deliberadamente diminuta —solo un número— para que la llamada sea lo más barata posible:

system: Eres un curador editorial. Evalúa la relevancia de la noticia
        para un sitio de tecnología y negocios. Responde SOLO un JSON:
        {"score": <0-100>}. Nada más.
user:   Título: {{title}}
        Contenido: {{markdown}}

Con esa puntuación, una condición simple decide el resultado: si llega al umbral que definí (70), la noticia pasa a la reescritura; si no, se descarta. La mayoría de las noticias se descartan en este punto, que es precisamente el objetivo: el modelo caro solo procesa lo que supera el filtro.

score ≥ 70  ──►  vale la pena, reescribir
score < 70  ──►  descartar (no llega al modelo caro)

Es exactamente el patrón de routing de modelos —barato por defecto, caro solo donde importa— pero aplicado como filtro de entrada en lugar de como elección de modelo. Le dediqué un post entero a la idea general en routing de modelos con OpenRouter y DeepSeek; aquí lo interesante es que el modelo barato no resuelve la tarea, solo decide si la tarea vale el gasto. Un clasificador de relevancia es la clase de tarea donde un modelo barato rinde igual que uno caro, así que pagar un modelo premium solo para puntuar sería un gasto innecesario.

Hay un orden que conviene respetar: el score va después del dedup pero antes de la reescritura. Deduplicar es gratis (una consulta a Postgres), puntuar es barato (un modelo pequeño con salida de un número) y reescribir es lo caro. Cada filtro descarta antes de que el siguiente, más caro, entre en juego.

Reescritura por niveles: Gemini o Claude según la puntuación

Las noticias que pasan el filtro no son todas iguales. Una nota rutinaria y una exclusiva que va a generar mucho tráfico merecen distinto esfuerzo. Como ya tengo un score de relevancia, lo reutilizo para elegir el modelo de reescritura: por encima de cierto umbral uso el modelo más capaz; por debajo, uno bueno pero más barato.

// El modelo de reescritura se elige según el mismo score de relevancia.
const model = score >= 85
  ? "anthropic/claude-sonnet-4.6"     // top: lo que va a rendir más
  : "google/gemini-3-flash-preview";  // sólido y más barato: el resto
Tramo de score Qué representa Modelo de reescritura
70-84 Relevante, cobertura estándar Gemini Flash (barato)
85-100 Alto interés, vale el esfuerzo extra Claude Sonnet (premium)
< 70 No pasa el filtro — (descartado)

Así el gasto premium se concentra donde el retorno es mayor. La mayoría de los artículos se reescriben con el modelo barato, y solo el tramo alto —el que probablemente atraerá más lectores— pasa al modelo caro. Es el mismo criterio aplicado un nivel más adentro: no solo decido si vale la pena reescribir, sino cuánto conviene gastar en cada reescritura.

La reescritura no es un “resume esto”. El prompt pide un artículo 100% original en español neutro, sin copiar frases del original, con una estructura periodística concreta: un primer párrafo que responda qué/quién/cuándo, secciones con subtítulos <h2>, datos clave en <strong> y un cierre de contexto. Y exige conservar cifras, nombres y fechas con exactitud, sin inventar nada. Reescribir no es plagiar ni alucinar: es producir una nota propia a partir de los hechos de la fuente.

Salida estructurada y parseo tolerante

Para que el resultado entre directo en WordPress, el modelo no devuelve prosa suelta sino un JSON con todo lo que necesita un post: título, excerpt (la meta description), content_html, una palabra clave para buscar imagen, los tags y la focus keyword de SEO.

{
  "title": "...",            // ≤ 60 caracteres, keyword al inicio
  "focus_keyword": "...",
  "excerpt": "...",          // 140-155 caracteres
  "content_html": "<p>...",  // ≥ 400 palabras, con <h2> y <strong>
  "image_keyword": "...",    // 1-2 palabras para buscar la foto
  "tags": ["...", "..."]
}

El problema clásico de pedir JSON a un LLM es que a veces lo envuelve en un bloque de markdown o le añade una frase antes (“Aquí tienes el artículo:”). Un JSON.parse directo se rompe con eso. La defensa barata es un parseo tolerante: en vez de confiar en que la respuesta es JSON puro, recorto desde la primera llave de apertura hasta la última de cierre y parseo solo eso.

// Recorta cualquier texto antes/después del JSON y parsea lo de en medio.
const raw = $json.choices[0].message.content;
const json = raw.substring(raw.indexOf("{"), raw.lastIndexOf("}") + 1);
const article = JSON.parse(json);

No es elegante, pero es robusto frente al fallo más común: un modelo que responde bien pero lo adorna. Después de ese parseo, un nodo de filtro comprueba que el objeto article exista de verdad antes de seguir. Si la reescritura salió mal y no hay JSON válido, el artículo se cae aquí en lugar de llegar roto a WordPress. La regla que me funciona en todo el pipeline: valida en cada frontera, y prefiere descartar un item antes que propagar basura.

De JSON a WordPress: imagen, borrador y portada

Con el artículo ya estructurado, la última parte es sobre todo integración, pero tiene un par de detalles que vale la pena explicar. El orden importa porque WordPress necesita la imagen subida antes de poder marcarla como portada.

  1. Buscar imagen. Con la image_keyword que generó el modelo, busco en Unsplash una foto horizontal y me quedo con la primera. Guardo también el crédito del autor —“Foto de {nombre} en Unsplash”— porque atribuir no es opcional.
  2. Descargar y subir. Bajo la imagen como binario y la subo a la librería de medios de WordPress vía su API REST. Eso me devuelve un media_id.
  3. Crear el borrador. Creo el post con el título, el content_html y el excerpt, en estado draft.
  4. Asignar la portada. Con el media_id del paso 2 y el id del post recién creado, hago una segunda llamada que fija el featured_media.
Unsplash ──► Download ──► WP: subir media ──► (media_id)

WP: crear borrador ──► (post_id) ─────────────────┤

                        WP: fijar imagen de portada (featured_media)

Esa secuencia de dos pasos —crear el post y luego asignarle la portada— existe porque la API de WordPress trata el contenido y la imagen destacada como operaciones separadas. Intentar hacerlo en una sola llamada no funciona; separarlo en dos lo resuelve.

Borradores, no publicación automática

La decisión más importante de todo el pipeline no es técnica: el post se crea en estado borrador, nunca publicado. La automatización llega hasta dejar el artículo listo para revisar, y ahí se detiene. El editor entra, lo lee, ajusta lo que haga falta y publica.

Esto es deliberado y no pienso cambiarlo pronto. Un modelo puede reescribir bien el 95% de las veces, pero el 5% restante —un dato mal interpretado, un titular desafortunado, una nota que en realidad no encajaba— es justo lo que no quieres que salga solo a tu sitio. El humano en el bucle cuesta unos minutos por artículo y evita el riesgo de publicar algo que después haya que retractar. La regla que dejé escrita en el propio workflow: el estado es borrador a propósito; cámbialo a publicado solo cuando confíes en la calidad de la salida.

Al terminar, el workflow marca el artículo como published = true en Postgres (guardando el wp_post_id) y envía un aviso por Telegram con los títulos de lo que acaba de entrar a borradores. El aviso es la pieza de observabilidad que evita que la automatización se vuelva invisible: si cada tres horas no llega nada durante un día entero, sé que algo se rompió antes de que el sitio se quede sin contenido.

Neon: marcar publicado ──► Telegram: "Nuevo borrador: {títulos}"

Limitaciones y lo que vigilo

Automatizar esto no es gratis en mantenimiento. Estas son las aristas que tengo presentes:

  • Calidad de las fuentes. El pipeline es tan bueno como sus feeds. Una fuente ruidosa o de baja calidad mete candidatos malos que el score tiene que filtrar; conviene curar la lista de feeds, no solo añadir.
  • Deriva del score. El umbral de 70 lo ajusté de forma aproximada. Si lo pongo muy bajo, entra contenido irrelevante; muy alto, se descartan notas que sí valían. Hay que revisar de vez en cuando qué se está descartando.
  • Scraping que falla. No todas las webs se dejan scrapear igual; algunas bloquean o devuelven contenido parcial. Cuando el scrape sale pobre, la reescritura también, y eso solo se ve revisando.
  • Imágenes genéricas. Buscar en Unsplash por una palabra clave da fotos correctas pero a veces demasiado genéricas. Para un medio serio, la portada a veces hay que cambiarla a mano.
  • Originalidad real. El prompt pide no copiar, pero la línea entre “reescribir” y “parafrasear de cerca” es fina. Reviso muestras para asegurarme de que el resultado es genuinamente una nota propia.

Ninguna es razón para no automatizar, pero sí para no darlo por terminado una vez que funciona. Cada feed que se añade es una fuente más que mantener y vigilar.

Cuándo NO automatizar así

Esto compensa en un caso concreto: muchas fuentes, volumen alto y una línea editorial con reglas claras. Yo no montaría este pipeline si:

  • Publicas poco. Si sacas un par de notas a la semana, el trabajo de construir y mantener el flujo no se justifica; conviene hacerlo a mano.
  • Tu valor es el análisis propio. Si lo que te distingue es la opinión, la investigación o la voz, la reescritura automática de fuentes ajenas no es tu producto y puede diluir lo que te hace único.
  • No puedes revisar antes de publicar. Sin un humano en el bucle, tarde o temprano publicas algo incorrecto. Si no vas a revisar, mejor no automatices la generación.
  • Tus fuentes prohíben el uso. Reescribir noticias de terceros tiene implicaciones legales y éticas según la licencia de cada fuente. Vale la pena revisarlo antes de escalar.

La automatización paga cuando te quita trabajo mecánico sin quitarte el control editorial. Si para lograrla tienes que renunciar a la revisión, probablemente estás automatizando la parte equivocada.

Preguntas frecuentes

¿Por qué n8n y no un script propio?

Podría haberlo escrito en código, pero n8n me da las integraciones (RSS, Postgres, WordPress, Telegram, HTTP) ya resueltas y un lienzo donde el flujo se ve de un vistazo. Cuando algo falla, veo en qué nodo y con qué datos, sin montar logging a mano. Para un pipeline con muchos pasos heterogéneos, esa visibilidad vale más que la flexibilidad de un script.

¿Cuánto cuesta en tokens?

Poco, precisamente por el diseño de filtros. La mayoría de los candidatos se descartan en el dedup (gratis) o en el score (un modelo pequeño con salida de un número). Solo una fracción llega a la reescritura, y de esa fracción solo el tramo alto pasa al modelo caro. El gasto premium queda reservado para los artículos que probablemente más rinden.

¿No es esto una fábrica de spam con IA?

Depende de cómo lo uses. Si publicas sin revisar y llenas el sitio de notas reescritas sin control, sí. Aquí el uso es distinto: el filtro de relevancia es estricto, la reescritura exige originalidad y exactitud, y nada se publica sin revisión humana. La automatización hace la parte mecánica; la decisión editorial sigue siendo de una persona.

¿Por qué dejar el post en borrador y no publicarlo directo?

Porque el coste de un error publicado es mucho mayor que el de revisar unos minutos. Un dato mal interpretado o un titular desafortunado en tu sitio daña la credibilidad. El borrador entrega el 90% del trabajo hecho y reserva la decisión final a una persona, que es donde más aporta.

¿Sirve para otros destinos además de WordPress?

Sí. La parte de captación, dedup y curaduría es agnóstica del destino. Cambiar WordPress por otro CMS, un Google Doc o un canal de Slack es reemplazar los últimos nodos. El núcleo —filtrar barato antes de producir caro— se mantiene igual.

Conclusión

El mayor error al automatizar contenido es pensar que el reto es “generar con IA”. El reto real es no gastar de más ni publicar sin control: filtrar de forma estricta antes de producir, y mantener a una persona revisando antes de publicar. Este pipeline no destaca por usar modelos de lenguaje, sino por el orden en que coloca los filtros —dedup gratis, score barato, reescritura cara, revisión humana— de modo que cada paso caro solo procesa lo que pasó los filtros anteriores. Empieza por deduplicar, usa un modelo barato para puntuar, reserva el caro para lo que más rinde, y deja siempre la publicación bajo control de una persona.

Seguir leyendo