AWS Builder Center
Nuestra herramienta no leía @graph y nos puso 0 en schema

Nuestra herramienta no leía @graph y nos puso 0 en schema

Un schema que sí existía, un puntaje que no se movía y una función que solo miraba la raíz del JSON. Así encontramos el bug de @graph en Entiscore.

Series: Building Entiscore with Kiro (8 articles)

  1. …
  2. 8
    Nuestra herramienta no leía @graph y nos puso 0 en schema This article
Software Developer
Un schema que sí existía, un puntaje que no se movía con ningún arreglo y una función que solo miraba la raíz del JSON.
Corrí Entiscore contra nuestro propio sitio y el reporte dijo que no teníamos ningún dato estructurado. Ese JSON-LD lo habíamos escrito a mano con Matías semanas antes, estaba en el <head> del documento y bastaba abrir el código fuente en el navegador para verlo.
Entiscore es la herramienta que construimos para auditar justamente eso en sitios ajenos. Durante semanas la probamos contra portfolios de otras personas, sitios de prueba y ejemplos armados para estresar cada parte del análisis, pero nunca la habíamos apuntado a nuestro dominio en producción. Lo hicimos sobre el final, casi por disciplina, y devolvió 52 sobre 100. El fallo no estaba en nuestro sitio sino en lo que la herramienta era capaz de leer, y encontrarlo cambió la forma en que pruebo mis propios proyectos.

Qué es Entiscore

Entiscore es un agente de auditoría de identidad digital que construimos con Matías Tula Sarquis para el hackathon de Kiro powered by AWS. Recibe una URL y estima qué tan reconocible es esa entidad para buscadores y para sistemas de inteligencia artificial, a partir de cuatro ejes que son datos estructurados, accesibilidad técnica, consistencia de identidad y señales de autoridad.
Todo el proyecto lo desarrollamos con Kiro como agente, siguiendo el flujo guiado por especificaciones que propone, con requisitos, diseño y tareas escritos antes de cualquier implementación. Kiro es el entorno de desarrollo con agentes de AWS, y el analizador de datos estructurados que falló en este caso se construyó dentro de ese mismo proceso.

Un reporte que contradecía el código fuente

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"overallScore": 52,
"maturityLevel": "medio",
"structuredData": {
"score": 0,
"status": "evaluated",
"findings": [
{
"type": "critical",
"title": "No se encontró schema markup en el sitio",
"description": "El sitio no tiene datos estructurados que permitan a buscadores e IA interpretar la identidad de forma precisa."
}
]
},
"technicalAccessibility": 100,
"identityConsistency": 60,
"authoritySignals": 52,
"code": "wild-pin-749"
}
La accesibilidad técnica estaba en 100. La consistencia de identidad daba 60 porque el analizador confirmaba el GitHub del proyecto pero no tenía más puntos de anclaje para cruzar. Las señales de autoridad daban 52, con un hallazgo sobre el meta tag de autor, que mostraba un solo nombre aunque el proyecto es de dos personas.
Lo que no encajaba era el 0 en datos estructurados junto a un hallazgo crítico que afirmaba algo falso. Sabíamos que era falso porque ese schema lo habíamos escrito nosotros.

Sospechar primero de nuestro propio trabajo

Antes de dudar de la herramienta dudamos de nosotros. Si el schema era mínimo, lo razonable era ampliarlo. Agregamos un tipo Organization junto al WebApplication que ya existía, declaramos a los dos integrantes como creadores, corregimos un enlace a GitHub que apuntaba a una URL vieja y ajustamos los metadatos de autor. Desplegamos, esperamos a confirmar que el deploy había terminado y volvimos a correr el análisis.
El resultado seguía en 0, con el mismo hallazgo crítico.

La pista estaba en que el número no se movía

Un schema mínimo y uno bastante más completo producían exactamente el mismo puntaje. Si el tamaño del schema no cambia el resultado, lo más probable es que quien mide no esté leyendo el schema en absoluto.
Si cambiás algo en tu sitio y la herramienta que lo mide devuelve exactamente lo mismo, ¿qué sospechás primero, tu cambio o el instrumento? A nosotros nos hizo falta un deploy completo para empezar a mirar hacia la herramienta.

Lo que había en el HTML desplegado

Dejamos de mirar el código de los componentes y revisamos el HTML final, el mismo que recibiría un crawler. El JSON-LD usaba @graph, que es la forma que ofrece el formato para describir varias entidades relacionadas dentro de un mismo documento. Según la especificación JSON-LD 1.1 del W3C , un objeto de nivel superior que solo contiene @context y @graph no se trata como una entidad sino como un contenedor de las entidades que lleva dentro.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"name": "Entiscore",
"url": "https://entiscore.vercel.app",
"founder": [
{ "@type": "Person", "name": "Carlos José Castro Galante" },
{ "@type": "Person", "name": "Matías Edgardo Tula Sarquis" }
]
},
{
"@type": "WebApplication",
"name": "Entiscore",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Web"
}
]
}
El origen estaba en la función del analizador que extraía los schemas, que buscaba @type directamente en la raíz del objeto.
1
2
3
4
5
6
7
8
9
10
11
12
function extractJsonLdSchemas(rawJson: string): SchemaEntity[] {
const parsed = JSON.parse(rawJson);

if (!parsed["@type"]) {
return [];
}

return [{
type: parsed["@type"],
properties: parsed,
}];
}
Con @graph, la raíz solo tiene @context y @graph. La condición fallaba, la función devolvía un arreglo vacío y el analizador concluía, sin lanzar ningún error, que el sitio no tenía schema. No había un crash que rastrear en un log. Había un hueco de cobertura, porque el código hacía exactamente lo que habíamos escrito y lo que habíamos escrito no contemplaba una forma válida del propio estándar que se suponía que debía interpretar.

Dos causas más chicas

Mientras diagnosticábamos aparecieron otras dos. La lista de tipos reconocidos no incluía SoftwareApplication con sus campos esperados, así que aunque la extracción hubiera funcionado, el analizador no habría sabido evaluar si esa entidad estaba completa, algo que sí hacía con Person y Organization.
La otra estaba en las señales de autoridad. En el HTML habíamos declarado un meta tag de autor por cada integrante.
1
2
<meta name="author" content="Carlos José Castro Galante" />
<meta name="author" content="Matías Edgardo Tula Sarquis" />
Un document.querySelector sobre ese selector devuelve solo la primera coincidencia, así que el analizador leía un único nombre. La información estaba completa en el HTML, pero declarada de una forma que nuestra herramienta no sabía leer.

El arreglo

Reemplazamos la extracción por una versión recursiva que recorre el árbol y trata @graph como lo que es, un contenedor de entidades.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function extractSchemasFromNode(node: unknown): SchemaEntity[] {
if (Array.isArray(node)) {
return node.flatMap(extractSchemasFromNode);
}

if (typeof node !== "object" || node === null) {
return [];
}

const record = node as Record<string, unknown>;

if (record["@graph"]) {
return extractSchemasFromNode(record["@graph"]);
}

if (!record["@type"]) {
return [];
}

return [{
type: record["@type"] as string,
properties: record,
}];
}

function extractJsonLdSchemas(rawJson: string): SchemaEntity[] {
const parsed = JSON.parse(rawJson);
return extractSchemasFromNode(parsed);
}
Agregamos SoftwareApplication a los tipos reconocidos, con sus campos esperados definidos igual que los de Person y Organization, y consolidamos los dos meta tags de autor en uno solo.
1
<meta name="author" content="Carlos José Castro Galante, Matías Edgardo Tula Sarquis" />

El segundo reporte

Volvimos a correr el análisis contra el mismo dominio y esta vez el resultado se parecía a lo que sabíamos de nuestro proyecto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"overallScore": 80,
"maturityLevel": "excelente",
"structuredData": {
"score": 92,
"status": "evaluated",
"findings": [
{ "type": "positive", "title": "Organization detectado correctamente" },
{ "type": "positive", "title": "SoftwareApplication detectado correctamente" },
{ "type": "warning", "title": "Faltan campos en Organization: contactPoint" }
]
},
"technicalAccessibility": 100,
"identityConsistency": 60,
"authoritySignals": 52,
"code": "bold-ray-395"
}
El puntaje general pasó de 52 a 80 y el nivel de madurez de "medio" a "excelente". Datos estructurados subió de 0 a 92 y ahora señala algo legítimo y específico, que a la Organization le falta contactPoint. Ese hallazgo antes era imposible de ver, porque el analizador no llegaba siquiera a leer el schema.
Las señales de autoridad siguieron en 52 aunque el meta tag ya mostraba los dos nombres, que fue el resultado que menos esperábamos. Ese eje combina varias señales, y consolidar una etiqueta resolvió el hallazgo puntual pero no alcanza para mover el puntaje completo. Lo que le falta es presencia pública verificable fuera del GitHub ya detectado, y eso no se arregla con markup.

Lo que deja este bug

Un resultado que no responde a ningún cambio que hacés puede estar hablando del instrumento y no de lo que medís. La señal que nos puso en el camino correcto no fue que el arreglo resultara insuficiente, sino que un schema chico y uno completo dieran exactamente el mismo número.
El bug también sobrevivió a semanas de pruebas porque nuestros ejemplos esperaban lo que ya imaginábamos encontrar, y no todo lo que el estándar permite. Lo encontró el único sitio del que sabíamos con certeza qué debía aparecer, el nuestro. La conclusión práctica es probar las herramientas contra el propio trabajo, esperando que el resultado se parezca a lo que ya sabemos de él.
Quedan pendientes el campo contactPoint y el eje de autoridad, que nos obliga a construir presencia pública verificable y no solo mejores etiquetas.
¿Tu sitio declara sus entidades con @graph? Si alguna herramienta de SEO o de IA te dijo alguna vez que no tenías datos estructurados y vos sabías que sí, me gustaría saber cuál fue y cómo lo descubriste. Te leo!

Series: Building Entiscore with Kiro (8 articles)

  1. …
  2. 8
    Nuestra herramienta no leía @graph y nos puso 0 en schema This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article