Inserta datos estructurados en un Magento de una forma automatizada y efectiva con programación.

Los datos estructurados son una de esas cosas que en Magento 2 vienen implementados de forma muy básica (por no decir pobre). La instalación por defecto con el tema Luma incluye algunos microdatos, pero le faltan campos importantes como availability, offers, shipping o reviews individuales.
Este artículo surge de una pregunta en mi comunidad de Discord, y como es algo que me preguntan bastante, he decidido desarrollarlo en profundidad.
Los datos estructurados (o schema markup) son código que ayuda a los buscadores a entender el contenido de tu página. En el caso de productos, le estás diciendo a Google: "esto es un producto, este es su precio, esta es su disponibilidad, estas son sus valoraciones".
¿El resultado? Los famosos rich snippets en los resultados de búsqueda: estrellas de valoración, precios, disponibilidad... Según varios estudios, los rich snippets pueden aumentar el CTR hasta un 150%.
Google prefiere el formato JSON-LD sobre los microdatos (el formato que usa Magento por defecto). Las ventajas de JSON-LD:
En Magento 2, la forma limpia y escalable de añadir datos estructurados es mediante un módulo propio. Esto te permite:
Vamos paso a paso.
Primero, creamos la estructura básica del módulo. En app/code/ creamos:
app/code/TuEmpresa/DatosEstructurados/
├── registration.php
├── etc/
│ └── module.xml
├── Block/
│ └── ProductSchema.php
├── view/
│ └── frontend/
│ ├── layout/
│ │ └── catalog_product_view.xml
│ └── templates/
│ └── product_schema.phtml
registration.php:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'TuEmpresa_DatosEstructurados',
__DIR__
);
etc/module.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="TuEmpresa_DatosEstructurados" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
</sequence>
</module>
</config>
Es decir, donde construimos los datos estructurados. Block/ProductSchema.php:
<?php
namespace TuEmpresa\DatosEstructurados\Block;
use Magento\Framework\View\Element\Template;
use Magento\Catalog\Model\ProductRepository;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Review\Model\ReviewFactory;
use Magento\Framework\Registry;
class ProductSchema extends Template
{
protected $productRepository;
protected $storeManager;
protected $reviewFactory;
protected $registry;
public function __construct(
Template\Context $context,
ProductRepository $productRepository,
StoreManagerInterface $storeManager,
ReviewFactory $reviewFactory,
Registry $registry,
array $data = []
) {
$this->productRepository = $productRepository;
$this->storeManager = $storeManager;
$this->reviewFactory = $reviewFactory;
$this->registry = $registry;
parent::__construct($context, $data);
}
public function getProduct()
{
return $this->registry->registry('current_product');
}
public function getJsonLdData()
{
$product = $this->getProduct();
if (!$product) {
return null;
}
$store = $this->storeManager->getStore();
$currencyCode = $store->getCurrentCurrencyCode();
// Estructura básica del producto
$schemaData = [
"@context" => "https://schema.org/",
"@type" => "Product",
"name" => $product->getName(),
"description" => strip_tags($product->getDescription()),
"sku" => $product->getSku(),
"image" => $product->getMediaGalleryImages()->getFirstItem()->getUrl(),
"url" => $product->getProductUrl(),
];
// Marca (si existe)
$brand = $product->getAttributeText('manufacturer');
if ($brand) {
$schemaData['brand'] = [
"@type" => "Brand",
"name" => $brand
];
}
// Offer (precio y disponibilidad)
$schemaData['offers'] = [
"@type" => "Offer",
"url" => $product->getProductUrl(),
"priceCurrency" => $currencyCode,
"price" => number_format($product->getFinalPrice(), 2, '.', ''),
"availability" => $product->isAvailable()
? "https://schema.org/InStock"
: "https://schema.org/OutOfStock",
"itemCondition" => "https://schema.org/NewCondition"
];
// Reviews y ratings (si existen)
$this->reviewFactory->create()->getEntitySummary($product, $store->getId());
$ratingSummary = $product->getRatingSummary();
$reviewCount = $product->getReviewsCount();
if ($reviewCount > 0 && $ratingSummary) {
$schemaData['aggregateRating'] = [
"@type" => "AggregateRating",
"ratingValue" => round($ratingSummary->getRatingSummary() / 20, 1),
"bestRating" => "5",
"worstRating" => "1",
"reviewCount" => $reviewCount
];
}
return $schemaData;
}
public function getJsonLdString()
{
$data = $this->getJsonLdData();
if (!$data) {
return '';
}
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
}
view/frontend/layout/catalog_product_view.xml:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="TuEmpresa\DatosEstructurados\Block\ProductSchema"
name="product.schema.jsonld"
template="TuEmpresa_DatosEstructurados::product_schema.phtml"
after="-"/>
</referenceContainer>
</body>
</page>
view/frontend/templates/product_schema.phtml:
<?php
$jsonLd = $block->getJsonLdString();
if ($jsonLd):
?>
<script type="application/ld+json">
<?= /* @noEscape */ $jsonLd ?>
</script>
<?php endif; ?>
Ejecuta los siguientes comandos desde la raíz de Magento:
bin/magento module:enable TuEmpresa_DatosEstructurados
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:clean
El código anterior es una base. En mis proyectos suelo añadir condicionales para diferentes escenarios:
La lógica puede complicarse bastante dependiendo del caso, pero siempre partiendo de esta estructura base. Esto es algo que vemos en detalle en mi máster de SEO técnico.
Si no quieres o no puedes crear un módulo completo, existe otra opción: usar el todopoderoso buffer de PHP para inyectar los datos estructurados en el HTML antes de enviarlo al navegador.
Esta técnica es especialmente útil cuando:
La idea es interceptar el HTML completo, detectar en qué página estamos, y añadir el JSON-LD correspondiente justo antes del </head> o del </body>.
Un ejemplo simplificado:
<?php
// En un observer o plugin que capture el output
public function addStructuredData($html)
{
// Detectar si es página de producto
if (strpos($_SERVER['REQUEST_URI'], '/product/') !== false) {
$schema = $this->buildProductSchema();
$schemaScript = '<script type="application/ld+json">' . json_encode($schema) . '</script>';
$html = str_replace('</head>', $schemaScript . '</head>', $html);
}
return $html;
}
Esta técnica requiere más cuidado porque trabajas con el HTML ya generado, pero te da flexibilidad total.
Existe la opción de añadir datos estructurados desde Google Tag Manager. Google en su documentación te especifica cómo hacerlo. Y realmente sería insertando una etiqueta de HTML dentro de un contenedor de la herramienta.
El resumen de cómo funciona esto: si el usuario rastrea la página con JavaScript, una de las funciones de este Tag Manager va a ser poner el código en cuestión. Pero va a tener que renderizar el JavaScript antes. Por lo cual, en el caso en el que Google rastree la página sin JavaScript (lo que es bastante posible), no se verá dicho dato estructurado.
Según él, Google no tiene problemas a la hora de rastrearlo, pero aun así recomienda insertar los datos estructurados en la propia web y no en herramientas externas por mantenimiento.
En mi propia experiencia, he visto ocasiones donde Google no ha sido capaz de ver los datos estructurados cuando se han insertado por JavaScript con CSR (que es la forma habitual de JS). Se puede comprobar si lo lee por medio de la Search Console.
Mi opinión: ¿por qué insertar los datos estructurados de una forma arriesgada y con menor escalabilidad cuando puedes hacerlo directamente en el servidor?
Después de implementar los datos estructurados, es fundamental verificar que todo está correcto.
La herramienta oficial de Google para validar datos estructurados: Rich Results Test. Introduce la URL de un producto y comprueba que detecta correctamente el schema de Product.
Para una validación más exhaustiva contra el estándar schema.org: Schema Markup Validator.
En Google Search Console, ve a Mejoras > Productos (o el tipo de schema que hayas implementado). Así verás si Google está detectando correctamente los datos estructurados y si hay errores o advertencias.
Lo más básico: abre el código fuente de la página (Ctrl+U) y busca application/ld+json. Deberías ver tu bloque de JSON-LD con todos los datos del producto.
number_format() correctamente.Además del schema de Product, considera implementar:
Te falta mi máster. Accede a una formación avanzada que te permitirá aplicar e implementar SEO en cualquier tipo de WEB
¡Accede al Máster de SEO Técnico!