Ir al contenido

Problemas del scan: registro de códigos ​

Cada problema de Pro tiene un código estable definido en Rankbeam\Seo\Pro\Scanning\IssueRegistry. Los escáneres construyen problemas con IssueRegistry::make(), que asigna gravedad y campo desde el registro y rechaza códigos no definidos. Este catálogo es un contrato para paneles, exportaciones y puntuación Pro, que consumen códigos en lugar de interpretar mensajes. La auditoría gratuita seo:audit utiliza su propio registro de metadatos del núcleo, con menor cobertura y algunos códigos hreflang diferentes.

Cada código incluye:

  • id: cadena estable guardada en seo_scan_issues.issue_type.
  • severity: critical, warning o notice, fija por código. Se separan códigos si necesitan distinta gravedad.
  • field: campo seo_meta afectado, o page para problemas de página.
  • Clase de ejecución: qué se necesita para detectarlo.
  • Evidencia: claves del array context del problema.

Clases de ejecución ​

Cada comprobación pertenece a una clase según lo que necesita:

ClaseNecesitaQuién la ejecuta
metadataModelo y resolvedor del núcleo, sin obtener la páginaPageScanner; seo:audit cubre un subconjunto
renderedHTML servido, mediante kernel dentro del proceso o petición externaUrlScanner
networkPetición saliente para validar otro destino, como un canonical diferenteUrlScanner, siempre mediante SsrfGuard

La auditoría gratuita dentro del proceso no equivale al scan completo de Pro: solo las comprobaciones metadata se calculan sin renderizar. Pro obtiene HTML y valida destinos canonical mediante red. Filtra el registro con IssueRegistry::byClass(IssueRegistry::EXEC_METADATA).

Códigos de metadatos ​

PageScanner los detecta desde el modelo y el resolvedor. El scan de URL también emite missing_title, missing_description y los códigos de longitud al medir el <head> servido, con el mismo significado.

CódigoGravedadCampoEvidenciaSignificado
missing_titlecriticaltitle— Sin título ni valor calculable de respaldo.
missing_descriptionwarningdescription— Sin meta description ni valor calculable de respaldo.
missing_og_imagenoticeog_image— Sin imagen Open Graph ni valor calculable de respaldo.
missing_focus_keywordnoticefocus_keywords— No se definió palabra clave objetivo.
duplicate_titlewarningtitletitle, duplicate_urls Título reutilizado en otras páginas del mismo idioma.
duplicate_descriptionwarningdescriptiondescription, duplicate_urls Descripción reutilizada en otras páginas del mismo idioma.
title_too_longwarningtitlelength, max, script Título resuelto superior a la recomendación del sistema de escritura: 60 para latino y aproximadamente 30 para CJK.
title_too_shortnoticetitlelength, min, script Título resuelto inferior al umbral: 30 para latino y aproximadamente 15 para CJK.
description_too_longwarningdescriptionlength, max, script Descripción resuelta superior a la recomendación: 160 / aproximadamente 80.
description_too_shortnoticedescriptionlength, min, script Descripción resuelta inferior al umbral: 70 / aproximadamente 35.
robots_conflict_indexingcriticalrobotsrobots Robots contiene index y noindex a la vez.
robots_conflict_followingwarningrobotsrobots Robots contiene follow y nofollow a la vez.
noindex_warningwarningrobotsrobots, canonical, page_url, shipping_signal Página noindex con canonical propio: heurística que revisar, no prueba de que deba indexarse. Se emite en scans de modelo y URL.
invalid_canonicalcriticalcanonicalcanonical Canonical no es una URL válida.
cross_domain_canonicalwarningcanonicalcanonical, page_url Canonical apunta a un host diferente al de la página.
shared_canonicalnoticecanonicalcanonical Varias páginas declaran el mismo canonical.
insecure_canonicalwarningcanonicalcanonical Canonical http:// en un sitio https.
hreflang_invalid_codewarningalternatesinvalid_codes Una alternativa hreflang no es x-default ni un código BCP-47 válido.
hreflang_missing_self_referencewarningalternateslocale, page_url Se declaran alternativas pero ninguna representa el idioma de la propia página.
hreflang_duplicate_codewarningalternatesduplicate_codes Un código hreflang corresponde a varias URL, creando un grupo ambiguo.
hreflang_missing_x_defaultnoticealternateslanguages Un grupo hreflang multilingüe carece de x-default.
aeo_missing_authornoticeschema— El artículo de los datos estructurados carece de entidad author; no explicita autoría/procedencia.
aeo_article_missing_datenoticeschema— El artículo de los datos estructurados carece de fecha de publicación o modificación; no explicita su cronología.

Los umbrales proceden de la política de longitud por sistema de escritura del núcleo, utilizada desde Pro 2.33: 60/160 para texto latino y aproximadamente 30/80 para CJK, contados en grafemas, en consonancia con los contadores del editor. Los límites inferiores son 30/70 para latino y aproximadamente la mitad para CJK. script indica el grupo aplicado. Se mide el título o descripción resuelto, incluidos valores de respaldo y sufijo.

Los códigos hreflang_* comprueban las alternativas declaradas por el resolvedor: códigos inválidos o duplicados, autorreferencia ausente y falta de x-default en grupos multilingües. Solo se ejecutan si se declaran alternativas. La reciprocidad entre páginas no pertenece a estas comprobaciones de metadatos; la comprobación de red opcional obtiene la otra página.

Los códigos aeo_* son señales de preparación para respuestas. Leen el grafo JSON-LD y solo se activan si declara un artículo (Article, BlogPosting, NewsArticle, etc.) sin entidad author o sin datePublished / dateModified. No señalan páginas sin artículo. Se controlan con seo-pro.scan.checks.aeo, activo por defecto, y coinciden con la auditoría gratuita seo:audit.

missing_focus_keyword requiere activación

Este aviso solo aparece si está habilitado el flujo del núcleo seo.keywords.enabled, desactivado por defecto. Con él apagado, no se señala la ausencia de palabra clave. Auditoría, scan y editor Filament leen la misma opción.

Códigos del HTML renderizado ​

UrlScanner los detecta en el HTML servido. Para el mismo host utiliza una petición al kernel dentro del proceso, sin tráfico saliente; para objetivos externos, una petición protegida.

CódigoGravedadCampoEvidenciaSignificado
http_errorcriticalpagestatus La URL respondió con estado 4xx/5xx.
empty_responsecriticalpage— La URL devolvió un cuerpo vacío.
missing_canonicalnoticecanonical— No hay <link rel="canonical"> en el head renderizado.
noindex_pagenoticerobotsrobots Página noindex, informativo. Si también tiene canonical propio, se convierte en noindex_warning puntuado.
missing_h1noticepage— No hay encabezado <h1>.
multiple_h1noticepagecount Hay más de un <h1>, informativo.
missing_image_altwarningpagecount, total, sample Imágenes de contenido sin atributo alt. Un alt="" explícito se considera decorativo y no se señala.
thin_contentnoticepageword_count, threshold, segmenter Texto inferior al número de palabras configurado. Usa el tokenizador de la lista de comprobación: espacios para escrituras que los utilizan, segmentación de diccionario ICU (segmenter: intl, requiere ext-intl) para chino, japonés y tailandés. Un artículo japonés de 400 palabras no se cuenta como una sola.
mixed_contentwarningpagecount, sample Subrecursos http:// en una página https.
html_lang_missingnoticepage— Falta <html lang> o está vacío; la tecnología de asistencia puede elegir una voz inadecuada.
html_lang_invalidnoticepagedeclared lang no es una etiqueta BCP-47 válida, por ejemplo english, en_US con guion bajo o jp.
html_lang_mismatchwarningpagedeclared, declared_script, detected_script El texto visible usa un sistema de escritura distinto del idioma declarado: lang="en" en una página japonesa o lang="ru" con texto latino. Solo compara sistemas de escritura, sin adivinar idiomas que comparten alfabeto; requiere al menos 40 letras en el cuerpo.

Códigos de red ​

UrlScanner los detecta cuando está activada la opción correspondiente: seo-pro.scan.url_checks.check_canonical_target para canonical y check_hreflang_reciprocity para alternativas. Todas las peticiones pasan por SsrfGuard, con restricciones de esquema/host, rechazo de IP privadas y límites de redirecciones, tiempo y tamaño. No siguen redirecciones, para detectar un canonical que redirige. Se omiten canonical y alternativas que apuntan a la propia página, ya obtenida.

CódigoGravedadCampoEvidenciaSignificado
blocked_urlnoticepagereason SsrfGuard rechazó el objetivo antes de realizar HTTP.
canonical_target_brokencriticalcanonicalcanonical, status Canonical apunta a una página con error HTTP.
canonical_target_redirectwarningcanonicalcanonical, status, location Canonical apunta a una redirección; utiliza la URL final.
canonical_target_noindexwarningcanonicalcanonical Canonical apunta a una página noindex.
canonical_target_blockednoticecanonicalcanonical, reason No se pudo verificar el destino canonical por rechazo de la protección o resolución fallida.
hreflang_not_reciprocalwarningalternateshreflang, href, status La alternativa no devuelve una referencia a la página. El par hreflang puede ignorarse; esto no hace por sí solo que la traducción no sea indexable.
hreflang_target_unverifiednoticealternateshreflang, href, reason No se pudo obtener la alternativa por rechazo, error HTTP, redirección o tamaño excesivo. No se comprobó reciprocidad: falta de evidencia, no defecto confirmado.

La reciprocidad obtiene como máximo hreflang_max_alternates, diez por defecto, incluidos x-default, omitiendo duplicados y la propia página. Los códigos metadata validan la lista declarada; esta comprobación necesita la otra página.

Todas las vías de red reutilizan SsrfGuard. Consulta el modelo de amenazas y la limitación residual TOCTOU en SECURITY.md.

Cómo afectan los códigos a la puntuación ​

La puntuación SEO Pro parte de 100 y resta una penalización fija por problema puntuado según su gravedad. La mayoría cuentan; se excluyen missing_focus_keyword, noindex_page, multiple_h1, las faltas de evidencia blocked_url / canonical_target_blocked / hreflang_target_unverified y las señales orientativas hreflang_*, html_lang_* y aeo_*. La referencia de puntuación contiene la lista completa y cada penalización.

Ciclo de vida de los problemas ​

El scan actualiza y concilia los problemas de cada objetivo, en lugar de borrarlos y recrearlos. Su identidad es el objetivo —scannable_type + scannable_id para modelos, o url para rutas/sitemaps— junto a issue_type. Cada código se emite como máximo una vez por objetivo y scan. Los códigos con varias instancias, como missing_image_alt, mixed_content o hreflang_*, las agrupan en una fila con count / sample, manteniendo la identidad única.

En cada scan y objetivo:

  • Un hallazgo sin fila previa se crea como open, con detected_at.
  • Uno que coincide con una fila abierta actualiza la evidencia y conserva el detected_at original.
  • Un problema abierto que una comprobación completada ya no encuentra se marca fixed, con resolved_at. La fila se conserva para registrar la corrección.
  • Un problema fixed que reaparece se reabre en la misma fila y actualiza detected_at.
  • Uno marcado ignored por un usuario no se modifica.
EstadoSignificadoQuién lo establece
openPresente actualmenteEl scan, nuevo o persistente
fixedEstaba presente y ya no se detectaEl siguiente scan que no lo encuentra
ignoredSilenciado por un usuario; excluido de totales abiertos y puntuaciónAcción Ignore del panel

Al conservar las correcciones, el informe puede mostrar recuentos reales de problemas nuevos y resueltos por período. Panel, seo-pro:scan-status y puntuación filtran por open, de modo que las filas resueltas no inflan los totales. Estas se atribuyen a la ejecución que las resolvió y se eliminan según su retención.

Configuración ​

php
// config/seo-pro.php → 'scan'
'url_checks' => [
    'enabled' => true,
    'crawl_external' => false,             // fetch external URL targets (guarded)
    'check_canonical_target' => false,     // EXEC_NETWORK canonical validation (guarded)
    'check_hreflang_reciprocity' => false, // EXEC_NETWORK hreflang link-back crawl (guarded)
    'hreflang_max_alternates' => 10,       // targets fetched per page by that crawl
],
'checks' => [
    'length' => true,            // title/description length (metadata + rendered)
    'rendered_content' => true,  // H1 / alt / thin content / mixed content / html lang
],
'content' => [
    'min_word_count' => 200,     // thin_content threshold
    'evidence_sample' => 5,      // max example URLs stored per issue
],

El límite de tamaño de las peticiones protegidas es seo-pro.http.max_response_bytes, 2 MB por defecto. El scan del mismo host dentro del proceso no tiene ese límite.

Compatibilidad: cambio de nombres de códigos ​

El antiguo robots_conflict, que admitía dos gravedades, se dividió para tener una sola gravedad por código:

Código anteriorCódigo nuevoGravedad
robots_conflict con index + noindexrobots_conflict_indexingcritical
robots_conflict con follow + nofollowrobots_conflict_followingwarning

Si guardabas o filtrabas robots_conflict, actualiza a los dos nuevos códigos.

rankbeam/laravel-seo se distribuye bajo la licencia MIT.