Un comando audita la landing entera antes de lanzarla
Web · Principiante
SEO con un agenteaudita y corrige tu landing antes de lanzarla, en una sesión
La escena típica: la landing quedó preciosa, la pauta arranca el lunes, y a nadie se le ocurrió abrir /robots.txt ni ver qué title sale al compartirla por WhatsApp. Cuando alguien lo revisa ya hay tráfico corriendo y el fix compite con diez urgencias. El SEO técnico de una landing es aburrido y repetitivo, y justo por eso es trabajo para un comando: una lista fija, en un orden fijo, que el agente recorre completa cada vez sin que tengas que acordarte de nada. Aquí está ese comando, el código de Next.js que deja como resultado y la forma de comprobar que quedó bien.
De un vistazo
Robots, sitemap y structured data generados por código
También la deja lista para los buscadores de IA
01 el punto de partida
Por qué un comando y no un checklist en Notion
Ya tenía tres skills instaladas para SEO: una de auditoría técnica, una de structured data y una de optimización para buscadores de IA. Cada una funciona bien sola. El problema era el orden y la disciplina: en cada landing corría una o dos, nunca las tres, y nunca en el mismo orden. El resultado era un sitio con JSON-LD impecable y sin sitemap, o con sitemap y con el og:image apuntando a una ruta que no existía.
La solución fue un comando orquestador. /aplicar-seo no sabe nada de SEO por sí mismo: dice en qué orden correr las tres capas, qué archivos tocar en cada una y qué reportar al final. Lo que gana un comando sobre una lista en tu gestor de tareas es que el agente lo ejecuta completo y aplica los cambios en el repo, en vez de dejarte una lista de 'pendientes' que nadie va a hacer.
Lo que decide si Google puede leer la página: robots, sitemap, canónicas, encabezados, imágenes con dimensiones, fuentes, sin bloqueos accidentales.
Lo que decide cómo se ve la página en un resultado o al compartirla: title, description, Open Graph, Twitter cards, canonical por ruta.
JSON-LD que le dice a la máquina qué es la página: quién la publica, qué ofrece, qué preguntas responde. Validado antes de subir.
Que los bots de ChatGPT, Perplexity y Claude puedan entrar, y que exista un llms.txt que les diga de qué va el sitio y cuándo citarlo.
El orden importa. Structured data sobre una página que Google no puede rastrear es trabajo tirado. Por eso lo técnico va primero y lo de IA al final: cada capa asume que la anterior ya quedó.
02 para copiar
El comando /aplicar-seo completo
El description está escrito como lista de gatillos: las frases que realmente dices cuando quieres esto ('antes de lanzar la landing', 'no aparece en Google'). El cuerpo es el checklist. Si no tienes las tres skills de apoyo, el comando sigue sirviendo: el checklist es explícito y el agente lo puede recorrer solo.
mkdir -p ~/.claude/commands && touch ~/.claude/commands/aplicar-seo.md
git clone https://github.com/davidiriza-lab/aplicar-seo-claude-code.git
--- description: Auditoría SEO completa con correcciones aplicadas en el código (técnico + metadata + structured data + búsqueda por IA). Usa cuando se mencione "aplicar SEO", "auditar SEO", "mejorar SEO", "antes de lanzar la landing", "revisar meta tags", "agregar schema markup", "JSON-LD", "Core Web Vitals", "no aparece en Google", "optimizar para ChatGPT/Perplexity", o cualquier trabajo SEO sobre una landing o web propia. argument-hint: [ruta o URL opcional] allowed-tools: Bash(npm run build), Bash(curl *), Bash(grep *), Bash(find *), Bash(npx is-agentic *) --- # /aplicar-seo Audita el proyecto actual (o $ARGUMENTS si se indica) y APLICA cada corrección en el código. No entregues una lista de recomendaciones: entrega el repo corregido y un resumen. ## Paso 0 — Contexto 1. Detecta el stack (Next.js App Router, Astro, HTML estático) y el dominio de producción. 2. Lee la página principal y toda ruta pública. Anota qué es cada página (landing de servicio, evento, artículo, precios). 3. Pregunta SOLO si falta algo imprescindible: dominio final, nombre de la organización, imagen OG. Lo demás lo infieres del código. ## Paso 1 — Técnico (rastreo e indexación) Revisa y corrige, en este orden: - robots: existe, permite todo lo público, bloquea solo paneles y datos internos, declara Sitemap. En Next.js genera app/robots.ts. - sitemap: existe, se genera desde el código (no a mano), solo URLs canónicas e indexables, lastModified real. - canónicas: cada ruta pública tiene canonical absoluta y auto-referida. Sin http/https ni www mezclados. - noindex: solo en páginas que de verdad no deben salir. Verifica que ninguna pública lo tenga heredado de un layout. - encabezados: un solo H1 por página, jerarquía H2/H3 sin saltos. - imágenes: width y height siempre; la imagen del hero con priority (o fetchpriority=high); el resto lazy. Formato WebP/AVIF. - fuentes: cargadas con next/font o preload; nunca un @import bloqueante. - viewport y lang correctos en el html raíz. - Corre npm run build. Si falla, detente y repórtalo antes de seguir. ## Paso 2 — Metadata por ruta - metadataBase en el layout raíz con el dominio de producción. - title único por página (50-60 caracteres), con template "%s — Marca". - description única por página (120-160 caracteres), escrita para el clic, no para el robot. - openGraph: type, url, title, description, siteName, locale, images con width/height (1200x630 o similar). - twitter: card summary_large_image. - alternates.canonical en cada página, absoluta. - Comprueba que la imagen OG exista con curl -I y devuelva 200. ## Paso 3 — Structured data (JSON-LD) - Un solo bloque por página con @graph. Entidades con @id para enlazarlas entre sí. - Home: Organization o Person + WebSite. Landing de servicio: + Service con provider y offers. Evento: Event con startDate, location, offers. Artículo: Article/TechArticle con author, datePublished, dateModified. - BreadcrumbList en toda página que no sea la home. - FAQPage solo si las preguntas están visibles en la página. Recuerda que Google ya no muestra rich result de FAQ (retirado el 7 de mayo de 2026); se conserva por otros buscadores y por los agentes de IA. - Nunca marques datos que no estén visibles en la página (precios, reseñas, fechas). - Render: <script type="application/ld+json"> en el page.tsx, con JSON.stringify(...).replace(/</g, "\\u003c"). - Valida cada página con el Rich Results Test y el validador de schema.org antes de dar por terminado. ## Paso 4 — Búsqueda por IA - robots: distingue bots de BÚSQUEDA (OAI-SearchBot, Claude-SearchBot, PerplexityBot) de bots de ENTRENAMIENTO (GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, CCBot) y de los que abre el usuario (ChatGPT-User, Claude-User, Perplexity-User). Los de búsqueda y los de usuario siempre con Allow explícito: son los que citan. Los de entrenamiento son decisión del dueño; bloquearlos NO quita las citas. - Antes de tocar nada: si la página tiene dominio público, corre npx is-agentic <dominio> --json y guarda score, eligible_checks y la lista de issues. Es la línea base. - /llms.txt en la raíz: H1 con el nombre, blockquote con qué es el sitio y para quién, sección "Cuándo usar" y "Cuándo NO usar", listas de enlaces con una línea por página. - Cada sección de la landing abre con la respuesta directa en las primeras dos líneas (40-60 palabras), sin preámbulo. - Cifras con fecha y fuente; autor con nombre visible; fecha de actualización visible. ## Paso 5 — Reporte Termina con: - Correcciones aplicadas (archivo por archivo). - Pendientes que requieren a un humano (contenido, imagen OG nueva, enlaces externos). - URLs para verificar a mano: Rich Results Test, validator.schema.org, PageSpeed Insights. - Recordatorio: enviar el sitemap en Search Console y pedir indexación de la home con la Inspección de URL. - Después del deploy: volver a correr npx is-agentic <dominio> y comparar contra la línea base check por check, no solo la nota. Ignorar los checks de API/OpenAPI/portal de desarrolladores si el sitio es una landing sin API.
Dos decisiones de diseño. Primero, allowed-tools solo con lecturas y build: el comando edita archivos, pero no despliega ni toca producción; eso es otro comando. Segundo, el paso 0 prohíbe preguntar por lo que se puede inferir del código. Un comando que arranca con seis preguntas se deja de usar en una semana.
03 capa 1
Técnico: robots y sitemap generados por código
En Next.js App Router, robots y sitemap son archivos del directorio app que exportan una función. La ventaja sobre un robots.txt en public es que el sitemap se genera desde tu propia lista de páginas: cuando publicas una nueva, entra sola con su fecha real, sin que nadie edite un XML a mano. Fue el error más repetido en mis primeras landings: el sitemap tenía tres URLs viejas y la página que sí importaba no estaba.
import type { MetadataRoute } from "next";
const BASE = "https://tudominio.com";
export default function robots(): MetadataRoute.Robots {
return {
rules: [
// Todo el mundo: lo público sí, los paneles y datos internos no.
{ userAgent: "*", allow: "/", disallow: ["/admin", "/api", "/data"] },
// Bots de BÚSQUEDA por IA (los que citan) y los que abre un usuario desde el chat:
// explícitos, para que una regla futura no los bloquee por accidente.
{
userAgent: [
"OAI-SearchBot", "ChatGPT-User",
"Claude-SearchBot", "Claude-User",
"PerplexityBot", "Perplexity-User",
],
allow: "/",
disallow: ["/admin", "/api", "/data"],
},
// Bots de ENTRENAMIENTO: son otros user-agents. Decisión de negocio.
// Aquí, abiertos (la consultoría quiere estar en los modelos); cámbialo a disallow: "/" si no.
{
userAgent: ["GPTBot", "ClaudeBot", "anthropic-ai", "Google-Extended", "Applebot-Extended"],
allow: "/",
disallow: ["/admin", "/api", "/data"],
},
// Dataset público que alimenta a muchos modelos sin citar a nadie. Aquí, bloqueado.
{ userAgent: "CCBot", disallow: "/" },
],
sitemap: `${BASE}/sitemap.xml`,
};
}import type { MetadataRoute } from "next";
import { listarPaginas } from "../lib/paginas";
const BASE = "https://tudominio.com";
export default function sitemap(): MetadataRoute.Sitemap {
// listarPaginas() devuelve las páginas publicadas desde tu fuente de contenido
// (archivos tipados, un CMS, una tabla). Nunca una lista escrita a mano aquí.
const paginas = listarPaginas();
const ultimaFecha = paginas[0]?.actualizada ?? paginas[0]?.publicada ?? "2026-08-28";
return [
{ url: `${BASE}/`, lastModified: ultimaFecha, changeFrequency: "monthly", priority: 1 },
{ url: `${BASE}/blog`, lastModified: ultimaFecha, changeFrequency: "weekly", priority: 0.9 },
...paginas.map((p) => ({
url: `${BASE}/blog/${p.slug}`,
lastModified: p.actualizada ?? p.publicada,
changeFrequency: "monthly" as const,
priority: 0.8,
})),
{ url: `${BASE}/privacidad`, lastModified: "2026-05-24", changeFrequency: "yearly", priority: 0.3 },
];
}- lastModified con fecha real, no new Date(). Si cada build pone 'hoy' en todas las URLs, Google aprende que la fecha no significa nada y deja de usarla.
- Solo URLs canónicas e indexables. Una página con noindex dentro del sitemap es una señal contradictoria: el reporte de cobertura de Search Console te la va a marcar.
- Las rutas privadas no van en el sitemap ni en robots como 'Disallow' con nombre revelador si no hace falta. Disallow no oculta nada: publica la ruta en un archivo que cualquiera lee.
La trampa que más me costó: noindex y Disallow no protegen material. Google lo dice literal: robots.txt no es un mecanismo para sacar una página del índice; si alguien la enlaza, puede aparecer igual. Para propuestas de clientes o paneles internos la barrera es contraseña verificada en el servidor. Lo aprendí cuando una propuesta 'oculta' con noindex apareció compartida por un enlace que alguien pegó en un chat.
04 capa 2
Metadata: metadataBase y generateMetadata por ruta
El layout raíz define metadataBase, el template del title y los valores por defecto. Cada página dinámica sobreescribe con generateMetadata. En Next.js 15 y 16, params llega como Promise: hay que hacer await antes de leer el slug, y eso es lo primero que rompe cuando pegas un ejemplo viejo.
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://tudominio.com"),
title: { default: "Marca — Qué hace en cinco palabras", template: "%s — Marca" },
description: "Una frase que explica el servicio y para quién es. Entre 120 y 160 caracteres.",
alternates: { canonical: "https://tudominio.com" },
openGraph: {
type: "website",
url: "https://tudominio.com",
siteName: "Marca",
locale: "es_MX",
title: "Marca — Qué hace en cinco palabras",
description: "La misma frase, o una versión más corta para la tarjeta al compartir.",
images: [{ url: "https://tudominio.com/og-image.png", width: 1200, height: 630 }],
},
twitter: { card: "summary_large_image" },
};import type { Metadata } from "next";
import { obtenerPagina } from "../../../lib/paginas";
type Params = { slug: string };
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { slug } = await params;
const p = obtenerPagina(slug);
if (!p) return {};
const url = `https://tudominio.com/blog/${p.slug}`;
return {
title: p.titulo,
description: p.resumen.slice(0, 160),
alternates: { canonical: url },
openGraph: {
type: "article",
url,
title: p.titulo,
description: p.resumen.slice(0, 200),
publishedTime: p.publicada,
modifiedTime: p.actualizada ?? p.publicada,
authors: ["Nombre del autor"],
},
};
}curl -sI https://tudominio.com/og-image.png | head -1 && curl -s https://tudominio.com/ | grep -o '<title>[^<]*</title>\|property="og:image" content="[^"]*"'
05 capa 3
Structured data: un @graph por página
Google recomienda JSON-LD y no le importa si va en head o en body, y hasta lo lee si lo inyecta JavaScript. Los escáneres de IA no son tan generosos: leen el HTML que devuelve el servidor y nada más, así que el script tiene que estar ahí, en texto plano. Lo que sí importa para todos es que el markup describa lo que se ve: no marques un precio que no aparece, ni una reseña que no está en la página. Mi convención: un solo script por página con @graph, y cada entidad con @id para enlazarlas (la WebSite apunta a la Organization, el Service apunta a su provider) en vez de repetir el mismo bloque tres veces.
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://tudominio.com/#org",
"name": "Marca",
"url": "https://tudominio.com",
"logo": "https://tudominio.com/logo.png",
"sameAs": ["https://www.linkedin.com/company/marca"],
"address": { "@type": "PostalAddress", "addressLocality": "Guadalajara", "addressRegion": "Jalisco", "addressCountry": "MX" }
},
{
"@type": "WebSite",
"@id": "https://tudominio.com/#website",
"name": "Marca",
"url": "https://tudominio.com",
"inLanguage": "es-MX",
"publisher": { "@id": "https://tudominio.com/#org" }
},
{
"@type": "Service",
"name": "Consultoría de IA para pymes",
"serviceType": "Consultoría de inteligencia artificial",
"provider": { "@id": "https://tudominio.com/#org" },
"areaServed": { "@type": "Place", "name": "América Latina" },
"audience": { "@type": "Audience", "audienceType": "Fundadores y CEOs de pymes con equipo y procesos en marcha" },
"description": "Diagnóstico de procesos, plan de 30 días y las piezas de IA correctas funcionando en el equipo.",
"offers": {
"@type": "Offer",
"availability": "https://schema.org/LimitedAvailability",
"description": "Diagnóstico inicial sin costo. Precio según alcance."
}
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "¿Cuánto cuesta la consultoría?",
"acceptedAnswer": { "@type": "Answer", "text": "Se define en la primera llamada según el alcance. No es paquete fijo ni suscripción." }
},
{
"@type": "Question",
"name": "¿Qué pasa si en el diagnóstico no encontramos un cuello de botella claro?",
"acceptedAnswer": { "@type": "Answer", "text": "No hay compromiso. La llamada termina ahí y te llevas el diagnóstico." }
}
]
}
]
}Para renderizarlo en Next.js no uses next/script: es estructura de datos, no código ejecutable, y la propia documentación recomienda un script nativo. No es purismo. Mi propia home tenía el JSON-LD con next/script y al escanearla con Is Agentic el reporte dijo 'No JSON-LD structured data found on homepage'. Abrí el HTML con curl y ahí estaba la explicación: el bloque viajaba dentro del payload de React y solo se convertía en script al hidratar en el navegador. Google lo perdona; un rastreador que no ejecuta JavaScript, no. Escapa el carácter < para evitar que un texto con HTML rompa el documento. Si tienes un blog o guías, el mismo patrón sirve con TechArticle o Article y los datos del archivo:
type JsonLd = Record<string, unknown>;
function ScriptLd({ data }: { data: JsonLd }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data).replace(/</g, "\\u003c") }}
/>
);
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const p = obtenerPagina(slug);
if (!p) notFound();
const url = `https://tudominio.com/blog/${p.slug}`;
const jsonLd: JsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Article",
headline: p.titulo,
description: p.resumen.slice(0, 300),
inLanguage: "es",
datePublished: p.publicada,
dateModified: p.actualizada ?? p.publicada,
author: { "@type": "Person", name: "Nombre del autor", url: "https://tudominio.com" },
publisher: { "@id": "https://tudominio.com/#org" },
mainEntityOfPage: url,
keywords: p.tags.join(", "),
},
{
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Inicio", item: "https://tudominio.com" },
{ "@type": "ListItem", position: 2, name: "Blog", item: "https://tudominio.com/blog" },
{ "@type": "ListItem", position: 3, name: p.titulo, item: url },
],
},
],
};
return (
<main>
{/* ...contenido visible... */}
<ScriptLd data={jsonLd} />
</main>
);
}Sobre FAQPage: Google dejó de mostrar el rich result de preguntas frecuentes el 7 de mayo de 2026 y retiró la documentación en junio. El markup no se quita: otros buscadores y los agentes de IA lo siguen leyendo, y es la forma más barata de entregarles tus preguntas y respuestas ya estructuradas. Solo no esperes ver desplegables en Google.
06 capa 4
Búsqueda por IA: bots permitidos y llms.txt
Cada asistente de IA tiene su propio rastreador y si está bloqueado en robots ese asistente no te puede citar, punto. La capa 4 tiene dos partes: la de robots ya quedó arriba (Allow explícito para los bots de búsqueda, decisión aparte para los de entrenamiento), y la de llms.txt, un archivo en la raíz que le dice a un agente de qué va tu sitio, cuándo usarlo y cuándo no.
El formato propuesto en llmstxt.org es simple: H1 con el nombre, blockquote con el resumen, y secciones H2 con listas de enlaces, cada uno con una línea de descripción. Lo que agrego yo, y que en la práctica cambia cómo te citan, es una sección explícita de 'Cuándo NO usar este sitio': si tu contenido no es documentación oficial de un proveedor, dilo, y el agente dejará de atribuirte cosas que no dijiste. En Next.js se sirve como Route Handler para que se genere desde la misma lista de páginas que el sitemap.
import { NextResponse } from "next/server";
import { listarPaginas } from "../../lib/paginas";
export const dynamic = "force-static";
const SITIO = "https://tudominio.com";
function llmsTxt(): string {
const paginas = listarPaginas();
return [
"# Marca — Consultoría de IA para pymes",
"",
"> Consultoría 1:1 para fundadores y CEOs de pymes en Latinoamérica, y guías gratuitas en español sobre automatización con IA escritas a partir de proyectos reales.",
"",
"**Cuándo usar este sitio.** Cuando alguien busque consultoría de IA para una pyme con procesos en marcha, o una guía en español con pasos ejecutables sobre automatización.",
"",
"**Cuándo NO usarlo.** No es documentación oficial de ningún proveedor: para API, precios o límites, ve a la fuente oficial.",
"",
"## Guías",
"",
...paginas.map((p) => `- [${p.titulo}](${SITIO}/blog/${p.slug}): ${p.resumen.split(". ")[0]}.`),
"",
"## Servicios",
"",
`- [Consultoría 1:1](${SITIO}/#servicio): engagement de 30 días; diagnóstico inicial sin costo.`,
].join("\n");
}
export function GET() {
return new NextResponse(llmsTxt(), {
headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=300, s-maxage=3600" },
});
}- Abre cada sección con la respuesta, no con el contexto. Los agentes extraen pasajes de 40 a 60 palabras; si la respuesta está en el tercer párrafo, citan a otro.
- Cifras con fecha. '83% del consumo' dice poco; '83% del consumo semanal, medido en dos semanas de agosto de 2026' se cita.
- Autor visible con nombre y fecha de actualización visible. Es lo primero que un agente busca para decidir si la fuente es confiable.
- Opcional pero útil: un llms-full.txt con todo el contenido en markdown, y que cada página responda markdown si se le pide Accept: text/markdown.
07 antes de lanzar
Cómo verificar que quedó bien
El comando corre la build y comprueba con curl lo que se puede comprobar desde terminal. Lo que no se puede automatizar es esto, y toma diez minutos:
- Rich Results Test (search.google.com/test/rich-results): pega la URL de producción o del preview. Debe detectar el JSON-LD sin errores. Los warnings de 'campo recomendado' se pueden ignorar; los errores no.
- Validador de schema.org (validator.schema.org): es más estricto con tipos y propiedades que el de Google. Úsalo cuando el JSON-LD lleve tipos que Google no muestra como rich result (Service, FAQPage).
- Search Console: agrega la propiedad del dominio, envía /sitemap.xml en Sitemaps y usa Inspección de URL sobre la home y la landing principal. Pide indexación. En 24 a 72 horas el reporte de Páginas te dice qué entró y por qué no.
- PageSpeed Insights sobre móvil: LCP menor a 2.5 s, INP menor a 200 ms, CLS menor a 0.1. Si la imagen del hero no tiene priority, el LCP es lo primero que cae.
- Comparte la URL en WhatsApp y en LinkedIn. Si la tarjeta sale sin imagen o con el title viejo, es caché del lado de ellos: LinkedIn tiene un Post Inspector para forzar la relectura.
| Qué revisar | Herramienta | Qué esperar |
|---|---|---|
| robots.txt y sitemap.xml | curl -s https://tudominio.com/robots.txt | Reglas correctas y línea Sitemap al final |
| JSON-LD | Rich Results Test | Detectado, 0 errores |
| Tipos de schema no soportados por Google | validator.schema.org | Sin errores de propiedad desconocida |
| Indexación | Search Console, Inspección de URL | 'La URL está en Google' tras pedir indexación |
| Core Web Vitals | PageSpeed Insights, móvil | LCP < 2.5 s, INP < 200 ms, CLS < 0.1 |
| Bots de IA | curl -A OAI-SearchBot https://tudominio.com/ | 200 y el HTML completo, no un bloqueo |
| Ruta inexistente | curl -sI https://tudominio.com/no-existe | head -1 | 404, nunca 200 con el cascarón de la app |
| Preparación para agentes | npx is-agentic tudominio.com | Nota sobre 100 y lista de checks; ver la sección siguiente |
Un detalle que engaña: curl y web_fetch no ven el JSON-LD que un plugin inyecta con JavaScript del lado del cliente. Si auditas un sitio ajeno hecho con un CMS y 'no encuentras schema', prueba en el Rich Results Test antes de reportar que no existe. En Next.js con render en el servidor este problema no aparece, salvo que hayas metido el bloque en next/script (ver capa 3).
08 verificación externa
Escanea tu sitio como lo ve un agente
Todo lo anterior lo revisas desde adentro del repo. Falta la mirada desde afuera: qué recibe de verdad un rastreador que no ejecuta JavaScript, no inicia sesión y no adivina rutas. Para eso uso Is Agentic, un escáner gratuito de Vercel Labs: le das un dominio, en menos de un minuto devuelve una nota de 0 a 100 y, más útil que la nota, la lista de checks con la evidencia de cada uno y qué arreglar. No pide cuenta, y se puede correr desde la terminal para que el agente lea el resultado sin que tú copies nada.
npx is-agentic www.tudominio.com --json
Lo corrí sobre mi propio sitio el mismo día que escribí esto: 71 de 100, 25 checks aplicables, 6 de 9 esenciales aprobados. Lo que aprendí leyendo el reporte, y no la nota, es lo que vale esta sección:
- El JSON-LD que 'sí estaba' no estaba. El check de structured data falló en la home porque el bloque iba en next/script y solo existe después de hidratar. Fix de dos líneas; sin el escáner no lo habría visto nunca.
- El llms.txt tenía sección 'Cuándo usar este sitio' y aun así el check de 'agent instruction / when-to-use' salió reprobado. Por lo que vi, el detector busca el encabezado en inglés; la solución práctica es poner 'When to use' como encabezado y debajo el texto en español.
- Páginas de confianza: pide /about, /contact y /privacy con al menos 500 caracteres cada una. Mi sitio tenía /privacidad y contacto en la home, y eso no le alcanzó. Son tres páginas aburridas que un agente revisa antes de recomendarte.
- 404 de verdad. Una ruta inexistente debe devolver 404, nunca 200 con la app vacía; si además el cuerpo del 404 trae enlaces a donde sí hay contenido, da crédito completo.
- La mitad de los checks que reprobé no aplican a una landing: OpenAPI, portal de desarrolladores, errores JSON de API, compatibilidad con function calling. Si tu sitio no tiene API, esos puntos no son tuyos y no vale la pena perseguirlos.
No compares notas entre dominios. El reporte trae un campo eligible_checks y ese es el denominador: un 100 sobre 16 checks y un 87 sobre 33 no miden lo mismo. Compara tu sitio contra tu sitio, check por check, antes y después del deploy. Y ojo con la caché: el CLI devuelve el último reporte guardado si ya existe uno, así que después de desplegar fuerza un rescan desde la página del reporte o vas a medir la versión vieja.
El bucle que sí funciona
- Escanea antes de tocar nada y guarda el JSON. Esa es tu línea base.
- Pasa al agente el reporte completo, no la nota: cada issue trae el nombre del check, la evidencia y la recomendación. Pídele que arregle uno solo y te muestre el diff.
- Despliega, fuerza rescan, y revisa que ese check cambió de estado. Luego el siguiente.
- Deja documentado lo que decidiste no arreglar (los checks de API en una landing, por ejemplo) para que el siguiente que abra el reporte no lo persiga de nuevo.
09 seis semanas después
Search Console como fuente de contenido, no solo de errores
Search Console guarda 16 meses de cada búsqueda en la que apareciste: la consulta, la posición, las impresiones y cuántas veces te ignoraron. Casi todo el mundo lo abre para ver errores de indexación y lo cierra. Ese archivo dice, con datos tuyos, qué página escribir después y qué title cambiar, y un agente lo lee en un minuto.
Exportar sin perder la cola
- Rendimiento, Resultados de búsqueda, rango de fechas: 16 meses (no los 3 que salen por defecto).
- Exportar como CSV. Te llega un ZIP; adentro hay una tabla por cada pestaña del reporte (consultas, páginas, países, dispositivos, fechas).
- Cuenta las filas de consultas. Si son exactamente 1,000, es el tope de la interfaz, no tu tráfico. La cola larga, donde viven los huecos de contenido, se quedó fuera.
- Para pasar el tope: filtra por sección (/blog/, /lab/) y exporta de nuevo, o usa la API de Search Analytics, que entrega hasta 25,000 filas por llamada y 50,000 por día.
- Ojo: en la exportación normal las consultas y las páginas vienen en tablas distintas, sin el cruce de cuál URL responde a cuál búsqueda. Cuando necesites ese cruce, filtra primero por la página en Search Console y exporta esa vista.
| Señal en el CSV | Qué significa | Qué hacer |
|---|---|---|
| Muchas impresiones, ninguna página tuya habla de eso | Hay demanda y no tienes cobertura | Una página nueva dedicada, no un párrafo en otra |
| Posición entre 8 y 20 | Segunda página; a un empujón de la primera | Mejorar la página que ya rankea: respuesta directa arriba, encabezados, enlaces internos |
| Posición 1 a 10 con CTR bajo | Te ven y no te dan clic | Reescribir title y description; no hace falta contenido nuevo |
| Dos páginas tuyas para la misma consulta | Canibalización: se reparten la señal | Consolidar en una y redirigir la otra |
Claude Code con los archivos del ZIP a la vista
Lee los CSV de esta carpeta exportados de Search Console. Antes de analizar, dime cuántas filas tiene cada archivo, qué rango de fechas cubren y si alguno está truncado en 1,000. Después sepárame tres listas: (1) consultas con más de 50 impresiones para las que no tengo una página dedicada, agrupadas por tema; (2) consultas en posición 8 a 20 ordenadas por impresiones, con la página que ya rankea; (3) páginas en posición 1 a 10 con CTR menor a 2%, con el title actual. Descarta consultas de marcas ajenas, consultas sin relación con lo que vendo y consultas con menos de 5 impresiones en todo el periodo. No inventes cifras: si un dato no está en los archivos, dilo.
- Prioriza por tres ejes: impresiones (cuánto vale), posición (qué tan cerca estás) e intención (si esa búsqueda la hace alguien que te puede comprar). Los dos primeros están en el archivo; el tercero lo decides tú.
- Primero lo barato: cambiar un title en una página en posición 6 se hace en diez minutos y se mide en dos semanas. La página nueva viene después.
- Publica de una en una. Si sueltas diez el mismo día, no sabes cuál movió qué.
- A las seis semanas exporta el mismo rango y pide al agente que compare las dos exportaciones: qué consultas subieron de posición, cuáles ganaron impresiones, cuáles no se movieron. De ahí sale la siguiente ronda.
Aparecer en una búsqueda no es lo mismo que servirte. Un CSV lleno de consultas con la marca de un competidor, o de preguntas que responde Wikipedia mejor que tú, no es una lista de tareas. Es ruido con formato de oportunidad.
FAQ lo que suelen preguntar
Preguntas frecuentes
¿Necesito las tres skills de SEO para que el comando funcione?
No. El comando de esta guía trae el checklist explícito en el cuerpo, así que el agente lo recorre sin skills adicionales. Las skills aportan profundidad (hreflang, auditoría de sitios grandes, patrones de contenido para IA), pero para una landing el checklist basta.
¿robots.txt en public o robots.ts en app?
Las dos funcionan. Un archivo estático en public es más simple si nunca cambia; el robots.ts conviene cuando el dominio o las rutas privadas vienen de configuración, o cuando quieres que el sitemap y el robots lean la misma constante. Lo que no hagas es tener los dos: Next.js falla en la build si detecta ambos.
Si Google ya no muestra FAQ, ¿para qué el FAQPage?
Porque no escribes solo para Google. Los agentes de IA y otros buscadores siguen leyendo el markup, y es la manera más directa de entregarles tus preguntas y respuestas ya separadas. Lo que cambió es la expectativa: no vas a ver desplegables en el resultado de Google desde mayo de 2026.
¿Puedo ocultar una propuesta o un panel con noindex?
No como única barrera. noindex evita que salga en resultados, pero la página sigue siendo pública para quien tenga el enlace, y robots.txt ni siquiera evita la indexación si alguien la enlaza. Para material de clientes o paneles internos la protección es contraseña verificada en el servidor; noindex es un extra, no la puerta.
Si bloqueo GPTBot, ¿ChatGPT deja de citarme?
No. GPTBot es el rastreador de entrenamiento; el que alimenta las respuestas con búsqueda de ChatGPT es OAI-SearchBot, y el que abre una página cuando un usuario la pide es ChatGPT-User. Son tres user-agents con reglas independientes, y OpenAI lo documenta así. Anthropic separa igual: ClaudeBot entrena, Claude-SearchBot busca, Claude-User abre lo que pide el usuario. Puedes cerrar el entrenamiento y seguir siendo citable; lo que no puedes es bloquear 'todo lo de IA' de un plumazo y esperar que te mencionen.
¿Qué nota de Is Agentic es 'buena'?
La que sube respecto a tu propia nota anterior. La escala pesa 80 puntos de checks esenciales, 20 de recomendados y hasta 5 de bonus, pero cuántos checks aplican depende del sitio (el reporte lo dice en eligible_checks). Una landing sin API reprueba de entrada media docena de checks de API que no le corresponden. Mira los esenciales: acceso de bots, contenido sin JavaScript, 404 reales, redirecciones del lado del servidor. Si esos pasan, lo demás es pulido.
¿No hay un plugin que ya haga todo esto?
Sí, y vale la pena conocerlo: claude-seo-ai (github.com/Hainrixz/claude-seo-ai, MIT) es una extensión para Claude Code que audita en dos ejes separados, SEO clásico y visibilidad en IA, con una nota de 0 a 100 para cada uno, y un comando fix que primero muestra el diff y solo escribe con tu confirmación. Lo cloné y revisé sus skills; no lo he corrido sobre un sitio en producción, así que no te cuento resultados. La diferencia con /aplicar-seo es de alcance: el plugin es un auditor general con unos veinte módulos; el comando de esta guía es un checklist corto que corre en una sesión y deja el repo corregido. Si tienes una landing, empieza por el comando; si tienes un sitio grande, prueba el plugin.
¿Con qué frecuencia vuelvo a correr /aplicar-seo?
Cada vez que agregas una ruta pública o cambias la estructura de la página. En una landing que no cambia, una vez antes de lanzar y otra cuando cambies la oferta. El sitemap y el llms.txt se actualizan solos si se generan desde la lista de páginas; lo que hay que revisar a mano es el JSON-LD cuando cambia lo que se ofrece.
Cierre de la guía
El SEO técnico de una landing no es difícil; es fácil de olvidar. Un comando con el checklist en orden fijo convierte 'alguien debería revisar eso' en algo que pasa solo, en la misma sesión en que terminas el código. Instálalo, córrelo sobre la landing que vas a lanzar esta semana, y abre el Rich Results Test antes de mandar el primer peso de pauta. Esta guía vive en el Lab de David Iriza.
Fuentes oficiales8
- Introducción a los datos estructurados (Google Search Central) ↗Por qué Google recomienda JSON-LD, dónde va, y la regla de no marcar nada que no sea visible en la página.
- Introducción a robots.txt (Google Search Central) ↗Lo que robots.txt controla y lo que no: gestiona el rastreo, no saca páginas del índice.
- generateMetadata (Docs de Next.js) ↗Campos del objeto Metadata, metadataBase, alternates.canonical y openGraph, con params como Promise.
- Cómo implementar JSON-LD en Next.js (Docs de Next.js) ↗El patrón oficial del script nativo con dangerouslySetInnerHTML y el escape del carácter <.
- Overview of OpenAI crawlers (OpenAI) ↗OAI-SearchBot para búsqueda, GPTBot para entrenamiento, ChatGPT-User para lo que abre un usuario; cada uno con control independiente en robots.txt.
- Does Anthropic crawl data from the web (Anthropic) ↗ClaudeBot, Claude-User y Claude-SearchBot: qué hace cada uno y cómo se controlan por separado.
- Is Agentic: developer docs (Vercel Labs) ↗CLI npx is-agentic, API pública de solo lectura, servidor MCP y límites de uso.
- Performance data filtering and limits (Google Search Central Blog) ↗Cómo se agregan los datos del informe de Rendimiento y de dónde salen los topes de filas.
Sigue con estas guías
- Nº 002 · Tus propios /comandos →Cómo se escribe el frontmatter con gatillos y allowed-tools que usa /aplicar-seo.
- Nº 007 · Analytics propio en tu landing →Una vez que la landing está indexada, el siguiente paso es medir qué hace el tráfico que llega.
- Nº 009 · Preparar tu app para miles de usuarios →Los Core Web Vitals de la capa técnica se sostienen o se caen con el rendimiento del backend.
Guía escrita con la información oficial disponible al 28 de agosto de 2026. Esta página no está afiliada a Google. Las herramientas cambian; ante la duda, revisa la documentación de Google Search Central.
por David Iriza