learning-front

Nivel 1 · HTML y CSS: la estructura y la piel

HTML semántico y estructura de carpetas

Etiquetas que describen su contenido, no su aspecto, y un proyecto ordenado: la diferencia entre una web que las máquinas entienden y un montón de divs.

Ya sabes marcar texto, poner enlaces, incrustar imágenes y construir listas y tablas. Con eso tienes el vocabulario básico. Ahora viene la pregunta de fondo: ¿qué significa cada pieza?

El HTML que escribes tiene dos públicos a la vez. Uno son las personas, que lo ven a través del navegador. El otro son las máquinas: el buscador que decide si apareces en Google, el lector de pantalla de quien no ve la pantalla, y tu yo de dentro de seis meses intentando entender el código. El HTML semántico habla a los dos. Un montón de <div> solo habla al primero, y a medias.

Empezamos a construir el Overwatch Team Builder, el hilo conductor del curso. Su primera pieza es esta página: una cabecera, un grid de héroes y un pie.

Semántico significa “con significado”, no “con aspecto”#

Esta es la idea que lo desbloquea todo: la etiqueta no decide cómo se ve algo, decide qué es. El aspecto lo pone el CSS. Por eso <div> y <span> son etiquetas neutras: cajas sin significado, útiles solo para agarrar algo desde el CSS o el JavaScript.

Frente a ellas, hay etiquetas que describen su contenido: <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>. Cambiar un <div> por un <article> no mueve un solo píxel, pero cambia lo que la página es para todo lo que no son ojos humanos.

El esqueleto: los landmarks#

Casi cualquier página se organiza en unas pocas regiones grandes, los landmarks:

html
<body>
  <!-- cabecera: logo, navegación -->
  <header>...</header>
  <!-- navegación principal -->
  <nav>...</nav>
  <!-- el contenido único de esta página (solo uno) -->
  <main>...</main>
  <!-- pie: avisos, enlaces secundarios -->
  <footer>...</footer>
</body>

No son decorativos. Quien usa un lector de pantalla puede saltar directamente a cualquiera de ellos: ir al menú, o ir al contenido principal sin escuchar antes toda la cabecera. Con <div> no hay regiones a las que saltar, solo una sopa por la que avanzar a ciegas. Regla práctica: un único <main> por página, con lo que de verdad distingue a esa página de las demás.

Un h1, y una jerarquía que tenga sentido#

Ya viste esto en el capítulo anterior: los encabezados son el índice del documento, no una herramienta de tamaño. Lo importante aquí es aplicarlo a una página real con varias secciones: un único <h1> para el título de la página, <h2> para cada sección principal y <h3> para los elementos dentro de cada sección (el nombre de cada héroe, en nuestro caso).

article, section o div: cuál y cuándo#

Las tres se parecen, pero responden a preguntas distintas:

  • <article> — contenido autónomo, que se entendería fuera de su contexto: una carta de héroe, un comentario, una noticia. Si lo pudieras copiar a otra página y seguiría teniendo sentido, es un article.
  • <section> — una agrupación temática con su propio encabezado: la sección “Plantilla” que envuelve el grid.
  • <div> — una caja sin significado, solo para enganchar estilos o layout. Es perfecta para eso; el error es usarla también para lo que sí tiene nombre.

Detalles pequeños que cambian mucho#

  • lang en el <html> (<html lang="es">): le dice al navegador y al lector en qué idioma está, para pronunciarlo bien.
  • alt en las imágenes: si la imagen informa, descríbela (alt="Gráfico de winrate por rol"). Si es decorativa y su info ya está en texto, déjala muda con alt="" para no repetir ruido —el atributo vacío es la forma correcta de marcar una imagen decorativa; aria-hidden="true" se reserva para ocultar elementos que no son imágenes, como un icono hecho con un <span>—.
  • Una colección de elementos iguales es una lista (<ul>/<li>), no una pila de divs.

Nombrar regiones: aria-label y aria-labelledby#

Los landmarks ya dan estructura, pero cuando hay dos del mismo tipo —por ejemplo dos <nav>, uno principal y otro de pie— el lector de pantalla los anuncia igual («navegación», «navegación») y no se distinguen. Para darles nombre hay dos atributos:

  • aria-label escribe el nombre a mano: <nav aria-label="Principal"> se anuncia como «navegación Principal». Úsalo cuando no hay un texto visible que sirva de nombre.
  • aria-labelledby toma el nombre de otro elemento de la página, por su id (el mismo id que viste en los enlaces de ancla). Sirve para atar una región a su propio título:
html
<!-- aria-labelledby apunta al id del encabezado que da nombre a esta región -->
<section aria-labelledby="titulo-plantilla">
  <!-- el id sirve de ancla para el aria-labelledby de arriba -->
  <h2 id="titulo-plantilla">Plantilla</h2>
  ...
</section>

La región se anuncia con el texto del <h2>. La ventaja sobre aria-label: reutiliza un texto que ya está visible, así que si cambias el título, el nombre accesible cambia con él. Regla práctica: si ya hay un texto visible que nombra la región, aria-labelledby; si no lo hay, aria-label.

Y la otra mitad: cómo organizas los ficheros#

La semántica ordena el HTML por dentro; la estructura de carpetas ordena el proyecto por fuera. El antipatrón es tenerlo todo tirado en la raíz. En cuanto el Team Builder crezca a un par de páginas con sus imágenes y scripts, acabas así:

texto
team-builder/
├── index.html
├── perfil.html
├── styles.css
├── perfil.css
├── app.js
├── logo.svg
├── tracer.png
├── reinhardt.png
└── mercy.png          ← y esto no para de crecer

Nadie encuentra nada. La regla sana es que la carpeta cuente de un vistazo qué hay y dónde. En un proyecto pequeño basta con separar por tipo:

texto
team-builder/
├── index.html
├── perfil.html
├── css/
│   ├── styles.css
│   └── perfil.css
├── js/
│   └── app.js
└── assets/
    ├── logo.svg
    └── heroes/
        ├── tracer.png
        ├── reinhardt.png
        └── mercy.png

Fíjate en que el ejercicio de este capítulo ya da el primer paso: el HTML en index.html y los estilos aparte en styles.css, no todo mezclado. En proyectos grandes se va más lejos y se agrupa por funcionalidad —todo lo del “perfil de héroe” (su HTML, su CSS, su JS) en la misma carpeta—, que es el mismo instinto que la semántica: dar a cada cosa un sitio que explique qué es. Lo retomaremos a fondo en la arquitectura del Nivel 7.

Pruébalo#

El playground incluye un fichero styles.css. No tienes que entenderlo ni tocarlo: el CSS lo verás a partir del capítulo «Qué es CSS». Aquí solo importa la estructura HTML.

Esta carta de héroe está bien construida por dentro. Mira el código de la izquierda: el <article>, el encabezado, la lista de descripción para las estadísticas. Cambia <article> por <div> y <h3> por <span> y comprueba que el resultado se ve idéntico: esa es justo la trampa de la semántica, que no se ve, pero está.

Comprueba lo que sabes#

Pregunta 1 de 5

Cambias un `<div>` por un `<article>` sin tocar el CSS. ¿Qué cambia en la página?

Tu turno#

Te toca maquetar la página entera, aquí mismo. El CSS ya está dado y estiliza por clases, así que el reto no es que se vea bien: es que esté bien construida. Edita el HTML en el playground; cuando creas que lo tienes, despliega las soluciones y compáralas con la tuya.

Ejercicio · en esta página

Maqueta la página del Team Builder

Construye en HTML la página principal del Team Builder: una cabecera con su navegación, un grid con una carta por héroe y un pie. El CSS ya está dado y estiliza por clases, así que tu página se verá bien la resuelvas como la resuelvas. Lo que se evalúa es la estructura: que la página signifique algo, no solo que se vea.

Paso 1: Que funcione

  • La página muestra la cabecera, los tres héroes y el pie.
  • Se ve correctamente (vale resolverla a base de div y span).
  • Los enlaces de la navegación están presentes.
Ver soluciones
<!doctype html>
<html lang="es">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Overwatch Team Builder — Plantilla</title>
    <link rel="stylesheet" href="../../starter/styles.css" />
  </head>
  <body>
    <!--
      ============================================================================
      NIVEL OK — "funciona y se ve"
      ============================================================================
      Todo resuelto con <div> y <span>. Renderiza EXACTAMENTE igual que los otros
      niveles, porque el CSS estiliza por clases. Y ese es el punto: a la vista no
      se distingue, pero la página no significa nada para una máquina.

      Sus límites (los que arregla el nivel "Mejor"):
        - Ni un solo landmark: un lector de pantalla no puede saltar a "el menú" o
          "el contenido principal"; solo ve una maraña de divs anidados.
        - Ningún encabezado real. El nombre de la app y los de los héroes son
          <div>: el buscador no sabe cuál es el título de la página, y quien navega
          por encabezados (algo habitual con lector de pantalla) no tiene nada.
        - El grid no es una lista, así que la tecnología asistiva no anuncia
          "3 héroes": solo lee tres bloques sueltos.
        - El retrato decorativo no está marcado como tal.
      Aun así: se ve y se lee con el ratón. Eso vale como OK.
    -->
    <div class="page">
      <div class="site-header">
        <div class="brand">Overwatch <span>Team Builder</span></div>
        <div class="site-nav">
          <a href="#">Héroes</a>
          <a href="#">Equipos</a>
          <a href="#">Estadísticas</a>
        </div>
      </div>

      <div>
        <div class="section-title">Plantilla</div>
        <div class="hero-grid">
          <div class="hero-card">
            <div class="hero-top">
              <div class="hero-portrait">TR</div>
              <div>
                <div class="hero-name">Tracer</div>
                <div class="hero-role">Daño</div>
              </div>
            </div>
            <div class="hero-stats">
              <div class="stat">
                <div class="stat-label">Partidas</div>
                <div class="stat-value">120</div>
              </div>
              <div class="stat">
                <div class="stat-label">Victorias</div>
                <div class="stat-value">78</div>
              </div>
              <div class="stat">
                <div class="stat-label">Winrate</div>
                <div class="stat-value">65%</div>
              </div>
            </div>
          </div>

          <div class="hero-card">
            <div class="hero-top">
              <div class="hero-portrait">RE</div>
              <div>
                <div class="hero-name">Reinhardt</div>
                <div class="hero-role">Tanque</div>
              </div>
            </div>
            <div class="hero-stats">
              <div class="stat">
                <div class="stat-label">Partidas</div>
                <div class="stat-value">90</div>
              </div>
              <div class="stat">
                <div class="stat-label">Victorias</div>
                <div class="stat-value">51</div>
              </div>
              <div class="stat">
                <div class="stat-label">Winrate</div>
                <div class="stat-value">57%</div>
              </div>
            </div>
          </div>

          <div class="hero-card">
            <div class="hero-top">
              <div class="hero-portrait">ME</div>
              <div>
                <div class="hero-name">Mercy</div>
                <div class="hero-role">Apoyo</div>
              </div>
            </div>
            <div class="hero-stats">
              <div class="stat">
                <div class="stat-label">Partidas</div>
                <div class="stat-value">200</div>
              </div>
              <div class="stat">
                <div class="stat-label">Victorias</div>
                <div class="stat-value">130</div>
              </div>
              <div class="stat">
                <div class="stat-label">Winrate</div>
                <div class="stat-value">65%</div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="site-footer">Overwatch Team Builder — proyecto del curso.</div>
    </div>
  </body>
</html>

Por qué este nivel

  • Resuelve la página entera con div y span: se ve perfecta y se lee con el ratón.
  • Funcionar y verse es el primer requisito, y lo cumple.
  • Sus límites: ni un landmark, ningún encabezado real y el grid no es una lista. Para una máquina (buscador, lector de pantalla) la página es una maraña sin estructura.