Separar lógica determinista y decisiones LLM

Publicado por Lino Uruñuela el 2026-05-19

 

TL;DRDurante las últimas semanas he medido cuántos tokens de pago necesito consumir para producir el mismo informe SEO ejecutado de dos formas distintas. Reescribir la skill como un script Python que solo invoca al LLM en 5 GATEs concretos bajó el coste de 8-10 € por auditoría a menos de 2 €, eliminó los pasos saltados por «eficiencia» del modelo y dejó cada run reanudable y reproducible meses después.  

 

 

Las últimas semanas las he pasado probando distintas maneras de ejecutar la misma tarea SEO, no para responder a "¿qué modelo funciona mejor?" sino a "Para el mismo informe final, ¿cuántos tokens de pago necesito consumir?".

La conclusión fue simple: lo que estaba diseñando con tanto prompt no era una skill, sino un programa. Y los programas se escriben en código, no en markdown.

 

Ejemplo de una auditoría de datos estructurados

Pongamos como ejemplo una auditoría de datos estructurados de una URL. A grandes rasgos los pasos son:

  1. Validar los datos estructurados de la URL usando la herramienta Rich Results Test de Google.

    No sirve una validación que el propio LLM haga con su propio conocimiento. Muchas veces no coincidirá con lo que dice el validador de Google, porque el modelo no sabe qué tipos son aptos para resultados enriquecidos o porque solo valida que el formato JSON sea correcto o valide con schema.org. Lo que queremos es saber si la herramienta de Google Prueba de resultados enriquecidos (Rich Results Test, RRT) reconoce los datos estructurados de la URL son aptos para generar resultados enriquecidos y si alguno tienen errores o warnings.

  2. Corregir los errores o warnings.

    Lo ideal es que un LLM corrija lo detectado por RRT. Para eso hay que pasarle (a) el código actual, (b) los errores que reportó la herramienta y (c) la documentación oficial de Google sobre las propiedades afectadas para cada @type. Con estos datos se genera un prompt que le pide al LLM el JSON-LD corregido.

  3. Validar el código generado por el LLM.

    No nos fiamos de lo que devuelva: hay que volver a pasarlo por la herramienta de Google. Si vuelve a fallar, reenviamos al LLM lo del paso 2 más el código que acaba de probar y los errores nuevos, para que sepa qué probó antes y qué corrigió o rompió. El máximo de reintentos lo dejamos en 3.

  4. Sugerir otros tipos de marcado.

    Aquí queremos saber si hay @type adicionales que merece la pena añadir y no estamos usando. Una forma es mirar qué resultados enriquecidos aparecen en las SERPs relacionadas con la URL objetivo y a qué tipos están asociados. Otra manera complementaria es ver qué competidores aparecen con esos snippets y qué marcado usan. Los @type que tienen ellos y nosotros no son la base para sugerencias de nuevos tipos.

    Para esto hay que saber qué buscar en Google, y eso se deriva del title, el H1 y los textos principales de la propia URL. Es decir, el flujo es: extraer on-page → decidir keywords → lanzar SERPs → identificar bloques enriquecidos → seleccionar competidores → extraer su marcado → cruzar con la URL objetivo → priorizar gaps → generar código corregido → validar → generar código nuevo → validar otra vez → redactar resumen.

 

 

El primer intento: una súper skill de 2.000 líneas

Al principio escribí una skill donde indicaba a Claude Code qué script podía usar para algunas subtareas, por ejemplo render_url.py para obtener el HTML, google_search_simple.py para lanzar una búsqueda, etc. Poco a poco fui creando pequeños scripts que resolvían subtareas que yo iba identificando como repetitivas e iba añadiendo a la documentación de la skill tanto el nuevo script como el modo de uso.

---
name: auditoria-marcado-datos
description: >
  **WHEN**: auditoría y optimización de datos estructurados (JSON-LD /
  Microdata / RDFa) de una URL. Opera con **scripts locales** - no
  delega en subagentes. Flujo determinista: extracción
  (`render_url.py` + `parse_schemas.py --json`), validación local
  opcional (`validate_jsonld.py`), validación **oficial** vía
  **Google Rich Results Test con sesión persistente**
  (`check_rich_results_url_authenticated.py` + fallback por código
  `check_rich_results_html_from_url_authenticated.py`)
  **solo para la URL objetivo**, …
---

 

El fichero completo de la skill (reglas, formato de salida, condiciones de fallback, etc.) acabó cerca de las 2.000 líneas. Demasiado largo según las recomendaciones habituales sobre cómo escribir skills. Pero funcionaba: si lo ejecutabas con Claude Code para una URL, generaba un informe completo y correcto.

El problema era el coste: una ejecución completa sobre una URL ecommerce mediana se iba entre 8 y 10 €. Sí, tal cual, por ejemplo estos son parte de los comandos y procesos que Claude Code realizaba para esta skill:

Click para ampliar
Logs de ejecución de Claude Code durante la auditoría SEO

 

 

Y este es el flujo, donde el propio LLM realizaba tareas extra además de las necesarias que yo iba definiendo. Por ejemplo tras una ejecución leía el resultado, con su coste añadido, comprobaba el número de líneas de un fichero o ejecutaba un comando en python importando librerías para corroborar que determinado dato estructurado tenía determinado elemento...

Flujo de ejecución de Claude Code con tareas adicionales realizadas por el LLM

 

Además del coste, tenía otro problema, a veces el LLM se salta algún paso por diversos e imprevisibles motivos. Al ejecutarlo desde Claude Code es raro que falle (la skill ha pasado por muchas iteraciones ajustándose concretamente a Claude), pero aun así también ocurren errores. Con Codex falla bastante más y el informe sale incompleto o con datos inventados.

Una de las cosas que añadí a la skill es la regla de que cualquier código Python que el agente generara durante la ejecución lo guardara en un script propio bajo el directorio codigo-usado/. Así, en un futuro, podría revisar qué código se generaba en cada ejecución y detectar subtareas repetitivas para las que el modelo siempre escribía un script nuevo «al vuelo». Cuando identificaba una, creaba un script y añadía la regla de usarlo y cómo usarlo en la propia skill.

Además también iba añadiendo otras reglas para que el LLM dejase de hacer algunas cosas: por ejemplo, prohibir generar Python para extraer schemas de un HTML porque ya existía parse_schemas.py. Poco a poco las reglas e instrucciones de la skill se iban convirtiendo en algo más parecido a pseudocódigo que a otra cosa.

 

El cambio: scripts deterministas + LLM solo en GATEs

Así que creé una segunda versión para que hiciese lo mismo pero basado en la ejecución de scripts específicos para aquellas subtareas que se pudiesen automatizar. El script en Python (run_auditoria.py) se encarga de coordinar la ejecución del resto de scripts y delega en el LLM solo cuando hace falta tomar alguna decisión.

Este script utiliza la suscripción que tengo y uso en Claude Code directamente, así que el coste real es 0 €. Pero calculo y almaceno cuánto habría costado la ejecución usando la API para todo lo que ocurre en Claude Code, también desde los scripts, que realmente usan claude code mediante el comando claude -p.

La idea es que todo lo que se pueda ejecutar desde un script, que lo haga el script. Los pasos donde hace falta una decisión los hace el LLM. En este ejemplo, el LLM solo interviene en cinco puntos concretos, los GATEs:

  • GATE 1: Selección de keywords SERP a partir los datos extraídos del HTML (title, H1, description, @types declarados).
  • GATE 2: Selección del TOP de competidores entre los dominios que aparecen en las SERPs capturadas.
  • GATE 3: Corrección del JSON-LD de la URL objetivo para resolver los errores y warnings que la herramienta de Google devolvió. Bucle de hasta 3 repeticiones si sigue encontrando errores o warnings.
  • GATE 4: JSON-LD sugerido que sugiere tipos de datos estructurados no presentes en la URL pero sí en competidores o en SERPs.
  • GATE 5: Resumen.

El resto del informe se genera en base a scripts (renderizar HTML, parsear schemas, lanzar SERPs, generar pantallazos, ensamblar markdown). Al ejecutar todo desde un orquestador puedo indicar qué modelo usar en cada fase, por ejemplo: Haiku para las decisiones sencillas como selección de keywords, competidores y la generación de un resumen. Y usar el modelo Opus para decisiones que requieran más "inteligencia" como generar el JSON-LD corregido o la sugerencia de nuevos tipos de datos y su código. Además no solo puedo elegir el LLM de Claude a usar en cada decisión sino que también puedo ejecutar LLM locales, en mi caso uso gemma4:e4b y qwen3.5:9b.

El coste de la ejecución completa de esta forma baja claramente frente a los 8-10 € de la ejecución de la skill directamente en Claude Code.

 

Comparativa de los dos enfoques

Enfoque Quién coordina Uso del LLM Coste estimado Reanudable Riesgo de pasos saltados
Skill larga en Claude Code LLM Todo el flujo 8-10 € Bajo / medio Alto
Orquestador Python + GATEs Script Solo decisiones concretas <2 € / coste real 0 € con suscripción Alto Bajo

 

 

Ejecución paso a paso

Al script orquestador se le pasa como argumento la URL a validar, el proyecto al que pertenece y una serie de parámetros: cuántas keywords analizar, cuántos competidores comparar y la fase (que por defecto es --step init, y es la que arranca el proceso completo). En el ejemplo de este artículo usé una URL de Zara:

url        = https://www.zara.com/es/es/mujer-vestidos-fiesta-l1581.html
proyecto   = STATE_OF_THE_ART
n_keywords = 3
n_comps    = 3

 

El fichero donde se guardan los datos y rutas que se usarán

El primer paso es generar un timestamp único (2026-05-12_07-09-09) y un slug a partir de la URL (www-zara-com-es-es-mujer-vestidos-fiesta-l1581-html). Este prefijo se usará en el nombre de todos los ficheros del proceso para poder saber en cualquier momento de la ejecución (y posterior) qué salida corresponde a qué ejecución.

El fichero state.json contiene todo lo que en el resto de pasos se va a necesitar: la URL, el proyecto, los parámetros, las 26 rutas absolutas de los ficheros que se van a generar, el estado actual de la ejecución, el historial y los contadores de repeticiones.

Si un paso necesita saber dónde está el HTML obtenido de la herramienta de datos estructurados, lo lee del state.json. Si necesita saber qué número de reintentos lleva procesados la corrección de JSON-LD, lo lee del state.json. Si necesita saber qué competidores se eligieron, lo lee del state.json.

Eso permite tres cosas que para mí son fundamentales:

  • Reanudación independiente de cada paso. Si el paso 11 peta por timeout del LLM, lo vuelves a lanzar sin tener que reejecutar del 1 al 10.
  • Reconstrucción de cada run. Dentro de seis meses puedo abrir el directorio y saber qué decidió el LLM en cada fase, qué respondió la herramienta de Google, en qué reintento consiguió solucionar los errores, etc.
  • Coste mínimo del agente. El modelo no necesita "recordar" todo lo que se ha hecho: en cada decisión recibe solo el contexto que necesita, nada más.

 

Validar la URL objetivo con Google Rich Results Test

El segundo paso es validar los datos estructurados de la URL objetivo con la herramienta de Google. El script accede a la URL de la herramienta usando una sesión persistente (un perfil de Chrome bajo un directorio concreto que mantiene la sesión Google del usuario), pega la URL y captura todo lo que la herramienta devuelve, y guarda en diferentes ficheros la información que usaremos en siguientes pasos:

Fichero Para qué sirve
PNG Captura del resultado de la herramienta
TSV Texto extraído del panel
HTML del panel HTML completo de lo que muestra la tool
HTML renderizado por Google DOM que Google ha visto al rastrear
summary.json Resumen estructurado de errores y warnings por @type

Para Zara, el resultado inicial fue:

ok                  = false
tipos detectados    = 4
tipos con errores   = 1
tipos con warnings  = 3
errores totales     = 1
warnings totales    = 10

Tipos detectados:

Tipo en RRT Estado
Fragmentos de productos 10 elementos válidos con warnings
Fichas de comerciantes 10 elementos válidos con warnings
Rutas de exploración 1 elemento no válido (error)
Carruseles 1 elemento válido con warnings

Warnings que la herramienta mostró:

  • Falta review.
  • Falta aggregateRating.
  • Falta availability.
  • Falta description.
  • Falta shippingDetails.
  • Falta hasMerchantReturnPolicy.
  • Falta un identificador internacional como GTIN o marca.
  • Falta url en el carrusel.

 

 

Qué ocurre cuando algo falla

El paso anterior no siempre funciona a la primera. Algunas URLs no son rastreables por Googlebot, otras tienen un meta robots noindex que impide a Google indexar la URL y genera errores en la tool de validación, etc.

Para esos casos en los que falla al validar los datos estructurados hay diferentes maneras de ejecutarlo:

  1. --mode url: pega la URL en la herramienta de datos estructurados de Google, la ejecuta, y espera el resultado.
  2. --mode code-from-url: si el anterior falla o RRT detecta «no rastreable por Googlebot», el script renderiza la URL localmente con Chrome, elimina las líneas que contienen <meta name="robots" content="noindex"> y pega ese HTML en la pestaña «Código» de la herramienta. Si el TSV de salida sigue vacío, reintenta usando User Agent de Googlebot.
  3. --mode payload: si todavía no hay salida válida, delega en el script render_url.py, que tiene sus propios métodos alternativos para obtener el HTML.

 

Qué HTML usar como base

Después de la validación con la tool, el sistema tiene que decidir desde qué HTML extraerá el on-page y los datos estructurados que usará en el resto del análisis. El script ejecuta una cascada determinista de 4 niveles:

  1. Si existe el HTML que devolvió la propia tool de Google (el que vio el rastreador), usa ese.
  2. Si no, ejecuta curl con User Agent de Googlebot.
  3. Si no, curl con UA de Chrome.
  4. Como último recurso, navegador real (shot-scraper con Chromium) ejecutando JavaScript.

En el ejemplo de Zara, el HTML base salió de la propia tool, que es el código que Google vio al rastrear, que es justo lo que nos vale como SEOs: el código que Google ve y valora.

 

Extracción on-page, schemas y validación local

Con el HTML ya obtenido se lanzan tres scripts en paralelo:

extract_onpage.py
parse_schemas.py
validate_jsonld_batch.py

El primero extrae con regex y reglas título, meta description, headings, canonical, hreflangs, robots, Open Graph y Twitter cards. En el caso de Zara:

title     = Vestidos de Fiesta | ZARA España
H1        = -
canonical = https://www.zara.com/es/es/mujer-vestidos-fiesta-l1581.html
hreflangs = 94 entradas

El segundo parsea los datos estructurados:

jsonld_count        = 1
microdata itemtypes = BreadcrumbList, ListItem

El tercero hace una validación local del JSON-LD. Es una validación secundaria que solo sirve como detección barata de errores antes de pasar por Google.

Con esto conseguimos que el LLM no vea ni procese el HTML completo. El HTML de Zara renderizado pesa varios MB y hacer que modelo "busque los schemas" en ese HTML cuando extraerlos mediante un script es muy sencillo y rápido sería tirar el dinero.

El script devuelve un JSON ya filtrado con lo que el siguiente paso necesita: title, description, tipos detectados, warnings, errores y bloques relevantes.

Los cinco GATEs del pipeline

 

GATE 1: seleccionar keywords

Hasta aquí no ha intervenido el LLM, todo ha sido procesado y ejecutado por scripts, y cada salida generada está almacenada en el fichero correspondiente.

En este paso el LLM decide qué keywords usará para realizar las búsquedas en Google. Para ello generamos un prompt en el que añadimos datos de contexto de la URL:

  • URL auditada.
  • Dominio.
  • Title.
  • H1.
  • Description.
  • Tipos de schema detectados.

En el prompt también se indica el número de keywords que queremos que devuelva, este dato se lo pasamos como argumento, y algunas reglas como "no usar keywords brand (que contengan el nombre del dominio)", o no usar "KWs navegacionales", etc.

En este ejemplo el LLM seleccionó las siguientes keywords:

vestidos de fiesta
vestidos de fiesta mujer
vestidos fiesta negros

Obtener datos de SERPs

Con las keywords seleccionadas, otro script (google_search_simple.py) ejecuta cada búsqueda y, por cada una, genera tres ficheros:

Fichero Contenido
JSON Resultados orgánicos + tipos de bloque (Carrusel, Listado de producto, Shopping, etc.)
PNG Captura de pantalla de la SERP
html.gz HTML completo de la SERP, comprimido

Para Zara:

Keyword Bloques detectados Aparición de la URL objetivo
vestidos de fiesta imágenes, PAA, orgánico 0/28 bloques
vestidos de fiesta mujer listado de productos, locales, imágenes, PAA, orgánico 0/33 bloques
vestidos fiesta negros listado de productos, sitios de productos, imágenes, PAA, orgánico 0/35 bloques

Esta información se guarda en un fichero llamado serp-blocks-…-2026-05-12_07-09-09.json, que será parte de la entrada del siguiente paso.

 

GATE 2: elegir competidores

Tras capturar las SERPs, el LLM vuelve a tomar una decisión, ahora debe elegir qué competidores analizar. Para ello se le pasa un JSON con los resultados de Google para cada una de las búsquedas, el tipo de resultado que es, y los propios datos estructurados de la URL examinada y se solicita que devuelva un número X que se le pasa también como argumento al inicio del proceso en este caso 3.

En el ejemplo de Zara seleccionó:

Competidor URL
coquettebonchic-es https://www.coquettebonchic.es/159-vestidos-de-fiesta
rosaclara-es https://www.rosaclara.es/es/vestidos-de-fiesta
shop-mango-com https://shop.mango.com/es/es/c/mujer/vestidos-y-monos/fiesta/a1ea71d0

Renderizar competidores y extraer su marcado

Por cada competidor el sistema (a) renderiza el HTML, (b) extrae los datos estructurados con parse_schemas.py y (c) los valida en local. Cada paso genera un fichero (render-…html, schemas-…json, localValidation-…json).

Cruzar datos para descubrir tipos de datos potenciales

El script schemas_matrix.py recibe todos los datos disponibles (schemas de la URL objetivo, validación local de la URL, schemas de cada competidor, validaciones locales de competidores, JSONs de SERP, pantallazos de SERP, resumen de la validación RRT) y genera un fichero llamado matrix-…-2026-05-12_07-09-09.json con cinco tipos de datos:

Capa Qué contiene
comparative_table Presencia de @type por URL objetivo y competidores
eligibility_matrix Qué tipos pueden activar rich results
serp_features Bloques que aparecen por keyword
serp_target_visibility Dónde aparece (o no) la URL objetivo en las SERPs
prioritized_recommendations Lista priorizada P0 / P1 / P2

En paralelo, se ejecuta el script schemas_doc_reference.py que consulta la documentación de Google previamente convertida a markdown y guardada en local, y añade la información correspondiente a los @type con errores o warnings detectados anteriormente.

Montar la plantilla del informe

Se crea la plantilla del informe y se deja lista para añadir las siguientes secciones:

  • Todas las secciones generadas por scripts deterministas (tabla resumen, datos RRT del target, comparativa, SERPs, recomendaciones, referencias) quedan añadidas al informe.
  • En los huecos donde luego irá el contenido del LLM, el script deja marcas identificables <!-- gate-section-XX:open --> … <!-- gate-section-XX:close -->, que serán reemplazadas por sus secciones correspondientes en cuanto se generen.

 

GATE 3: corregir errores en el JSON-LD actual

En este paso el LLM genera el JSON-LD corregido para cada tipo de marcado de datos que tuvo errores o warnings. Para generar el JSON corregido se genera un prompt con la información obtenida de la validación del código de la URL examinada, la documentación de Google referente a los atributos de cada tipo de datos con errores o warnings y se añaden también algunas reglas como:

  • Un fichero JSON por @type raíz (no todo en un único blob).
  • No recortar listas anidadas.
  • No dejar warnings pendientes.
  • No inventarse datos como si fueran reales (los inventados van marcados en el informe).

El proceso en caso de fallar volverá a intentarlo hasta tres veces:

  1. El LLM escribe los ficheros JSON corregidos por tipo.
  2. build_jsonld_payload.py los empaqueta en el HTML mínimo necesario para validar.
  3. Ese HTML se valida en la herramienta de datos estructurados en modo "Código", es decir, se accede a la URL de la tool, se pega el HTML corregido en la pestaña de "Código" y se ejecuta.
  4. Si Google devuelve errors=0 AND warnings=0, la validación fue correcta y anota state.iter_final['fixed'] con la iteración ganadora.
  5. Si sigue mostrando errores o warnings y el número de reintentos es menor de 3, se vuelve al inicio de la fase GATE 3, al que se le añade como contexto al nuevo prompt el resultado de la validación anterior y el código usado.
  6. Si se agotan los 3 reintentos, se marca state.residuals['fixed']=true y el informe deja la sección como residual no resuelto.

En el ejemplo de Zara el LLM generó dos ficheros:

breadcrumblist.json
itemlist.json

Y la validación cerró:

ok               = true
errores totales  = 0
warnings totales = 0

El LLM corrigió correctamente los datos estructurados que contenían error o warning (Fragmentos de productos, Fichas de comerciantes, Rutas de exploración y Carruseles) en una sola iteración.

Y es que no se debe confiar en que el LLM haya escrito bien el JSON-LD, se debe validar de nuevo el código generado usando la herramienta de Google para comprobar si es correcto.

 

 

GATE 4: sugerir marcado nuevo

Las condiciones para que se sugiera un nuevo JSON con algún tipo de dato estructurado que no está presente en la URL auditada son que:

  1. El @type aparece en al menos un competidor.
  2. Está asociado a algún rich result visible en las SERPs capturadas.
  3. La URL objetivo tiene contenido real que respaldaría el marcado.

Antes de validar, se comprueba si un tipo concreto propuesto ya existe en el JSON-LD corregido de la fase GATE 3; si lo está, se descarta. No tiene sentido sugerir lo que ya tienes.

Este paso también se puede saltar (skip_suggested=true) si no se detectan gaps justificables. Es decir, el sistema sabe decir "no hay nada que sugerir" en vez de inventarse oportunidades por alucinación.

En Zara generó un único fichero:

image-object.json

Y el JSON sugerido validó a la primera:

Metadatos de imagen - 1 elemento válido
errores  = 0
warnings = 0

GATE 5: resumen ejecutivo

El último GATE escribe un breve resumen.

El resultado:

 

 

El infomre completo lo podéis ver aquí

Informe de datos estructurados de ZaraEl infomre completo lo podéis ver aquí

 

Lecciones aprendidas

El ahorro no viene solo por usar modelos más baratos, que también. El ahorro viene de quitarle al LLM todo lo que no debería estar haciendo.

  • Si una tarea puede resolverse con un script, no debería consumir tokens haciendo pensar a un modelo de pago.
  • Si un proceso tiene pasos, estados, reintentos y salidas intermedias, probablemente no es una skill: es un programa o flujo de ejecución.
  • El LLM funciona mejor cuando decide en puntos concretos, con contexto mínimo y entradas ya filtradas.
  • Una auditoría generada por un agente no debería depender de que el modelo “recuerde” lo que hizo antes. El estado debe estar en ficheros.
  • Y, sobre todo, no se debe confiar en que el LLM haya escrito bien el JSON-LD: se debe validar de nuevo con la herramienta de Google.

Para mí, ese es el cambio importante: pasar de pedirle a un LLM que ejecute una auditoría completa a diseñar un sistema donde el LLM solo entra cuando aporta algo que un script no puede resolver de forma determinista.

 




Lea otros artículos de Arquitectura de agentes para SEO

Últimos posts

Últimos comentarios


LarisaRit
Thanks!
Post: Experimento para comprobar la teoría del primer enlace

Lino
@Javier Lorente ez horregatik!! Hasta parece que sabemos euskera :D
Post: Orquestando subagentes y LLMs locales para validar datos estructurados

Javier Lorente
Eskerrik asko!!
Post: Orquestando subagentes y LLMs locales para validar datos estructurados

Lino
@Emil8ano, no son tokens, son caracteres... Pero estoy casi seguro que el limite de texto en cada llamada aumentará rápidamente.
Post: aNótame: extensión para guardar notas y generar resumenes usando Gemini de manera local

Emiliano
Gran idea! Pregunta. Los 8000 caracteres no son tokens no? Si es así ojo que sin 8000 entre entrada y salida. O sea si te comes 6000 de ent
Post: aNótame: extensión para guardar notas y generar resumenes usando Gemini de manera local

Lino
@spamloco a tí r hacerme ver que no soy al único que le importa :p A ver si nos vemos!
Post: ¿Cómo decide Google que URL debe rastrear?

Alejandro
Gracias Lino, siempre investigando un poco más allá.
Post: ¿Cómo decide Google que URL debe rastrear?

Lino
3,2,1... Gracias a ti Pedro!! y sí, parece que los humanos somos expertos en haciendo ruido cuando intentamos que alguien nos escuche... :p
Post: ¿Cómo decide Google que URL debe rastrear?

Pedro
1,2...1,2... probando. Gracias por el artículo, verdaderamente interesante ver cómo no paramos de generar ruido :)
Post: ¿Cómo decide Google que URL debe rastrear?

Lino
Funcionan!! Ahora solo tengo que generar engagement :D A ver si quito lo de avisar por Twitter... no sé cuántos años llevará sin funcio
Post: ¿Cómo decide Google que URL debe rastrear?