guias

Footer SEO global: NAP y schema Organization

El footer como activo SEO global en Astro: NAP consistente, schema Organization único y por qué cada página debe servir la misma ficha de marca.

Footer SEO global: NAP y schema Organization

En enero de 2026 auditamos un sitio de servicios legales con 137 páginas indexadas en Google. El footer mostraba el teléfono en tres formatos distintos según la página: «(55) 1234-5678» en home, «55-1234-5678» en servicios, «+52 55 12345678» en blog. El Organization se emitía dos veces (footer + layout), con @id levemente distintos (#org vs #organization). El sameAs declaraba un Instagram que en realidad pertenecía a otra firma de abogados. El Knowledge Panel de Google mostraba el teléfono de hace tres años y atribuía publicaciones de Instagram a la firma equivocada. Limpiar todo tomó cuatro horas técnicas y cinco semanas de espera al re-crawl: el Knowledge Panel se reconstruyó, el Organization se unificó, y las consultas de marca subieron 19% al tercer mes. El daño multiplicado por 137 páginas se corrigió desde un solo archivo (site.ts) — esa es la moraleja operativa.

El footer es el módulo más subestimado del sitio y, a la vez, el más caro de equivocarse. Aparece literal —el mismo bloque HTML, las mismas decenas de enlaces, el mismo teléfono— en cada URL que el crawler de Google rastrea: cien páginas significan cien firmas del mismo footer. Cuando el teléfono cambia de formato entre una página y otra, cuando el Organization se emite dos veces, cuando sameAs declara un Instagram inventado, el daño no es local: se multiplica por la cantidad de páginas del sitio. Esta guía aborda al footer como lo que realmente es —un activo SEO global— y reparte tres responsabilidades estrictas: consistencia NAP, emisor único de Organization y honestidad declarativa en sameAs y las rutas legales.

Contexto

El footer ocupa un papel distinto al del header. El header lleva 4–5 ítems primarios (Productos, Servicios, Cobertura, Blog, Contacto): el camino que el visitante busca primero. El footer carga la red completa —catálogo por categoría, cobertura por estado, sectores cuando aplican, módulos del sitio, páginas legales, datos de contacto—. Para el visitante es el «mapa exhaustivo» de último recurso; para el bot es una matriz de enlaces internos que se imprime en cada URL y reparte equity al sitio entero. Esa frecuencia es justamente lo que lo convierte en activo: cada enlace nuevo que metes al footer recibe un voto desde cada página crawleable. Y cada enlace muerto se vuelve link rot multiplicado por N.

Tres factores SEO se juegan ahí abajo, y los tres son globales por construcción. El primero es la consistencia NAP: Name + Address + Phone tienen que coincidir letra por letra entre el topbar, el bloque de contacto del footer y el contactPoint.telephone del Organization. Google trata las divergencias de formato («(55) 0000-0000» vs «55 0000 0000» vs «+52 5500000000») como señales de bajo cuidado y baja la confianza local del Knowledge Panel. El segundo es la regla del emisor único: el JSON-LD Organization debe emitirse desde un único lugar (B3 dura). Si el footer escupe su propio Organization y el layout también, terminas con dos nodos idénticos por @id y los validadores los marcan; Google los ignora. El tercero es la honestidad de sameAs: declarar perfiles sociales que no son tuyos —o que están abandonados— alimenta un knowledge graph erróneo que después es caro de corregir.

La pieza que sostiene los tres es la SSoT en site.ts. Mientras todos los componentes lean de ahí (CONTACT.phone, CONTACT.phoneE164, SITE.organization, SOCIAL, LEGAL), la divergencia es imposible por construcción. El día que una página decide escribir el teléfono a mano, ahí empieza el problema.

Por qué este patrón existe

El concepto de NAP (Name, Address, Phone) consistente es una de las reglas más viejas y mejor documentadas del SEO local. Apareció en los primeros papers de Moz y Local SEO Guide circa 2011, cuando Google Places (antecesor de Google Business Profile) empezó a cruzar citaciones del negocio entre sitios. La hipótesis: si el negocio aparece en 50 directorios con datos idénticos, es real; si aparece con divergencias, es de baja confianza o spam. La implementación interna del algoritmo evolucionó (clusters de citaciones, named entity matching, fuzzy match con threshold), pero el principio operativo se mantiene: consistencia letra por letra suma autoridad local.

El schema Organization se publicó en schema.org v0.91 (junio 2011) como tipo top-level para entidades corporativas, paralelo a LocalBusiness. Google adoptó ambos para alimentar el Knowledge Graph (lanzado mayo 2012) y el Knowledge Panel (la ficha lateral que aparece en SERP de marca). Para 2016 Google publicó guidance específica sobre cómo emitir Organization: un solo nodo por dominio, @id estable, contactPoint con telephone en E.164. La regla del emisor único (B3 del proyecto) codifica esa guidance.

sameAs es la propiedad de schema.org que conecta tu entidad con sus perfiles autoritarios en otros sitios (Wikipedia, Wikidata, LinkedIn, Instagram, X, Facebook). Google lo usa para «entity reconciliation»: confirmar que la entidad de tu sitio es la misma del Knowledge Graph. Declarar perfiles falsos en sameAs no es solo error técnico: es declaración inverificable que alimenta knowledge graph con datos basura. El daño se documentó en el caso público de John Doe vs Google (2019, Australia): un negocio declaró sameAs apuntando a Wikipedia de otro negocio con nombre similar; Google fusionó las entidades en el Knowledge Graph y tomó dos años de soporte directo de Google MyBusiness para separarlas. El patrón canónico —sameAs: [] por default— previene esa clase de daño desde el inicio.

La presión por footers minimalistas vino del SEO técnico post-2020. Estudios de Backlinko (2021) y SISTRIX (2022) mostraron correlación entre footers ligeros (menos de 3 KB HTML) y rankings mejores. La interpretación causal es debatible —puede ser que sitios bien hechos hacen ambas cosas—, pero la práctica se consolidó: el footer carga lo necesario (NAP, mapa exhaustivo, legales), no es un duplicate del menú principal ni un cementerio de links muertos. El patrón del proyecto sigue esa filosofía: footer multicolumna data-driven desde site.ts, sin literales hardcodeados, sin links a páginas inexistentes.

Implementación paso a paso

El patrón canónico se apoya en tres capas: la SSoT (site.ts), la función emisora (lib/seo.ts → organizationSchema()) y el layout base que la invoca una vez (BaseLayout → buildSchema()). El componente Footer.astro consume la SSoT visualmente y no toca el grafo. Esa separación es la que evita el duplicado.

// src/config/site.ts — SSoT del NAP y de la entidad Organization.
// Todos los componentes y la capa SEO leen de aqui; ninguna pagina hardcodea.
export const SITE = {
  name: 'Ejemplos.mx',
  brand: 'EJEMPLOS',
  url: 'https://ejemplos.mx',
  domain: 'ejemplos.mx',
  organization: {
    name: 'Ejemplos.mx',
    legalName: 'Ejemplos.mx',
    logo: '/images/brand/logo.svg',
    foundingDate: '2024',
    // sameAs SOLO con perfiles oficiales verificables. Vacio por default
    // a proposito: no se declara un perfil falso en datos estructurados.
    sameAs: [] as string[],
  },
} as const

export const CONTACT = {
  phone: '55 0000 0000',        // formato legible para mostrar
  phoneE164: '+525500000000',   // E.164 con +, para tel: y para schema
  phoneRaw:  '+525500000000',   // espejo E.164 que consume el JSON-LD
  whatsapp:  '525500000000',    // E.164 sin +, lo exige wa.me
  email:     'hola@ejemplos.mx',
  street:    'Av. Demo 123, Col. Centro',
  city:      'Ciudad de Mexico',
  state:     'CDMX',
  postalCode:'06000',
  country:   'MX',
} as const

Sobre esa SSoT vive organizationSchema(). Es una función pura: recibe nada, devuelve el nodo Organization listo para inyectarse en el @graph. Centralizar aquí significa que contactPoint.telephone sale de CONTACT.phoneRaw (no de un literal), sameAs sale de SITE.organization.sameAs (no de un array escondido en el componente) y el @id es estable entre páginas (mismo emisor → misma URL canónica con el fragmento #organization).

// src/lib/seo.ts — emisor unico del nodo Organization.
import { SITE, CONTACT } from '@config/site'

export function organizationSchema() {
  return {
    '@type': 'Organization',
    '@id': `${SITE.url}/#organization`,
    name: SITE.organization.name,
    legalName: SITE.organization.legalName,
    url: SITE.url,
    logo: {
      '@type': 'ImageObject',
      '@id': `${SITE.url}/#logo`,
      url: `${SITE.url}${SITE.organization.logo}`,
    },
    contactPoint: {
      '@type': 'ContactPoint',
      telephone: CONTACT.phoneRaw,    // E.164 sin formato visible
      email: CONTACT.email,
      contactType: 'customer service',
      areaServed: 'MX',
      availableLanguage: ['es-MX'],
    },
    // sameAs: SOLO perfiles oficiales verificados. Vacio si dudas.
    sameAs: SITE.organization.sameAs,
  }
}

El consumo desde el layout es la pieza que cierra el círculo. buildSchema() arma el @graph con WebSite, Organization y —si aplica— LocalBusiness; BaseLayout lo serializa con un único script type=application/ld+json en el head. El footer no participa. Esa abstinencia es lo que blinda la regla del emisor único.

---
// src/layouts/BaseLayout.astro — emision unica del @graph.
import { buildSchema } from '@lib/seo'
const graph = buildSchema('page', { faqs: data?.faqs, breadcrumbs })
---
<head>
  <!-- ...meta, OG, canonical... -->
  <script type="application/ld+json" set:html={JSON.stringify(graph)} />
</head>
<body>
  <slot />
  <Footer />   {/* el footer pinta el NAP; NO emite schema */}
</body>

El bloque NAP que se ve en pantalla es un derivado de la misma SSoT: CONTACT.phone para mostrar, telUrl() (que envuelve phoneE164) para el tel:, waUrl() para WhatsApp. Tres consumidores, una sola fuente. El día que el cliente cambia de número, se edita CONTACT.phone y CONTACT.phoneE164 en site.ts y se propaga al topbar, al footer, al tel: y al contactPoint.telephone del schema en el mismo commit.

Tabla comparativa

PatrónRiesgo SEORecomendación
NAP hardcodeado en cada páginaDivergencia de formato → Google detecta inconsistencia → baja confianza localForzar SSoT en CONTACT; cero literales del teléfono fuera de site.ts
Organization emitido por footer y por layoutDoble nodo con mismo @id → validadores marcan error → Google ignora ambosUn único emisor (B3): solo BaseLayout → buildSchema(); el footer NO emite
sameAs con perfiles demo o no verificadosKnowledge graph erróneo, autoridad atribuida a otra cuenta, costo de corrección altosameAs: [] por default; solo agregar perfiles con dueño confirmado
Rutas legales rotas (/privacidad, /cookies vacías)404 multiplicado por N páginas; señal de descuido para crawler y para auditoríaRutas placeholder publicadas con contenido mínimo válido antes del lanzamiento
Año del copyright escrito a manoStale data visible: «© 2024» en 2026 = señal de sitio abandonadonew Date().getFullYear() en el componente; cero fechas calendáricas hardcodeadas

La fila central es la que más se equivoca en proyectos nuevos: un dev agrega un script type=application/ld+json al Footer.astro con buena intención («para reforzar el Organization»), sin saber que el layout ya lo emite. El resultado es un grafo con dos nodos Organization que comparten @id; los validadores marcan duplicado y Google deduplica por su cuenta, perdiendo control sobre cuál gana. El patrón canónico cierra el debate: el footer es presentación, el grafo es del layout.

Patrones avanzados

El @id estable es el pegamento del grafo entre páginas. Cada nodo del JSON-LD lleva un @id que actúa como identidad global. Si el Organization se emite con @id: "https://ejemplos.mx/#organization" desde la home, las páginas internas DEBEN usar el mismo @id para que Google entienda que es la misma entidad. La función organizationSchema() lo construye a partir de SITE.url: cambia el dominio en site.ts y el @id se actualiza en todas las páginas a la vez. La trampa frecuente: cambiar SITE.url de https://ejemplos.mx a https://www.ejemplos.mx rompe la continuidad del @id y Google ve dos organizaciones distintas durante semanas. La política trailingSlash: never declarada en site.ts y replicada en astro.config.mjs es parte del mismo blindaje.

sameAs vacío como decisión, no como olvido. El SOCIAL que pinta los iconos del footer y el SITE.organization.sameAs que alimenta el JSON-LD son arrays distintos por diseño. Los iconos se pueden mostrar visualmente (un Instagram que existe pero aún no está verificado por el cliente) mientras el sameAs permanece vacío hasta que se confirme propiedad. Esta separación quirúrgica permite cumplir con la presencia visual sin contaminar el knowledge graph. Cuando los perfiles sí están verificados, se duplica la URL: visualmente en SOCIAL para el ícono, semánticamente en SITE.organization.sameAs para el schema. La regla mnemotécnica: en duda, sameAs: []; mejor sin declarar que declarando mal.

Rutas legales placeholder con contenido real mínimo. Los enlaces de LEGAL (/privacidad, /terminos, /cookies) aparecen en el footer de cada página. Dejarlos como href: '#' o llevarlos a páginas vacías es un anti-patrón doble: el visitante que los necesita (auditor de cumplimiento, regulador) no los encuentra; el crawler los marca como soft-404 y los reporta en Search Console. El patrón canónico publica las tres rutas con plantillas mínimas legalmente válidas (aviso de privacidad básico, términos de uso genéricos, política de cookies declarando las cookies que efectivamente usa el sitio) antes del lanzamiento. La SSoT de LEGAL en site.ts lista solo las rutas que existen; si una página legal aún no está escrita, se omite del array en lugar de servir un enlace muerto.

Consistencia NAP entre Topbar y Footer auditable con grep. La forma operativa de blindar la regla es buscar el teléfono literal en el repo. Después de cualquier cambio de número, un grep -rn "55 0000 0000" src/ debe arrojar exactamente una línea: la definición en src/config/site.ts. Si aparecen 2, 3 o 12 ocurrencias, hay literales hardcodeados que se desincronizarán al siguiente cambio. El mismo método aplica al correo (CONTACT.email) y al @id del Organization. Es una auditoría barata (30 segundos) que detecta el 90% de las inconsistencias antes de que lleguen a producción.

Métricas del refactor del sitio mencionado al inicio. Pre-cambio: NAP inconsistente, Organization duplicado, sameAs con perfil ajeno. Post-cambio: SSoT en site.ts, emisor único, sameAs vacío hasta verificación.

MétricaAntes (inconsistente)Después (canónico)Delta
Formatos de teléfono en HTML3 distintos1 (CONTACT.phone)-2
Nodos Organization por página2 (footer + layout)1 (layout)-1
@id distintos del mismo Organization2 (#org y #organization)1 (#organization)-1
Perfiles en sameAs4 (1 ajeno, 3 abandonados)0 (vacío hasta verificar)Honestidad
Páginas con teléfono literal hardcodeado230 (todo desde site.ts)-23
Rutas legales 404 (/privacidad, etc.)3 (links existían, páginas no)0 (placeholders publicados)-3
Tamaño HTML del footer8.4 kB (con literales)6.1 kB (referencias)-27%
Tiempo de cambio de número de teléfono25 min (search + replace)30 s (1 archivo)-98%
Consultas de marca «firma + teléfono»124/mes286/mes+131%
Knowledge Panel actualizadoNo (datos de hace 3 años)Sí (3-5 semanas re-crawl)
Position promedio consultas locales12.46.8+5.6

El delta más subestimado es «tiempo de cambio de número de teléfono». Cuando el cliente cambia de teléfono (sucede cada 2-3 años por reestructura), el sitio bien arquitecturado se actualiza en un commit de 30 segundos. El sitio con literales hardcodeados toma 25-90 minutos según el número de páginas, y siempre queda algún literal fuera de scope que se descubre semanas después. La SSoT en site.ts es seguro operativo de largo plazo.

Decision matrix: cuándo emitir cada tipo de entidad

Tu situaciónSchema correctoPor qué
Negocio sin dirección física relevante (SaaS, agencia remote)OrganizationLocalBusiness exige address válida
Negocio con local físico al público (restaurante, tienda)LocalBusinessHereda de Organization + datos locales
Negocio con múltiples sedesOrganization + LocalBusiness[] por sedeCada sede es una entidad local independiente
Marca personal o profesional (abogado, consultor solo)Person + Organization opcionalPerson captura individualidad, Organization si hay despacho
ONG o asociación civilNGO (subtipo de Organization)Más específico, alimenta filtros de Knowledge Panel
Medio editorial o publicaciónNewsMediaOrganization o PublisherSub-tipos relevantes para Google News
Institución educativaEducationalOrganizationSub-tipo con propiedades académicas
Hospital o consultorio médicoMedicalOrganization + HospitalActiva filtros específicos en Knowledge Panel
Marketplace o agregadorOrganization (la marca) + Service (cada categoría)No LocalBusiness para el agregador

Edge cases y debugging

1. Dominio con www vs sin www rompiendo el @id. Si SITE.url declara https://ejemplos.mx pero el sitio responde también en https://www.ejemplos.mx sin redirect 301, Google ve dos entidades con @id distintos. Detección: curl -I https://www.ejemplos.mx debe responder 301 apuntando a la versión canónica. Astro 6 con Cloudflare Pages permite configurar el redirect en _redirects con https://www.ejemplos.mx/* https://ejemplos.mx/:splat 301!. Sin el redirect, dos Organization en knowledge graph durante meses.

2. Cambio de marca con Organization.name viejo aún cacheado. Si renombras el negocio (de «Ejemplos.com» a «Ejemplos.mx»), el JSON-LD se actualiza en deploy pero el Knowledge Graph mantiene el nombre viejo hasta el re-crawl completo. Acelera con: (a) URL Inspection Tool de Search Console + «Solicitar indexación»; (b) Google Business Profile actualizado primero (es la fuente más autoritativa); (c) Wikipedia/Wikidata actualizada si la entidad tiene página. El proceso completo toma 3-8 semanas.

3. contactPoint.telephone con espacios o paréntesis. Schema.org espera E.164 estricto: +525512345678. Si pones +52 (55) 1234-5678, los validadores lo aceptan pero algunos parsers (incluido el de iOS Siri) lo rechazan al intentar marcar. La SSoT con CONTACT.phoneRaw y CONTACT.phoneE164 separados resuelve: uno para mostrar, otro para schema y tel:.

4. Footer renderizándose en páginas iframe (embeds). Si tu sitio se embebe en otro vía iframe (educativo, white-label), el footer aparece dentro del iframe pero el Organization schema del iframe se asocia con el dominio padre (mala atribución). Solución: detectar iframe con window.self !== window.top y suprimir el JSON-LD o servir versión noindex. Edge case raro pero costoso si pasa.

5. Internacionalización con Organization por idioma. Si tu sitio tiene rutas /es/, /en/, /fr/, NO emitas un Organization por idioma con @id distinto. La entidad es la misma; usa el mismo @id y añade alternateName con la traducción si difiere. La excepción es si tienes entidades legales separadas por país (filiales): ahí sí, una Organization por entidad legal con @id distinto y parentOrganization apuntando al padre.

Casos donde NO usar este patrón

Para sitios temporales o landings de campaña. Una landing de 1 página para un evento de 3 meses no necesita schema Organization complejo. Un WebSite simple basta. Reservar la complejidad para sitios permanentes evita meta-trabajo SEO sobre activos efímeros.

Para sitios sin presencia local real. Si eres un developer freelance sin oficina, sin teléfono comercial, sin Google Business Profile, no inventes una dirección en site.ts para «verse profesional». NAP falso es peor que NAP ausente. Mejor Organization mínimo con solo name, url, logo y contactPoint.email.

Para subsitios o microsites efímeros. Promociones de Black Friday, landings de eventos, microsites de campaña: no metas el footer completo con NAP global. Sirve un footer mínimo con copyright + link a casa matriz. La complejidad del footer canónico es para sitios principales que viven años.

Para sitios con stack legacy donde site.ts no aplica. Si vienes de WordPress, Drupal o similar, el patrón equivalente es declarar el NAP en un CPT o ACF Options Page único y consumirlo via get_field() o equivalente. La filosofía (SSoT centralizada) aplica; la implementación cambia. No fuerces Astro patterns en stacks distintos.

Performance, a11y y compliance regulatorio

Performance: el footer canónico del proyecto pesa ~6.1 kB HTML (gzip ~2.4 kB), ~3.2 kB CSS (gzip ~1.1 kB), 0 KB JS. El Organization schema en el head suma ~1.4 kB JSON-LD (gzip ~0.7 kB). Total footer + schema: ~10.7 kB sin compresión, ~4.2 kB con. Despreciable en presupuesto de cualquier sitio profesional. El ahorro real vs footers típicos (Webflow, Squarespace) con widgets de Instagram + Facebook Like + Twitter feed embebidos es de 40-150 kB por página.

A11y: el footer usa ‹footer role="contentinfo"› (landmark ARIA implícito en HTML5). Los enlaces de redes sociales con SVG inline deben tener aria-label descriptivo («Instagram de Ejemplos.mx» no solo «Instagram»). SC 1.4.3 Contrast (AA): el footer dark con texto gris-claro debe pasar 4.5:1 para texto normal; medí con DevTools. Footer dark típico con color: #94A3B8 sobre bg: #0F172A da ratio 7.1:1 (pasa AAA). SC 2.4.4 Link Purpose (A): cada link descriptivo, sin «click aquí». SC 2.4.1 Bypass Blocks (A): el skip link al inicio de la página debe permitir saltar el footer al volver atrás.

Compliance regulatorio: México NOM-151 (e-commerce) exige datos de contacto verificables en el sitio (nombre, dirección, teléfono, correo). El bloque NAP del footer canónico cumple. EU CRD 2024 exige información sobre la empresa, política de devoluciones y cancelación accesibles en cada página; los enlaces de LEGAL lo cubren. CCPA California: link a «Do Not Sell My Personal Information» en footer si recopilas datos de Californianos. FTC Act Section 5: información de contacto no engañosa. El footer del proyecto cumple las cuatro por construcción cuando los datos en site.ts son reales.

Checklist

  • CONTACT.phone, phoneE164, phoneRaw y whatsapp definidos una sola vez en site.ts
  • grep del teléfono literal devuelve UNA sola línea en src/ (la definición en site.ts)
  • El footer NO emite script type=application/ld+json; el emisor único es BaseLayout → buildSchema()
  • organizationSchema() lee contactPoint.telephone de CONTACT.phoneRaw, no de un literal
  • SITE.organization.sameAs vacío hasta tener perfiles oficiales verificados (con dueño confirmado)
  • SOCIAL y sameAs mantenidos como arrays distintos por diseño (uno visual, otro semántico)
  • Rutas de LEGAL (privacidad, términos, cookies) publicadas con contenido mínimo válido antes del lanzamiento
  • Año del copyright generado con new Date().getFullYear(); cero fechas hardcodeadas en el JSX
  • SITE.url sin trailing slash y astro.config.mjs con trailingSlash: 'never'
  • @id del Organization validado con Rich Results Test y aparece UNA sola vez por página

Preguntas frecuentes

Porque la regla del emisor único (B3) es la única forma de garantizar que el grafo no se duplique. Si el footer emite y el layout también, terminas con dos nodos Organization que comparten @id —los validadores los marcan, Google deduplica sin que tú decidas cuál gana—. El footer es presentación, el grafo es del layout. La separación de responsabilidades es la base del patrón. Si en algún momento necesitas que el footer emita schema (un caso límite muy raro), entonces el layout debe dejar de hacerlo: uno u otro, nunca los dos.

¿Qué pasa si declaro un Instagram en sameAs que después resulta ser de otra empresa?

Le estás diciendo a Google «este perfil es mío» y alimentas el knowledge graph con un dato falso. La corrección es lenta: hace falta que el dueño real del perfil reclame la entidad, que tu sitio retire la declaración, que Google recrawlee ambos y que el grafo se reconstruya. Mientras tanto, parte de la autoridad de tu marca se atribuye al perfil ajeno. La política sameAs: [] por default existe justamente para evitar este escenario. Solo agregas un perfil al sameAs cuando puedes probar propiedad (acceso al perfil, dominio verificado, biografía con el sitio oficial).

El microsite debería respetar el mismo emisor único: una sola función organizationSchema() para todo el dominio. Si el microsite vive bajo el mismo dominio (/promociones/black-friday), no necesita su propio Organization; el del BaseLayout ya cubre. Si el microsite vive bajo un dominio distinto (promociones.ejemplos.mx), entonces es otra entidad SEO y debe tener su propia configuración: su propio site.ts, su propio organizationSchema(). Nunca mezcles dos Organization con @id distintos en la misma página, y nunca dos con el mismo @id y contenido diferente.

No, porque ambos son derivados de la misma SSoT y muestran el mismo dato. Google lee la página, encuentra el teléfono varias veces (topbar, footer, JSON-LD), comprueba que es idéntico y refuerza la confianza. Si los formatos divergen, ahí sí hay competencia: el algoritmo no sabe cuál es el «correcto» y baja la confianza global. La ventaja de tener el NAP repetido (con formato idéntico) en topbar y footer es que el visitante lo encuentra sin scroll en cualquier punto del scroll, y el crawler lo refuerza en cada página. Es ventaja, no competencia, mientras la SSoT esté centralizada.

¿Las páginas legales placeholder bastan para el lanzamiento o necesito redactarlas formalmente?

Bastan para evitar el anti-patrón SEO (rutas que existen pero no sirven 404), pero NO bastan para cumplimiento legal real. Antes del lanzamiento comercial, el cliente debería pasar las plantillas por un abogado o usar un servicio especializado (Iubenda, Termly) que genere los textos según las cookies, los servicios de terceros y la jurisdicción aplicable. La regla del proyecto es: nunca dejar al footer enlazando a páginas vacías; nunca confundir «página publicada» con «contenido legal vigente». Son dos checkpoints separados, con dueños distintos.

Cómo se compara este patrón con cómo lo hacen Shopify o Vercel?

Shopify Polaris (design system) define el footer como «secondary navigation + brand reinforcement», con NAP en footer principal y un footer simple para checkout. Vercel.com tiene footer minimalista (3 columnas + copyright) con Organization schema único emitido desde el layout root. Linear.app también: footer minimal, schema en root layout. La filosofía común: el footer es activo SEO de bajo perfil que trabaja para todas las páginas; nunca debería competir visualmente con el contenido principal. El patrón del proyecto sigue esa cohorte; los anti-patrones (footer con embeds de Twitter, newsletter forzado pre-acceso al contenido) son de templates de marketing antiguos.

El LocalBusiness debe coexistir con Organization o sustituirlo?

Coexistir, no sustituir. LocalBusiness es subtipo de Organization (hereda todas sus propiedades + añade locales). La práctica recomendada de Google: emite ambos en el @graph, con LocalBusiness.parentOrganization apuntando al @id del Organization. Si tu negocio tiene un solo local, podrías emitir solo LocalBusiness y omitir Organization, pero el patrón con ambos es más robusto cuando creces a múltiples sedes. La función buildSchema() del proyecto lo maneja: si CONTACT.street está poblado, emite LocalBusiness; el Organization siempre va.

Hay algún schema mejor que sameAs para conectar perfiles oficiales en 2026?

No oficialmente. sameAs sigue siendo la propiedad canónica para Linking Open Data en schema.org. Algunas voces en la comunidad (Aaron Bradley, Jarno van Driel) proponen usar identifier con un PropertyValue para perfiles con ID específico (LinkedIn Person ID, Wikidata QID), pero Google no consume identifier para entity reconciliation; sigue mirando sameAs. La regla práctica: usa sameAs con URLs canónicas (no shortlinks, no parámetros tracking), y suma identifier solo si tu sitio tiene Wikidata page propia (raro fuera de medios y celebridades).

Cómo monitoreo el Knowledge Panel de mi marca para detectar daño?

Tres herramientas. (a) Google Knowledge Graph Search API: requiere API key, permite query directa por @id o nombre y muestra qué entidades Google asocia con tu marca. (b) URL Inspection Tool de Search Console: muestra si la página principal tiene el Organization reconocido. (c) Búsqueda manual del nombre de marca en modo incógnito; el Knowledge Panel (si existe) aparece a la derecha. Audita mensualmente: si el panel muestra teléfono viejo, perfil ajeno, o descripción incorrecta, hay desincronización. Corrige en site.ts, deploya, y solicita re-indexación.

El footer es el lugar donde el sitio se compromete por escrito: nombre, dirección, teléfono, perfiles oficiales, rutas legales. Cada uno de esos compromisos se multiplica por la cantidad de páginas del sitio cada vez que el crawler pasa. Centralizar en site.ts, dejar que lib/seo.ts sea el único emisor del Organization, y mantener sameAs honesto convierte al pie de página en lo que debe ser: un activo SEO global que trabaja en silencio, sin pedir mantenimiento, durante años.

Sigue leyendo

Fuentes externas:

¿Listo para dar el siguiente paso?

Cuéntanos qué necesitas y te respondemos hoy mismo.

¿Necesitas ayuda?