top of page

De la estructura al diseño: formularios HTML y primeros pasos con CSS

hace 12 horas
13 min de lectura

Una página web necesita una organización que permita comprender su contenido y realizar acciones con facilidad. HTML describe el contenido, la estructura y los controles. CSS define su presentación y disposición visual.


En este artículo construiremos en Visual Studio Code una página para preparar una solicitud de préstamo de equipo de cómputo. Primero organizaremos el documento, después construiremos el formulario y finalmente aplicaremos estilos.


El usuario podrá capturar información, comprobar los campos obligatorios y visualizar un resumen. Es una demostración local: mostrar el resumen no registra un préstamo real. Para conservarlo y comprobar la disponibilidad se necesita un servidor y almacenamiento.


Guía de trabajo


Lee cada explicación y prueba sus fragmentos. La sección «Proyecto terminado» reúne los tres archivos completos: index.html contiene la estructura; estilos.css define la presentación; app.js permite revisar los datos en el navegador. Los ejemplos intermedios son explicativos y no deben pegarse duplicados encima de la versión final.


2.6 Estructura y disposición


Organizar el significado


La estructura establece las relaciones entre las partes del documento. Una solicitud necesita una presentación, instrucciones, controles y un resultado. El orden del código debe ser comprensible antes de agregar estilos: propósito, instrucciones, captura y revisión.


header contiene información introductoria; nav agrupa la navegación principal; main contiene la información principal; section agrupa un tema y normalmente tiene un encabezado; footer contiene información de cierre. div es una agrupación genérica útil cuando no corresponde un significado más específico.


<header>
  <h1>Préstamo de equipos</h1>
  <p>Prepara y revisa tu solicitud.</p>
</header>
<nav aria-label="Navegación principal">
  <a href="#instrucciones">Instrucciones</a>
  <a href="#solicitud">Solicitud</a>
</nav>
<main>
  <section id="instrucciones">
    <h2>Antes de comenzar</h2>
  </section>
  <section id="solicitud">
    <h2>Datos de la solicitud</h2>
  </section>
</main>
<footer>
  <p>Proyecto de práctica de desarrollo web.</p>
</footer>

Los identificadores enlazan cada vínculo con su destino. Las etiquetas semánticas describen funciones; no obligan a colocar una sección en una posición visual particular. Cambiar posiciones mediante CSS tampoco garantiza cambiar el orden de lectura o de navegación con teclado.


Disposición y clases


La disposición es la distribución visual: una o varias columnas, alineaciones y separaciones. Se controla principalmente con CSS. Una clase conecta los elementos HTML con reglas reutilizables.


<main class="disposicion">
  <section class="tarjeta"><h2>Instrucciones</h2></section>
  <section class="tarjeta"><h2>Solicitud</h2></section>
</main>

Escribir class="tarjeta" no dibuja una tarjeta automáticamente. Una regla establece su apariencia:


.tarjeta {
  background-color: white;
  padding: 1.5rem;
  border-radius: 0.75rem;
}

Flujo normal y modelo de caja


Sin reglas especiales de distribución, el navegador coloca los elementos en el flujo normal. Los párrafos y secciones suelen comenzar en otra línea; los enlaces pueden integrarse con el texto. CSS puede modificar ese comportamiento: «bloque» y «en línea» no son características inmutables del nombre de una etiqueta.


Cada caja tiene contenido, padding (espacio interior), border (borde) y margin (espacio exterior). En content-box, width indica el ancho del contenido. Una caja con width:320px, padding:16px y border:2px ocupa 356 píxeles sin contar márgenes: 320 + 16 + 16 + 2 + 2.


* {
  box-sizing: border-box;
}

Con border-box, el ancho declarado incluye contenido, relleno y borde. El margen queda fuera. No conviene utilizar saltos de línea repetidos o espacios para distribuir componentes.


Distribución adaptable


El ejercicio tendrá una columna en espacios estrechos y dos cuando exista suficiente ancho:


.disposicion {
  display: grid;
  gap: 1.5rem;
}
@media (min-width: 52rem) {
  .disposicion {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  }
}

Grid distribuye las secciones; gap las separa. Las fracciones reparten el espacio aproximadamente en una proporción de uno a dos. minmax(0, ...) permite reducir las columnas sin imponer el mínimo automático del contenido. @media aplica reglas cuando se cumple una condición.


2.7 Formularios


Un formulario reúne controles para capturar datos y realizar operaciones. Puede servir para buscar, registrarse, iniciar sesión o preparar solicitudes. El elemento form organiza los controles.


Destino y método


<form action="/solicitudes" method="post">
  <!-- Controles -->
</form>

El atributo action indica el destino; method indica el método de envío. La ruta /solicitudes debe existir en un servidor: escribirla no crea el servicio. GET se utiliza frecuentemente para consultas y agrega los datos a la URL. POST los envía en el cuerpo de una solicitud. POST no cifra por sí mismo: la protección en tránsito requiere HTTPS.


En nuestro programa, JavaScript interrumpe el envío y muestra una revisión local. El botón se habilita cuando ese comportamiento está preparado.


Etiquetas e identificación


<label for="nombre">Nombre completo</label>
<input id="nombre" name="nombre" type="text">

for coincide con id y asocia la etiqueta al control. Al seleccionar la etiqueta se facilita activar el campo. Esta asociación también ayuda a las tecnologías de asistencia. Un placeholder no sustituye la etiqueta.


id identifica el elemento en el documento; name identifica el dato al recopilar el formulario; value representa un valor según el control. Un campo sin name puede verse en pantalla, pero normalmente no se incluye en la recopilación ordinaria.


Nombre y correo


<input id="nombre" name="nombre" type="text"
  autocomplete="name" minlength="2" maxlength="80" required>
<input id="correo" name="correo" type="email"
  autocomplete="email" required>

required exige completar el campo. minlength y maxlength establecen longitudes. autocomplete ofrece una pista para autocompletar. type="email" comprueba un formato básico y puede ofrecer un teclado apropiado. No verifica que la dirección exista ni que pertenezca al usuario. Las restricciones del nombre tampoco comprueban identidad.


Selección, fecha y duración


<label for="equipo">Equipo</label>
<select id="equipo" name="equipo" required>
  <option value="">Selecciona una opción</option>
  <option value="Laptop 01">Laptop 01</option>
  <option value="Laptop 02">Laptop 02</option>
  <option value="Proyector 01">Proyector 01</option>
</select>
<label for="fecha">Fecha de uso</label>
<input id="fecha" name="fecha" type="date" required>
<label for="horas">Horas: entre 1 y 8</label>
<input id="horas" name="horas" type="number"
  min="1" max="8" step="1" value="1" required>

La opción inicial tiene valor vacío, por lo que no satisface required. El texto mostrado y value pueden ser distintos: una opción podría mostrar «Laptop 12» y enviar «12». El control date almacena una fecha válida en formato aaaa-mm-dd aunque su apariencia varíe. min, max y step limitan la duración a enteros entre uno y ocho. Son reglas del ejercicio, no una política universal.


Modalidad: radio, fieldset y legend


<fieldset>
  <legend>Modalidad de trabajo (obligatoria)</legend>
  <div class="opcion">
    <input id="individual" name="modalidad" type="radio"
      value="Individual" required>
    <label for="individual">Individual</label>
  </div>
  <div class="opcion">
    <input id="equipo-trabajo" name="modalidad" type="radio"
      value="En equipo" required>
    <label for="equipo-trabajo">En equipo</label>
  </div>
</fieldset>

Los radios comparten name para formar un grupo de selección única. Sus value distinguen las alternativas. fieldset agrupa los controles y legend describe el grupo.


Observaciones y confirmación


<label for="observaciones">Observaciones</label>
<textarea id="observaciones" name="observaciones"
  rows="4" maxlength="500"></textarea>
<div class="opcion">
  <input id="condiciones" name="condiciones" type="checkbox"
    value="aceptadas" required>
  <label for="condiciones">He leído las condiciones.</label>
</div>

textarea permite varias líneas y necesita cierre. Su contenido inicial se coloca entre las etiquetas; no mediante value. Una casilla representa una selección independiente: marcada aporta su valor; sin marcar normalmente no aparece entre los datos. required exige marcarla antes de revisar.


Botones y validación


submit intenta enviar el formulario y activa la validación correspondiente. reset restablece los valores iniciales. button no envía y requiere definir su acción cuando corresponda. Es conveniente declarar el tipo explícitamente.


<button type="submit">Revisar solicitud</button>
<button type="reset">Limpiar</button>
<button type="button">Otra acción</button>

Las restricciones deben explicarse junto al campo. El navegador puede bloquear datos vacíos, formatos incorrectos o números fuera del intervalo. Sin embargo, la validación del cliente puede omitirse: un sistema real debe validar nuevamente en el servidor y comprobar sus reglas de negocio.


disabled deshabilita el control y lo excluye de la recopilación ordinaria. readonly impide editar en los controles que lo admiten, pero permite incluir el dato. Son distintos. disabled="false" no habilita: el atributo booleano sigue presente.


3.1 Introducción a CSS


CSS significa Cascading Style Sheets: Hojas de Estilo en Cascada. Define colores, fuentes, separación, dimensiones, bordes, distribución y estados. HTML conserva el significado mientras CSS modifica la presentación.


<h1>Préstamo de equipos</h1>

h1 {
  color: #173b72;
}

CSS puede escribirse en línea con style, dentro de un elemento style o en una hoja externa. Usaremos estilos.css para separar responsabilidades y reutilizar reglas.


<link rel="stylesheet" href="estilos.css">
<script src="app.js" defer></script>

Crea la carpeta solicitud-equipos y ábrela en Visual Studio Code. Dentro crea index.html, estilos.css y app.js, todos al mismo nivel. defer permite que el script externo se ejecute después de analizar el documento. Visual Studio Code incorpora soporte para HTML y CSS, sugerencias y ayudas de edición.


3.2 Sintaxis


Anatomía de una regla


.tarjeta {
  background-color: white;
  padding: 1.5rem;
  border-radius: 0.75rem;
}

.tarjeta es el selector. Las llaves delimitan el bloque. background-color es una propiedad y white su valor. El par propiedad: valor; es una declaración. Los dos puntos separan propiedad y valor; el punto y coma separa declaraciones.


Es incorrecto escribir color = blue. Debe escribirse color: blue;. Una declaración inválida puede descartarse mientras otras funcionan, de modo que un resultado parcial no demuestra que todo el código sea correcto. También deben cerrarse las llaves.


/* Comentario de CSS */
body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: #172033;
  background-color: #eef2f7;
}

Los comentarios CSS se delimitan con /* y */. Los comentarios HTML no son el mecanismo apropiado dentro de la hoja.


Propiedades y valores


color controla el texto; background-color, el fondo; font-family, la fuente; font-size, su tamaño; font-weight, su peso; line-height, la altura de línea. padding y margin controlan espacio interior y exterior. border y border-radius definen bordes y redondeo; max-width limita ancho; display determina presentación; gap separa componentes en Grid o Flexbox.


1rem depende del tamaño de la fuente raíz; 1px representa un píxel CSS; un porcentaje depende de la propiedad; line-height:1.6 utiliza un multiplicador. Si la fuente raíz mide 16 píxeles, 1.5rem equivale a 24 píxeles CSS. El tamaño raíz puede variar.


Herencia y cascada


color y font-family suelen heredarse; margin no se hereda de la misma manera. Los controles pueden tener estilos propios, por eso utilizamos font:inherit para inputs, selects, áreas de texto y botones.


p { color: black; }
.aviso { color: #7c3b00; }

Un párrafo con clase aviso coincide con ambas reglas. En un caso sencillo de declaraciones normales del mismo origen y sin diferencias de capas, la clase tiene mayor especificidad que el tipo. Si las condiciones relevantes y la especificidad empatan, puede decidir el orden posterior.


La cascada no significa «siempre gana lo último»: considera origen, importancia y capas antes de resolver ciertos conflictos por especificidad. Conviene utilizar clases claras y evitar arreglar todo con !important.


3.3 Selectores


Los selectores indican a qué elementos se aplican las declaraciones.


Tipo, clase, identificador y universal


legend { font-weight: 700; }
.tarjeta { padding: 1.5rem; }
#resultado { border-left: 0.35rem solid #087443; }
* { box-sizing: border-box; }

legend selecciona por tipo. El punto selecciona una clase reutilizable. # selecciona un identificador único en el documento. El asterisco selecciona todos los elementos. Las clases son apropiadas para componentes repetidos; el identificador puede señalar una región particular.


Agrupaciones y relaciones


input, select, textarea, button { font: inherit; }
.menu a { color: #173b72; }
.campo > label { display: block; }
.boton.secundario { background-color: white; }

La coma agrupa selecciones independientes. El espacio selecciona descendientes aunque existan contenedores intermedios. > exige hijo directo. .boton.secundario exige ambas clases en el mismo elemento; .boton .secundario expresa descendencia y significa algo distinto.


Atributos, hermanos y estados


input[type="email"] { border-left: 0.25rem solid #1757a6; }
input[type="radio"]:checked + label { font-weight: 700; }
.boton:hover { background-color: #123b7a; }
:focus-visible { outline: 3px solid #a44400; }

El selector de atributo comprueba un valor; [required] comprueba presencia. + selecciona el hermano inmediatamente posterior, por eso la etiqueta debe seguir al radio. ~ selecciona hermanos posteriores sin exigir adyacencia.


:hover representa el puntero sobre el elemento; :focus, el foco; :focus-visible, el foco que el navegador considera necesario mostrar; :checked, la selección; :disabled, la deshabilitación; :invalid, el incumplimiento de restricciones. No debe dependerse solamente de hover: los dispositivos táctiles se utilizan de otra manera.


Pseudoelementos


.aviso::before {
  content: "Nota: ";
  font-weight: 700;
}

::before genera contenido antes del elemento. La información esencial debe permanecer en HTML. No debe utilizarse como única etiqueta de un control.


Integración del ejercicio


La clase control dará ancho, relleno y borde a campos de texto, correo, fecha, número, select y textarea. No se asigna a radios ni casillas para evitar que ocupen todo el ancho. Flexbox alinea las opciones y permite distribuir botones con flex-wrap. Grid organiza las áreas principales.


El resultado empieza con hidden. JavaScript escucha submit, evita la navegación con preventDefault, recopila los datos mediante FormData y muestra texto con textContent. En el envío normal mediante el botón, la validación HTML se comprueba antes de submit. Una entrada inválida puede bloquear el intento sin ejecutar ese controlador.


Proyecto terminado: index.html


Copia el siguiente código en index.html. Las etiquetas están asociadas, las restricciones están declaradas y el resumen comienza oculto.


<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Ejercicio de formularios HTML y presentación con CSS.">
  <title>Solicitud de préstamo de equipos</title>
  <link rel="stylesheet" href="estilos.css">
  <script src="app.js" defer></script>
</head>
<body>
  <header class="cabecera">
    <div class="contenedor">
      <h1>Préstamo de equipos</h1>
      <p>Completa los datos y revisa tu solicitud.</p>
    </div>
  </header>
  <nav class="menu contenedor" aria-label="Navegación principal">
    <a href="#instrucciones">Instrucciones</a>
    <a href="#solicitud">Formulario</a>
  </nav>
  <main class="contenedor disposicion">
    <section id="instrucciones" class="tarjeta" aria-labelledby="titulo-instrucciones">
      <h2 id="titulo-instrucciones">Antes de comenzar</h2>
      <ol>
        <li>Captura tus datos.</li>
        <li>Selecciona un equipo.</li>
        <li>Indica fecha y duración.</li>
        <li>Revisa el resumen.</li>
      </ol>
      <p class="aviso">Esta demostración muestra los datos en tu navegador. No envía ni guarda solicitudes.</p>
      <h3>Condiciones del ejercicio</h3>
      <ul>
        <li>Duración: entre una y ocho horas.</li>
        <li>Uso destinado a actividades académicas.</li>
        <li>La disponibilidad debe confirmarse con el encargado.</li>
      </ul>
    </section>
    <div>
      <section id="solicitud" class="tarjeta" aria-labelledby="titulo-solicitud">
        <h2 id="titulo-solicitud">Datos de la solicitud</h2>
        <p>Los campos indicados como obligatorios deben completarse.</p>
        <noscript><p class="aviso">Activa JavaScript para probar la revisión local del formulario.</p></noscript>
        <form id="formulario-prestamo" method="post">
          <div class="campo">
            <label for="nombre">Nombre completo (obligatorio)</label>
            <input class="control" id="nombre" name="nombre" type="text"
              autocomplete="name" minlength="2" maxlength="80" required>
          </div>
          <div class="campo">
            <label for="correo">Correo electrónico (obligatorio)</label>
            <input class="control" id="correo" name="correo" type="email"
              autocomplete="email" required>
          </div>
          <div class="campo">
            <label for="equipo">Equipo (obligatorio)</label>
            <select class="control" id="equipo" name="equipo" required>
              <option value="">Selecciona una opción</option>
              <option value="Laptop 01">Laptop 01</option>
              <option value="Laptop 02">Laptop 02</option>
              <option value="Proyector 01">Proyector 01</option>
            </select>
          </div>
          <div class="campo">
            <label for="fecha">Fecha de uso (obligatoria)</label>
            <input class="control" id="fecha" name="fecha" type="date" required>
          </div>
          <div class="campo">
            <label for="horas">Horas de uso: entre 1 y 8 (obligatorio)</label>
            <input class="control" id="horas" name="horas" type="number"
              min="1" max="8" step="1" value="1" required>
          </div>
          <fieldset>
            <legend>Modalidad de trabajo (obligatoria)</legend>
            <div class="opcion">
              <input id="individual" name="modalidad" type="radio" value="Individual" required>
              <label for="individual">Individual</label>
            </div>
            <div class="opcion">
              <input id="equipo-trabajo" name="modalidad" type="radio" value="En equipo" required>
              <label for="equipo-trabajo">En equipo</label>
            </div>
          </fieldset>
          <div class="campo">
            <label for="observaciones">Observaciones (opcional)</label>
            <textarea class="control" id="observaciones" name="observaciones"
              rows="4" maxlength="500" aria-describedby="ayuda-observaciones"></textarea>
            <small id="ayuda-observaciones">Puedes escribir hasta 500 caracteres.</small>
          </div>
          <div class="opcion confirmacion">
            <input id="condiciones" name="condiciones" type="checkbox" value="aceptadas" required>
            <label for="condiciones">He leído las condiciones del ejercicio (obligatorio).</label>
          </div>
          <div class="acciones">
            <button class="boton" id="revisar" type="submit" disabled>Revisar solicitud</button>
            <button class="boton secundario" type="reset">Limpiar</button>
          </div>
        </form>
      </section>
      <section class="tarjeta" id="resultado" tabindex="-1"
        aria-labelledby="titulo-resultado" hidden>
        <h2 id="titulo-resultado">Resumen de la solicitud</h2>
        <p>Revisión local completada. Estos datos no se han enviado ni guardado.</p>
        <dl class="resumen" id="resumen"></dl>
      </section>
    </div>
  </main>
  <footer class="pie contenedor">
    <p>Ejercicio de estructura HTML, formularios y selectores CSS.</p>
  </footer>
</body>
</html>

El div que agrupa formulario y resultado organiza la disposición sin añadir significado temático. aria-describedby relaciona las observaciones con su ayuda. aria-labelledby identifica las secciones mediante sus encabezados.


Proyecto terminado: estilos.css


La hoja se organiza por responsabilidades: modelo de caja, presentación, navegación, distribución, formulario, botones, foco, resultado y adaptación. Las propiedades inline y block expresan ejes de escritura; en este documento en español corresponden al horizontal y vertical, respectivamente.


/* Modelo de caja */
*, *::before, *::after {
  box-sizing: border-box;
}
/* Presentación general */
body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: #172033;
  background-color: #eef2f7;
}
.contenedor {
  width: 100%;
  max-width: 70rem;
  margin-inline: auto;
  padding-inline: 1rem;
}
.cabecera {
  padding-block: 2rem;
  color: white;
  background-color: #173b72;
}
.cabecera h1 {
  margin: 0;
  font-size: 2rem;
}
.cabecera p { margin-bottom: 0; }
/* Navegación */
.menu {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  padding-block: 1rem;
}
.menu a {
  color: #173b72;
  font-weight: 700;
  text-underline-offset: 0.2rem;
}
/* Disposición */
.disposicion {
  display: grid;
  gap: 1.5rem;
  align-items: start;
}
.disposicion > * { min-width: 0; }
.tarjeta {
  padding: 1.5rem;
  border: 1px solid #cbd5e1;
  border-radius: 0.75rem;
  background-color: white;
}
.tarjeta h2 { margin-top: 0; }
.aviso {
  padding: 1rem;
  border-left: 0.3rem solid #986000;
  background-color: #fff6df;
}
.aviso::before {
  content: "Nota: ";
  font-weight: 700;
}
/* Formulario */
input, select, textarea, button { font: inherit; }
.campo { margin-bottom: 1rem; }
.campo > label {
  display: block;
  margin-bottom: 0.35rem;
  font-weight: 600;
}
.control {
  width: 100%;
  min-width: 0;
  padding: 0.7rem;
  border: 1px solid #718096;
  border-radius: 0.4rem;
  color: #172033;
  background-color: white;
}
input[type="email"] { border-left: 0.25rem solid #1757a6; }
textarea.control { resize: vertical; }
.campo small {
  display: block;
  margin-top: 0.3rem;
  color: #475569;
}
fieldset {
  min-width: 0;
  margin: 0 0 1rem;
  padding: 1rem;
  border: 1px solid #718096;
  border-radius: 0.4rem;
}
legend {
  padding-inline: 0.25rem;
  font-weight: 600;
}
.opcion {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-block: 0.5rem;
}
input[type="radio"]:checked + label {
  color: #173b72;
  font-weight: 700;
}
.confirmacion { margin-block: 1.25rem; }
/* Botones */
.acciones {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.boton {
  padding: 0.75rem 1rem;
  border: 2px solid #1757a6;
  border-radius: 0.4rem;
  color: white;
  background-color: #1757a6;
  cursor: pointer;
}
.boton:hover { background-color: #123b7a; }
.boton.secundario {
  color: #173b72;
  background-color: white;
}
.boton.secundario:hover { background-color: #e8eef8; }
.boton:disabled {
  color: #475569;
  border-color: #94a3b8;
  background-color: #e2e8f0;
  cursor: not-allowed;
}
/* Foco */
:focus-visible {
  outline: 3px solid #a44400;
  outline-offset: 3px;
}
/* Resultado */
#resultado {
  margin-top: 1.5rem;
  border-left: 0.35rem solid #087443;
}
.resumen dt {
  margin-top: 0.75rem;
  font-weight: 700;
}
.resumen dd {
  margin-left: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.pie {
  padding-block: 1.5rem;
  color: #475569;
}
@media (min-width: 52rem) {
  .disposicion {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  }
}

Proyecto terminado: app.js


El programa localiza elementos, escucha submit, evita navegar, verifica un nombre significativo, recopila los valores y construye una lista de términos y descripciones. Después muestra y enfoca el resultado. Limpiar restablece los controles y oculta el resumen.


const formulario = document.querySelector("#formulario-prestamo");
const nombre = document.querySelector("#nombre");
const botonRevisar = document.querySelector("#revisar");
const resultado = document.querySelector("#resultado");
const resumen = document.querySelector("#resumen");
const campos = [
  ["nombre", "Nombre"],
  ["correo", "Correo electrónico"],
  ["equipo", "Equipo"],
  ["fecha", "Fecha de uso (aaaa-mm-dd)"],
  ["horas", "Horas de uso"],
  ["modalidad", "Modalidad"],
  ["observaciones", "Observaciones"]
];
nombre.addEventListener("input", () => {
  nombre.setCustomValidity("");
});
formulario.addEventListener("submit", (evento) => {
  evento.preventDefault();
  if (nombre.value.trim().length < 2) {
    nombre.setCustomValidity(
      "Escribe al menos dos caracteres, sin contar espacios exteriores."
    );
    nombre.reportValidity();
    return;
  }
  nombre.setCustomValidity("");
  const datos = new FormData(formulario);
  resumen.replaceChildren();
  for (const [campo, etiqueta] of campos) {
    const termino = document.createElement("dt");
    const descripcion = document.createElement("dd");
    const valor = String(datos.get(campo) ?? "").trim();
    termino.textContent = etiqueta;
    descripcion.textContent = valor || "Sin observaciones";
    resumen.append(termino, descripcion);
  }
  resultado.hidden = false;
  resultado.focus();
});
formulario.addEventListener("reset", () => {
  nombre.setCustomValidity("");
  resultado.hidden = true;
  resumen.replaceChildren();
});
botonRevisar.disabled = false;

setCustomValidity establece una restricción adicional; una cadena vacía elimina el error personalizado. El evento input lo retira al volver a escribir. textContent trata lo capturado como texto. No hay base de datos ni almacenamiento local. Recargar inicia otra ejecución, aunque el navegador pueda restaurar campos mediante sus propias funciones.


Ejecutar y comprobar


Guarda los tres archivos y abre index.html con un navegador. Los recursos CSS y JavaScript se cargan desde la misma carpeta; no se necesita instalar un servidor para esta demostración.


Prueba primero los errores: intenta revisar sin datos; escribe un correo inválido; deja el equipo sin seleccionar; indica cero, nueve o una cantidad fraccionaria de horas; omite modalidad o condiciones; escribe solo espacios en el nombre. Cada caso debe bloquear la revisión y mostrar la restricción correspondiente.


Después utiliza datos ficticios: Ana López, ana@example.com, Laptop 01, una fecha para la prueba, dos horas, modalidad Individual y «Actividad de programación» como observación. Al revisar debe aparecer el resumen. Modifica datos y repite: el resultado anterior se reemplaza. Limpiar restablece campos y oculta el resumen.


Reduce el ancho de la ventana: la distribución debe pasar a una columna. Recorre los controles con teclado y comprueba el foco. El ejercicio no comprueba fechas pasadas, horarios ni disponibilidad real.


Las herramientas de desarrollo permiten revisar si estilos.css se cargó, los errores de app.js, las reglas que coinciden con cada elemento y las dimensiones. Si no aparece el diseño, revisa href y el nombre del archivo. Si el botón permanece deshabilitado, revisa la ruta del script y la consola.


Errores frecuentes


for e id diferentes rompen la asociación de la etiqueta. Omitir name excluye el dato ordinariamente. Un name distinto en cada radio rompe la selección única. Repetir identificadores crea ambigüedad. Usar «color = blue» produce CSS inválido. Olvidar una llave puede afectar reglas posteriores. Escribir tarjeta sin punto selecciona un tipo, no la clase. Confundir .boton.secundario con .boton .secundario cambia la relación. disabled="false" sigue deshabilitando. Mostrar un resumen no equivale a registrar una operación en el servidor.


El ejercicio terminado conecta estructura semántica, controles identificados y selectores coherentes. Cada archivo cumple una responsabilidad y permite modificar una parte de la solución sin mezclar todo en un único bloque.



Video explicativo: HTML y CSS

Complementa esta lección con el siguiente video sobre estructura HTML, formularios y estilos CSS.



Referencias y consultas


1. MDN. Estructura de documentos: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Structuring_content/Structuring_documents 2. WHATWG. Formularios HTML: https://html.spec.whatwg.org/dev/forms.html 3. MDN. Elemento form: https://developer.mozilla.org/es/docs/Web/HTML/Reference/Elements/form 4. MDN. Cómo estructurar un formulario: https://developer.mozilla.org/es/docs/Learn_web_development/Extensions/Forms/How_to_structure_a_web_form 5. MDN. Elemento input: https://developer.mozilla.org/es/docs/Web/HTML/Reference/Elements/input 6. MDN. Validación de formularios: https://developer.mozilla.org/es/docs/Learn_web_development/Extensions/Forms/Form_validation 7. MDN. Introducción a CSS: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/What_is_CSS 8. MDN. Primeros pasos con CSS: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Getting_started 9. MDN. Modelo de caja: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Box_model 10. MDN. Selectores: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors 11. MDN. Cascada: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Introduction 12. MDN. Grid: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Grids 13. MDN. Flexbox: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Flexbox 14. MDN. FormData: https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData 15. MDN. Evento submit: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event 16. Microsoft. CSS en Visual Studio Code: https://code.visualstudio.com/docs/languages/css


 
 
 

Entradas recientes

Ver todo

Comentarios


bottom of page

Laboratorio interactivo de ondas

Ajusta la frecuencia y la longitud de onda. Observa cómo cambia la rapidez de propagación y el perfil de la onda.

v = f × λ
v = f λ = 2.0 × 3.0 = 6.0 m/s
Punto de observaciónλ
Un punto del medio oscila mientras la onda transporta energía.