📗 Curso HTML · Nivel N2 Básico

Bloque 2: Estructura semántica y multimedia

Con las etiquetas básicas dominadas es hora de escribir HTML profesional: páginas que los buscadores entienden y las personas pueden recorrer con un lector de pantalla, tablas de datos correctas, formularios que se validan solos y contenido multimedia accesible.

⏱ ≈ 10 horas 📚 4 lecciones ✍️ 12 ejercicios + reto formulario 📝 Examen superable (≥ 70 %)
Lección 1 · Teoría

HTML semántico: la estructura profesional

El HTML semántico usa etiquetas que describen el papel de cada zona de la página en lugar de cajas genéricas <div>. Un <header> «dice» encabezado; un <div class="cabecera"> solo dice «caja cualquiera».

¿Por qué importa tanto?

  • SEO: Google prioriza páginas cuya estructura entiende sin esfuerzo.
  • Accesibilidad: los lectores de pantalla permiten saltar directamente al <main> o a la <nav>.
  • Mantenimiento: cualquier desarrollador entiende tu código a primera vista.

El mapa de una página moderna

<body>
├── <header>   ← logo, título del sitio, navegación principal
│     └── <nav>     ← menús de enlaces
├── <main>     ← SOLO uno por página: el contenido único
│     ├── <article> ← pieza independiente (post, noticia)
│     ├── <section> ← agrupación temática (con su h2…)
│     └── <aside>   ← contenido relacionado lateral
└── <footer>   ← pie del sitio: legal, contacto, redes
Guía rápida de etiquetas semánticas
EtiquetaÚsala para…Clave
<header>Cabecera del sitio o de un article/sectionPuede repetirse
<nav>Grupos de navegación importantesNo para listas de links cualesquiera
<main>El contenido exclusivo de esta páginaSolo UNO visible por página
<article>Contenido independiente: post, noticia, comentarioTiene sentido fuera de contexto
<section>Capítulos o temas dentro de la páginaCasi siempre lleva su heading
<aside>Relacionado pero secundario: widgets, adsNo es contenido principal
<footer>Pie del sitio o de una secciónPuede repetirse
<time>Fechas y horas legibles por máquinasUsa atributo datetime
<address>Datos de contacto del autorDentro de footer suele vivir

Ejemplo completo: entrada de blog

post.html
<body>
  <header>
    <p class="logo">Técnico Web</p>
    <nav>
      <ul>
        <li><a href="/">Inicio</a></li>
        <li><a href="/cursos">Cursos</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <article>
      <header>
        <h1>Aprende HTML en 2026</h1>
        <p>Publicado el
          <time datetime="2026-08-22">22 de agosto de 2026</time></p>
      </header>

      <section>
        <h2>Por dónde empezar</h2>
        <p>Empieza por la estructura básica…</p>
      </section>

      <footer>
        <address>Escrito por <a href="mailto:ana@ejemplo.com">Ana García</a></address>
      </footer>
    </article>
  </main>

  <footer>
    <p><small>© 2026 Técnico Web</small></p>
  </footer>
</body>
✅ Test del widget: ¿el bloque tiene sentido solo, en otro sitio? → article.
✅ ¿Solo agrupa contenido temático? → section (con heading).
❌ ¿No aporta ningún significado? Ahí sí: usa div, como último recurso.
Lección 2 · Teoría

Tablas de datos completas

Las tablas son para datos tabulares: notas, precios, horarios. Nunca para maquetar la página — para eso ya tienes CSS.

Anatomía completa

tabla-notas.html
<table>
  <caption>Notas finales del curso de HTML</caption>

  <thead>
    <tr>
      <th scope="col">Alumno</th>
      <th scope="col">Bloque 1</th>
      <th scope="col">Bloque 2</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row">Ana</th>
      <td>95</td>
      <td>88</td>
    </tr>
    <tr>
      <th scope="row">Luis</th>
      <td>82</td>
      <td>90</td>
    </tr>
  </tbody>

  <tfoot>
    <tr>
      <td colspan="2">Media de la clase</td>
      <td>89,7</td>
    </tr>
  </tfoot>
</table>
  • <caption>: título descriptivo de la tabla (accesibilidad). Va justo tras <table>.
  • <thead>/<tbody>/<tfoot>: separan cabecera, cuerpo y resúmenes; también permiten fijar cabeceras al hacer scroll con CSS.
  • <th scope="col|row">: marca celdas de encabezado y dice si encabezan columna o fila.
  • colspan fusiona hacia los lados; rowspan hacia abajo. El valor indica cuántas celdas ocupa.

Fusiones: colspan y rowspan en acción

| Lunes | Martes | Miércoles | Jueves | Viernes |
|  MATE  |  LENG  |    FÍS    |  QUIM  |  INGL   |
|         R E C R E O (colspan="5")             |
|  HIST  |  ED.FÍ  |    ART   |  MÚS  |  TUTO   |
<tr>
  <td colspan="5" class="recreo">Recreo</td>
</tr>
💡 Regla mental: si tus datos tienen filas y columnas con encabezados cruzados, es tabla. Si no, probablemente sea una lista o unas tarjetas con CSS Grid.
Lección 3 · Teoría

Formularios: recogiendo datos del usuario

Los formularios son el puente entre el usuario y tu aplicación. Cada control necesita tres piezas: etiqueta (<label>), control (<input>, <select>…) y el atributo name, que es la clave con la que el dato viaja al servidor.

estructura base
<form action="/registro" method="post">

  <label for="email">Correo:</label>
  <input type="email"
         id="email" name="email"
         required>

  <button type="submit">Enviar</button>
</form>
GET vs POST
GET  → datos en la URL
       (búsquedas, filtros)
POST → datos ocultos en el cuerpo
       (contraseñas, registros)

Regla: si modifica algo → POST

La galería de input types

Cada dato con su tipo ideal
TypePara…Extras típicos
textTexto libre cortomaxlength, placeholder
emailCorreos (valida formato)multiple para varios
passwordContraseñas (oculta caracteres)nunca enviar por GET
numberNúmerosmin, max, step
tel / urlTeléfonos / URLsteclado adaptado en móvil
dateFechas con selectormin, max
checkboxMúltiple elección / términoscada opción su name
radioOpción única dentro de grupomismo name = mismo grupo
fileSubir archivosaccept=".pdf,.jpg"
range / colorDeslizadores / selector colorvalor inicial con value

Selects, textareas y datalist

controles.html
<!-- lista desplegable -->
<label for="pais">País:</label>
<select id="pais" name="pais" required>
  <option value="">— Elige un país —</option>
  <optgroup label="Europa">
    <option value="es" selected>España</option>
    <option value="pt">Portugal</option>
  </optgroup>
  <optgroup label="América">
    <option value="mx">México</option>
  </optgroup>
</select>

<!-- texto largo -->
<textarea id="msg" name="mensaje"
          rows="5" placeholder="Cuéntanos…"></textarea>

<!-- autocompletado con sugerencias -->
<input list="lenguajes" name="favorito">
<datalist id="lenguajes">
  <option value="HTML">
  <option value="CSS">
  <option value="JavaScript">
</datalist>

Validación nativa: el navegador trabaja gratis

✅ required — campo obligatorio
✅ minlength / maxlength — longitud del texto
✅ min / max / step — rangos numéricos y fechas
✅ pattern="[0-9]{5}" + title — formatos exactos
❌ placeholder no sustituye al label: desaparece al escribir
✅ autocomplete="email" — ayuda a rellenar rápido

Si algo falla, el navegador muestra el mensaje, enfoca el campo y no envía el formulario. Sin una sola línea de JavaScript.

⚠️ disabled desactiva el campo y sus datos no se envían; readonly solo bloquea la edición pero el valor sí viaja. Agrupa opciones relacionadas con <fieldset> + <legend>: mejora la comprensión y la accesibilidad.
💡 Prueba este formulario real: intenta enviarlo vacío y observa cómo el navegador lo impide solo.
Mini-demo: contacto express
Lección 4 · Teoría

Audio, vídeo e iframes

Audio con formatos alternativos

podcast.html
<figure>
  <audio controls preload="metadata">
    <source src="audio/ep1.mp3" type="audio/mpeg">
    <source src="audio/ep1.ogg" type="audio/ogg">
    Tu navegador no soporta audio nativo.
  </audio>
  <figcaption>Episodio 1 — Primeros pasos</figcaption>
</figure>

Vídeo con subtítulos accesibles

tutorial.html
<video controls
       poster="img/portada.jpg"
       width="720" height="405"
       preload="metadata">
  <source src="video/demo.mp4"  type="video/mp4">
  <source src="video/demo.webm" type="video/webm">

  <track src="video/subs-es.vtt" kind="subtitles"
         srclang="es" label="Español" default>
  <track src="video/subs-en.vtt" kind="subtitles"
         srclang="en" label="English">

  <p>Descarga la
    <a href="video/transcripcion.txt" download>transcripción</a>.
  </p>
</video>
  • controls muestra los botones nativos; sin él, el vídeo es invisible e inmanejable.
  • poster: imagen de portada antes de dar a play.
  • preload="metadata" carga solo lo mínimo: la página vuela.
  • Varios <source> = el navegador elige el primer formato que entienda (mp4 + webm cubre todo).
  • <track kind="captions"> añade subtítulos: obligatorio para accesibilidad seria.
⚠️ Evita autoplay con sonido: los navegadores lo bloquean, los usuarios lo odian. Si lo usas, debe ser siempre autoplay muted loop (fondos animados).

Iframes: incrustando otros sitios

donde-estamos.html
<!-- vídeo de YouTube -->
<iframe
  src="https://www.youtube.com/embed/ID_DEL_VIDEO"
  title="Vídeo tutorial de HTML"
  width="720" height="405"
  loading="lazy"
  allowfullscreen></iframe>

<!-- mapa de Google Maps (comparte → insertar) -->
<iframe
  src="https://www.google.com/maps/embed?pb=..."
  title="Mapa de nuestra academia en Madrid"
  width="720" height="360"
  loading="lazy"
  referrerpolicy="no-referrer-when-downgrade"
  allowfullscreen></iframe>
  • title en cada iframe es imprescindible: los lectores de pantalla anuncian qué contiene.
  • loading="lazy" evita cargar YouTube/Maps hasta llegar a ellos: ahorra muchísimo.
  • Los iframes cargan código ajeno: usa servicios de confianza y considera sandbox para contenido no confiable.
Práctica guiada

✍️ Los 12 ejercicios del bloque

Del 1 al 11 consolidas lecciones; el 12 es el reto del bloque: el formulario de registro completo que valida la unidad.

1 Esqueleto de portada de noticias

Crea la estructura semántica (solo etiquetas, contenido breve) de una portada de periódico: cabecera con menú, contenido principal con dos noticias independientes, columna lateral de «lo más leído» y pie legal.

Ver solución
<header>
  <h1>El Diario Web</h1>
  <nav><ul>
    <li><a href="#">Portada</a></li>
    <li><a href="#">Deportes</a></li>
  </ul></nav>
</header>

<main>
  <article><h2>Noticia 1</h2><p>…</p></article>
  <article><h2>Noticia 2</h2><p>…</p></article>
  <aside><h2>Lo más leído</h2><ul>
    <li><a href="#">Artículo popular</a></li>
  </ul></aside>
</main>

<footer><small>© 2026 El Diario Web</small></footer>

2 Rescatar una sopa de divs

Este código funciona pero es ilegible. Reescríbelo con etiquetas semánticas equivalentes:

<div class="top">Mi Tienda</div>
<div class="menu"><a href="#">Inicio</a></div>
<div class="contenido">
  <div class="post">Ofertas de agosto</div>
  <div class="lateral">Envíos gratis hoy</div>
</div>
<div class="pie">Contacto: info@tienda.es</div>
Ver solución
<header>Mi Tienda</header>
<nav><a href="#">Inicio</a></nav>
<main>
  <article>Ofertas de agosto</article>
  <aside>Envíos gratis hoy</aside>
</main>
<footer>Contacto: <address>info@tienda.es</address></footer>

3 Noticia de nivel periodístico

Escribe un <article> de noticia con: cabecera interna (titular h2 + fecha legible con <time datetime="2026-08-20T09:30">), dos párrafos y pie interno con <address> del autor.

Ver solución
<article>
  <header>
    <h2>La ciudad estrena carril bici inteligente</h2>
    <p>
      <time datetime="2026-08-20T09:30">
        20 de agosto de 2026, 09:30
      </time>
    </p>
  </header>

  <p>El nuevo carril conecta once barrios…</p>
  <p>Los vecinos celebran la medida…</p>

  <footer>
    <address>
      Por <a href="mailto:marta@diario.es">Marta Ruiz</a>
    </address>
  </footer>
</article>

4 Horario semanal perfecto

Tabla de un horario de clase (L–V × mañana/tarde) con caption, thead con scope="col", tbody con horas en th scope="row", una fila «Recreo» fusionada con colspan y tfoot con total de horas.

Ver solución
<table>
  <caption>Horario semanal — Grupo A</caption>
  <thead><tr>
    <th scope="col">Tramo</th>
    <th scope="col">Lun</th>
    <th scope="col">Mar</th>
    <th scope="col">Mié</th>
    <th scope="col">Jue</th>
    <th scope="col">Vie</th>
  </tr></thead>
  <tbody>
    <tr>
      <th scope="row">09–11</th>
      <td>MATE</td><td>LENG</td><td>FÍS</td><td>QUIM</td><td>INGL</td>
    </tr>
    <tr><td colspan="6">Recreo</td></tr>
    <tr>
      <th scope="row">12–14</th>
      <td>HIST</td><td>ED.FÍ</td><td>ART</td><td>MÚS</td><td>TUTO</td>
    </tr>
  </tbody>
  <tfoot><tr>
    <td colspan="6">Total: 10 horas semanales</td>
  </tr></tfoot>
</table>

5 Facturación con rowspan

Tabla de ventas donde la categoría «Hardware» abarca dos filas (rowspan="2") y el pie suma totales por trimestre.

Ver solución
<table>
  <caption>Ventas Q3 2026 (miles €)</caption>
  <thead><tr>
    <th scope="col">Categoría</th>
    <th scope="col">Producto</th>
    <th scope="col">Julio</th>
    <th scope="col">Agosto</th>
  </tr></thead>
  <tbody>
    <tr>
      <th rowspan="2" scope="row">Hardware</th>
      <td>Teclados</td><td>12</td><td>15</td>
    </tr>
    <tr><td>Ratones</td><td>9</td><td>11</td></tr>
  </tbody>
  <tfoot><tr>
    <td colspan="2">Total</td>
    <td>21</td><td>26</td>
  </tr></tfoot>
</table>

6 Contacto accesible

Formulario de contacto dentro de un <section id="contacto"> con título, fieldset «Mensaje», nombre, email, asunto select y textarea, todo con labels correctos.

Ver solución
<section id="contacto">
  <h2>Contacta con nosotros</h2>
  <form action="/contacto" method="post">
    <fieldset>
      <legend>Mensaje</legend>

      <label for="cNombre">Nombre:</label>
      <input type="text" id="cNombre" name="nombre" required>

      <label for="cEmail">Email:</label>
      <input type="email" id="cEmail" name="email" required>

      <label for="cAsunto">Asunto:</label>
      <select id="cAsunto" name="asunto">
        <option value="consulta">Consulta general</option>
        <option value="soporte">Soporte técnico</option>
      </select>

      <label for="cMsg">Mensaje:</label>
      <textarea id="cMsg" name="mensaje" rows="6" required></textarea>

      <button type="submit">Enviar</button>
    </fieldset>
  </form>
</section>

7 Encuesta de gustos tech

Una encuesta con: radios (¿frontend o backend?, un solo grupo), checkboxes (lenguajes que conoces, selección múltiple), select de país con optgroup por continente y un input con datalist de sugerencias.

Ver solución
<fieldset>
  <legend>¿Qué te llama más?</legend>
  <input type="radio" id="fe" name="rama" value="front" checked>
  <label for="fe">Frontend</label>
  <input type="radio" id="be" name="rama" value="back">
  <label for="be">Backend</label>
</fieldset>

<fieldset>
  <legend>Lenguajes que conoces</legend>
  <input type="checkbox" id="html" name="langs" value="html">
  <label for="html">HTML</label>
  <input type="checkbox" id="css" name="langs" value="css">
  <label for="css">CSS</label>
</fieldset>

<label for="paisE">País:</label>
<select id="paisE" name="pais">
  <optgroup label="Europa">
    <option value="es">España</option>
  </optgroup>
  <optgroup label="América">
    <option value="mx">México</option>
  </optgroup>
</select>

<label for="nextLang">Siguiente lenguaje:</label>
<input list="sugerencias" id="nextLang" name="siguiente">
<datalist id="sugerencias">
  <option value="JavaScript">
  <option value="PHP">
  <option value="Python">
</datalist>

8 Validación nativa exprés

Un mini-registro donde: el email use type=email required, la contraseña minlength="8", el CP pattern="[0-9]{5}" con title explicativo, la edad type=number min="13" max="120" y la web personal type=url opcional.

Ver solución
<form action="/registro-rapido" method="post">
  <label for="rEmail">Email:</label>
  <input type="email" id="rEmail" name="email"
         autocomplete="email" required>

  <label for="rPass">Contraseña:</label>
  <input type="password" id="rPass" name="pass"
         minlength="8" required>

  <label for="rCP">Código postal:</label>
  <input type="text" id="rCP" name="cp"
         pattern="[0-9]{5}"
         title="Introduce exactamente 5 dígitos" required>

  <label for="rEdad">Edad:</label>
  <input type="number" id="rEdad" name="edad"
         min="13" max="120">

  <label for="rWeb">Web personal (opcional):</label>
  <input type="url" id="rWeb" name="web"
         placeholder="https://…">

  <button type="submit">Crear cuenta</button>
</form>

9 Pódcast publicable

Publica un episodio: <figure> con <audio controls> que ofrezca MP3 y OGG, preload="metadata" y figcaption con número y título del episodio.

Ver solución
<figure>
  <audio controls preload="metadata">
    <source src="media/ep07.mp3" type="audio/mpeg">
    <source src="media/ep07.ogg" type="audio/ogg">
    Tu navegador no reproduce audio nativo.
  </audio>
  <figcaption>
    Episodio 07 — Formularios que se validan solos
  </figcaption>
</figure>

10 Vídeo 100% accesible

Embebe un tutorial con: poster propio, mp4 + webm, subtítulos español (por defecto) e inglés mediante <track>, enlace descargable a la transcripción y sin autoplay.

Ver solución
<figure>
  <video controls poster="media/portada-curso.jpg"
         width="720" height="405"
         preload="metadata">
    <source src="media/clase01.mp4" type="video/mp4">
    <source src="media/clase01.webm" type="video/webm">
    <track src="media/clase01-es.vtt" kind="subtitles"
           srclang="es" label="Español" default>
    <track src="media/clase01-en.vtt" kind="subtitles"
           srclang="en" label="English">
    <p>¿Sin soporte? Descarga la
      <a href="media/clase01-transcripcion.txt" download>
        transcripción completa</a>.</p>
  </video>
  <figcaption>Clase 01 — Estructura semántica</figcaption>
</figure>

11 Página «Dónde estamos»

Crea una sección con mapa de Google Maps incrustado (title descriptivo + loading="lazy") y, debajo, un vídeo de presentación de YouTube también perezoso.

Ver solución
<section id="donde-estamos">
  <h2>Dónde estamos</h2>

  <iframe
    src="https://www.google.com/maps/embed?pb=TU_CODIGO"
    title="Mapa con la ubicación de la academia"
    width="720" height="360"
    style="border:0"
    loading="lazy"
    allowfullscreen></iframe>

  <h3>Conócenos en 2 minutos</h3>
  <iframe
    src="https://www.youtube.com/embed/TU_ID"
    title="Vídeo de presentación de la academia"
    width="720" height="405"
    loading="lazy"
    allowfullscreen></iframe>
</section>

12 🏆 Reto del bloque: formulario de registro completo

Crea registro.html: página semántica con <main> y un formulario method="post" dividido en tres <fieldset>:

  • Datos personales: nombre (required), email (type email + required), fecha de nacimiento (type date) y edad (number, 13–120)
  • Credenciales: usuario, contraseña (minlength 8, required) y repetir contraseña
  • Preferencias: radios de nivel (principiante checked / intermedio / avanzado), select de país requerido con optgroup y placeholder vacío, checkbox «acepto los términos» required

Todo con labels correctos, autocomplete donde toque y validación 100% nativa.

Ver solución orientativa
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Registro — Academia Online</title>
</head>
<body>
<main>
  <h1>Crea tu cuenta</h1>

  <form action="/registro" method="post">

    <fieldset>
      <legend>Datos personales</legend>
      <label for="nombre">Nombre:</label>
      <input type="text" id="nombre" name="nombre" required>

      <label for="email">Email:</label>
      <input type="email" id="email" name="email"
             autocomplete="email" required>

      <label for="nacimiento">Fecha de nacimiento:</label>
      <input type="date" id="nacimiento" name="nacimiento"
             min="1930-01-01">

      <label for="edad">Edad:</label>
      <input type="number" id="edad" name="edad"
             min="13" max="120">
    </fieldset>

    <fieldset>
      <legend>Credenciales</legend>
      <label for="usuario">Usuario:</label>
      <input type="text" id="usuario" name="usuario"
             autocomplete="username" required>

      <label for="pass">Contraseña:</label>
      <input type="password" id="pass" name="pass"
             minlength="8" autocomplete="new-password" required>

      <label for="pass2">Repite la contraseña:</label>
      <input type="password" id="pass2" name="pass2"
             minlength="8" required>
    </fieldset>

    <fieldset>
      <legend>Preferencias</legend>

      <input type="radio" id="n1" name="nivel" value="inicial" checked>
      <label for="n1">Empiezo desde cero</label>
      <input type="radio" id="n2" name="nivel" value="medio">
      <label for="n2">Nivel intermedio</label>
      <input type="radio" id="n3" name="nivel" value="pro">
      <label for="n3">Soy desarrollador/a</label>

      <label for="pais">País:</label>
      <select id="pais" name="pais" required>
        <option value="">— Elige tu país —</option>
        <optgroup label="Europa">
          <option value="es">España</option>
          <option value="pt">Portugal</option>
        </optgroup>
        <optgroup label="América">
          <option value="mx">México</option>
          <option value="ar">Argentina</option>
        </optgroup>
      </select>

      <input type="checkbox" id="terminos" name="terminos" required>
      <label for="terminos">Acepto los términos y condiciones</label>
    </fieldset>

    <button type="submit">Registrarme</button>
  </form>
</main>
</body>
</html>
Evaluación del bloque

📝 Examen del Bloque 2

Como siempre: test teórico autoevaluable (mínimo 70 %) + reto práctico con checklist de autocorrección.

Parte A · Test teórico (aprueba con 9/12)

1 · ¿Cuántas etiquetas <main> puede tener una página?
2 · Un post de blog que tendría sentido fuera de la página es un…
3 · ¿Qué diferencia a <section> de <div>?
4 · El título descriptivo de una tabla se escribe en…
5 · Un <th> que encabeza una columna usa…
6 · Para que una celda ocupe 3 columnas…
7 · ¿Por qué todo control necesita su <label>?
8 · Un formulario que envía contraseñas debe usar…
9 · Los radios de un mismo grupo comparten…
10 · ¿Qué atributo hace imprescindible rellenar un campo?
11 · El atributo pattern valida mediante…
12 · Todo <iframe> accesible necesita…

Parte B · Reto práctico final

Construye el ejercicio 12 («registro.html») sin mirar la solución y valida tú mismo/a. Mínimo para aprobar: 7 de 8 casillas.

Cumplidos: 0 / 8

🎉 ¿Superaste las dos partes? Bloque 2 aprobado. Ya construyes documentos semánticos, tablas correctas y formularios profesionales que se validan solos. El Bloque 3 te espera con SEO, accesibilidad avanzada y auditoría de calidad.