John Dalton · Laboratorio de desarrollo web

Así se construye una web que más personas pueden usar

Explora el código del portal, comprueba sus decisiones de accesibilidad y crea tu propio temporizador. Una guía para aprender haciendo en el aula de informática.

01 Estructura con sentido

HTML describe; Hugo une las piezas

HTML es el lenguaje que organiza el contenido. Sus etiquetas indican qué función cumple cada parte. Elegirlas por su significado se llama usar HTML semántico.

Mapa del documento

Selecciona una pieza. Con teclado, llega con Tab y actívala con Intro.

Sin JavaScript puedes seguir los enlaces y leer todas las piezas debajo.

<header>

La presentación del sitio

Reúne la identidad del proyecto y su presentación. En la base de John Dalton se incluye con el parcial header.html.

<header>
  <a href="/">John Dalton</a>
  <p>Aprendemos sobre salud visual</p>
</header>

Un parcial es una plantilla pequeña que se inserta dentro de otra.

<nav>

Los caminos de la página

Agrupa los enlaces de navegación. Si hay varios menús, sus nombres deben permitir distinguirlos.

<nav aria-label="Módulos de la guía">
  <a href="#estructura">HTML y Hugo</a>
  <a href="#taller">Taller de código</a>
</nav>

Los enlaces reales de esta guía usan identificadores: nombres únicos como taller.

<main>

Lo principal de cada página

Contiene el contenido central. El encabezado y el pie compartidos quedan fuera de este elemento.

<a href="#contenido">Saltar al contenido</a>
<main id="contenido">
  <h1>Guía para estudiantes</h1>
</main>

La base de Hugo ya añade main. Los módulos no deben repetirlo.

<section>

Un grupo con un tema

Separa una parte temática, como los módulos de esta guía. Un título ayuda a entender su propósito.

<section id="taller" aria-labelledby="taller-titulo">
  <h2 id="taller-titulo">Taller de código</h2>
  <p>Probemos un temporizador.</p>
</section>

aria-labelledby toma el nombre de la sección del título indicado.

<article>

Una pieza que tiene sentido por sí sola

Sirve para contenido independiente. Una ficha de un programa de apoyo puede tener su propio título, descripción y enlace.

<article>
  <h3>Temporizador de práctica</h3>
  <p>Un ejercicio de diez segundos.</p>
</article>

No toda caja visual necesita article: elige según el contenido.

<aside>

Una explicación complementaria

Guarda contenido relacionado que se puede leer por separado, como una recomendación para desarrollar el ejercicio.

<aside aria-labelledby="nota-titulo">
  <h2 id="nota-titulo">Antes de empezar</h2>
  <p>Guarda una copia de tus archivos.</p>
</aside>

Su apariencia depende del CSS; no tiene que estar a la derecha.

Del archivo al navegador

Hugo crea HTML a partir de contenido y plantillas. El archivo content/guia-estudiantes.md elige student-guide como plantilla. La base aporta el documento común; esta plantilla ordena sus parciales:

{{ define "main" }}
  <div class="student-guide">
    {{ partial "guide/intro.html" . }}
    {{ partial "guide/structure.html" . }}
    {{ partial "guide/accessibility.html" . }}
    {{ partial "guide/workshop.html" . }}
    {{ partial "guide/responsibility.html" . }}
    {{ partial "guide/challenges.html" . }}
  </div>
{{ end }}

El navegador recibe HTML, CSS y JavaScript; no necesita ejecutar Hugo. CSS define la presentación y JavaScript añade las interacciones.

Para ampliar: regiones de una página, W3C (sitio externo) · plantillas parciales, documentación de Hugo (sitio externo).

02 Diseñar y comprobar

La accesibilidad se pone a prueba

Las WCAG son pautas de accesibilidad del contenido web. Ayudan a revisar decisiones concretas. Un color o una herramienta automática no demuestra que todo un sitio sea accesible.

01 · Teclado

Haz todo sin ratón

Recorre los enlaces y botones con Tab y Mayús+Tab. Prueba Intro en enlaces y botones, y Espacio en botones. El foco, que indica dónde estás, debe verse siempre.

Teclado en W3C (externo)
02 · Texto

Amplía y vuelve a leer

Prueba el zoom al 200 %. Comprueba que puedes leer y usar los controles. rem es una unidad relativa al tamaño base del texto; ayuda a ajustar tamaños, pero el resultado hay que probarlo.

Ampliación en W3C (externo)
03 · Mensajes

Informa sin interrumpir

Un lector de pantalla presenta el contenido mediante voz o braille. role="status" permite anunciar un cambio, como el fin de la cuenta, sin mover el foco. No anuncies cada segundo.

Mensajes de estado en W3C (externo)
04 · Imágenes

Describe su propósito

El texto alt depende de la función de la imagen. Describe la información relevante; para una imagen decorativa usa alt="". No hay un mínimo universal de palabras.

Decidir el alt con W3C (externo)

Comparación estática de contraste

El contraste compara la luminosidad del texto y el fondo. Para texto normal, el criterio 1.4.3 pide al menos 4,5:1; para texto grande, 3:1. Estos pares se calcularon con la fórmula de luminancia relativa de WCAG.

Aprender con claridad
12,54:1 · Texto blanco sobre azul #00355f. Supera 4,5:1.
Aprender con claridad
6,43:1 · Texto blanco sobre verde azulado #006972. Supera 4,5:1.
2,32:1 · No alcanza 4,5:1. La muestra dice «Ejemplo difícil de leer»: gris #aaaaaa sobre blanco.

Los valores se muestran con dos decimales; el umbral se compara con el resultado sin redondear. Esta comparación revisa solo esos pares de colores.

Contraste mínimo, cálculo y definición de texto grande en W3C (sitio externo).

03 Aprender haciendo

Tres archivos, un ejemplo completo

Descarga el ZIP, extrae su contenido y abre index.html. No hace falta un servidor ni conexión a internet. Si copias el código, guarda cada bloque con el nombre indicado en la misma carpeta.

¿Qué hace cada archivo?

  1. index.html: estructura, texto y controles.
  2. style.css: colores, espacios y foco visible.
  3. script.js: iniciar, pausar, reanudar y reiniciar.

El mismo script.js controla los temporizadores del portal. Busca cada contenedor data-timer y usa su duración en segundos.

Descargar los tres archivos (.zip)

Abrir el ejemplo autónomo

Para copiar manualmente, selecciona el contenido del bloque y pulsa Ctrl+C (Cmd+C en Mac). Los bloques se pueden recorrer con teclado y desplazar si una línea es larga.

index.html

<!doctype html>
<html lang="es-CO">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Pausa visual · Ejemplo John Dalton</title>
  <link rel="stylesheet" href="style.css">
  <script src="script.js" defer></script>
</head>
<body>
  <a class="skip-link" href="#contenido">Saltar al contenido</a>
  <header>
    <p class="eyebrow">John Dalton · Laboratorio web</p>
    <h1>Un temporizador que puedes modificar</h1>
    <p>Un ejemplo educativo con HTML, CSS y JavaScript, sin bibliotecas ni conexión a internet.</p>
  </header>
  <main id="contenido">
    <section class="timer" data-timer data-duration="10" aria-labelledby="timer-titulo">
      <h2 id="timer-titulo">Prueba de diez segundos</h2>
      <p>Inicia la cuenta, haz una pausa y reanuda cuando quieras. Reiniciar devuelve el tiempo al valor original y detiene la cuenta.</p>
      <p class="time-label">Tiempo restante <span class="time" data-timer-display>00:10</span></p>
      <p class="status" data-timer-status role="status">Activa JavaScript para usar el temporizador.</p>
      <div class="controls" data-timer-controls hidden>
        <button type="button" data-timer-toggle>Iniciar</button>
        <button type="button" class="secondary" data-timer-reset>Reiniciar</button>
      </div>
      <noscript><p>Puedes seguir leyendo y editar el código. La cuenta necesita JavaScript.</p></noscript>
    </section>
    <section aria-labelledby="experimento-titulo">
      <h2 id="experimento-titulo">Haz un experimento</h2>
      <p>Cambia <code>data-duration="10"</code> por <code>data-duration="20"</code>, guarda este archivo y vuelve a abrirlo. El número expresa la duración en segundos.</p>
      <p>El aviso final es un recordatorio de práctica; este ejemplo no evalúa tu visión ni reemplaza una valoración profesional.</p>
    </section>
  </main>
  <footer><p>John Dalton · Ejemplo para aprender programación y accesibilidad.</p></footer>
</body>
</html>

Los atributos data- guardan información que lee el script; hidden oculta los controles hasta que estén listos.

style.css

/* Estilos autónomos: no necesitan los archivos del portal. */
:root {
  font-family: system-ui, sans-serif;
  color: #121c27;
  background: #f7f9ff;
  line-height: 1.6;
}
* { box-sizing: border-box; }
body { max-width: 48rem; margin: 0 auto; padding: 2rem; }
h1, h2 { color: #00355f; line-height: 1.2; }
h1 { font-size: 2.2rem; }
h2 { font-size: 1.5rem; }
section { margin-block: 2rem; }
.eyebrow { color: #006972; font-weight: 700; }
.timer { padding: 2rem; border: 1px solid #d5dce2; border-radius: 1rem; background: #fff; }
.time-label { text-align: center; }
.time { display: block; font-size: 3rem; font-weight: 750; font-variant-numeric: tabular-nums; color: #00355f; }
.status { min-height: 3.2em; text-align: center; }
.controls { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: center; }
button { border: 2px solid #00355f; border-radius: .5rem; padding: .7rem 1.1rem; font: inherit; font-weight: 700; color: #fff; background: #00355f; cursor: pointer; }
button.secondary { background: #fff; color: #00355f; }
button:hover { background: #006972; border-color: #006972; color: #fff; }
a { color: #00355f; }
:focus-visible { outline: 3px solid #006972; outline-offset: 4px; }
code { font-size: .95em; overflow-wrap: anywhere; }
footer { border-top: 1px solid #d5dce2; margin-top: 2rem; }
.skip-link { position: absolute; top: .5rem; left: .5rem; padding: .6rem; background: #fff; transform: translateY(-200%); }
.skip-link:focus { transform: none; }
[hidden] { display: none !important; }

Observa las unidades relativas, el foco visible y los controles que pueden ocupar otra línea.

script.js

(() => {
  'use strict';

  document.querySelectorAll('[data-timer]').forEach(contenedor => {
    const duracion = Number(contenedor.dataset.duration) * 1000;
    const pantalla = contenedor.querySelector('[data-timer-display]');
    const estado = contenedor.querySelector('[data-timer-status]');
    const alternar = contenedor.querySelector('[data-timer-toggle]');
    const reiniciar = contenedor.querySelector('[data-timer-reset]');
    const controles = contenedor.querySelector('[data-timer-controls]');
    if (!Number.isFinite(duracion) || duracion <= 0 || !pantalla || !estado || !alternar || !reiniciar || !controles) return;

    let restante = duracion;
    let fin = 0;
    let intervalo = null;
    let enMarcha = false;
    let terminado = false;

    function mostrarTiempo() {
      const segundos = Math.ceil(restante / 1000);
      pantalla.textContent = `${String(Math.floor(segundos / 60)).padStart(2, '0')}:${String(segundos % 60).padStart(2, '0')}`;
    }

    function detener() {
      clearInterval(intervalo);
      intervalo = null;
      enMarcha = false;
    }

    function actualizar() {
      // La hora de llegada evita sumar el retraso de una pestaña en segundo plano.
      restante = Math.max(0, fin - Date.now());
      mostrarTiempo();
      if (restante === 0) {
        detener();
        terminado = true;
        alternar.textContent = 'Volver a iniciar';
        estado.textContent = 'Cuenta terminada. Puedes hacer una pausa y apartar la mirada de la pantalla.';
      }
    }

    alternar.addEventListener('click', () => {
      if (enMarcha) {
        actualizar();
        if (!enMarcha) return;
        detener();
        alternar.textContent = 'Reanudar';
        estado.textContent = 'Temporizador en pausa.';
        return;
      }

      if (terminado) {
        restante = duracion;
        terminado = false;
      }
      fin = Date.now() + restante;
      enMarcha = true;
      alternar.textContent = 'Pausar';
      estado.textContent = 'Temporizador en marcha.';
      mostrarTiempo();
      intervalo = setInterval(actualizar, 250);
    });

    reiniciar.addEventListener('click', () => {
      detener();
      restante = duracion;
      terminado = false;
      alternar.textContent = 'Iniciar';
      estado.textContent = 'Temporizador reiniciado. Listo para iniciar.';
      mostrarTiempo();
    });

    mostrarTiempo();
    controles.hidden = false;
    estado.textContent = 'Listo para iniciar.';
  });
})();

Date.now() consulta la hora actual. La cuenta compara esa hora con la de llegada: así descuenta el retraso si el navegador actualiza más tarde.

Comprueba el resultado

Inicia, pausa y espera: el tiempo debe quedarse quieto. Reanuda hasta cero y vuelve a iniciar. Prueba Reiniciar durante la cuenta; debe detenerla y mostrar diez segundos. Repite con teclado.

04 Publicar con cuidado

Explica de dónde sale la información y hasta dónde llega

Un portal educativo también enseña a revisar sus fuentes. Una página atractiva no convierte una afirmación en un hecho comprobado.

Consulta la fuente original

Prefiere documentos de la entidad responsable. Anota el enlace y la fecha de revisión. Comprueba que la fuente diga lo mismo que tu resumen y distingue un dato de una opinión o publicidad.

En accesibilidad, compara tus decisiones con las pautas WCAG de W3C (sitio externo).

Reconoce los límites de una prueba

Los ejercicios y las pruebas enlazadas son recursos informativos. No los presentes como diagnóstico, certificado ni sustituto de una valoración profesional.

El temporizador de esta guía demuestra programación: no mide la salud de tus ojos.

Revisa antes de afirmar

Una herramienta automática encuentra algunos problemas. Complementa sus resultados con teclado, ampliación del texto y revisión del contenido.

No publiques sellos de conformidad ni promesas de beneficios sin una evaluación que las respalde. Cómo evaluar accesibilidad, W3C (sitio externo).

05 Tu siguiente paso

Tres retos para practicar

Haz cada cambio en una copia del ejemplo. Primero intenta resolverlo; después abre la solución y comprueba el resultado en el navegador.

CSS · Colores

1. Crea una variante de alto contraste

Añade la clase alto-contraste al body del ejemplo. Usa fondo negro y texto blanco, incluidos la tarjeta y sus títulos. Conserva un foco visible.

Ver una solución

Cambia la etiqueta de apertura a <body class="alto-contraste"> y añade al final de style.css:

body.alto-contraste,
.alto-contraste .timer {
  background: #000;
  color: #fff;
}
.alto-contraste h1,
.alto-contraste h2,
.alto-contraste .eyebrow,
.alto-contraste .time,
.alto-contraste a {
  color: #fff;
}
.alto-contraste :focus-visible {
  outline-color: #ff0;
}
.alto-contraste .skip-link {
  background: #000;
}

Revisa también los botones y el salto al contenido. La combinación negro/blanco tiene contraste 21:1; eso describe ese par, no una evaluación del sitio completo.

HTML · Estructura

2. Añade una ficha con sentido

Dentro de main, después de las secciones, añade una ficha independiente sobre el experimento. Dale un título y explica qué has cambiado sin prometer resultados médicos.

Ver una solución
<article>
  <h2>Mi prueba de veinte segundos</h2>
  <p>Cambié data-duration a 20.
  Probé iniciar, pausar y reiniciar
  usando solo el teclado.</p>
</article>

El título tiene nivel h2 porque depende del título principal h1. La ficha cuenta un experimento completo.

JavaScript · Estados

3. Mejora el aviso final

Cambia el mensaje que aparece al terminar. Conserva role="status", no muevas el foco y evita avisos cada segundo. Comprueba que puedes iniciar otra cuenta.

Ver una solución

En script.js, reemplaza solo el mensaje dentro de if (restante === 0) por:

estado.textContent =
  'La práctica terminó. Puedes iniciar '
  + 'otra cuenta cuando quieras.';

Conserva detener(), terminado = true y el cambio de nombre del botón. Así el temporizador puede volver a empezar.

Ampliación opcional: si exploras un sonido, permite que la persona lo solicite con un botón. No lo reproduzcas automáticamente ni lo uses como único aviso.

Comparte lo que comprobaste

Tu explicación también forma parte del proyecto

Cuenta qué cambiaste, cómo lo probaste y qué queda pendiente. Otra persona debe poder repetir tu prueba.