Saltar al contenido
Entrar
Suscribirme

Cómo montar una auditoría SEO que corre sola (y sabe cuándo no enviar el informe)

Cada lunes a las 00:30, un cron lanza Screaming Frog contra cinco sitios; a las 04:00 un orquestador reclama cada crawl en Postgres, lo cruza con GA4 y Search Console, lo pasa por un modelo de lenguaje y suelta los informes entre las 07:45 y las 07:54. Lo difícil no fue que corriera sin nadie delante: fue enseñarle a decir que no. Jacobo J. Nasser cuenta cómo un guardián retuvo un informe que no cuadraba, cómo un reloj dejó de sonar sin dejar rastro y qué hace falta para fiarse de un sistema que trabaja mientras duermes.

Jacobo J. Nasser con ayuda de IA

Su autor lo publicó primero en Athens SEO el 8 de septiembre de 2026

Publicado el Revisado el 16 min de lectura

Diagrama

Un lunes, del crawl a la bandeja de entrada, con el vigilante mirando desde fuera.

El lunes 31 de agosto, a las 04:00:34, el sistema hizo exactamente lo que tenía que hacer: se despertó, reclamó el primer trabajo y empezó a analizar. A las 04:02:05 estaba muerto. Un número donde se esperaba una cadena: 1 en vez de «1». A las 06:00 el vigilante mandó su informe con un aviso: nada se había asentado. Yo lo leí a las 09:30. Este artículo va de todo lo que pasó entre esas tres horas.

Llevo trece años haciendo esto. Llevo sesenta y siete en el mundo. Cuando decidí que las auditorías técnicas de mis clientes tenían que correr sin mí, no quería solo que corrieran desatendidas. Quería algo más difícil: un sistema que supiera cuándo el informe no debe salir. Lo que sigue es cómo se construyó, dónde se rompió con carga real y el momento en que dejó de ser una colección de automatizaciones y pasó a ser un sistema del que me fío.

Qué hace falta

Nada exótico. Todo lo de abajo corre en mi propio servidor, un VPS con Debian y Docker, con estas piezas:

  • Screaming Frog SEO Spider, con licencia, porque el modo de línea de comandos (headless) es de pago, y con las integraciones de GA4 y Search Console configuradas para cada sitio.
  • n8n como orquestador. Podría ser cualquier otra herramienta capaz de despertarse a una hora, ejecutar SSH y hablar con una base de datos; el diseño no depende de n8n.
  • PostgreSQL como única fuente de verdad. Aquí importa una capacidad en concreto: SELECT … FOR UPDATE SKIP LOCKED.
  • Acceso a las propiedades de GA4 y de Search Console de cada sitio auditado.
  • Una clave de API de un modelo de lenguaje, para el análisis URL a URL, y un buzón desde el que enviar los informes.
  • Un cron. El del propio sistema operativo. Nada más.

Qué significa «desatendido» de verdad

La cadena, tal como corre cada lunes en ese servidor, tiene ocho pasos.

  1. Un crawl sin nadie delante. A las 00:30 un cron lanza Screaming Frog en modo headless contra cinco sitios, uno detrás de otro, con un bloqueo que impide que dos crawls corran a la vez. Cada crawl tiene acceso a las propiedades de GA4 y Search Console de ese sitio y deja una carpeta con su CSV. Los cinco sitios en serie tardan alrededor de una hora.
Listado de carpetas en el servidor, una por crawl, con la fecha y la hora exactas en el nombre
Cada carpeta es un trabajo; su nombre lleva la hora exacta del crawl.
  1. Una tabla que manda. Cada carpeta se registra como un trabajo en Postgres con un estado (descubierto → reclamado → importado → analizando → informe listo → informe enviado), y cada cambio de estado deja un evento con quién lo hizo y cuándo. Hasta agosto, «la verdad» vivía en un fichero de texto. Ya no.
La tabla de trabajos en Postgres, con una fila por carpeta, su estado y sus eventos
La tabla manda; el informe y el vigilante solo la leen. Dominios de clientes difuminados.
  1. Una reclamación que no puede chocar. A las 04:00 un planificador despierta al orquestador. Lo primero que hace es reclamar un trabajo en una sola transacción: SELECT … FOR UPDATE SKIP LOCKED coge la siguiente fila pendiente y, en la misma operación, un UPDATE la marca como reclamada. Dos reclamaciones simultáneas nunca pueden llevarse la misma carpeta. La idea entera cabe en dos líneas:
WITH cand AS (SELECT id FROM jobs WHERE status IN ('discovered', 'retry') ORDER BY folder DESC LIMIT 1 FOR UPDATE SKIP LOCKED)
UPDATE jobs SET status = 'claimed', claimed_at = now() FROM cand WHERE jobs.id = cand.id RETURNING jobs.folder;

(Los nombres de la tabla y de las columnas están traducidos; la forma es la que corre en producción.)

  1. El dominó. El orquestador procesa una carpeta y se vuelve a llamar a sí mismo cada 90 segundos mientras queden trabajos, con un tope de doce rondas. Cinco sitios, cinco rondas, unos siete minutos.
Las ejecuciones del orquestador en n8n el 4 de septiembre: una arranca a las 04:00 y las demás se encadenan cada 90 segundos
Un reloj; el resto es dominó. Cada ejecución se queda abierta hasta la puerta de las 07:45, de ahí que dure más de tres horas.
  1. El análisis. Un segundo flujo, con 48 nodos, cruza el crawl con GA4 y Search Console URL a URL y hace triaje. Los umbrales son decisiones de esta instalación, no leyes: fuera las URL no indexables, las respuestas 4xx y 5xx, todo lo que tenga menos de diez impresiones o esté más allá de la posición 50, y un tope de URL nuevas por ejecución para que ningún lunes dispare la factura. Después mira una caché. Cada URL lleva una huella calculada a partir de sus métricas; si la huella no ha cambiado y el veredicto tiene menos de 30 días, se reutiliza el análisis guardado y no se paga uno nuevo. Lo que pasa el filtro va al modelo de lenguaje. El informe final se escribe en el idioma del dueño del negocio, no en el de quien mide.
El lienzo del flujo de análisis en n8n, con sus 48 nodos
De izquierda a derecha: caché, triaje, análisis y render. Los nombres de los nodos son reales; solo son demasiado pequeños a esta escala.
  1. El guardián (Gatekeeper). Antes de enviar nada, un nodo cuadra el informe. Buena parte de este artículo va de ese nodo.
  2. La puerta. Los informes preparados de madrugada se retienen y se sueltan entre las 07:45 y las 07:54, escalonados 75 segundos. Un informe enviado a las 04:10 amanece enterrado bajo el correo de la noche. Enviado justo antes de las 08:00, queda cerca de lo alto de la bandeja.
  3. La confirmación. «Entregado» es un estado interno con una definición deliberadamente estrecha: Gmail aceptó el envío, devolvió un identificador de mensaje y ese identificador quedó guardado en el trabajo. No significa que el correo se haya leído, ni que no pueda rebotar después. Significa que el mensaje salió de verdad del sistema y hay pruebas de que salió.

Y fuera de la cadena, mirando hacia dentro, está el vigilante (watchdog). Cada cinco minutos, un cron lee la tabla. En cuanto no hay nada en marcha y algo se ha asentado, manda un informe preliminar. Una vez. Si a las 06:00 no se ha asentado nada, manda el informe igualmente, esta vez con un aviso delante. Un lunes en silencio es el peor informe de todos. A las 08:30 llega el informe final; ese ya no avisa. Cuenta.

El vigilante es el único componente que no depende del sistema que vigila. Su contrato entero cabe en una línea del crontab:

*/5 4-8 * * 1  /opt/jobs/watchdog.sh   # lunes, 04:00–08:59: lee la tabla; a las 06:00 avisa si nada se ha asentado
El correo del vigilante del 31 de agosto a las 06:00, con el aviso de que nada se había asentado
El fusible haciendo su trabajo: nada se había asentado, y el sistema lo dijo igualmente. Traducido del original.

El reto

Automatizar un crawl es una tarde. Automatizar un informe es una semana. Lo caro de verdad es automatizar la palabra no: construir un sistema que pueda decidir que un informe no debe salir, y decirte por qué.

Un cliente que recibe un informe con una URL menos de las que debería puede no darse cuenta. Un cliente que recibe el mismo informe dos veces, sí. Y un cliente que recibe un informe construido sobre un crawl a medias puede tomar decisiones con datos que no existen. Ninguna de las tres cosas tiene que pasar para que sea un problema. El 17 y el 24 de agosto se reprocesó el mismo dominio, y solo la suerte evitó un duplicado. Esa es la suerte que decidí quitar de en medio.

Estrategia: enseñar al sistema a decir que no

El guardián cuadra tres cosas

Antes de que salga un correo, el guardián compara:

  • Lo que el informe enseña con lo que debería enseñar: las URL de la caché más las nuevas que admitió el tope de coste.
  • Lo que hay en la base de datos con lo que se admitió a análisis: si se pidieron veinte análisis nuevos, tiene que haber veinte filas completadas.
  • Si el trabajo ya se ha entregado. Si tiene marca de envío, se retiene aunque todo lo demás cuadre.

Si algo no cuadra, el trabajo pasa a «retenido», yo recibo un correo interno con la conciliación línea a línea, y el cliente no recibe nada.

No hay una sola barrera contra los duplicados. Hay tres, cada una en su sitio. Primera, la reclamación atómica: una carpeta solo se puede reclamar una vez. Segunda, la identidad al reclamar: mismo dominio, misma fecha de crawl, misma versión del pipeline y ya entregado; el trabajo se aparta solo. Tercera, la marca de envío que comprueba el guardián.

No prometo que nada sea imposible. Prometo que, para que un cliente reciba el mismo informe dos veces, tienen que fallar las tres barreras.

El correo interno del guardián del 17 de agosto, con la conciliación línea a línea y dos líneas marcadas como MISMATCH
Lo que esperaba (63), lo que encontró (62 y 62), por qué cerró la puerta y lo que se aprendió una semana después. Dominio del cliente y URL difuminados de forma irreversible; sin metadatos.

El día que dijo que no, aunque el problema no era el que parecía

El 17 de agosto a las 08:08, el guardián contó 63 URL en la entrada, 62 en el informe y 62 en la base de datos. Marcó MISMATCH en dos líneas, identificó la URL que faltaba y no envió.

Hizo exactamente lo que tenía que hacer con la información que tenía: ante una diferencia que no podía explicar, cerró la puerta.

Una semana después, investigando, descubrimos que el descuadre podía ser un falso positivo. Las URL que quedaban fuera por el tope de coste se contaban como «desaparecidas» cuando en realidad se habían apartado a propósito. Corregimos el guardián para que supiera distinguirlas.

El orden importa: el guardián dijo que no, un humano investigó y el guardián aprendió a distinguir. Eso es lo que significa las decisiones siguen siendo humanas. No significa que un humano tenga que pulsar el botón de enviar. Significa que un humano decide qué quiere decir «mal».

Los otros «no» del sistema

  • Huérfanos. Un trabajo reclamado que lleva tres horas sin moverse vuelve a la cola como reintento y deja un evento. Ese lo pagué el 31 de agosto: un trabajo atascado cinco horas.
  • Falla abierto, pero con red. Si la base de datos no responde, la reclamación recae temporalmente en el viejo camino de ficheros para que el lunes no se bloquee. En cuanto la base de datos vuelve, la propia reclamación concilia lo que pasó en su ausencia antes de tocar nada nuevo. El guardián, que falla cerrado, sigue impidiendo el correo duplicado. Dos capas; cada una con su regla.
  • Todavía no. La puerta de las 07:45 también es un «no»: ahora no. Desatendido no significa «cuanto antes». Significa «cuando el humano lo va a leer».

El siguiente guardián

Hoy, el guardián comprueba que el informe cuadra consigo mismo. Lo que todavía no comprueba es si cuadra también con su destinatario: si el cliente, el sitio, el análisis, el informe y la dirección de correo son exactamente los que deben ser antes de que se abra la puerta.

Es el mismo principio aplicado a la entrega, y es lo siguiente que hay que construir. Lo menciono porque no quiero que nadie lea en este artículo una garantía que todavía no existe.

Hoy los informes aterrizan en mi bandeja como última parada antes del cliente; enrutar cada uno a su destinatario es la siguiente pieza, y por eso el guardián de la entrega va primero.

El reloj que no sonó

El lunes 31 el reloj sonó y el pipeline murió 91 segundos después por un carácter. Eso se arregla en cinco minutos:

String(Number(previous.iteracion || 0) + 1)   // el disparador quería "1"; el dominó pasaba 1

El martes fue peor, porque no dejó rastro.

Para probar el arreglo, monté un ensayo. Por la API añadí al reloj una segunda regla para el martes a las 04:00. El crawl corrió a las 00:30: cinco de cinco. A las 04:00, nada. Ni ejecución, ni error, ni una sola línea en ningún log. El vigilante hizo su trabajo: a las 06:00 mandó el aviso.

La investigación llevó una mañana. Esto es lo que pude verificar: n8n se había actualizado tres días antes; en esa versión, cada vez que se guarda un flujo activo, el planificador quita sus relojes de memoria y los vuelve a registrar; ese registro solo se escribe en el log a nivel de depuración, así que en producción es invisible; y el registro de eventos mezcla dos zonas horarias sin decirlo, la del flujo y la de la instancia, que si no se configura es la de Nueva York. Esto último no causó el fallo, pero puede convertir una lectura apresurada de los logs en una conclusión falsa.

Leí el código del planificador de principio a fin. Sobre el papel, el nuevo registro es correcto. Lo que no pude demostrar fue por qué esa regla añadida no produjo ninguna ejecución: si tuvo que ver con la actualización, con guardar desde la interfaz o con alguna otra condición.

Al día siguiente, las tres reglas que añadí por la API sonaron.

Las tres.

Sigo sin saber por qué.

Y hasta que lo sepa, no declararé sano un reloj solo porque el flujo corra a mano. El vigilante externo seguirá leyendo la tabla, y el aviso de las 06:00 seguirá siendo el fusible.

Lo que sí sé es lo que aprendí:

  • La ausencia de errores no es salud. Un reloj que no suena no escribe nada. Lo único que se dio cuenta fue un cron fuera del sistema, mirando una tabla.
  • Una ejecución manual no prueba el disparador. Ejecuta el lienzo, no lo publicado. La ejecución manual en verde del lunes me hizo creer que el reloj estaba sano.
  • La alarma tiene que llegar a donde está el humano. El aviso de las 06:00 fue perfecto, y fue a un buzón que no miré hasta las 09:30 porque estaba absorto en otra cosa. Ahora la misma alarma aparece en la terminal cada vez que abro una sesión de trabajo, sea cual sea el proyecto. No es la solución completa: mientras yo sea la única persona que puede reaccionar, el sistema tiene un punto único de fallo con nombre y apellido. El segundo canal y el segundo par de ojos son el siguiente trabajo.

Miércoles: tres relojes, cinco informes, ningún humano

Para despejar la duda, el miércoles 2 de septiembre puse tres relojes por la API a lo largo de la mañana: 08:30, 08:45 y 09:30. Cada uno estaba ahí para consumir lo que el crawl hubiera dejado.

Sonaron los tres.

Se entregaron cinco informes entre las 08:34 y las 09:35. Cero retenidos. Cero errores. Ciclos del crawl al correo de entre 5 y 25 minutos.

Esa tarde repetí el lunes entero, comprimido: crawl a las 12:00, un solo reloj a las 15:30, el vigilante con su fusible, la puerta a las 19:15 y el informe a las 20:00. Todo sonó y todo salió cuando debía, sin que nadie tocara nada en medio.

Pero un lunes comprimido sigue siendo un ensayo a las horas equivocadas.

La prueba que yo quería era la de verdad.

Viernes: la cadena del lunes, a las horas del lunes

Así que añadí una regla más al reloj: viernes 4 de septiembre a las 04:00. El horario exacto del lunes. La cadena entera dormida.

Y yo dormido también.

Hora de MadridPiezaResultado
00:30:01 → 01:28:09crawl, cinco sitios en serie5/5
04:00:34suena el reloj; el orquestador reclama el primer trabajocorrecto
cada ~90 s, cuatro vecesel dominócorrecto
04:10informe preliminar del vigilante5 listos · 0 retenidos · 0 fallidos, sin aviso
06:00el fusible del silenciono saltó
07:46:15 → 07:52:30la puerta suelta los cinco correos, escalonadoscorrecto
08:30:01informe final0 retenidos · 0 en marcha

Cinco informes con el guardián abierto, de 2 a 76 URL cada uno, todos con su identificador de Gmail guardado en el trabajo.

Ejecuciones con error en todo el día: cero.

La bandeja de entrada del viernes 4 de septiembre: el informe del vigilante a las 04:10, los cinco correos entre las 07:46 y las 07:52 y el recuento de las 08:30
Un lunes en una bandeja: el vigilante a las 04:10, la puerta entre las 07:46 y las 07:52, el recuento a las 08:30. Dominios de clientes difuminados; los dos visibles son sitios míos.
El informe final de operaciones del viernes, con quince entregas y cinco trabajos descartados
La ventana del informe cubre tres días, así que cuenta las quince entregas del miércoles por la mañana, el miércoles por la tarde y el viernes. Los cinco trabajos «fallidos» son los de la madrugada del miércoles, que descarté a propósito para que el ensayo arrancara con un crawl fresco; cada uno tiene su motivo registrado. Traducido del original.

Ese fue el momento en que dejé de tener una colección de automatizaciones y empecé a tener un sistema.

No cuando funcionó.

Cuando falló de dos formas distintas, me lo dijo por su cuenta y después completó una ejecución con forma de lunes sin ningún humano en medio.

El plano, por si construyes el tuyo

Lo mínimo que debe guardar cada trabajo. Su propio identificador; los identificadores del análisis (lote) y del sitio; el dominio; el estado; la versión del pipeline; la fecha del crawl; el número de intentos; el resultado del guardián, línea a línea; y los identificadores del informe y del envío.

Con eso, cualquier lunes se puede reconstruir sin depender de la memoria de nadie.

Cinco preguntas antes de declarar el verde:

  1. ¿Se procesó el trabajo correcto, y solo una vez?
  2. ¿El recuento esperado coincide con lo producido y con lo que dice la base de datos?
  3. ¿Se puede repetir sin duplicar resultados ni correos?
  4. ¿Hay un observador fuera del sistema capaz de detectar el silencio?
  5. ¿Se puede reconstruir lo que pasó sin preguntar al autor?

Lo que te diría si construyes uno

  1. Automatiza el «no» antes que el «sí». El crawl y el informe son la parte fácil. El guardián, la comprobación de identidad y el fusible son lo que te deja dormir.
  2. Una fuente de verdad, con eventos. Un fichero de texto no te dice quién cambió qué, ni cuándo. Una tabla con eventos, sí.
  3. Pon un vigía fuera del sistema. Si el sistema es lo único capaz de avisarte de que el sistema ha fallado, no te avisará.
  4. Lleva la alarma a donde estás. Después, llévasela a alguien más. Un correo perfecto en un buzón que nadie lee vale exactamente nada.
  5. Desconfía de la ejecución manual. Una ejecución a mano prueba el lienzo, no el reloj.
  6. Decide cuándo no enviar todavía. La hora a la que llega un informe es parte del informe.
  7. Deja que el guardián se equivoque, y después corrígelo. El falso positivo del 17 de agosto fue lo mejor que le pasó al guardián: aprendió a distinguir.

El sistema encuentra los problemas. Las decisiones siguen siendo humanas.

Y todo esto, por cierto, está escrito a dos dedos.

Markdown:

Comentarios

Solo los usuarios registrados con cuenta activa pueden participar en los comentarios.

No hay comentarios aún. Sé el primero en opinar.

Más en Software

Software

All in One SEO corrige dos fallos que se explotaban sin iniciar sesión

El plugin All in One SEO para WordPress, con más de dos millones de instalaciones activas, ha parcheado dos vulnerabilidades de gravedad media descubiertas por Wordfence: una ejecución de shortcodes arbitrarios (CVE-2026-100152) y un XSS basado en DOM (CVE-2026-85492). Las dos las puede lanzar alguien sin cuenta en la web, pero cada una exige una condición concreta. La versión 5.0.2.1 cierra las dos; la actual es la 5.0.3.

Índigo ClaudioIA revisado por Gema MagónIA 3 min
Siguiente noticia ¿Es el Update de Septiembre el verdadero fin de los nichos?