Módulo del sitio · Footer

El Footer: el cierre del sitio que recoge lo que faltó arriba

El pie de página que aparece en TODAS las páginas del sitio: banda CTA, columna de marca con NAP completo, 4 columnas de navegación data-driven, banda opcional de cumplimiento normativo y barra inferior con copyright, enlaces legales y botón «volver arriba».

Esta página no es una ficha técnica: es el módulo entero, abierto y explicado. Qué problema resuelve y por qué el pie de página es un activo de SEO global (cada enlace aparece en miles de URLs y reparte equity interno a todo el sitio), de qué cinco zonas se compone (CTA + cuerpo + cumplimiento opcional + barra inferior + acento), cómo se comporta en el teléfono (stack progresivo, acordeón nativo opcional, botones full-width, hoja de impresión) y, al final, cómo está construido por dentro —desde la SSoT en site.ts hasta el JSON-LD Organization que centraliza el NAP en lib/seo.ts—.

Con tres particularidades que separan al footer del resto de los módulos. Primera: es el ÚNICO módulo que aparece en cada página, por eso su contenido es estratégico —cada enlace multiplica equity, cada cambio impacta a todo el sitio—. Segunda: es el bloque donde el SEO local se juega la consistencia NAP (nombre, dirección, teléfono idénticos entre Topbar, Footer y JSON-LD Organization); la SSoT en CONTACT lo garantiza, pero la regla queda explícita aquí. Tercera: el componente NO emite JSON-LD propio —ni siquiera con prop—; el schema vive en lib/seo.ts → organizationSchema() y solo lo emite buildSchema() desde BaseLayout (regla B3 dura, un único emisor por página).

Definición

¿Qué es el módulo Footer?

El pie de página del sitio: cierre visual e informativo que aparece en cada URL, con cinco zonas (CTA + cuerpo NAP/columnas + cumplimiento opcional + barra inferior + acento decorativo). Data-driven desde site.ts (SITE · CONTACT · PRODUCT_CATEGORIES · SERVICES · COVERAGE_STATES · BRANCHES · SOCIAL · LEGAL); JSON-LD Organization centralizado en lib/seo.ts y emitido por buildSchema() en BaseLayout.

El Footer (Footer.astro) es la última franja de cualquier página: la zona que recoge al visitante que dudó arriba y le ofrece la red de seguridad —contacto, mapa del sitio, legales—. Está compuesto por cinco zonas en orden estricto: banda CTA pre-footer (último intento de conversión, eyebrow + título + dos botones), cuerpo con la columna de marca + 4 columnas de navegación data-driven (Productos, Servicios+Sectores, Cobertura, Empresa), banda opcional de cumplimiento normativo (badges/certificaciones), barra inferior con copyright dinámico + enlaces legales + botón «volver arriba», y la barra de acento decorativa en la parte superior absoluta.

En este proyecto vive en un único componente que SE MONTA UNA SOLA VEZ en PageLayout. Todas las páginas del sitio heredan su footer sin saberlo —incluyendo esta misma guía, que cierra abajo con el componente REAL, no con una réplica anotada—. La data llega íntegra desde site.ts: marca de SITE, NAP de CONTACT, taxonomía de PRODUCT_CATEGORIES/SERVICES/SECTORS/COVERAGE_STATES, sucursales de BRANCHES (vacío en esta plantilla), redes de SOCIAL (auto-oculta si []), legales de LEGAL. La única lógica de presentación es: usar telUrl() para el teléfono, waUrl(WA_MESSAGES.cotizacion) para WhatsApp, y new Date().getFullYear() para el copyright. Para el SEO local, el JSON-LD Organization se centraliza en lib/seo.ts → organizationSchema() (alias canónico de orgSchema) y se emite UNA sola vez desde buildSchema() en BaseLayout —el componente NO toca el grafo—.

Función e importancia

¿Para qué sirve?

Hace tres trabajos a la vez, todos críticos: distribuye autoridad interna a TODAS las páginas con un mismo set de enlaces (activo SEO global), garantiza la consistencia NAP entre Topbar/Footer/JSON-LD (factor de confianza local) y concentra las señales de trust que NO caben arriba (dirección física, horario, redes, certificaciones, legales).

Su función primaria es estructural: el footer es la red de seguridad de la navegación —si el visitante llegó hasta abajo sin encontrar algo, aquí lo encuentra—. A diferencia del header (que lleva 4-5 ítems primarios, los que el visitante busca primero), el footer carga las 50+ rutas que componen el sitio: catálogo completo por categoría, cobertura por estado, todos los servicios, sectores cuando aplican, páginas legales, contacto, blog. Es el «mapa exhaustivo» del sitio, ordenado por temática. Para el visitante: la última oportunidad de descubrir. Para el crawler de Google: una matriz de internal linking que se imprime en cada URL y reparte equity equitativamente al sitio entero.

Su función secundaria es SEO local: aquí se materializa la consistencia NAP (Name + Address + Phone), uno de los factores más antiguos del ranking local. El teléfono que aparece en el bloque NAP del Footer DEBE ser idéntico —letra por letra, formato por formato— al que muestra el Topbar y al que declara el JSON-LD Organization en contactPoint.telephone. La SSoT en CONTACT lo garantiza: CONTACT.phone para mostrar, telUrl() (que lee phoneE164) para el tel:, CONTACT.phoneRaw para el schema. Si una página cualquiera reescribe el teléfono con un formato distinto, Google detecta la divergencia y baja la confianza del Knowledge Panel. Y la terciaria es de conversión: la banda CTA pre-footer es el último intento de mover al visitante a WhatsApp/contacto antes del cierre informativo, con eyebrow + título + dos botones (WhatsApp con mensaje pre-armado vía WA_MESSAGES.cotizacion + ghost a /contacto).

Activo SEO global: cada enlace del footer multiplica equity interno

El footer aparece en TODAS las páginas del sitio. Eso significa que cada enlace que vive aquí —a /privacidad, a /cobertura/cdmx, a /modulos/topbar— recibe un voto desde cada URL crawleable. Por eso el patrón es estricto: enlaces del footer = secundarios pero estables (legales, taxonomía completa, cobertura), NUNCA páginas efímeras de campaña; el header lleva los primarios (qué vendes), el footer lleva la red. Los 4-5 ítems del menú principal en el header, pero las 50+ rutas que componen el sitio aquí abajo, ordenadas y data-driven.

NAP consistency: el dato de negocio coincide letra por letra

Name + Address + Phone (la tríada NAP del SEO local) DEBE ser idéntica entre Topbar, Footer y JSON-LD Organization. La SSoT es CONTACT en site.ts (phone, phoneE164, street, city, state, postalCode) + SITE.organization (name, legalName). Si una página reescribe el teléfono con formato distinto («(55) 0000-0000» en lugar de «55 0000 0000»), Google la marca como inconsistencia y baja la confianza del Knowledge Panel. Aquí no hay ese riesgo porque ningún componente hardcodea: todos leen de site.ts.

Trust signals concentrados: la zona donde el visitante revalida

El visitante que llega al footer es el que dudó. Por eso aquí concentramos las señales de confianza que NO caben arriba: dirección física (negocio real, no fachada), horario explícito (atención humana), redes verificables (no enlaces a perfiles vacíos), certificaciones (cuando aplican) y enlaces legales completos. El patrón canónico: si una página no es navegable arriba pero existe (privacidad, términos, cookies), su lugar es el footer. Nada efímero, todo verificable.

Anatomía

¿Qué lleva por dentro?

Cinco zonas en orden estricto, cada una con su rol y su fondo. Mapean 1:1 con el código de Footer.astro y reaparecen en el ejemplo en vivo del cierre de esta misma página (regla del molde: si el módulo se rompe, esta guía se rompe a la vista).

El componente no es un bloque monolítico: son cinco zonas con responsabilidades distintas. La 1 (banda CTA) intenta la última conversión, con fondo distinto y borde inferior. La 2 (cuerpo) es el grid principal, con la marca + NAP a la izquierda y las 4 columnas de navegación a la derecha. La 3 (cumplimiento) es opcional, una franja angosta solo aparece si la prop certifications llega poblada. La 4 (barra inferior) es el cierre legal, con fondo negro absoluto y tres bloques (copyright/tagline · legales · scroll-top). La 5 (barra de acento) es la única decoración «no-funcional», un degradé de 3 px en la parte superior absoluta.

Cada zona tiene su SSoT clara —ver la columna «dato» en cada ficha—. No hay magia: si quieres entender de dónde sale algo, lee el dato; si quieres modificarlo, edita esa SSoT en site.ts. El componente solo presenta lo que recibe.

  1. Banda CTA pre-footer

    La franja del cierre activo: eyebrow («¿Listo para empezar?») + título («Cuéntanos tu proyecto y te respondemos hoy mismo.») + dos botones (WhatsApp con waUrl(WA_MESSAGES.cotizacion) + ghost a /contacto). Es el último intento de conversión antes del cierre informativo; lleva fondo distinto (--ft-bg-cta, #121218) y borde inferior para diferenciarse del cuerpo del footer.

    SSoT: <section class="footer__cta"> · waUrl(WA_MESSAGES.cotizacion) · href="/contacto"

  2. Cuerpo — marca + 4 columnas

    El grid principal: minmax(280px, 1.6fr) repeat(4, 1fr). Columna de marca (logo wordmark + descripción + bloque NAP completo + redes sociales) + 4 columnas data-driven (Productos desde PRODUCT_CATEGORIES, Servicios+Sectores desde SERVICES+SECTORS, Cobertura desde COVERAGE_STATES, Empresa con enlaces fijos). El bloque NAP usa <address> con telUrl(), correo, dirección con link a Google Maps, sucursales (BRANCHES) y horario (CONTACT.schedule).

    SSoT: <div class="footer__body"> · PRODUCT_CATEGORIES · SERVICES · COVERAGE_STATES · CONTACT · BRANCHES · SOCIAL

  3. Banda de cumplimiento (opcional)

    Franja angosta para badges de normativas, certificaciones o garantías («ISO 9001», «PCI DSS», «GDPR-compliant»). NO viene por default; se activa pasando la prop certifications a <Footer />. Vive en la lista <ul class="footer__cert-list"> con borde sutil; cada badge es un chip plano sin enlace. Patrón pro: el cumplimiento se afirma con el sello visible, no con un párrafo.

    SSoT: props certifications?: Cert[] · <div class="footer__cert-band">

  4. Barra inferior — copyright + legales + volver-arriba

    La franja final, fondo negro absoluto (--ft-bg-bottom, #000). Tres bloques: izquierda (copy con año dinámico + tagline desde SITE.tagline), centro (enlaces legales separados por «·» desde el array LEGAL), derecha (botón scroll-to-top con guard prefers-reduced-motion). El año se calcula con new Date().getFullYear() —se actualiza solo cada 1 de enero, sin tocar el componente—.

    SSoT: <div class="footer__bottom"> · new Date().getFullYear() · LEGAL · scroll-to-top

  5. Barra de acento (decorativa)

    Línea de 3 px en la parte superior absoluta del footer: degradé de --c-primary a --c-primary-light y se desvanece al 90%. Es la única decoración «no-funcional» del componente y sirve para separar visualmente el pie del contenido de la página sin recurrir a un border-top duro.

    SSoT: <div class="footer__accent-bar"> · linear-gradient(90deg, --c-primary → --c-primary-light → transparent)

Otros diseños y aplicaciones

Variantes del Footer

Seis configuraciones: tres salen de combinar props/data existentes (REAL) y tres requieren agregar una zona, una prop o un slot al componente (EXTENSIÓN). Cada réplica pinta el layout en miniatura con clases .fv (footer variant) y deja claro qué se reordena.

El footer admite variantes sin perder identidad: cambia el énfasis (compacto vs multi-columna), la zona de cumplimiento (visible vs oculta), las redes (presentes vs auto-ocultadas), la banda CTA (WhatsApp vs newsletter) o el cierre legal (sin créditos vs con crédito de agencia). El componente actual cubre las REAL; las EXTENSIÓN documentan cómo escalarlo sin romper la arquitectura.

Cada variante tiene su contexto de aplicación: el negocio local quiere la canónica, la landing one-page quiere la compacta, el SaaS regulado quiere el cumplimiento poblado, el blog editorial quiere el newsletter. Reusar el mismo componente con configuraciones distintas es la lección del sistema —el footer NO es seis componentes, es uno con seis caras—.

  • ¿Listo?Contacto
    © 2026· Privacidad · Términos ·

    Canónica multi-columna con NAP completo

    Negocio local · Servicios · E-commerce con dirección física

    La cara natural del componente: banda CTA + cuerpo de 5 columnas (marca + 4 nav) + barra inferior. NAP completo en la columna de marca (WhatsApp como CTA destacado, teléfono, correo, dirección con Maps, horario). Es la variante REAL del sitio actual: <Footer /> sin props, con SOCIAL poblado y BRANCHES vacío. Aplica a cualquier sitio con presencia física y un catálogo de tamaño medio.

  • © 2026 Marca· Privacidad · Términos · Cookies ·

    Compacta — solo barra inferior

    Landing one-page · Funnel · Microsite de campaña

    EXTENSIÓN: hoy el componente siempre renderiza CTA + cuerpo. Para una landing one-page (un solo scroll, sin necesidad de mapa interno) hace falta una prop compact?: boolean que omita la banda CTA y el cuerpo, dejando solo copyright + legales + scroll-top. El SEO igual lo emite BaseLayout vía organizationSchema; aquí solo se reduce el footprint visual del cierre.

  • ¿Listo?
    © 2026

    Sin redes (SOCIAL=[])

    Marca sin redes verificadas · Sitio interno corporativo · Producto MVP

    REAL data-driven: si SOCIAL = [] en site.ts, la fila de iconos sociales desaparece sola del DOM (guardia {SOCIAL.length > 0 && ...} en el componente). No hay que tocar el Footer ni pasar prop; es el comportamiento por default. Útil para marcas nuevas, sitios corporativos sin presencia social pública, o cuando los perfiles existen pero aún no están verificados (mejor sin redes que con redes rotas).

  • Newslettertu@correo
    © 2026

    Con newsletter inline en la banda CTA

    Blog · Media · Comercio con email marketing

    EXTENSIÓN: la banda CTA actual lleva 2 botones (WhatsApp + ghost a /contacto). Para sitios con newsletter, reemplazar uno por un form inline (<input type="email"> + botón «Suscribirme»). Requiere prop ctaMode?: 'wa' | 'newsletter' (default 'wa') o pasar slots; el form integra con el proveedor del cliente (Brevo, ConvertKit, Mailchimp) vía endpoint. Patrón pro: captura de email al cierre, sin invadir el cuerpo.

  • ¿Listo?
    CUMPLIMIENTO:ISO 9001PCI DSSGDPR
    © 2026

    Con banda de certificaciones poblada

    Servicios B2B regulados · E-commerce con sello · Salud y finanzas

    REAL: pasar certifications={[{label: 'ISO 9001'}, {label: 'PCI DSS'}, {label: 'GDPR'}]} al <Footer />. La zona 3 (banda de cumplimiento) aparece entre el cuerpo y la barra inferior, con cada label como chip plano. Usa solo certificaciones REALES y verificables (no badges decorativos); si te las inventas, el visitante que conoce la norma lo detecta y pierdes credibilidad.

  • © 2026 Cliente· Privacidad ·Sitio por Estudio

    Modo «agencia» con créditos

    Sitio hecho por agencia · Portfolio del estudio · Acuerdo de visibilidad

    EXTENSIÓN: agregar un párrafo de crédito en la barra inferior («Sitio diseñado y construido por [Estudio]» con enlace dofollow). Requiere prop credits?: { text: string; href?: string; rel?: 'dofollow' | 'nofollow' } o slot dedicado. Patrón propio del modelo de agencia (visibilidad de marca a cambio de descuento); cuidado con `rel`: si abusas de dofollow en cientos de sitios, Google puede tratarlos como link scheme.

Responsive y móvil

El footer, en el teléfono y en papel

Cuatro patrones reales: stack progresivo del grid (default, mobile-first), acordeón nativo con <details> cuando hay muchas columnas, botones full-width en la banda CTA y hoja de impresión que reduce el footer al NAP. Todo CSS puro, cero JavaScript adicional.

El footer es más complejo que un hero o un menú: tiene cinco columnas con datos heterogéneos (texto largo + listas + iconos + botones), por lo que el reflow al teléfono no es trivial. El patrón canónico es mobile-first y progresivo: 5 columnas en escritorio → 3 en tablet (marca ocupa la fila completa, las 3 nav debajo) → 2 en móvil estrecho → 1 en pantalla mínima. Cero JavaScript, todo CSS Grid nativo.

A eso se suman dos detalles que pocos footers cubren: el acordeón opcional con <details>/<summary> (cuando hay muchas columnas con muchos enlaces, evita un footer de 600+ px de alto en el teléfono) y la hoja de impresión (un sitio se imprime más de lo que se cree —recibos, fichas técnicas, propuestas— y el footer impreso debe mostrar el NAP, no las redes ni el scroll-top que no se pueden tocar en papel—).

1 · Stack progresivo (mobile-first)

El cuerpo del footer es un grid con tres cortes: 5 columnas en escritorio (marca + 4 nav), 3 en tablet (marca ocupa grid-column: 1 / -1), 2 en móvil, 1 en pantalla mínima. Cero JavaScript: el reflow lo hace CSS Grid solo. Es el default del componente actual.

CSS · grid responsive del cuerpo
/* MÓVIL · STACK A UNA COLUMNA (default del cuerpo)
   El cuerpo del footer es un grid responsive de 3 cortes:
   - default (escritorio ≥1100px): marca (1.6fr) + 4 cols (1fr)
   - tablet (≤1100px):              1fr 1fr 1fr · marca ocupa 1/-1
   - móvil  (≤760px):               1fr 1fr     (2 columnas)
   - estrecho (≤480px):             1fr         (una sola columna)
   Resultado: la marca y el NAP nunca se aplastan; las 4 cols de
   nav se reflowan a 1 columna en el teléfono. Cero JS, cero
   librería, todo CSS Grid nativo. */

.footer__grid {
  display: grid;
  grid-template-columns: minmax(280px, 1.6fr) repeat(4, 1fr);
  gap: 3rem 2.25rem;
}

@media (max-width: 1100px) {
  .footer__grid { grid-template-columns: 1fr 1fr 1fr; gap: 2.5rem 2rem; }
  .footer__brand { grid-column: 1 / -1; max-width: 620px; }
}
@media (max-width: 760px)  { .footer__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px)  { .footer__grid { grid-template-columns: 1fr; gap: 2rem; } }

2 · Acordeón nativo con <details>

Cuando el footer tiene muchas columnas y cada una lleva 10+ enlaces, el stack vertical produce un pie de 600+ px en el teléfono. Reemplazar cada <nav class="footer__col"> por un <details> con <summary> deja al visitante abrir solo lo que le interesa. EXTENSIÓN sin JS, soportada nativamente.

CSS · acordeón nativo con <details>
/* MÓVIL · COLUMNAS COLAPSABLES CON <details> (extensión, cero JS)
   Cuando hay 4 columnas con 8-12 enlaces cada una, el stack
   vertical en móvil produce un footer de 600+ px de alto. Un
   acordeón nativo con <details>/<summary> permite que el visitante
   abra solo la columna que le interesa. Sin JavaScript, soportado
   por todos los navegadores. Reemplaza cada <nav class="footer__col">
   por un <details class="footer__col footer__col--acc"> en ≤640px. */

@media (max-width: 640px) {
  /* Mostrar cada columna como acordeón cerrado por default. */
  .footer__col-acc {
    border-top: 1px solid var(--ft-border);
    padding: var(--sp-2) 0;
  }
  .footer__col-acc > summary {
    /* Reset del marker nativo, lo reemplazamos por flecha controlada */
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--sp-3) 0;
    /* Mantenemos el .footer__col-heading visual; el caret va después. */
  }
  .footer__col-acc > summary::-webkit-details-marker { display: none; }
  .footer__col-acc > summary::after {
    content: '+';
    font-family: var(--font-heading);
    font-size: 1.2rem;
    color: var(--ft-text);
    transition: transform .2s ease;
  }
  .footer__col-acc[open] > summary::after { content: '−'; }

  /* La lista se renderiza solo cuando [open]; el navegador la oculta solo. */
  .footer__col-acc .footer__nav-list { padding: var(--sp-2) 0 var(--sp-3); }
}

3 · Botones CTA full-width

En escritorio, los 2 botones de la banda CTA van inline con gap; en móvil deben ocupar el ancho completo y apilarse, con área táctil ≥48 px (WCAG SC 2.5.5). El componente actual ya lo hace en ≤760px: flex-direction:column en el inner, flex:1 en cada botón, y padding más ajustado.

CSS · banda CTA full-width en móvil
/* MÓVIL · BOTONES FULL-WIDTH EN LA BANDA CTA
   La banda CTA pre-footer trae 2 botones (WhatsApp + ghost contacto).
   En escritorio van inline con gap; en móvil deben ocupar el ancho
   completo y apilarse, porque cualquier acción del pulgar pide área
   táctil generosa. El componente ya lo hace en ≤760px (flex: 1 sobre
   los botones; flex-direction:column en el inner; padding más justo). */

@media (max-width: 760px) {
  .footer__cta-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.25rem;
    padding: 2rem var(--container-px, 1.5rem);
  }
  .footer__cta-actions { width: 100%; }
  .footer__btn {
    flex: 1;
    justify-content: center;
    /* Asegura área táctil cómoda (44 px mínimo, WCAG SC 2.5.5). */
    min-height: 48px;
  }
}

4 · Hoja de impresión (@media print)

Una página impresa (recibo, propuesta, ficha) NO necesita la fila de redes ni el botón «volver arriba» —no se tocan en papel—. La hoja de impresión reduce el footer al bloque NAP + legales en blanco y negro: cumple con la información esencial sin gastar tinta en gradientes ni iconos sociales. Patrón a11y barato.

CSS · @media print del footer
/* IMPRESIÓN · footer reducido al NAP, sin redes ni scroll-top
   Una página impresa (recibo, ficha técnica, propuesta) NO necesita
   la fila de redes ni el botón «volver arriba» (no se puede tocar en
   papel). Sí queremos el bloque NAP (nombre, dirección, teléfono,
   correo): es el dato de contacto que justifica el documento. Receta
   en @media print, parte de la accesibilidad real del componente. */

@media print {
  /* Quitar lo que no aporta en papel: gradientes, redes, scroll-top, CTA. */
  .footer__accent-bar,
  .footer__cta,
  .footer__social,
  .footer__top,
  .footer__cert-band {
    display: none !important;
  }

  /* El cuerpo y la barra inferior se vuelven neutros (sin fondos oscuros
     que gasten tinta). El NAP queda como referencia legible. */
  .footer {
    background: #fff !important;
    color: #000 !important;
  }
  .footer__brand-desc,
  .footer__contact-row,
  .footer__copy,
  .footer__seo-tagline,
  .footer__legal-link {
    color: #000 !important;
  }
  .footer__bottom { background: #fff !important; }

  /* Mantener legible la jerarquía: NAP + legales son el dato del documento. */
  .footer__nav-link { color: #333 !important; text-decoration: underline; }
}

Posición en el layout

¿Dónde va en la página?

Al cierre de TODAS las páginas del sitio, montado UNA sola vez en src/layouts/PageLayout.astro. No se importa página por página: PageLayout lo emite por debajo del slot principal, así que cualquier ruta que extienda PageLayout (o que use BaseLayout vía PageLayout) hereda el footer automáticamente.

La regla canónica del sistema: el Footer es un componente de LAYOUT, no de página. Eso significa que cada nueva página (.astro nueva en src/pages/) hereda el footer sin hacer nada —no hay que importarlo ni renderizarlo manualmente—. Si necesitas omitirlo en una página puntual (un funnel de cierre, un wizard sin escapes), debería existir una prop showFooter?: boolean en PageLayout (hoy NO existe; es una EXTENSIÓN documentada en la sección 4 del molde).

El cierre de cada página tiene un orden estable: contenido principal del slot → SectionMenu de cierre (interlinking, opcional) → Footer (cierre informativo, siempre). El SectionMenu es donde la página decide a qué llevar al visitante después; el Footer es donde el sitio entero le ofrece el mapa. Las dos piezas no compiten: el menú reparte hacia adelante (lo siguiente que ver), el footer reparte hacia los lados (todo el resto del sitio).

Capa técnica

Cómo está construido

El componente vive en src/components/Footer.astro y consume site.ts como SSoT total. El JSON-LD Organization vive en src/lib/seo.ts → organizationSchema() (alias canónico de orgSchema), emitido UNA vez por buildSchema() en BaseLayout (regla B3). Tres recetas cubren el espectro de uso: básico data-driven, con sponsors/badges, y la integración SEO con organizationSchema.

La arquitectura es estricta: el componente no decide datos, los presenta. El componente no emite schema, lo deja al emisor central. El componente no hardcodea enlaces, los lee de la taxonomía. Esa separación de responsabilidades es lo que mantiene el footer estable y data-driven —cambias un teléfono en CONTACT y se propaga al Topbar, al Footer, al telUrl(), al JSON-LD; cambias un nombre legal en SITE.organization y se actualiza el copyright (si se conecta) y el schema Organization—.

Las tres recetas que siguen son el camino completo: la receta A muestra el uso default sin props, la B muestra cómo poblar la banda de cumplimiento con certificaciones reales, y la C documenta la integración con organizationSchema —dónde vive, quién lo emite, por qué el Footer NO debe emitirlo—.

A · Uso básico data-driven desde site.ts
---
// USO BÁSICO · footer data-driven desde site.ts, cero hardcoding.
// El componente se monta UNA vez en PageLayout y se reutiliza en cada
// página del sitio. Toda la data (marca, NAP, columnas, redes, legales)
// LEE de site.ts: SITE, CONTACT, PRODUCT_CATEGORIES, SERVICES, SECTORS,
// COVERAGE_STATES, BRANCHES, SOCIAL, LEGAL. Para cambiar un teléfono o
// agregar una red, se edita site.ts y se propaga al sitio entero.
import Footer from '@components/Footer.astro'
---
{/* Sin props: el footer se construye desde site.ts y muestra todo lo
    que existe (omite branches, social, certifications si están vacíos). */}
<Footer />

{/* Para agregar sucursales: poblar BRANCHES en site.ts (NO pasar props):
    export const BRANCHES = [
      { label: 'Matriz CDMX', address: 'Av. Cuauhtémoc 145', mapsUrl: '...' },
      { label: 'Sucursal Norte', address: 'Periférico 800', mapsUrl: '...' },
    ]
    El footer las pinta automáticamente en el bloque NAP de la marca. */}
B · Sponsors / certificaciones via prop certifications
---
// SPONSORS / BADGES como ItemList interno · cuando hay partners,
// medios donde el sitio fue mencionado, o sellos de pago (Visa, MC,
// Amex), se usa la PROP certifications: Cert[]. Cada Cert es un
// chip plano en la banda de cumplimiento (zona 3 del footer). El
// componente los pinta como <ul> sin enlaces, con título opcional
// (atributo title para tooltip de la norma completa).
import Footer from '@components/Footer.astro'

// Lista de certificaciones / sellos / sponsors verificables.
// Cada entry: { label: 'Texto del chip', title?: 'Tooltip largo' }.
// REGLA: solo poner sellos REALES, con derecho a uso. Inventar un sello
// (ISO sin certificar, PCI sin contrato) tiene consecuencias legales.
const certifications = [
  { label: 'ISO 9001:2015', title: 'Sistema de gestión de calidad certificado por TÜV Rheinland' },
  { label: 'PCI DSS Level 1', title: 'Estándar de seguridad para procesamiento de pagos' },
  { label: 'GDPR Compliant', title: 'Conforme al Reglamento General de Protección de Datos UE 2016/679' },
  { label: 'AMVO', title: 'Asociación Mexicana de Venta Online' },
]
---
<Footer certifications={certifications} />

{/* La banda aparece entre el cuerpo y la barra inferior. Visualmente,
    cada label es un chip con borde sutil; el title se muestra como
    tooltip nativo al hover. Si certifications=[] (default), la banda
    se omite por completo: no hay franja vacía ni separadores sueltos. */}
C · Integración SEO con organizationSchema (regla B3)
---
// INTEGRACIÓN SCHEMA · DÓNDE VIVE EL JSON-LD Organization.
// Regla dura B3: UN único emisor por página. organizationSchema() es
// la función pura en lib/seo.ts que devuelve el bloque Organization
// con NAP + contactPoint + sameAs; buildSchema() la invoca en TODA
// página (BaseLayout) dentro del @graph base, junto con WebSite y
// LocalBusiness. El Footer.astro NO emite JSON-LD propio: sería
// duplicado y Google ignoraría el rich result.
//
// FLUJO:
//   1. site.ts define SITE.organization, CONTACT y SOCIAL/sameAs.
//   2. lib/seo.ts → organizationSchema() lee esa SSoT y arma el nodo.
//   3. buildSchema('home'|'page'|...) lo incluye en el @graph base.
//   4. BaseLayout serializa el array con <script type="application/ld+json">.
//   5. Footer.astro pinta el NAP visualmente, sin emitir nada.
import { organizationSchema } from '@lib/seo'

// USO TÍPICO: NO se llama manualmente; buildSchema ya lo invoca.
// Solo se llama directo si una página standalone arma su propio @graph.
const node = organizationSchema()
// node = {
//   '@type': 'Organization',
//   '@id': 'https://ejemplos.mx/#organization',
//   name: 'Ejemplos.mx',
//   url: 'https://ejemplos.mx',
//   logo: { '@type': 'ImageObject', '@id': '...', url: '...' },
//   contactPoint: { telephone: '+52...', email: 'hola@...', ... },
//   sameAs: [],   // SITE.organization.sameAs — vacío por default a propósito
// }
---
{/* En cualquier página, lo correcto es DELEGAR en el layout/buildSchema.
    Wrapper canonical: open-tag de PageLayout con props title + description
    + pageType="page", contenido en el slot, el footer aparece solo (lo emite
    PageLayout) y el JSON-LD Organization lo emite BaseLayout via buildSchema. */}

{/* CONSISTENCIA NAP: site.ts → CONTACT.phone/phoneRaw == lo que pinta
    el Footer == lo que emite organizationSchema en contactPoint.telephone.
    Si reescribes el formato en una página («(55) 0000-0000» en vez de
    «55 0000 0000»), Google detecta divergencia y baja la confianza
    local. UNA SSoT, tres consumidores leyendo de ella. */}

Buenas prácticas

Qué hacer y qué evitar

Seis hábitos que mantienen al footer como activo SEO global, NAP consistente y trust signal verificable; seis errores que lo degradan a vertedero de enlaces, fuente de inconsistencias NAP o emisor duplicado de schema. Tratar el footer como decoración es el camino corto a las dos cosas.

La diferencia entre un footer que multiplica equity y uno que solo ocupa espacio es disciplinaria: data-driven sin excepciones, NAP letra por letra, schema centralizado, certificaciones verificables, año dinámico, redes solo si están vivas. Cada regla viene de un caso real visto en sitios mexicanos: footers con teléfono distinto al header (banco perdido en Google), copyrights del 2018 en sitios de 2024, badges «ISO» sin certificación adjunta, mapas del sitio con páginas que ya no existen.

Los sí son hábitos de SSoT; los no son tentaciones que parecen rápidas y salen caras. Léelos juntos —cada par sí/no apunta al mismo problema desde dos ángulos—.

  • Mantén el componente data-driven al 100%: marca, NAP, redes y legales LEEN de site.ts (SITE, CONTACT, SOCIAL, LEGAL, BRANCHES). El día que cambies un teléfono, lo cambias UNA vez en CONTACT.phone/phoneE164 y se propaga al Topbar, al Footer, al telUrl() y al JSON-LD Organization. Si una página hardcodea su propio número en un texto, lo agregas como demo PERO con un comentario que aclare que es ejemplo y que la SSoT es CONTACT.
  • Conserva el footer como ACTIVO SEO global: usa las 4 columnas (Productos, Servicios+Sectores, Cobertura, Empresa) data-driven desde la taxonomía (PRODUCT_CATEGORIES, SERVICES, SECTORS, COVERAGE_STATES). Cuando agregues una página de categoría, sector o cobertura nueva, el footer la lista solo, sin tocarlo. Resultado: linking interno coherente en todas las páginas, equity bien repartido, descubrimiento de URLs garantizado para el crawler.
  • Emite el JSON-LD Organization desde UN solo lugar — lib/seo.ts → organizationSchema(), invocado por buildSchema() en BaseLayout (regla B3 dura). El Footer NO emite schema propio. Eso evita el doble JSON-LD que se ve en muchos sitios (la entidad aparece dos veces, Google ignora ambos) y deja al footer cumpliendo un solo papel: presentación del NAP, navegación secundaria y trust. La separación de responsabilidades es la base del patrón.
  • Pasa certificaciones reales (no inventadas) por la prop opcional certifications: Cert[] del componente. Cuando el negocio tenga ISO 9001, PCI DSS, sello de calidad sectorial o NOMs aplicables, agrégalas como { label, title? } y la banda aparecerá. Cada chip es una afirmación verificable; si te la inventas, el visitante que conoce la norma se da cuenta y pierdes credibilidad. En la duda: vacío. El componente omite la banda si certifications: [].
  • Activa el botón «volver arriba» (ya viene de fábrica) y respeta prefers-reduced-motion: el handler del script lo hace solo —si el usuario configuró menos movimiento, hace jump instantáneo en vez de smooth-scroll—. Es un detalle de accesibilidad barato (4 líneas de JS) que cuenta para WCAG 2.1 SC 2.3.3 (Animation from Interactions). Si te lo saltas, una persona con vestíbulo sensible puede marearse en cada página larga del sitio.
  • Mantén el bloque de redes (SOCIAL) data-driven con la regla del auto-ocultado: si pasas SOCIAL = [] en site.ts (la marca aún no tiene perfiles), la fila desaparece sola del DOM. NO dejes URLs DEMO en producción —llevarían al visitante a perfiles inexistentes o, peor, a perfiles de otra marca—. Si las redes existen pero NO están verificadas, agrégalas a SOCIAL (visualmente) pero deja SITE.organization.sameAs = [] (semánticamente): no se declara un perfil falso en datos estructurados.

No

  • NO emitas JSON-LD Organization desde el Footer (ni con prop emitSchema, ni con un <script type="application/ld+json"> a mano). Romperás la regla B3 («un único emisor por página») y producirás Organization duplicado: Google ignora el rich result Y los validadores de schema marcan error. El emisor canónico es buildSchema() en BaseLayout vía organizationSchema() de lib/seo.ts; el Footer es presentación, no SEO.
  • NO mezcles formatos de teléfono entre Topbar, Footer y JSON-LD. Si Topbar muestra «55 0000 0000», Footer muestra «(55) 0000-0000» y JSON-LD declara «+52 5 5000 0000», Google detecta la inconsistencia NAP y baja la confianza local del Knowledge Panel. La SSoT está en CONTACT: usa CONTACT.phone para mostrar, telUrl() para llamar, CONTACT.phoneRaw / phoneE164 para schema. UNA fuente, tres representaciones derivadas.
  • NO uses el footer como vertedero de enlaces de campaña efímeros («Black Friday 2026», «Webinar de mayo»). Cada enlace del footer aparece en MILES de páginas del sitio durante el ciclo de vida de la URL —si la URL muere, dejas link rot en todas partes—. El footer es para rutas estables (taxonomía, legales, cobertura, contacto). Las campañas viven en hero, banners pre-footer o landing pages dedicadas, no en el pie.
  • NO escribas el año del copyright a mano («© 2025»). Se rompe el 1 de enero de cada año y queda años desactualizado en cientos de páginas, una señal de descuido que el visitante detecta de inmediato. Usa new Date().getFullYear() —el componente ya lo hace, pero si replicas el patrón en otra plantilla o si subes una sección de «Acerca de», respeta la regla—. Cero datos calendáricos hardcodeados que envejecen solos.
  • NO dejes URLs DEMO en SOCIAL para producción (https://instagram.com/ejemplos.mx, https://facebook.com/ejemplos.mx, etc.). Llevan al visitante a perfiles inexistentes o a la marca equivocada. Patrón: si el cliente aún no tiene redes verificadas, pasa SOCIAL = [] (la fila se autoesconde) o comenta los entries DEMO en site.ts hasta que existan. Mejor sin redes que con redes rotas.
  • NO declares perfiles sociales en SITE.organization.sameAs si NO son perfiles oficiales VERIFICABLES (con dueño confirmado, no abandonados, no de un homónimo). sameAs alimenta el JSON-LD Organization y le dice a Google «estos perfiles son míos»; si declaras un Twitter de otra empresa o un LinkedIn sin verificar, alimentas un knowledge graph erróneo. En la duda, sameAs vacío; el footer puede mostrar las redes visualmente sin que el schema las declare.
¿Necesitas ayuda?