Saltar al contenido
ejercicios-html.html · devschool

Ejercicios de HTML resueltos

Lección 39 de 39 · 13 min de lectura · Actualizado el

En esta lección
  1. Nivel 1: lo básico
  2. Nivel 2: tablas, formularios y semántica
  3. Nivel 3: más allá de lo básico
  4. Errores frecuentes
  5. Resumen

Ha llegado el momento de practicar. Estos 12 ejercicios recorren todo el curso de HTML, desde la estructura de un documento hasta una página completa con SEO y accesibilidad. Están ordenados de fácil a difícil: intenta resolver cada uno en el editor o en tu propio proyecto antes de abrir la solución. Si te atascas, cada ejercicio indica la lección que conviene repasar.

No hay una única solución correcta. Si la tuya es distinta pero cumple el enunciado, es válida HTML bien formado y semántico, está bien.

Nivel 1: lo básico

Ejercicio 1: el esqueleto de una página

Escribe un documento HTML completo para la página “Horarios de la biblioteca municipal”. Debe estar en español, admitir tildes, verse bien en el móvil y tener un título de pestaña adecuado. En el cuerpo, solo un título principal y un párrafo. Repasa estructura básica.

Ver solución
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Horarios · Biblioteca municipal de Laredo</title>
</head>
<body>
  <h1>Horarios de la biblioteca</h1>
  <p>Abrimos de lunes a sábado. Consulta aquí los horarios de cada sala.</p>
</body>
</html>

lang="es" permite a los lectores de pantalla usar la pronunciación correcta, charset evita los caracteres raros en las tildes y viewport hace que el móvil no muestre la página en miniatura.

Ejercicio 2: una receta

Marca esta receta: título “Tortilla de patatas”; un párrafo con el tiempo (30 minutos, en negrita por ser importante); un subtítulo “Ingredientes” con una lista de 4 ingredientes en cualquier orden; y un subtítulo “Preparación” con 3 pasos que sí tienen orden. Repasa títulos y párrafos y listas.

Ver solución
<article>
  <h1>Tortilla de patatas</h1>
  <p>Tiempo: <strong>30 minutos</strong>.</p>

  <h2>Ingredientes</h2>
  <ul>
    <li>4 patatas medianas</li>
    <li>6 huevos</li>
    <li>1 cebolla</li>
    <li>Aceite de oliva y sal</li>
  </ul>

  <h2>Preparación</h2>
  <ol>
    <li>Pela y corta las patatas y la cebolla en láminas finas.</li>
    <li>Fríelas a fuego medio hasta que estén blandas y escúrrelas.</li>
    <li>Mézclalas con los huevos batidos y cuaja la tortilla por los dos lados.</li>
  </ol>
</article>

<ul> para lo que no tiene orden, <ol> para los pasos. <strong> indica importancia; si solo quisieras resaltar visualmente, sería trabajo de CSS.

Ejercicio 3: menú de navegación y enlaces

Una web tiene esta estructura de carpetas:

gimnasio/
├── index.html
├── clases/
│   └── yoga.html
└── contacto.html

Desde clases/yoga.html, escribe un menú con enlaces a Inicio y Contacto. Añade al pie un enlace para enviar un correo a info@gimnasiocumbre.example, otro para llamar al 942 111 222 y un enlace “Volver arriba” que lleve al título de la página (id="arriba"). Repasa enlaces y rutas.

Ver solución
<nav aria-label="Principal">
  <ul>
    <li><a href="../index.html">Inicio</a></li>
    <li><a href="../contacto.html">Contacto</a></li>
  </ul>
</nav>

<h1 id="arriba">Clases de yoga</h1>

<footer>
  <a href="mailto:info@gimnasiocumbre.example">info@gimnasiocumbre.example</a> ·
  <a href="tel:+34942111222">942 111 222</a> ·
  <a href="#arriba">Volver arriba</a>
</footer>

yoga.html está dentro de clases/, así que hay que subir un nivel con ../. En tel: conviene escribir el número completo con prefijo y sin espacios.

Ejercicio 4: una imagen con pie de foto

Muestra la foto img/faro.jpg (1200 × 800 píxeles) de un faro al atardecer con el pie “Faro de Cabo Mayor, Santander”. Debe tener un texto alternativo útil, reservar su hueco antes de cargar y no descargarse hasta que el usuario se acerque a ella. Además, ofrece la versión img/faro.webp a los navegadores que la admitan. Repasa imágenes e imágenes responsive.

Ver solución
<figure>
  <picture>
    <source srcset="img/faro.webp" type="image/webp">
    <img src="img/faro.jpg" alt="Faro blanco sobre un acantilado con el cielo naranja del atardecer"
         width="1200" height="800" loading="lazy">
  </picture>
  <figcaption>Faro de Cabo Mayor, Santander</figcaption>
</figure>

width y height evitan que la página salte al cargar la imagen, loading="lazy" retrasa la descarga y <picture> deja que el navegador elija el WebP si lo entiende. El alt describe lo que se ve; el lugar ya lo dice el pie.

Nivel 2: tablas, formularios y semántica

Ejercicio 5: el horario de un gimnasio

Crea una tabla accesible con el horario de la mañana: columnas Hora, Lunes, Martes y Miércoles; filas 9:00 (Spinning, Yoga, Spinning) y 10:00 (Pilates, Pilates y Pilates, que es la misma clase que dura de lunes a miércoles). Añade un título a la tabla. Repasa tablas.

Ver solución
<table>
  <caption>Clases de la mañana</caption>
  <thead>
    <tr>
      <th scope="col">Hora</th>
      <th scope="col">Lunes</th>
      <th scope="col">Martes</th>
      <th scope="col">Miércoles</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">9:00</th>
      <td>Spinning</td>
      <td>Yoga</td>
      <td>Spinning</td>
    </tr>
    <tr>
      <th scope="row">10:00</th>
      <td colspan="3">Pilates</td>
    </tr>
  </tbody>
</table>

<caption> da nombre a la tabla, scope dice si cada <th> encabeza una columna o una fila y colspan="3" une las tres celdas de Pilates.

Ejercicio 6: formulario de reserva

Haz un formulario que envíe por POST a /reservar con: nombre (obligatorio), correo (obligatorio y con formato de correo), número de personas (de 1 a 10), fecha de la reserva y un desplegable de turno (comida o cena) en el que haya que elegir. Cada campo con su etiqueta. Repasa formularios y validar formularios.

Ver solución
<form action="/reservar" method="post">
  <label for="nombre">Nombre</label>
  <input id="nombre" name="nombre" autocomplete="name" required>

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

  <label for="personas">Personas</label>
  <input id="personas" name="personas" type="number" min="1" max="10" value="2">

  <label for="fecha">Fecha</label>
  <input id="fecha" name="fecha" type="date" required>

  <label for="turno">Turno</label>
  <select id="turno" name="turno" required>
    <option value="">Elige un turno</option>
    <option value="comida">Comida</option>
    <option value="cena">Cena</option>
  </select>

  <button type="submit">Reservar</button>
</form>

La primera opción del <select> tiene value="", así que required obliga a elegir otra. Sin name, un campo no se envía.

Ejercicio 7: encuentra los errores

Este fragmento tiene seis errores. Encuéntralos y corrígelos:

<h1>Rutas de senderismo<h1>
<p>Te proponemos <b>tres rutas<b> para el fin de semana.
  <p>Todas son circulares.</p>
</p>
<img src="ruta-1.jpg">
<a href="ruta-1.html" id="ruta">Ver la ruta 1</a>
<a href="ruta-2.html" id="ruta">Ver la ruta 2</a>
<ul>
  <p>Recomendaciones</p>
  <li>Lleva agua</li>
</ul>
Ver solución
<h1>Rutas de senderismo</h1>
<p>Te proponemos <strong>tres rutas</strong> para el fin de semana.</p>
<p>Todas son circulares.</p>
<img src="ruta-1.jpg" alt="Sendero entre hayas junto a un río" width="800" height="533">
<a href="ruta-1.html" id="ruta-1">Ver la ruta 1</a>
<a href="ruta-2.html" id="ruta-2">Ver la ruta 2</a>
<h2>Recomendaciones</h2>
<ul>
  <li>Lleva agua</li>
</ul>
  1. El <h1> no se cerraba (<h1> en lugar de </h1>).
  2. El <b> tampoco se cerraba; además, si el texto es importante, <strong> es más adecuado.
  3. Un <p> no puede ir dentro de otro <p>.
  4. La imagen no tenía alt (y conviene width y height).
  5. Dos elementos con el mismo id: debe ser único en la página.
  6. Dentro de <ul> solo puede haber <li>; el título va fuera.

Ejercicio 8: de <div> a semántica

Reescribe esta página usando las etiquetas semánticas adecuadas. Repasa HTML semántico.

<div class="cabecera">
  <div class="logo">Librería Página 42</div>
  <div class="menu"><a href="/">Inicio</a> <a href="/novedades/">Novedades</a></div>
</div>
<div class="contenido">
  <div class="noticia">
    <div class="titulo">Llega la nueva novela de Sara Ortiz</div>
    <div>Publicada el 3 de octubre de 2026.</div>
  </div>
  <div class="lateral">Horario: de 10:00 a 20:00</div>
</div>
<div class="pie">© 2026 Librería Página 42</div>
Ver solución
<header>
  <p class="logo">Librería Página 42</p>
  <nav aria-label="Principal">
    <ul>
      <li><a href="/">Inicio</a></li>
      <li><a href="/novedades/">Novedades</a></li>
    </ul>
  </nav>
</header>
<main>
  <article>
    <h1>Llega la nueva novela de Sara Ortiz</h1>
    <p>Publicada el <time datetime="2026-10-03">3 de octubre de 2026</time>.</p>
  </article>
  <aside>
    <h2>Horario</h2>
    <p>De 10:00 a 20:00</p>
  </aside>
</main>
<footer>
  <p>© 2026 Librería Página 42</p>
</footer>

Cada parte tiene ahora un significado que entienden los buscadores y los lectores de pantalla: cabecera, navegación, contenido principal, artículo, información complementaria y pie. La fecha va en <time> para que las máquinas la lean.

Nivel 3: más allá de lo básico

Ejercicio 9: un vídeo con subtítulos y un mapa

Inserta el vídeo video/presentacion.mp4 con controles, una imagen de portada img/portada-video.jpg y subtítulos en español (video/subtitulos-es.vtt) activados por defecto. Debajo, incrusta un mapa con la URL https://www.openstreetmap.org/export/embed.html?bbox=-3.83,43.45,-3.78,43.48 que no se cargue hasta que haga falta y que tenga un nombre accesible. Repasa audio, vídeo e iframes.

Ver solución
<video controls width="640" height="360" poster="img/portada-video.jpg" preload="metadata">
  <source src="video/presentacion.mp4" type="video/mp4">
  <track src="video/subtitulos-es.vtt" kind="subtitles" srclang="es" label="Español" default>
  Tu navegador no puede reproducir el vídeo. <a href="video/presentacion.mp4">Descárgalo aquí</a>.
</video>

<iframe src="https://www.openstreetmap.org/export/embed.html?bbox=-3.83,43.45,-3.78,43.48"
        title="Mapa del centro de Santander" width="640" height="360" loading="lazy"></iframe>

default activa los subtítulos, preload="metadata" evita descargar el vídeo entero al cargar la página, y el title del iframe es lo que anuncia el lector de pantalla.

Ejercicio 10: preguntas frecuentes sin JavaScript

Crea un bloque de tres preguntas frecuentes de una academia de idiomas que se desplieguen al pulsarlas. Solo puede haber una abierta a la vez y la primera debe aparecer abierta. Repasa details y dialog.

Ver solución
<section aria-labelledby="t-faq">
  <h2 id="t-faq">Preguntas frecuentes</h2>

  <details name="faq" open>
    <summary>¿Hacéis prueba de nivel?</summary>
    <p>Sí, es gratuita y dura unos 20 minutos.</p>
  </details>

  <details name="faq">
    <summary>¿Cuántos alumnos hay por grupo?</summary>
    <p>Un máximo de ocho.</p>
  </details>

  <details name="faq">
    <summary>¿Puedo cambiar de horario?</summary>
    <p>Sí, siempre que haya plazas libres en el otro grupo.</p>
  </details>
</section>

Los <details> que comparten el mismo name forman un acordeón exclusivo: al abrir uno se cierra el que estaba abierto. open deja abierto el primero.

Ejercicio 11: arregla la accesibilidad

Este fragmento funciona con el ratón, pero no es accesible. Corrígelo. Repasa accesibilidad y ARIA a fondo.

<div class="boton" onclick="buscar()"><img src="lupa.svg"></div>
<input type="text" placeholder="Busca un libro">
<img src="separador.png">
<a href="catalogo.pdf">Pincha aquí</a>
<p style="color: #bbb; background: #fff">Envío gratis desde 30 €</p>
Ver solución
<search>
  <form action="/buscar">
    <label for="q">Busca un libro</label>
    <input id="q" name="q" type="search">
    <button type="submit"><img src="lupa.svg" alt="Buscar" width="20" height="20"></button>
  </form>
</search>
<img src="separador.png" alt="">
<a href="catalogo.pdf">Descarga el catálogo de otoño (PDF, 2 MB)</a>
<p class="aviso">Envío gratis desde 30 €</p>
  • Un <div> con onclick no se puede usar con el teclado ni lo anuncia el lector de pantalla: un <button> sí. Su icono necesita alt, porque es el único texto del botón.
  • El placeholder no sustituye a una etiqueta.
  • La imagen decorativa lleva alt="" para que se ignore.
  • “Pincha aquí” no dice adónde lleva el enlace; el texto nuevo indica también el formato y el tamaño.
  • El gris #bbb sobre blanco no tiene contraste suficiente. Muévelo a CSS con un color más oscuro, como #4b5563.

Ejercicio 12: la página completa de un evento

Crea la página completa de la “Carrera popular de Castro Urdiales” (domingo 15 de noviembre de 2026, 10:00): documento con <head> completo para buscadores y redes sociales, favicon, cabecera con navegación, contenido principal con la información de la carrera, una tabla de categorías y precios, un enlace a la inscripción y el pie. Añade los datos estructurados del evento en JSON-LD. Repasa el head, SEO en HTML y datos estructurados.

Ver solución
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Carrera popular de Castro Urdiales 2026 · 10 km y 5 km</title>
  <meta name="description" content="Carrera popular de Castro Urdiales el 15 de noviembre de 2026: recorridos de 5 y 10 km junto al mar, categorías, precios e inscripción online.">
  <link rel="canonical" href="https://carreracastro.example/">
  <link rel="icon" href="/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="/css/estilos.css">
  <meta property="og:title" content="Carrera popular de Castro Urdiales 2026">
  <meta property="og:description" content="5 y 10 km junto al mar. Domingo 15 de noviembre.">
  <meta property="og:image" content="https://carreracastro.example/img/carrera.jpg">
  <meta property="og:type" content="website">
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "SportsEvent",
    "name": "Carrera popular de Castro Urdiales 2026",
    "startDate": "2026-11-15T10:00:00+01:00",
    "location": {
      "@type": "Place",
      "name": "Paseo marítimo de Castro Urdiales",
      "address": "Castro Urdiales, Cantabria, España"
    },
    "offers": {
      "@type": "Offer",
      "price": "12",
      "priceCurrency": "EUR",
      "url": "https://carreracastro.example/inscripcion"
    }
  }
  </script>
</head>
<body>
  <a class="saltar" href="#contenido">Saltar al contenido</a>

  <header>
    <p class="logo">Carrera de Castro</p>
    <nav aria-label="Principal">
      <ul>
        <li><a href="/" aria-current="page">Inicio</a></li>
        <li><a href="/recorrido/">Recorrido</a></li>
        <li><a href="/inscripcion">Inscripción</a></li>
      </ul>
    </nav>
  </header>

  <main id="contenido">
    <h1>Carrera popular de Castro Urdiales 2026</h1>
    <p>
      <time datetime="2026-11-15T10:00">Domingo 15 de noviembre, a las 10:00</time>,
      salida desde el paseo marítimo.
    </p>
    <img src="/img/carrera.jpg" alt="Corredores en el paseo marítimo con la iglesia de Santa María al fondo"
         width="1200" height="675">

    <h2>Categorías y precios</h2>
    <table>
      <caption>Precio de la inscripción por distancia</caption>
      <thead>
        <tr><th scope="col">Distancia</th><th scope="col">Edad mínima</th><th scope="col">Precio</th></tr>
      </thead>
      <tbody>
        <tr><th scope="row">5 km</th><td>12 años</td><td>8 €</td></tr>
        <tr><th scope="row">10 km</th><td>16 años</td><td>12 €</td></tr>
      </tbody>
    </table>

    <p><a href="/inscripcion">Inscríbete en la carrera</a></p>
  </main>

  <footer>
    <p>Organiza el Club Atletismo Castro · <a href="mailto:carrera@carreracastro.example">carrera@carreracastro.example</a></p>
  </footer>
</body>
</html>

Este ejercicio junta casi todo el curso: un <head> con título y descripción únicos, canonical, favicon y Open Graph; datos estructurados para que Google pueda mostrar el evento con fecha y precio; landmarks y enlace de salto; un solo <h1>; imagen con alt y dimensiones; una tabla con caption y scope; y fechas en <time>.

Errores frecuentes

  • Mirar la solución antes de intentarlo: aprenderás mucho más equivocándote primero. Dale al menos diez minutos a cada ejercicio.
  • Dar por buena una solución porque “se ve bien”: el navegador perdona casi todo. Pasa tu código por el validador del W3C.
  • Olvidar el alt, las etiquetas de formulario o el lang: son los fallos que más se repiten en los exámenes y en las webs reales.
  • Elegir etiquetas por su aspecto: <h3> porque “es más pequeño” o <blockquote> para sangrar. El aspecto es cosa de CSS.
  • Copiar la solución sin entenderla: cambia algo (otra estructura, otro tema) y comprueba que sigue siendo válida.

Resumen

EjercicioPractica
1Estructura del documento y <head> mínimo
2Títulos, párrafos, <strong> y listas
3Enlaces, rutas relativas, mailto:, tel: y anclas
4<figure>, <picture>, alt, dimensiones y loading
5Tablas con caption, scope y colspan
6Formularios y validación nativa
7Detectar errores de anidamiento y atributos
8Etiquetas semánticas
9Vídeo con subtítulos e iframes
10Acordeón con <details name>
11Accesibilidad: botones, etiquetas, alt, enlaces y contraste
12Página completa con SEO, Open Graph y JSON-LD

Con esto terminas el curso de HTML. El siguiente paso es darle diseño a tus páginas en el curso de CSS, empezando por ¿qué es CSS?.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

ejercicios-html.html
Resultado

Pon a prueba lo que has aprendido

[HTML] Un compañero escribe <h3> para un título "porque se ve más pequeño". ¿Cuál es el problema?

[HTML] Tu HTML "se ve bien" en el navegador. ¿Por qué conviene pasarlo además por el validador del W3C?

[HTML] Para un acordeón de preguntas frecuentes sin JavaScript, ¿qué elemento usas?

[HTML] ¿Cuál de estas imágenes cumple mejor las buenas prácticas?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.