📙 Curso HTML · Nivel N3 Intermedio

Bloque 3: Profesionalización

Tu código ya funciona; ahora tiene que competir: páginas que cualquier persona puede usar con teclado o lector de pantalla, que Google premia en sus resultados y cuya calidad demuestras con números. Accesibilidad WCAG, SEO técnico, HTML5 avanzado y auditoría profesional.

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

Accesibilidad WCAG: páginas para todas las personas

La accesibilidad web (a11y) garantiza que cualquiera pueda usar tu página: personas ciegas que navegan con lector de pantalla, usuarios que solo usan el teclado, gente con baja visión, dislexia o movilidad reducida. La referencia mundial son las pautas WCAG del W3C, con tres niveles de conformidad: A (mínimo), AA (estándar legal en la UE y objetivo de este curso) y AAA (excelencia).

1.1 · La regla de oro: semántica primero, ARIA después

La primera norma de ARIA dice literalmente: «no uses ARIA si existe un elemento HTML nativo que haga el trabajo». Un <button> ya es enfocable, ya responde a Espacio/Enter y ya anuncia su rol. Si lo sustituyes por un <div onclick>, tendrás que reconstruir a mano todo eso con ARIA… y casi siempre lo harás peor.

❌ inaccessible
<div class="menu-btn" onclick="abrir()">☰</div>
✅ accesible
<button class="menu-btn"
        aria-expanded="false"
        aria-controls="menu-principal">
  ☰ <span class="sr-only">Abrir menú</span>
</button>

Cuando la semántica nativa no llega (un componente tipo acordeón, una pestaña, un interruptor), ahí sí entran los roles (role="tab", role="switch", role="alert"…) y los estados ARIA:

Atributos ARIA imprescindibles
AtributoPara qué sirveEjemplo
aria-labelNombra un elemento sin texto visibleIcono de búsqueda: aria-label="Buscar"
aria-labelledbyNombra usando el texto de OTRO elemento (por id)Diálogo nombrado por su título
aria-describedbyVincula una descripción/ayuda adicionalPista bajo un campo de contraseña
aria-expandedIndica si un desplegable está abiertoMenú hamburguesa, acordeón
aria-controlsApunta al id del contenido que gestionaEl botón y su menú
aria-currentMarca el elemento activo de una listaPágina actual del menú
aria-hiddenOculta a tecnologías de asistenciaIconos decorativos repetidos
aria-liveAnuncia cambios dinámicos«Añadido al carrito»
💡 role="alert" implica aria-live="assertive": el lector de pantalla interrumpe y lee el mensaje nada más aparecer. Perfecto para errores de formulario.

1.2 · Navegación por teclado

Muchos usuarios no tocan el ratón nunca. Tu página debe poder recorrerse completa con Tab / Shift+Tab, en un orden lógico y viendo siempre dónde está el foco. Las reglas:

✅ tabindex="0": añade un elemento al orden natural de tabulación (usar con moderación).
✅ tabindex="-1": enfocable solo mediante JavaScript (element.focus()).
❌ tabindex="1" o superior: rompe el orden esperado. Prohibido en este curso.
❌ Quitar el contorno del foco con outline: none sin dar alternativa visible.

El gesto más profesional de accesibilidad es el enlace de salto («skip link»): invisible hasta que recibe foco, permite saltarse el menú y llegar directo al contenido:

skip-link.html
<body>
  <a class="skip-link" href="#contenido">Saltar al contenido</a>
  <header>…menú largo con 20 enlaces…</header>
  <main id="contenido" tabindex="-1">…</main>
</body>

<!-- CSS mínimo del truco -->
.skip-link { position:absolute; top:-100%; left:1rem; }
.skip-link:focus { top:1rem; }
⚠️ El contraste mínimo WCAG AA es 4.5:1 para texto normal y 3:1 para textos grandes. Gris claro sobre blanco suele suspender: compruébalo siempre con una herramienta antes de publicar.
Lección 2 · Teoría

SEO on-page: hazte entender por Google

El SEO on-page es todo lo que puedes controlar dentro de tu HTML para que los buscadores entiendan, posicionen y presenten bien tus páginas. Empieza por el <head>: es la ficha técnica que lee Google antes que nadie.

✅ <title> único por página, 50–60 caracteres, palabra clave al inicio.
✅ <meta name="description"> única, 140–160 caracteres, con gancho comercial.
✅ Un solo <h1> que resuma el tema; jerarquía h2/h3 sin saltos.
❌ Títulos duplicados, vacíos o rellenos de palabras clave sin sentido.

2.1 · El head de un profesional

head-seo.html
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Curso de HTML desde cero | Técnico Web</title>
<meta name="description"
       content="Aprende HTML de cero a nivel profesional con ejercicios guiados,
                exámenes y proyectos reales. Gratis y en español.">
<link rel="canonical" href="https://tecnicoweb.es/curso-html">

<!-- Open Graph: cómo se ve al compartir en redes -->
<meta property="og:title" content="Curso de HTML desde cero">
<meta property="og:description" content="Ejercicios guiados y examen final.">
<meta property="og:image" content="https://tecnicoweb.es/img/portada.png">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">

Tres piezas clave del ejemplo: canonical declara la URL oficial cuando hay duplicados (con/sin barra, con parámetros…); Open Graph controla título, descripción e imagen cuando alguien comparte tu enlace en redes; y twitter:card adapta esa vista a X/Twitter.

2.2 · Datos estructurados (JSON-LD)

Además de leer, Google quiere clasificar: «esto es un curso», «esta es su valoración», «este es el precio». Eso se declara con vocabulario schema.org en formato JSON-LD (el que recomienda Google), dentro de una etiqueta <script type="application/ld+json">:

datos-estructurados.html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Course",
  "name": "Curso de HTML desde cero",
  "description": "De principiante a nivel profesional en 4 bloques.",
  "provider": {
    "@type": "Organization",
    "name": "Técnico Web",
    "url": "https://tecnicoweb.es"
  }
}
</script>
💡 Valida tu JSON-LD en la herramienta «Prueba de resultados Enriquecidos» de Google. Un error de sintaxis JSON (una coma de más) invalida TODO el bloque.

Y recuerda: el mejor SEO sigue siendo contenido único y de calidad con URLs limpias (/curso-html/bloque-3 gana a /p?id=88&ref=x), imágenes con alt descriptivo y una página rápida.

Lección 3 · Teoría

HTML5 avanzado: componentes que parecen mágicos

3.1 · <datalist>: autocompletado nativo

Un input de texto con sugerencias, sin una línea de JavaScript. El input referencia la lista mediante list, y cada <option> es una sugerencia (el usuario puede escribir algo distinto):

datalist.html
<label for="ciudad">Ciudad:</label>
<input type="text" id="ciudad" list="ciudades-es">
<datalist id="ciudades-es">
  <option value="Madrid">
  <option value="Barcelona">
  <option value="Sevilla">
  <option value="Bilbao">
</datalist>
Demo en vivo — escribe «se», «bi» o «ma»…

3.2 · <details> + <summary>: acordeones nativos

Contenido desplegable sin JavaScript y accesible de serie. El atributo booleano open lo deja expandido por defecto. De hecho, los temarios y las soluciones de esta web funcionan exactamente así.

faq.html
<details>
  <summary>¿El curso es realmente gratuito?</summary>
  <p>Sí: todos los bloques, ejercicios y exámenes son gratis.</p>
</details>
<details open>
  <summary>¿Necesito saber programar?</summary>
  <p>No. Empezamos desde cero absoluto.</p>
</details>

3.3 · SVG inline: gráficos que escalan solos

Un SVG escrito directamente en el HTML (inline) se pinta como parte de la página: escala sin pixelarse, pesa poco y puedes colorearlo con CSS usando currentColor. Para que sea accesible: role="img" + <title>.

logo.svg inline
<svg viewBox="0 0 24 24" width="48" height="48"
     role="img" aria-labelledby="titLogo">
  <title id="titLogo">Logo de la tienda</title>
  <path fill="currentColor"
        d="M12 2 2 7v10l10 5 10-5V7L12 2z"></path>
</svg>
resultado en vivo

3.4 · <canvas>: lienzo dibujado con JavaScript

<canvas> crea un rectángulo en blanco que se dibuja mediante JavaScript (juegos, gráficas, edición de imagen). Lo importante en HTML: su contenido interno actúa como texto alternativo para navegadores antiguos y lectores de pantalla.

canvas.html
<canvas id="grafica" width="400" height="200">
  Tu navegador no soporta canvas: aquí va la tabla de datos.
</canvas>

<script>
  // Esto es JavaScript (llegará en su propio curso 😉)
  const lienzo = document.getElementById('grafica').getContext('2d');
  lienzo.fillStyle = '#7c3aed';
  lienzo.fillRect(20, 40, 80, 120);   // barra morada
  lienzo.fillStyle = '#e6c229';
  lienzo.beginPath();
  lienzo.arc(200, 100, 45, 0, Math.PI * 2);  // círculo dorado
  lienzo.fill();
</script>
Otras joyas de HTML5 que debes conocer
ElementoPara qué sirveDetalle clave
<progress>Barra de progreso de una tareavalue="70" max="100"
<meter>Medición dentro de un rango conocidoDisco usado, batería, nota
<output>Resultado de un cálculoCompañero natural de formularios
<picture>Imágenes responsive por tipo/tamañoVarios <source> + un <img>
<dialog>Ventana modal nativaSe abre con showModal() vía JS
Lección 4 · Teoría

Calidad medible: validación W3C y auditoría Lighthouse

Los profesionales no dicen «creo que mi página está bien»: lo demuestran con herramientas. Las dos imprescindibles son gratuitas y oficiales.

4.1 · El validador W3C

El validador Nu del W3C comprueba tu HTML contra el estándar. Flujo de trabajo:

  1. Enviar: pega la URL, sube el archivo o copia el código en validator.w3.org/nu.
  2. Leer errores primero: un solo error inicial (una etiqueta sin cerrar) suele arrastrar diez avisos posteriores.
  3. Corregir arriba abajo: arregla el primer error y revalida; verás cómo desaparecen en cascada.
  4. Objetivo: «Document checking completed. No errors or warnings to show.» 🟢
❌ Texto suelto dentro de <ul> o <table> (solo admiten sus hijos válidos).
❌ <div> dentro de <p>, o un <h1> después de cerrar el contenedor de su sección.
❌ Atributos duplicados (id repetidos) o valores sin comillas.
✅ Los <details> de estas páginas pasan la validación: cópialos como plantilla.

4.2 · Lighthouse: tu inspector de calidad

Lighthouse viene integrado en Chrome (F12 → pestaña Lighthouse) y audita tu página devolviendo notas de 0 a 100 en cuatro categorías:

Categorías de Lighthouse y qué miden
CategoríaQué analizaMeta profesional
PerformancePeso, imágenes modernas, carga≥ 90
AccessibilityContraste, alts, labels, ARIA, idioma≥ 95
Best practicesHTTPS, consola limpia, errores JS≥ 95
SEOTitle, meta description, links rastreables100
💡 Audita siempre en modo mobile: es el caso más exigente y el que prioriza Google. Y tras cada corrección, vuelve a ejecutar la auditoría: la mejora debe verse en los números, no solo en tu opinión.

Con esto cierras el ciclo profesional: construyes accesible → optimizas para buscadores → usas HTML5 avanzado → y demuestras la calidad con evidencias. Es exactamente lo que auditará el examen de este bloque.

Práctica guiada

✍️ Los 10 ejercicios del bloque

Del 1 al 9 practicas las lecciones; el 10 es el reto del bloque: una auditoría real de principio a fin.

1 Rescatar el botón hamburguesa

Este menú móvil funciona con ratón pero es invisible para teclado y lectores de pantalla. Reconviértelo en algo accesible conservando el mismo aspecto previsto (la clase se queda igual):

<div class="burger" onclick="toggleNav()">☰</div>
Ver solución
<button class="burger"
        type="button"
        aria-label="Abrir menú de navegación"
        aria-expanded="false"
        aria-controls="navPrincipal">
  ☰
</button>

<!-- Al abrir/cerrar desde JS se alternaría también: -->
<!-- aria-expanded="true|false" y el aria-label Abrir/Cerrar -->

2 Galería con alts que enseñan

Escribe una galería <figure> de tres fotos de un viaje: dos informativas (describen la foto concreta) y una puramente decorativa (línea divisoria gráfica). Usa <figcaption> en la primera.

Ver solución
<figure>
  <img src="fotos/playa.jpg"
       alt="Atardecer en la playa de Bolonia con el dunar al fondo">
  <figcaption>Bolonia, agosto de 2026</figcaption>
</figure>

<figure>
  <img src="fotos/mercado.jpg"
       alt="Puesto de frutas del mercado central con mangos y papayas">
</figure>

<!-- Decorativa: el lector de pantalla debe ignorarla -->
<img src="fotos/separador-olas.svg" alt="">

3 Plantilla con skip link

Partiendo de una página con header + main + footer, añade el enlace «Saltar al contenido» con su CSS mínimo y haz que el destino principal sea enfocable.

Ver solución
<body>
  <a class="skip-link" href="#principal">Saltar al contenido</a>
  <header>…</header>
  <main id="principal" tabindex="-1">
    <h1>Bienvenida a mi portfolio</h1>
  </main>
  <footer>…</footer>
</body>

4 Formulario que habla claro

Campo de «Contraseña» con pista de ayuda («Mínimo 8 caracteres») vinculada mediante aria-describedby, agrupado en un <fieldset> con <legend> junto a un campo de email con su label.

Ver solución
<fieldset>
  <legend>Credenciales de acceso</legend>

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

  <label for="pass">Contraseña</label>
  <input type="password" id="pass"
         aria-describedby="ayudaPass" required>
  <small id="ayudaPass">Mínimo 8 caracteres, con un número.</small>
</fieldset>

5 Head completo: Clínica Dental Sonrisa

Escribe el <head> completo (SEO + Open Graph) para la home de «Clínica Dental Sonrisa» en Valencia: título con localismo, description comercial, canonical, imagen de portada sonrisa-portada.png y tarjeta Twitter grande.

Ver solución
<title>Clínica Dental Sonrisa en Valencia | Dentistas de confianza</title>
<meta name="description"
      content="Ortodoncia, implantes y limpiezas en Valencia. Primera consulta
               gratis con los dentistas de Clínica Dental Sonrisa.">
<link rel="canonical" href="https://dentalsorrisa.es/">
<meta property="og:title" content="Clínica Dental Sonrisa · Valencia">
<meta property="og:description" content="Primera consulta gratis.">
<meta property="og:image" content="https://dentalsorrisa.es/sonrisa-portada.png">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">

6 Datos estructurados de receta

Crea el JSON-LD @type: «Recipe» para la «Tortilla de patatas de la abuela», con autor, 35 minutos de preparación y rendición para 4 personas.

Ver solución
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Recipe",
  "name": "Tortilla de patatas de la abuela",
  "author": { "@type": "Person", "name": "Rosa García" },
  "totalTime": "PT35M",
  "recipeYield": "4 raciones"
}
</script>

<!-- PT35M = 35 minutos en formato ISO 8601 -->

7 Encuesta con datalist, meter y progress

Página de encuesta tecnológica: ciudad con <datalist>, satisfacción mostrada con <meter> (7 de 10) y progreso de participación con <progress> (820 de 1000 respuestas).

Ver solución
<label for="ciudadEncuesta">Tu ciudad</label>
<input type="text" id="ciudadEncuesta" list="listaCiudades">
<datalist id="listaCiudades">
  <option value="Madrid">
  <option value="Barcelona">
  <option value="Sevilla">
</datalist>

<p><label>Satisfacción media:
  <meter value="7" min="0" max="10">7 de 10</meter>
</label></p>

<p><label>Participación:
  <progress value="820" max="1000">82 %</progress>
</label></p>

8 Icono SVG accesible y temable

Icono de estrella para un sistema de valoraciones: SVG inline con currentColor, role="img" y <title>, de forma que herede el color del texto donde se inserte.

Ver solución
<svg viewBox="0 0 24 24" width="24" height="24"
     role="img" aria-labelledby="titEstrella">
  <title id="titEstrella">Valoración: 4 de 5 estrellas</title>
  <path fill="currentColor"
        d="M12 2l2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 17.2 5.9 20.6l1.4-6.8L2.2 9.1l6.9-.8z"></path>
</svg>

<!-- En CSS bastará: .valoracion { color: gold } -->

9 Depurando al estándar

Este fragmento da errores en el W3C. Localízalos todos y reescribe la versión válida:

<ul>
  Opciones disponibles:
  <li><a href="#">Descargas</li></a>
  <p><li><a href="#">Soporte</a></li></p>
</ul>
Ver solución

Errores: texto suelto dentro de <ul>; etiquetas cruzadas (</li></a> en orden inverso); y un <p> envolviendo elementos de lista, que no es hijo válido de <ul>.

<p>Opciones disponibles:</p>
<ul>
  <li><a href="#">Descargas</a></li>
  <li><a href="#">Soporte</a></li>
</ul>

10 🔥 Reto: auditoría real de principio a fin

Elige una página pública tuya (o genera una con errores a propósito). Ejecuta el validador W3C y una auditoría Lighthouse mobile, anota los fallos principales, corrígelos y documenta el antes/después con capturas de las notas. Este ejercicio alimenta directamente la Parte B del examen.

Ver proceso modelo
  1. Línea base: captura las notas iniciales de Lighthouse (Performance, Accessibility, Best Practices, SEO) y guarda el informe.
  2. W3C primero: corrige errores de estructura; muchos avisos de accesibilidad desaparecen solos después.
  3. Accesibilidad: alts, labels, contraste y skip link según la Lección 1.
  4. SEO: title, description, canonical y Open Graph según la Lección 2.
  5. Re-auditoría: repite Lighthouse y compara. Documenta cada cambio y su impacto.
Evaluación del bloque

📝 Examen del Bloque 3

Test teórico autoevaluable (mínimo 70 %) + reto práctico de auditoría con checklist de autocorrección.

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

1 · ¿Cuál es la primera regla de ARIA?
2 · ¿Qué hace tabindex="0"?
3 · Un botón que despliega un menú debe informar de su estado con…
4 · Una imagen puramente decorativa debe llevar…
5 · role="alert" se usa para…
6 · Longitud recomendada del <title>
7 · ¿Qué controla la vista previa al compartir tu enlace en redes?
8 · Formato que Google recomienda para datos estructurados
9 · rel="canonical" sirve para…
10 · Para dibujar dentro de <canvas> necesitas…
11 · En SVG inline, el valor que hereda el color del texto es…
12 · ¿Cuál NO es una categoría de Lighthouse?

Parte B · Reto práctico: tu primera auditoría

Completa el ejercicio 10 sobre tu propia página sin atajos. Mínimo para aprobar: 7 de 8 casillas. El laboratorio comprueba automáticamente los criterios estructurales; las capturas de Lighthouse (casillas 2 y 8) las marcas tú.

Cumplidos: 0 / 8

🎉 ¿Superaste las dos partes? Bloque 3 aprobado. Ya trabajas como un profesional: accesible, optimizado y con calidad demostrable en números. El Bloque 4 corona el curso con tu proyecto final.