Guía · Topbar

Barra utilitaria superior (la franja por encima del menú). Cada punto se alimenta de src/config/site.ts (fuente única); no se escribe a mano. Esto es lo que va en cada lugar:

  1. 1

    Propuesta principal

    Lo primero que se lee: una frase corta que posiciona la marca. El logotipo NO va aquí — va en el Header, justo debajo.

    Dato SITE.tagline

  2. 2

    Horario

    Señal de disponibilidad y confianza. Se oculta en móvil para priorizar el teléfono y WhatsApp.

    Dato CONTACT.schedule.display

  3. 3

    Teléfono

    Contacto directo con clic-para-llamar. El enlace tel: lo construye telUrl(), no se escribe a mano.

    Dato CONTACT.phone · telUrl()

  4. 4

    WhatsApp

    CTA principal de contacto. El enlace SIEMPRE se arma con waUrl(); el mensaje precargado sale de WA_MESSAGES.

    Dato waUrl(WA_MESSAGES.cotizar)

Edita en src/components/TopBar.astro · src/config/site.ts

Guía · Header

Barra de navegación principal (logotipo + menú), bajo el topbar. Todo el menú —escritorio, paneles y móvil— se genera desde NAV en src/config/site.ts (fuente única); no se escribe a mano. Esto es lo que va en cada lugar:

  1. 1

    Logotipo

    La marca, a la izquierda, enlazando a la home. Es el ancla de identidad y el «volver al inicio» que todos esperan. Aquí SÍ va el logo (en el topbar no).

    Dato SITE.brand · SITE.name

  2. 2

    Navegación

    Las secciones del sitio. No se hardcodea ningún enlace: se itera NAV, la misma fuente para escritorio y móvil. En móvil colapsa en el menú ☰.

    Dato NAV

  3. 3

    Paneles (mega / dropdown)

    Las secciones con hijos despliegan un panel al pasar el cursor o con el teclado; su contenido sale de la taxonomía, no de una lista aparte.

    Dato NAV[].panel · items

  4. 4

    CTA · Cotizar

    El botón de conversión a WhatsApp, siempre visible a la derecha. El enlace se arma con waUrl(); el mensaje precargado sale de WA_MESSAGES.

    Dato waUrl(WA_MESSAGES.cotizacion)

Edita en src/components/Header.astro · src/config/site.ts

Guía · Migas de pan

La ruta que muestra dónde está el visitante dentro de la jerarquía del sitio, justo debajo del header. Sirve para dos cosas a la vez: orientar y dejar volver a cualquier nivel superior, y alimentar el BreadcrumbList de schema.org que el buscador usa para mostrar la ruta en sus resultados. Cada página define su ruta una sola vez con la prop breadcrumbs; el JSON-LD lo emite buildSchema (no este componente, para no duplicarlo). Esto es cada eslabón:

  1. 1

    Raíz (Inicio)

    El primer eslabón: siempre enlaza a la home. Es el punto de partida de la ruta y el «volver al inicio» que todos esperan de la jerarquía.

    Dato items[0] · href '/'

  2. 2

    Eslabón intermedio

    Cada nivel ancestro entre la home y la página actual (categoría, subcategoría). Son enlaces: dejan saltar a cualquier nivel superior.

    Dato items[].href

  3. 3

    Separador

    El icono (›) entre eslabones. Es decorativo —va con aria-hidden— y solo marca la dirección de la jerarquía; nunca es un enlace.

    Dato SVG · aria-hidden

  4. 4

    Página actual

    El último eslabón: la página donde estás. No enlaza (ya estás ahí) y se marca con aria-current="page" para los lectores de pantalla.

    Dato item sin href · aria-current

Edita en src/components/Breadcrumbs.astro · prop breadcrumbs de cada página

guias

Section headings: jerarquía visual y SEO Astro

Cómo diseñar section headings que enganchen sin romper la jerarquía H1-H6 ni la legibilidad para buscadores, con ejemplos de Astro y tokens CSS.

Section headings: jerarquía visual y SEO Astro

Un sitio con encabezados desordenados se ve bonito en la PC del diseñador y mal en todos lados al mismo tiempo: el buscador no lo entiende, el lector de pantalla salta de H2 a H4 sin avisar y el visitante escanea el muro de párrafos sin pistas. El section heading es la pieza que vuelve esa página legible para personas y previsible para máquinas. Esta guía explica cómo se diseña uno que cumpla ambos contratos sin negociar uno por el otro.

Contexto

La regla de oro para SEO técnico no cambió en los últimos diez años: un H1 por página y los siguientes niveles escalonados sin saltos, donde cada ‹h2› abre una sección del cuerpo y los ‹h3› cuelgan de su ‹h2› padre. La tentación de meter “otro H1 porque pesa más en la búsqueda” sigue circulando en foros, pero los crawlers modernos —Googlebot incluido— interpretan la jerarquía como árbol semántico: dos H1 hermanos confunden el modelo del documento y diluyen la señal de tema principal.

El problema no es solo de SEO. La accesibilidad usa exactamente el mismo árbol. Un lector de pantalla como VoiceOver o NVDA permite navegar página por encabezados con una sola tecla; si saltas de H2 a H4 sin pasar por H3, la persona ciega percibe una sub-sección huérfana y no sabe a qué bloque pertenece. La regla “no saltes niveles” no es cosmética: es la diferencia entre una página navegable y una que solo se puede leer en orden lineal.

A esto se suma una capa visual. El section heading no es solo un ‹h2› a secas: es un patrón compuesto por eyebrow (rótulo corto en mayúsculas), título (la promesa del bloque) y descripción (1-2 frases que amplían). El eyebrow no debe ser un heading HTML —no aporta nivel jerárquico, es metadata visual— y el accent (segunda línea de color) tampoco debe romper el título en dos elementos semánticos. La trampa común es marcar el eyebrow como ‹h3› “para que se vea más importante”: rompe la jerarquía y mete una sub-sección fantasma en el árbol del documento.

Implementación paso a paso

El componente SectionHeading.astro de esta plantilla resuelve estas tres trampas a la vez. Vale la pena leerlo antes de copiar nada: la regla canónica está cableada en los defaults, no en la documentación.

---
// src/components/SectionHeading.astro
export interface Props {
  id?: string
  eyebrow?: string
  /** Primera linea del titulo (obligatoria). */
  title: string
  /** Segunda linea, resaltada con color de acento. */
  titleAccent?: string
  desc?: string
  layout?: 'simple' | 'duo'
  align?: 'center' | 'left'
  body?: string[]
  dark?: boolean
  /** Etiqueta semantica del encabezado de bloque. */
  as?: 'h2' | 'h3'
}
const { id, eyebrow, title, titleAccent, desc, layout = 'simple',
        align = 'center', body = [], dark = false, as = 'h2' } = Astro.props
const Tag = as
---

La prop as con default 'h2' es la primera línea de defensa: a menos que se pase as="h3" explícitamente, cada SectionHeading emite un ‹h2›. Imposible meter sin querer un segundo H1 (no existe as="h1" en el tipo) o saltar de H2 a H4 (los únicos valores válidos son h2 y h3). La jerarquía está cableada en el tipo de TypeScript.

El segundo punto es cómo se construye el título cuando hay acento. La tentación es partir la frase en dos elementos —un ‹h2› para la primera línea y un ‹p› o ‹span› para la segunda—. El componente resuelve diferente:

<Tag class="sech__title" id={id}>
  {title}{titleAccent && <><br /><span class="sech__accent">{titleAccent}</span></>}
</Tag>

Un solo ‹h2› con un ‹br› y un ‹span› dentro. El buscador lee el texto completo como un solo heading; los lectores de pantalla anuncian la frase entera sin interrupciones; el CSS aplica el color de marca al .sech__accent sin pedir un elemento semántico extra. El accent es decoración visual sobre un heading único, no un heading partido en dos.

El tercer punto, el eyebrow, sigue la misma disciplina:

{eyebrow && <p class="sech__eyebrow"><span class="sech__eyebrow-bar" aria-hidden="true"></span>{eyebrow}</p>}

Un ‹p› —no un heading— con una barra de acento marcada como aria-hidden="true" para que los lectores de pantalla no la anuncien como “decoración”. El eyebrow es texto plano marcado como párrafo, estilizado como rótulo en mayúsculas via CSS:

.sech__eyebrow {
  font-size: var(--text-xs, .8125rem);
  font-weight: var(--weight-bold, 700);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-red, var(--c-primary, #C41E24));
}

text-transform: uppercase deja el contenido del DOM en minúsculas y solo lo pinta en mayúsculas: los buscadores ven la palabra escrita normalmente (mejor para indexación), el copywriter escribe sin gritar y el visitante percibe el rótulo en ALL CAPS. Tres ganancias por una propiedad CSS.

Tabla comparativa

Decisión semánticaPatrón correctoAnti-patrón frecuente
H1 por páginaUno solo, en el heroUn H1 por sección “para reforzar SEO”
Eyebrow del section heading‹p› con text-transform: uppercase‹h3› o ‹h4› “para que pese más”
Título con acento de colorUn ‹h2› con ‹span› internoDos elementos (‹h2› + ‹p›) hermanos
Sub-sección dentro de H2‹h3› via prop as="h3"‹h4› o ‹div class="titulo"›
Barra de acento decorativaaria-hidden="true"Sin atributo (la lee VoiceOver)
Mayúsculas del eyebrowCSS text-transformEscrita en mayúsculas en el MDX
Descripción del bloque‹p› debajo del títuloOtro ‹h2› “porque también es importante”
Anchor para deep-linkProp id que se aplica al heading‹a name› antes del heading

Patrones avanzados

Accent como destacado semántico, no como dos H2. El error más común con la segunda línea resaltada es marcarla como un encabezado separado para que el color “se vea”. El componente la mantiene dentro del mismo ‹h2›, así que tanto el texto plano del SEO como el flujo del lector de pantalla la perciben como una única promesa. La regla práctica: si retiras todos los estilos CSS, el HTML debe seguir contando una historia legible —un solo encabezado con una frase completa, no dos fragmentos colgando—. Si el contenido se rompe sin CSS, el HTML está mal escrito.

Eyebrow ALL CAPS por CSS, no por copia. Escribir “DEFINICIÓN” en el MDX provoca tres efectos perversos: los buscadores que normalizan mayúsculas para indexar pierden la palabra clave, los lectores de pantalla algunos las deletrean letra por letra (“D-E-F-I-N-I-C-I-Ó-N”) y el copywriter siente que está gritando. Escribir “Definición” en el MDX y dejar que text-transform: uppercase haga el trabajo visual evita los tres. Es la misma razón por la que los logos en minúsculas escriben “ejemplos.mx” y no “EJEMPLOS.MX” en el alt de la imagen.

Balance visual del duo + ancho de medida en la descripción. El layout duo enfrenta dos columnas: título a la izquierda, body a la derecha. La descripción del título tiene un max-width: 60ch en simple y max-width: none en duo —porque en duo el ancho ya lo da el grid, no la propia descripción—. Si en duo dejas el 60ch, la columna izquierda queda con un párrafo flaco al lado de un grid generoso a la derecha y el balance se rompe. La regla está en el CSS scoped:

.sech--duo .sech__desc { max-width: none; }

Es un detalle de tres palabras que decide si la página se siente cuidada o rota.

Dark variant con contraste AA en el body, no solo en el título. Pasar dark cambia el color del .sech__title a #fff (contraste excelente sobre fondo negro), pero el .sech__desc y el .sech__body-p bajan a rgba(255,255,255,.75). Ese 75 % de opacidad sobre fondo #111 da un contraste de 9.2:1 —muy por encima del 4.5:1 exigido por WCAG AA para texto normal—. Si quisieras bajar más la opacidad para “que el cuerpo se sienta secundario”, revisa en una herramienta de contraste antes de empujar a producción: a 50 % de opacidad el ratio cae a 5.1:1 (todavía AA), pero a 35 % cae a 3.4:1 (falla incluso para texto grande).

Checklist

  • Un solo ‹h1› por página, ubicado en el hero.
  • Todos los títulos de sección usan SectionHeading (cero ‹h2› sueltos en .astro).
  • El eyebrow es un ‹p› con text-transform: uppercase por CSS, no escrito en mayúsculas.
  • El titleAccent se renderiza dentro del mismo ‹h2›, no como elemento hermano.
  • La barra decorativa del eyebrow lleva aria-hidden="true".
  • Las sub-secciones usan as="h3" (no se inventan ‹h4› saltando un nivel).
  • En layout="duo" la descripción no lleva max-width en ch (lo controla el grid).
  • En dark, el contraste de texto sobre fondo cumple ≥ 4.5:1 (verificado).

Preguntas frecuentes

¿Por qué no usar ‹h1› en cada sección si Google “ahora soporta varios H1”? Soportar no es premiar. Google parsea sin reventar una página con varios H1, pero el modelo de documento sigue siendo un árbol con un tema principal. Dos H1 hermanos compiten por esa señal y bajan la claridad del tópico. La regla “un H1 por página” sigue siendo el consenso entre SEO técnico y accesibilidad, y el costo de cumplirla es cero.

¿El eyebrow afecta el SEO si tiene palabras clave? Sí, pero como texto plano dentro de un ‹p› —no como heading—. Los buscadores lo indexan como cualquier párrafo: aporta contexto léxico al bloque sin competir con el ‹h2›. La práctica de meter palabras clave repetidas en el eyebrow es contraproducente: si “Definición · Plomería en Polanco · Servicios” aparece arriba de cada sección, el sitio se ve spam y el lector de pantalla anuncia tres rótulos antes del título.

¿Cómo decido entre as="h2" y as="h3"? Pregunta de árbol: si el bloque está al primer nivel del cuerpo (debajo del hero ‹h1›), va h2. Si está dentro de una sección que ya abrió con un h2, sus sub-bloques van h3. Más allá de h3h4, h5— casi nunca es necesario: si tu documento exige cinco niveles de profundidad, probablemente la página debería partirse en dos URLs.

¿Puedo poner un anchor (#mi-seccion) en el título? Sí, vía la prop id: el componente la aplica directamente al ‹h2› (‹Tag class="sech__title" id=❴id❵›). Esto habilita deep-linking (#mi-seccion desde una tabla de contenidos) y evita el anti-patrón de meter un ‹a name="..."› vacío arriba del heading, que ensucia el DOM.

¿El ‹br› dentro del título no es semánticamente sucio? En este caso no. El ‹br› se usa porque la frase es una sola promesa visualmente partida en dos líneas: el HTML5 admite ‹br› cuando el salto es parte del contenido (poesía, direcciones, títulos de doble línea). Lo que sería sucio es usar ‹br›‹br› para simular un párrafo, o partir un párrafo largo en varios ‹br› en vez de usar ‹p›.


El section heading bien construido es una pieza pequeña que decide cosas grandes: si la página rankea por el tema que esperabas, si una persona ciega la puede recorrer en treinta segundos, si el visitante se queda a leer o cierra la pestaña. La buena noticia es que el patrón cabe en un solo componente y se obedece con dos disciplinas: nunca saltar niveles de heading y nunca convertir decoración visual en estructura semántica.

Sigue leyendo

¿Listo para dar el siguiente paso?

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

¿Necesitas ayuda?