Saltar al contenido
proyecto-pagina.html · devschool

Proyecto: tu primera web en HTML

Lección 23 de 23 · 11 min de lectura · Actualizado el

En esta lección
  1. Paso 1: planificar antes de escribir
  2. Paso 2: la portada, index.html
  3. Paso 3: un <head> completo
  4. Paso 4: la página de actividades
  5. Paso 5: la página de contacto
  6. Paso 6: una hoja de estilos mínima
  7. Paso 7: validar y probar
  8. Errores frecuentes
  9. Lista de comprobación final

Ha llegado el momento de juntar todo lo que has aprendido en el curso y construir una web completa de varias páginas, como la que podrías entregar en clase o enseñar en tu portfolio. Vas a hacer la web de Club Vertical, un club de escalada ficticio: una portada, una página de actividades y una de contacto con formulario. Verás cómo organizar las carpetas, cómo repetir la navegación en todas las páginas, cómo escribir un <head> completo para buscadores y cómo validar el resultado.

Aquí solo usaremos HTML y una hoja de estilos mínima. El diseño lo aprenderás en el curso de CSS, en el proyecto de maquetación.

Paso 1: planificar antes de escribir

Antes de abrir el editor, responde a tres preguntas:

  1. ¿Qué páginas necesito? Inicio, Actividades y Contacto.
  2. ¿Qué contenido va en cada una? Haz una lista a mano o en papel.
  3. ¿Qué se repite en todas? La cabecera con el menú y el pie de página.

Después, crea la estructura de carpetas:

club-vertical/
├── index.html
├── actividades.html
├── contacto.html
├── gracias.html
├── css/
│   └── estilos.css
└── img/
    ├── favicon.svg
    ├── portada.jpg
    └── logo.svg

Algunas normas para los nombres de archivo:

  • Todo en minúsculas, sin espacios, sin tildes ni eñes: actividades.html, no Actividades Club.html.
  • Separa palabras con guiones: curso-iniciacion.html.
  • La portada se llama siempre index.html: es el archivo que abre el servidor cuando alguien entra en la carpeta.

Como todas las páginas están en la misma carpeta, los enlaces entre ellas son simples: href="contacto.html". Si tienes dudas, repasa rutas de archivos.

Paso 2: la portada, index.html

Empieza por la página principal. Este es el código completo:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Club Vertical · Club de escalada en Burgos</title>
  <meta name="description" content="Club de escalada en Burgos con rocódromo propio, cursos de iniciación y salidas a roca cada mes. Hazte socio y escala con nosotros.">
  <link rel="canonical" href="https://clubvertical.example/">
  <link rel="icon" href="img/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="css/estilos.css">
  <meta property="og:title" content="Club Vertical · Escalada en Burgos">
  <meta property="og:description" content="Rocódromo, cursos y salidas a roca.">
  <meta property="og:image" content="https://clubvertical.example/img/portada.jpg">
  <meta property="og:type" content="website">
</head>
<body>
  <a class="saltar" href="#contenido">Saltar al contenido</a>

  <header>
    <a href="index.html"><img src="img/logo.svg" alt="Club Vertical, ir al inicio" width="160" height="40"></a>
    <nav aria-label="Principal">
      <ul>
        <li><a href="index.html" aria-current="page">Inicio</a></li>
        <li><a href="actividades.html">Actividades</a></li>
        <li><a href="contacto.html">Contacto</a></li>
      </ul>
    </nav>
  </header>

  <main id="contenido">
    <section>
      <h1>Escala con nosotros en Burgos</h1>
      <p>Somos un club sin ánimo de lucro con rocódromo propio y salidas a roca cada mes.</p>
      <img src="img/portada.jpg" alt="Una socia escala una pared de roca caliza al atardecer" width="1200" height="600">
      <p><a href="actividades.html#iniciacion">Ver cursos de iniciación</a></p>
    </section>

    <section aria-labelledby="t-proximas">
      <h2 id="t-proximas">Próximas salidas</h2>
      <article>
        <h3>Escalada en La Pedriza</h3>
        <p><time datetime="2026-10-18">18 de octubre</time> · Nivel medio</p>
      </article>
      <article>
        <h3>Bautismo de roca en Ojo Guareña</h3>
        <p><time datetime="2026-11-08">8 de noviembre</time> · Iniciación</p>
      </article>
    </section>

    <section aria-labelledby="t-cifras">
      <h2 id="t-cifras">El club en cifras</h2>
      <ul>
        <li>210 socios y socias</li>
        <li>120 vías en el rocódromo</li>
        <li>12 salidas al año</li>
      </ul>
    </section>
  </main>

  <footer>
    <address>
      Calle del Pino 4, 09001 Burgos ·
      <a href="mailto:hola@clubvertical.example">hola@clubvertical.example</a> ·
      <a href="tel:+34947000000">947 000 000</a>
    </address>
    <p>© 2026 Club Vertical</p>
  </footer>
</body>
</html>

Fíjate en las decisiones importantes:

  • Un solo <h1> con el tema de la página, y <h2> para cada sección.
  • Landmarks: <header>, <nav>, <main> y <footer>. Así un lector de pantalla puede saltar al contenido.
  • aria-current="page" marca en el menú la página en la que estás.
  • Cada salida es un <article>, porque tiene sentido por sí sola, y la fecha va en <time> con un formato que entienden las máquinas.
  • Las imágenes llevan alt, width y height.

Paso 3: un <head> completo

El <head> no se ve, pero decide cómo aparece tu web en Google y en las redes. Repasa cada línea:

LíneaPara qué sirve
charset="utf-8"Tildes y eñes correctas
viewportQue se vea bien en el móvil
<title>Pestaña del navegador y título en Google. Distinto en cada página
descriptionEl resumen que suele mostrar Google. Unos 150 caracteres, distinto en cada página
canonicalLa dirección oficial de la página, para evitar duplicados
iconEl icono de la pestaña (favicon)
stylesheetEnlaza el CSS
og:*Título, texto e imagen al compartir en redes

Tienes cada etiqueta explicada en la cabecera de un documento.

Paso 4: la página de actividades

Copia index.html, renómbralo a actividades.html y cambia lo que no se repite: el <title>, la description, el canonical, el aria-current del menú y el contenido de <main>. La cabecera y el pie quedan igual. Para no alargar, en esta página y las siguientes el <head> omite las etiquetas og:; añádelas si quieres que se compartan bien en redes. Así queda el archivo completo:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Actividades y cursos · Club Vertical</title>
  <meta name="description" content="Cursos de iniciación y perfeccionamiento, rocódromo libre y salidas a roca. Consulta horarios y precios de las actividades del club.">
  <link rel="canonical" href="https://clubvertical.example/actividades.html">
  <link rel="icon" href="img/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="css/estilos.css">
</head>
<body>
  <a class="saltar" href="#contenido">Saltar al contenido</a>

  <header>
    <a href="index.html"><img src="img/logo.svg" alt="Club Vertical, ir al inicio" width="160" height="40"></a>
    <nav aria-label="Principal">
      <ul>
        <li><a href="index.html">Inicio</a></li>
        <li><a href="actividades.html" aria-current="page">Actividades</a></li>
        <li><a href="contacto.html">Contacto</a></li>
      </ul>
    </nav>
  </header>

  <main id="contenido">
    <h1>Actividades</h1>

    <section id="iniciacion">
      <h2>Curso de iniciación</h2>
      <p>Ocho sesiones para aprender a asegurar, nudos básicos y técnica de pies.</p>
      <details>
        <summary>¿Qué incluye?</summary>
        <ul>
          <li>Préstamo de arnés y pies de gato</li>
          <li>Seguro de actividad</li>
        </ul>
      </details>
    </section>

    <section>
      <h2>Horarios y precios</h2>
      <table>
        <caption>Tarifas de la temporada 2026-2027</caption>
        <thead>
          <tr><th scope="col">Actividad</th><th scope="col">Horario</th><th scope="col">Precio</th></tr>
        </thead>
        <tbody>
          <tr><th scope="row">Iniciación</th><td>Martes y jueves, 18:00</td><td>90 €</td></tr>
          <tr><th scope="row">Perfeccionamiento</th><td>Lunes y miércoles, 19:30</td><td>110 €</td></tr>
          <tr><th scope="row">Rocódromo libre</th><td>Todos los días, 10:00-22:00</td><td>35 €/mes</td></tr>
        </tbody>
      </table>
    </section>
  </main>

  <footer>
    <address>
      Calle del Pino 4, 09001 Burgos ·
      <a href="mailto:hola@clubvertical.example">hola@clubvertical.example</a> ·
      <a href="tel:+34947000000">947 000 000</a>
    </address>
    <p>© 2026 Club Vertical</p>
  </footer>
</body>
</html>

El enlace de la portada actividades.html#iniciacion lleva directamente a la sección con id="iniciacion".

Consejo: repetir la cabecera en cada archivo es incómodo: si añades una página, tienes que tocar el menú en todas. En proyectos reales esto se resuelve con plantillas (Astro, PHP, un generador de sitios…). Para una web de tres páginas, copiar y pegar es aceptable.

Paso 5: la página de contacto

contacto.html mantiene la misma cabecera y pie. Este es el archivo completo:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Contacto · Club Vertical</title>
  <meta name="description" content="Escribe al Club Vertical para resolver tus dudas sobre cursos de escalada, cuotas de socio o salidas a roca. Te respondemos en 48 horas.">
  <link rel="canonical" href="https://clubvertical.example/contacto.html">
  <link rel="icon" href="img/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="css/estilos.css">
</head>
<body>
  <a class="saltar" href="#contenido">Saltar al contenido</a>

  <header>
    <a href="index.html"><img src="img/logo.svg" alt="Club Vertical, ir al inicio" width="160" height="40"></a>
    <nav aria-label="Principal">
      <ul>
        <li><a href="index.html">Inicio</a></li>
        <li><a href="actividades.html">Actividades</a></li>
        <li><a href="contacto.html" aria-current="page">Contacto</a></li>
      </ul>
    </nav>
  </header>

  <main id="contenido">
    <h1>Contacto</h1>
    <p>¿Tienes dudas sobre los cursos? Escríbenos y te respondemos en 48 horas.</p>

    <form action="gracias.html" method="get">
      <p>
        <label for="nombre">Nombre</label>
        <input type="text" id="nombre" name="nombre" autocomplete="name" required>
      </p>
      <p>
        <label for="email">Correo electrónico</label>
        <input type="email" id="email" name="email" autocomplete="email" required>
      </p>

      <fieldset>
        <legend>¿Qué te interesa?</legend>
        <label><input type="radio" name="interes" value="iniciacion" checked> Curso de iniciación</label>
        <label><input type="radio" name="interes" value="socio"> Hacerme socio</label>
        <label><input type="radio" name="interes" value="otro"> Otra cosa</label>
      </fieldset>

      <p>
        <label for="mensaje">Mensaje</label>
        <textarea id="mensaje" name="mensaje" rows="5" minlength="10" required></textarea>
      </p>
      <p>
        <label><input type="checkbox" name="privacidad" required> Acepto la política de privacidad</label>
      </p>
      <button type="submit">Enviar mensaje</button>
    </form>
  </main>

  <footer>
    <address>
      Calle del Pino 4, 09001 Burgos ·
      <a href="mailto:hola@clubvertical.example">hola@clubvertical.example</a> ·
      <a href="tel:+34947000000">947 000 000</a>
    </address>
    <p>© 2026 Club Vertical</p>
  </footer>
</body>
</html>

Cada campo tiene su <label>, un name y la validación nativa (required, type="email", minlength). Los botones de opción van agrupados en un <fieldset> con su <legend>.

Una web solo con HTML no puede recibir mensajes: alguien tiene que procesarlos en un servidor. Aquí el formulario simplemente lleva a gracias.html. Como no aporta nada en los buscadores, lleva noindex para que Google no la muestre:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Mensaje enviado · Club Vertical</title>
  <meta name="robots" content="noindex">
  <link rel="icon" href="img/favicon.svg" type="image/svg+xml">
  <link rel="stylesheet" href="css/estilos.css">
</head>
<body>
  <a class="saltar" href="#contenido">Saltar al contenido</a>

  <header>
    <a href="index.html"><img src="img/logo.svg" alt="Club Vertical, ir al inicio" width="160" height="40"></a>
    <nav aria-label="Principal">
      <ul>
        <li><a href="index.html">Inicio</a></li>
        <li><a href="actividades.html">Actividades</a></li>
        <li><a href="contacto.html">Contacto</a></li>
      </ul>
    </nav>
  </header>

  <main id="contenido">
    <h1>Gracias por escribirnos</h1>
    <p>Hemos recibido tu mensaje. Te responderemos en 48 horas.</p>
    <p><a href="index.html">Volver a la página de inicio</a></p>
  </main>

  <footer>
    <address>
      Calle del Pino 4, 09001 Burgos ·
      <a href="mailto:hola@clubvertical.example">hola@clubvertical.example</a> ·
      <a href="tel:+34947000000">947 000 000</a>
    </address>
    <p>© 2026 Club Vertical</p>
  </footer>
</body>
</html>

Para que el formulario funcione de verdad necesitarás un servicio externo de formularios o tu propio servidor, como el que aprenderás a hacer con Express.

Paso 6: una hoja de estilos mínima

Las cuatro páginas enlazan css/estilos.css. Con muy poco CSS la web ya es legible y el enlace de salto queda oculto hasta que recibe el foco:

body { font-family: system-ui, sans-serif; line-height: 1.5; margin: 0; color: #1f2937; }
header, main, footer { padding: 16px; max-width: 60rem; margin: 0 auto; }
nav ul { list-style: none; padding: 0; display: flex; gap: 16px; }
img { max-width: 100%; height: auto; }
.saltar { position: absolute; left: -9999px; }
.saltar:focus { left: 8px; top: 8px; background: #fff; padding: 8px; }

La línea img { max-width: 100%; height: auto; } evita que la foto de portada, de 1200 píxeles, se salga de la pantalla en el móvil.

Paso 7: validar y probar

Con las páginas escritas, toca revisarlas:

  1. Validador del W3C (validator.w3.org): sube cada archivo y corrige todos los errores. Te lo explicamos en buenas prácticas.
  2. Prueba los enlaces. Pulsa todos los del menú en las tres páginas y el de #iniciacion.
  3. Recorre la web con el teclado. Tab desde el principio: debe aparecer el enlace de salto, luego el menú y luego el contenido.
  4. Lighthouse. Analiza la portada con las categorías Accessibility y SEO.
  5. Prueba en el móvil con el modo de dispositivo de las herramientas de desarrollo.

Errores frecuentes

  • Mismo <title> en todas las páginas. Google no sabe cuál mostrar y la persona no distingue las pestañas.
  • Rutas con mayúsculas o espacios. En tu ordenador funciona, en el servidor (que distingue mayúsculas) no.
  • Olvidar cambiar aria-current al copiar la cabecera en otra página.
  • Formularios sin name: los datos no se envían.
  • Maquetar con tablas. La tabla de precios está bien porque son datos; la estructura de la página, no.
  • Dejar la validación para el final. Valida cada página nada más terminarla: los errores son más fáciles de encontrar.

Lista de comprobación final

Antes de dar la web por terminada, repasa esta lista en cada página:

  • <!DOCTYPE html> y <html lang="es">
  • charset, viewport, <title> y description propios de la página
  • Un único <h1> y encabezados sin saltos de nivel
  • <header>, <nav>, <main> y <footer> presentes
  • Menú idéntico en todas las páginas, con aria-current en la actual
  • Todas las imágenes con alt, width y height
  • Enlaces con texto descriptivo y rutas relativas correctas
  • Formularios con <label>, name y validación
  • Tablas con <caption> y <th> con scope
  • Nombres de archivo en minúsculas y sin espacios
  • Cero errores en el validador del W3C
  • Se puede usar entera con el teclado

Si cumples toda la lista, tienes una web sólida, accesible y lista para que le pongas estilo. El siguiente paso es darle diseño en el curso de CSS, empezando por la introducción a CSS.

Pruébalo tú

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

proyecto-pagina.html
Resultado

Pon a prueba lo que has aprendido

[HTML] ¿Qué nombre debe tener el archivo de la página principal de tu web?

[HTML] Copias la cabecera de index.html en contacto.html. ¿Qué debes cambiar en el menú?
<li><a href="index.html" aria-current="page">Inicio</a></li>
<li><a href="actividades.html">Actividades</a></li>
<li><a href="contacto.html">Contacto</a></li>

[HTML] Tu web tiene tres páginas y las tres usan el mismo <title> y la misma meta description. ¿Qué problema hay?

[HTML] ¿Por qué el formulario de contacto de una web hecha solo con HTML no puede guardar ni enviar los mensajes por sí mismo?

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