Tutorial: aplicación para calcular el cuadrado de un número

Este tutorial explica cómo se ha construido la aplicación incluida en c010-06-cuadrado. El documento parte del código original y avanza desde la estructura HTML hasta el comportamiento JavaScript, pasando por la presentación CSS, la accesibilidad, la usabilidad y la seguridad.

Importante: los tres archivos del apartado «Código completo original» se reproducen sin modificaciones. Cuando se comenta una posible simplificación, se identifica expresamente como opcional y se diferencia del código original.

Índice

  1. Presentación de la aplicación
  2. Código completo original
    1. Archivo HTML
    2. Archivo CSS
    3. Archivo JavaScript
  3. Organización del proyecto
  4. A. Estructura y semántica HTML
  5. B. Presentación y organización del CSS
  6. C. Funcionamiento de JavaScript
  7. D. Accesibilidad
  8. E. Usabilidad
  9. F. Seguridad del código
  10. Revisión crítica del código
  11. Resumen y cierre
  12. Actividad de comprobación
  13. Lista de verificación

1. Presentación de la aplicación

La aplicación solicita un número y calcula su cuadrado, es decir, el resultado de multiplicarlo por sí mismo. Por ejemplo, el cuadrado de 5 es 25, porque 5 × 5 = 25. También admite números negativos y decimales.

Su objetivo didáctico es mostrar cómo colaboran las tres tecnologías básicas del desarrollo web:

  • HTML define el significado y la estructura del contenido: el título, el formulario, el campo numérico, el botón y el resultado.
  • CSS controla la presentación: colores, tamaños, separación, distribución, estados interactivos, adaptación a pantallas pequeñas y animaciones.
  • JavaScript aporta el comportamiento: obtiene el número, lo valida, calcula su cuadrado y actualiza el resultado.

Los archivos se relacionan de la siguiente manera:

  1. El navegador abre index.html.
  2. El elemento <link> hace que el navegador cargue style.css.
  3. El elemento <script> carga script.js.
  4. JavaScript localiza mediante sus identificadores los elementos creados por HTML.
  5. Cuando se envía el formulario, JavaScript realiza el cálculo y modifica el elemento <output>.

Al reproducir la aplicación, el alumnado aprenderá a:

  • crear un documento HTML semántico;
  • construir un formulario asociado correctamente a sus etiquetas y ayudas;
  • organizar estilos mediante variables y la metodología BEM;
  • diseñar una interfaz adaptable y visible al usar el teclado;
  • escuchar el evento submit con addEventListener();
  • obtener y validar un número;
  • usar el operador de exponenciación **;
  • mostrar datos como texto sin interpretarlos como HTML;
  • distinguir entre accesibilidad, usabilidad y seguridad.

Alcance de la aplicación: es una aplicación íntegramente local. No envía información a un servidor, no guarda datos y no utiliza bibliotecas externas.

Vista previa funcional de la aplicación

Introduce un número y activa el botón para probar la misma aplicación que se analiza a continuación. También puedes abrirla de forma independiente en una pestaña nueva.

Aplicación en funcionamiento HTML · CSS · JavaScript Abrir en una pestaña nueva (se abre en una pestaña nueva)

2. Código completo original

Antes de estudiar cada parte, conviene observar el código completo. Así se obtiene una visión general de los tres archivos que después se explicarán por bloques.

2.1. Archivo HTML: index.html

Ejemplo 1 HTML
<!doctype html>
<html lang="es">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta name="description" content="Aplicación didáctica para calcular el cuadrado de un número." />

        <title>Calcular el cuadrado de un número</title>

        <link rel="stylesheet" href="./assets/style/style.css" />

        <!--
            defer hace que el script se ejecute cuando el HTML ya se ha
            analizado, sin bloquear la construcción inicial de la página.
        -->
        <script src="./assets/js/script.js" defer></script>
    </head>

    <body class="page">
        <!--
            <main> identifica el contenido principal y único de la página.
            aria-labelledby relaciona este contenido con su encabezado <h1>.
        -->
        <main class="app" aria-labelledby="titulo-aplicacion">
            <header class="app__header">
                <h1 id="titulo-aplicacion" class="app__title">Calcular el cuadrado</h1>
            </header>

            <div class="app__content">
                <!--
                    El formulario se procesa con JavaScript. Su evento submit
                    permite activarlo tanto con el botón como con la tecla
                    Enter mientras el campo tiene el foco.
                -->
                <form id="formulario-cuadrado" class="calculation-form">
                    <div class="calculation-form__field">
                        <!--
                            for enlaza la etiqueta con el campo numero.
                            Al pulsar la etiqueta, el campo recibe el foco.
                        -->
                        <label class="calculation-form__label" for="numero">Introduce un número</label>

                        <!--
                            Este texto contiene instrucciones complementarias.
                            aria-describedby lo asociará al campo numérico.
                        -->
                        <p id="ayuda-numero" class="calculation-form__help">
                            Puedes utilizar números positivos, negativos o decimales.
                        </p>

                        <!--
                            type="number" solicita un dato numérico y step="any"
                            admite tanto enteros como números decimales.

                            required impide enviar el formulario vacío mediante
                            la validación nativa del navegador.

                            aria-describedby permite que los lectores de
                            pantalla anuncien la ayuda al enfocar el campo.
                        -->
                        <input
                            class="calculation-form__input"
                            type="number"
                            id="numero"
                            name="numero"
                            step="any"
                            inputmode="decimal"
                            aria-describedby="ayuda-numero"
                            required
                        />
                    </div>

                    <!--
                        type="submit" conserva el comportamiento semántico del
                        formulario y permite utilizarlo con teclado.
                    -->
                    <button class="calculation-form__button" type="submit">Calcular el cuadrado</button>
                </form>

                <!--
                    La sección tiene un encabezado propio. aria-labelledby usa
                    ese encabezado como nombre accesible de la región.
                -->
                <section class="result" aria-labelledby="titulo-resultado">
                    <h2 id="titulo-resultado" class="result__title">Resultado del cálculo</h2>

                    <!--
                        <output> representa el resultado de una operación.
                        for lo relaciona con el dato de entrada y form indica a
                        qué formulario pertenece, aunque esté fuera de él.

                        role="status" y aria-live="polite" permiten anunciar
                        los cambios sin interrumpir bruscamente a quien use un
                        lector de pantalla. aria-atomic solicita leer el nuevo
                        resultado completo.
                    -->
                    <output
                        id="resultado"
                        class="result__output"
                        name="cuadrado"
                        for="numero"
                        form="formulario-cuadrado"
                        role="status"
                        aria-live="polite"
                        aria-atomic="true"
                    >El resultado aparecerá aquí.</output>
                </section>
            </div>
        </main>
    </body>
</html>

2.2. Archivo CSS: assets/style/style.css

Ejemplo 2 CSS
/* + VARIABLES DEL TEMA */
:root {
    --color-primary: #ffe000;
    --color-primary-soft: #fff8bf;
    --color-text: #191919;
    --color-text-muted: #5f5f5f;
    --color-surface: #ffffff;
    --color-border: #d8d8d8;
    --color-background: #f5f5f2;
    --shadow-card: 0 1.25rem 3.5rem rgb(31 31 20 / 14%);
    --radius-large: 1.5rem;
    --radius-medium: 0.9rem;
}

/* + AJUSTES GENERALES */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    min-height: 100%;
    color-scheme: light;
}

body {
    margin: 0;
}

button,
input,
output {
    font: inherit;
}

/* + BLOQUE BEM: PAGE */
.page {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: clamp(1rem, 5vw, 3rem);
    background:
        radial-gradient(circle at 10% 10%, rgb(255 224 0 / 28%), transparent 34rem),
        linear-gradient(145deg, var(--color-background), #ecece7);
    color: var(--color-text);
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    line-height: 1.5;
}

/* + BLOQUE BEM: APP */
.app {
    position: relative;
    width: min(100%, 48rem);
    overflow: hidden;
    border: 1px solid rgb(25 25 25 / 10%);
    border-radius: var(--radius-large);
    background-color: rgb(255 255 255 / 92%);
    box-shadow: var(--shadow-card);
    animation: app-entry 600ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.app::before {
    position: absolute;
    inset: 0 0 auto;
    height: 0.4rem;
    background-color: var(--color-primary);
    content: "";
}

.app__header {
    padding: clamp(2rem, 6vw, 3.25rem) clamp(1.25rem, 5vw, 3rem) 1rem;
    text-align: center;
}

.app__title {
    margin: 0;
    font-size: clamp(2rem, 8vw, 3.25rem);
    line-height: 1.05;
    letter-spacing: -0.045em;
}

.app__title::after {
    display: block;
    width: 3.75rem;
    height: 0.35rem;
    margin: 1rem auto 0;
    border-radius: 999px;
    background-color: var(--color-primary);
    content: "";
}

.app__content {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1rem;
    padding: 1rem clamp(1.25rem, 5vw, 3rem) clamp(1.5rem, 5vw, 3rem);
}

/* + BLOQUE BEM: CALCULATION-FORM */
.calculation-form {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 1.5rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-medium);
    background-color: var(--color-surface);
    animation: content-entry 500ms 120ms ease-out both;
}

.calculation-form__field {
    display: grid;
    gap: 0.6rem;
}

.calculation-form__label {
    font-size: 1.05rem;
    font-weight: 750;
}

.calculation-form__help {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 0.9rem;
}

.calculation-form__input {
    width: 100%;
    min-height: 3.25rem;
    padding: 0.75rem 0.9rem;
    border: 2px solid var(--color-border);
    border-radius: 0.7rem;
    background-color: var(--color-surface);
    color: var(--color-text);
    transition:
        border-color 180ms ease,
        box-shadow 180ms ease;
}

.calculation-form__input:hover {
    border-color: #a7a7a7;
}

.calculation-form__input:focus-visible {
    border-color: var(--color-text);
    outline: none;
    box-shadow: 0 0 0 0.25rem rgb(255 224 0 / 55%);
}

.calculation-form__button {
    min-height: 3.25rem;
    padding: 0.75rem 1rem;
    border: 2px solid var(--color-text);
    border-radius: 0.7rem;
    background-color: var(--color-primary);
    color: var(--color-text);
    font-weight: 800;
    cursor: pointer;
    box-shadow: 0 0.35rem 0 var(--color-text);
    transition:
        transform 160ms ease,
        box-shadow 160ms ease,
        background-color 160ms ease;
}

.calculation-form__button:hover {
    background-color: #ffea3d;
    transform: translateY(-0.12rem);
    box-shadow: 0 0.47rem 0 var(--color-text);
}

.calculation-form__button:active {
    transform: translateY(0.18rem);
    box-shadow: 0 0.12rem 0 var(--color-text);
}

.calculation-form__button:focus-visible {
    outline: 0.2rem solid var(--color-text);
    outline-offset: 0.22rem;
}

/* + BLOQUE BEM: RESULT */
.result {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 1rem;
    padding: 1.5rem;
    border: 1px solid rgb(210 184 0 / 45%);
    border-radius: var(--radius-medium);
    background: linear-gradient(145deg, var(--color-primary-soft), #fffdf0);
    animation: content-entry 500ms 200ms ease-out both;
}

.result__title {
    margin: 0;
    font-size: 1.15rem;
}

.result__output {
    display: block;
    min-height: 8rem;
    padding: 1rem;
    overflow-wrap: anywhere;
    border: 2px dashed rgb(25 25 25 / 32%);
    border-radius: 0.7rem;
    background-color: rgb(255 255 255 / 78%);
    color: var(--color-text);
    font-size: 1.1rem;
    white-space: pre-wrap;
}

/* + ANIMACIONES */
@keyframes app-entry {
    from {
        opacity: 0;
        transform: translateY(0.8rem) scale(0.985);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

@keyframes content-entry {
    from {
        opacity: 0;
        transform: translateY(0.45rem);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* + DISEÑO ADAPTABLE */
@media (max-width: 42rem) {
    .app__content {
        grid-template-columns: 1fr;
    }

    .result__output {
        min-height: 6rem;
    }
}

/* Respeta la preferencia de quienes necesitan reducir el movimiento. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

2.3. Archivo JavaScript: assets/js/script.js

Ejemplo 3 JavaScript
'use strict';

/*
 * Referencias a los tres elementos que intervienen en la aplicación.
 * Se obtienen una sola vez para evitar búsquedas repetidas en el DOM.
 */
const formularioCuadrado = document.getElementById('formulario-cuadrado');
const campoNumero = document.getElementById('numero');
const resultado = document.getElementById('resultado');

/*
 * Esta comprobación facilita detectar un error en el HTML si se cambia o se
 * elimina accidentalmente alguno de los identificadores anteriores.
 */
if (!formularioCuadrado || !campoNumero || !resultado) {
    throw new Error('No se han encontrado todos los elementos necesarios de la aplicación.');
}

/*
 * Esta es la función manejadora del evento submit. Se escribe con un parámetro
 * porque el navegador le entrega automáticamente el objeto del evento.
 */
function calcularCuadrado(evento) {
    /* Evita que el formulario recargue la página al enviarse. */
    evento.preventDefault();

    /* valueAsNumber obtiene directamente el valor numérico del campo. */
    const numero = campoNumero.valueAsNumber;

    /*
     * La validación nativa impide normalmente llegar aquí con un dato vacío o
     * incorrecto. Esta comprobación adicional hace que la función siga siendo
     * segura si se invoca desde otro código.
     */
    if (!Number.isFinite(numero)) {
        resultado.value = 'Introduce un número válido.';
        campoNumero.focus();
        return;
    }

    const cuadrado = numero ** 2;

    /* Evita mostrar Infinity cuando el resultado excede el rango de JavaScript. */
    if (!Number.isFinite(cuadrado)) {
        resultado.value = 'El número es demasiado grande para calcular su cuadrado.';
        return;
    }

    const base = numero < 0 ? `(${numero})` : String(numero);

    /*
     * value actualiza el elemento <output> como texto. No se usa innerHTML, de
     * modo que el contenido introducido no puede interpretarse como código HTML.
     */
    resultado.value = `${base}² = ${cuadrado}`;
}

/*
 * Se pasa calcularCuadrado sin paréntesis porque se entrega una referencia a la
 * función. El navegador la ejecutará únicamente cuando se envíe el formulario.
 *
 * calcularCuadrado   -> referencia a la función
 * calcularCuadrado() -> ejecución inmediata de la función
 */
formularioCuadrado.addEventListener('submit', calcularCuadrado);

3. Organización del proyecto

Para que las rutas relativas del HTML funcionen, hay que conservar esta organización:

  • c010-06-cuadrado/
    • index.html
    • assets/
      • style/
        • style.css
      • js/
        • script.js

El punto . con el que empiezan las rutas significa «desde la carpeta actual». Por tanto, desde index.html, esta ruta busca el CSS dentro de assets/style/:

Ejemplo 4 HTML
<link rel="stylesheet" href="./assets/style/style.css" />

Y esta otra busca el JavaScript dentro de assets/js/:

Ejemplo 5 HTML
<script src="./assets/js/script.js" defer></script>

Error habitual: cambiar el nombre de una carpeta o trasladar un archivo sin actualizar su ruta. En ese caso, la página puede aparecer sin estilos o no reaccionar al botón.

4. A. Estructura y semántica HTML

La semántica HTML consiste en elegir elementos que describan el significado del contenido, no solo su aspecto. Por ejemplo, <button> expresa una acción, <form> agrupa controles relacionados y <output> representa el resultado de un cálculo. Esta elección ayuda al navegador, a los lectores de pantalla, a los buscadores y a quienes mantengan el código.

Paso 1. Crear el documento y sus metadatos

1. Objetivo del paso

Preparar un documento HTML válido, declarar su idioma, configurar la codificación y la visualización en dispositivos móviles, y conectar los otros dos archivos.

2. Explicación teórica

La cabecera <head> contiene información sobre la página que no forma parte de su contenido principal. La declaración <!doctype html> activa el modo estándar de HTML moderno. Los metadatos indican al navegador cómo interpretar y presentar el documento.

3. Fragmento exacto relacionado

Ejemplo 6 HTML
<!doctype html>
<html lang="es">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta name="description" content="Aplicación didáctica para calcular el cuadrado de un número." />

        <title>Calcular el cuadrado de un número</title>

        <link rel="stylesheet" href="./assets/style/style.css" />

        <!--
            defer hace que el script se ejecute cuando el HTML ya se ha
            analizado, sin bloquear la construcción inicial de la página.
        -->
        <script src="./assets/js/script.js" defer></script>
    </head>

4. Cómo funciona

  • lang="es" declara que el contenido está en español.
  • charset="UTF-8" permite representar correctamente letras acentuadas, la ñ y el símbolo ².
  • viewport adapta el ancho de la página al dispositivo.
  • description resume el propósito del documento.
  • <title> proporciona el nombre que aparece en la pestaña del navegador.
  • <link> carga el CSS.
  • <script> carga JavaScript y defer retrasa su ejecución hasta que el HTML ya se ha analizado.

5. Relación con el resto de la aplicación

Sin <link>, el contenido conservaría su estructura, pero perdería la presentación diseñada. Sin <script>, el formulario seguiría apareciendo, pero no se realizaría el cálculo. Sin defer, este script concreto podría intentar buscar elementos antes de que el navegador los hubiera creado si se ejecutase demasiado pronto.

6. Errores habituales

  • escribir una ruta que no coincide con las carpetas reales;
  • omitir lang, lo que dificulta que las tecnologías de asistencia elijan la pronunciación correcta;
  • colocar un valor de codificación incorrecto;
  • olvidar defer cuando el script está en <head> y depende de elementos posteriores del documento.

7. Conclusión del paso

El documento ya conoce su idioma, su codificación, su comportamiento en pantallas móviles y la ubicación de los archivos de presentación y comportamiento.

Paso 2. Definir el contenido principal y la jerarquía de encabezados

1. Objetivo del paso

Crear una zona principal claramente identificada y darle un único encabezado de nivel 1.

2. Explicación teórica

<main> representa el contenido principal de la página. <header> introduce el contenido que encabeza y <h1> expresa el tema general. La jerarquía no se elige por el tamaño visual, sino por la organización lógica del contenido.

3. Fragmento exacto relacionado

Ejemplo 7 HTML
<body class="page">
    <!--
        <main> identifica el contenido principal y único de la página.
        aria-labelledby relaciona este contenido con su encabezado <h1>.
    -->
    <main class="app" aria-labelledby="titulo-aplicacion">
        <header class="app__header">
            <h1 id="titulo-aplicacion" class="app__title">Calcular el cuadrado</h1>
        </header>

4. Cómo funciona

La clase page permite aplicar estilos al cuerpo completo. La clase app identifica visualmente el bloque principal. aria-labelledby="titulo-aplicacion" toma como nombre del bloque el texto del elemento cuyo id es titulo-aplicacion. El <h1> comunica el propósito de toda la aplicación.

5. Relación con el resto de la aplicación

Dentro de <main> se incluirán el formulario y el resultado. Más adelante, el resultado tendrá un <h2> porque es una subsección del tema presentado por el <h1>.

6. Errores habituales

  • usar un <div> para todo sin aprovechar los elementos semánticos;
  • elegir <h1> o <h2> por su tamaño predeterminado;
  • repetir un mismo id;
  • escribir en aria-labelledby un identificador que no existe.

7. Conclusión del paso

La página dispone de un contenido principal y de una jerarquía de encabezados comprensible tanto visualmente como mediante tecnologías de asistencia.

Paso 3. Construir el formulario accesible

1. Objetivo del paso

Solicitar un número mediante un control apropiado, explicar qué valores admite y proporcionar un botón de envío.

2. Explicación teórica

Un formulario agrupa datos y acciones relacionadas. Cada control debe tener un nombre visible mediante <label>. Las instrucciones adicionales pueden enlazarse con aria-describedby. El botón con type="submit" envía el formulario y permite centralizar el comportamiento en el evento submit.

3. Fragmento exacto relacionado

Ejemplo 8 HTML
<form id="formulario-cuadrado" class="calculation-form">
    <div class="calculation-form__field">
        <!--
            for enlaza la etiqueta con el campo numero.
            Al pulsar la etiqueta, el campo recibe el foco.
        -->
        <label class="calculation-form__label" for="numero">Introduce un número</label>

        <!--
            Este texto contiene instrucciones complementarias.
            aria-describedby lo asociará al campo numérico.
        -->
        <p id="ayuda-numero" class="calculation-form__help">
            Puedes utilizar números positivos, negativos o decimales.
        </p>

        <!--
            type="number" solicita un dato numérico y step="any"
            admite tanto enteros como números decimales.

            required impide enviar el formulario vacío mediante
            la validación nativa del navegador.

            aria-describedby permite que los lectores de
            pantalla anuncien la ayuda al enfocar el campo.
        -->
        <input
            class="calculation-form__input"
            type="number"
            id="numero"
            name="numero"
            step="any"
            inputmode="decimal"
            aria-describedby="ayuda-numero"
            required
        />
    </div>

    <!--
        type="submit" conserva el comportamiento semántico del
        formulario y permite utilizarlo con teclado.
    -->
    <button class="calculation-form__button" type="submit">Calcular el cuadrado</button>
</form>

4. Cómo funciona

  • El id del formulario permitirá encontrarlo desde JavaScript y asociarle el <output>.
  • for="numero" en <label> coincide con id="numero" en <input>.
  • type="number" comunica que se espera un dato numérico.
  • step="any" elimina la restricción predeterminada a determinados incrementos y permite decimales.
  • inputmode="decimal" sugiere un teclado decimal en dispositivos compatibles.
  • required activa la validación nativa cuando el campo está vacío.
  • aria-describedby="ayuda-numero" añade al campo la descripción del párrafo de ayuda.
  • El botón de tipo submit permite enviar el formulario al hacer clic o al pulsar Enter desde el campo.

5. Relación con el resto de la aplicación

JavaScript localizará el formulario por formulario-cuadrado y el campo por numero. El valor del campo será el dato de entrada del cálculo. El envío no se gestiona con un onclick aislado, sino mediante el evento del formulario completo.

6. Errores habituales

  • dejar vacío el atributo for de la etiqueta;
  • hacer que for e id tengan valores diferentes;
  • usar type="text" sin necesidad y tener que resolver más validación manual;
  • omitir step="any" y provocar que algunos decimales no superen la validación;
  • usar un <div> con aspecto de botón en lugar de un <button> real;
  • escuchar solo el clic y olvidar el envío mediante teclado.

7. Conclusión del paso

El formulario expresa con claridad qué dato se solicita, qué valores admite y cuál es la acción principal, además de conservar los comportamientos nativos del navegador.

Paso 4. Crear una zona semántica para el resultado

1. Objetivo del paso

Reservar una sección identificable en la que JavaScript mostrará el resultado del cálculo.

2. Explicación teórica

Una <section> agrupa contenido temáticamente relacionado y normalmente posee un encabezado. <output> es el elemento específico para representar el resultado de una operación realizada a partir de datos de formulario.

3. Fragmento exacto relacionado

Ejemplo 9 HTML
<section class="result" aria-labelledby="titulo-resultado">
    <h2 id="titulo-resultado" class="result__title">Resultado del cálculo</h2>

    <!--
        <output> representa el resultado de una operación.
        for lo relaciona con el dato de entrada y form indica a
        qué formulario pertenece, aunque esté fuera de él.

        role="status" y aria-live="polite" permiten anunciar
        los cambios sin interrumpir bruscamente a quien use un
        lector de pantalla. aria-atomic solicita leer el nuevo
        resultado completo.
    -->
    <output
        id="resultado"
        class="result__output"
        name="cuadrado"
        for="numero"
        form="formulario-cuadrado"
        role="status"
        aria-live="polite"
        aria-atomic="true"
    >El resultado aparecerá aquí.</output>
</section>

4. Cómo funciona

El <h2> nombra la sección. for="numero" relaciona el resultado con el campo empleado en el cálculo y form="formulario-cuadrado" lo asocia al formulario aunque el <output> esté fuera de él. La región activa anuncia los cambios de manera cortés y aria-atomic="true" solicita leer el mensaje actualizado completo.

5. Relación con el resto de la aplicación

El id="resultado" permite que JavaScript obtenga una referencia al elemento y cambie su propiedad value. El texto inicial informa de que todavía no se ha realizado ningún cálculo.

6. Errores habituales

  • mostrar el resultado en un elemento sin significado cuando existe <output>;
  • repetir el id o cambiarlo solo en HTML, dejando JavaScript desactualizado;
  • omitir el encabezado de la sección;
  • actualizar la interfaz con innerHTML aunque únicamente sea necesario mostrar texto.

7. Conclusión del paso

El resultado queda situado en una región semántica, relacionada con su dato de entrada y preparada para recibir actualizaciones dinámicas.

5. B. Presentación y organización del CSS

CSS aplica reglas formadas por un selector y un conjunto de declaraciones. El selector decide qué elementos se ven afectados; las propiedades definen su presentación. El archivo utiliza variables, selectores de clase, pseudoclases, pseudoelementos, reglas @media y animaciones.

También emplea BEM (Block, Element, Modifier), una convención de nombres que distingue bloques independientes y elementos pertenecientes a esos bloques. Por ejemplo:

Nombre Tipo BEM Significado
.app Bloque Componente general de la aplicación.
.app__title Elemento Título que pertenece al bloque app.
.calculation-form Bloque Formulario de cálculo.
.calculation-form__input Elemento Campo perteneciente al formulario.
.result__output Elemento Salida perteneciente al bloque result.

No aparecen modificadores del tipo .bloque--variante, porque la aplicación no necesita variantes de sus componentes.

Paso 5. Definir variables y ajustes generales

1. Objetivo del paso

Centralizar los valores visuales repetidos y establecer una base coherente para todos los elementos.

2. Explicación teórica

Las propiedades personalizadas de CSS, conocidas habitualmente como variables CSS, se declaran con nombres que empiezan por -- y se recuperan mediante var(). :root representa el elemento raíz y permite reutilizarlas en todo el documento.

3. Fragmento exacto relacionado

Ejemplo 10 CSS
/* + VARIABLES DEL TEMA */
:root {
    --color-primary: #ffe000;
    --color-primary-soft: #fff8bf;
    --color-text: #191919;
    --color-text-muted: #5f5f5f;
    --color-surface: #ffffff;
    --color-border: #d8d8d8;
    --color-background: #f5f5f2;
    --shadow-card: 0 1.25rem 3.5rem rgb(31 31 20 / 14%);
    --radius-large: 1.5rem;
    --radius-medium: 0.9rem;
}

/* + AJUSTES GENERALES */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    min-height: 100%;
    color-scheme: light;
}

body {
    margin: 0;
}

button,
input,
output {
    font: inherit;
}

4. Cómo funciona

Las variables reúnen colores, sombras y radios de borde. box-sizing: border-box hace que el ancho y el alto declarados incluyan el relleno y el borde. Se elimina el margen predeterminado de <body>. Los controles heredan la tipografía para mantener la coherencia visual. color-scheme: light indica que la interfaz se ha diseñado con un esquema claro.

5. Relación con el resto de la aplicación

Las reglas posteriores reutilizan valores como var(--color-primary) o var(--radius-medium). Cambiar una variable permite actualizar varias partes de la interfaz desde un único lugar.

6. Errores habituales

  • escribir var(--color-primary) con un nombre distinto del declarado;
  • creer que box-sizing se hereda automáticamente y aplicarlo solo a html;
  • eliminar todos los estilos nativos sin reconstruir estados importantes como el foco;
  • repetir colores muchas veces en lugar de emplear las variables existentes.

7. Conclusión del paso

La hoja de estilos dispone de una base predecible y de un tema fácil de mantener.

Paso 6. Centrar y presentar el bloque principal

1. Objetivo del paso

Crear el fondo de página, centrar la aplicación y presentarla como una tarjeta de anchura limitada.

2. Explicación teórica

CSS Grid puede centrar un único elemento con place-items: center. Funciones como min() y clamp() permiten que las medidas se adapten al espacio disponible sin crecer o reducirse más allá de ciertos límites.

3. Fragmento exacto relacionado

Ejemplo 11 CSS
.page {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: clamp(1rem, 5vw, 3rem);
    background:
        radial-gradient(circle at 10% 10%, rgb(255 224 0 / 28%), transparent 34rem),
        linear-gradient(145deg, var(--color-background), #ecece7);
    color: var(--color-text);
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    line-height: 1.5;
}

.app {
    position: relative;
    width: min(100%, 48rem);
    overflow: hidden;
    border: 1px solid rgb(25 25 25 / 10%);
    border-radius: var(--radius-large);
    background-color: rgb(255 255 255 / 92%);
    box-shadow: var(--shadow-card);
    animation: app-entry 600ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

4. Cómo funciona

.page ocupa al menos toda la altura visible y centra .app. El relleno evita que la tarjeta toque los bordes. width: min(100%, 48rem) hace que la tarjeta use el ancho disponible sin superar 48rem. Los degradados crean profundidad sin imágenes externas. La pila tipográfica recurre a fuentes del sistema si Inter no está instalada.

5. Relación con el resto de la aplicación

.page corresponde a <body class="page"> y .app a <main class="app">. El resto de componentes se distribuye dentro de esta tarjeta.

6. Errores habituales

  • declarar height: 100vh y provocar desbordamiento cuando el contenido necesita más altura;
  • olvidar el límite máximo de anchura y obtener líneas demasiado extensas en pantallas grandes;
  • aplicar place-items sin establecer antes un contenedor Grid;
  • usar una fuente externa no cargada y confiar en que siempre estará disponible.

7. Conclusión del paso

La aplicación queda centrada, mantiene márgenes seguros y se adapta tanto a ventanas estrechas como amplias.

Paso 7. Distribuir y dimensionar el formulario y el resultado

1. Objetivo del paso

Organizar el formulario y el resultado en dos columnas equilibradas, además de darles una apariencia diferenciada.

2. Explicación teórica

Grid es apropiado para distribuir bloques en filas y columnas. Flexbox resulta útil para ordenar el contenido interno en una sola dirección. La aplicación combina ambos sistemas según la necesidad.

3. Fragmento exacto relacionado

Ejemplo 12 CSS
.app__content {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 1rem;
    padding: 1rem clamp(1.25rem, 5vw, 3rem) clamp(1.5rem, 5vw, 3rem);
}

.calculation-form {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 1.5rem;
    padding: 1.5rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-medium);
    background-color: var(--color-surface);
    animation: content-entry 500ms 120ms ease-out both;
}

.result {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 1rem;
    padding: 1.5rem;
    border: 1px solid rgb(210 184 0 / 45%);
    border-radius: var(--radius-medium);
    background: linear-gradient(145deg, var(--color-primary-soft), #fffdf0);
    animation: content-entry 500ms 200ms ease-out both;
}

4. Cómo funciona

grid-template-columns crea dos columnas del mismo tamaño. minmax(0, 1fr) permite que el contenido pueda reducirse sin forzar un desbordamiento. El formulario y el resultado son contenedores flexibles verticales. Sus fondos y bordes diferentes permiten reconocer la zona de entrada y la zona de salida.

5. Relación con el resto de la aplicación

Las clases coinciden con <div class="app__content">, <form class="calculation-form"> y <section class="result">. La distribución visual refuerza la secuencia conceptual «dato de entrada → resultado».

6. Errores habituales

  • usar anchos fijos para las columnas;
  • olvidar gap y separar los componentes mediante márgenes difíciles de mantener;
  • confundir justify-content con align-items;
  • omitir min-width: 0 cuando el contenido de un elemento Grid o Flex puede crecer demasiado.

7. Conclusión del paso

La interfaz queda dividida en dos áreas claras y equilibradas, cada una organizada con el sistema de disposición más apropiado.

Paso 8. Diseñar los controles y sus estados interactivos

1. Objetivo del paso

Hacer que el campo y el botón sean legibles, cómodos de pulsar y reconocibles cuando reciben el puntero, el foco o una pulsación.

2. Explicación teórica

Las pseudoclases describen estados. :hover se activa al situar el puntero, :active durante la activación y :focus-visible cuando el navegador considera necesario mostrar una indicación de foco, especialmente al navegar con teclado.

3. Fragmento exacto relacionado

Ejemplo 13 CSS
.calculation-form__input {
    width: 100%;
    min-height: 3.25rem;
    padding: 0.75rem 0.9rem;
    border: 2px solid var(--color-border);
    border-radius: 0.7rem;
    background-color: var(--color-surface);
    color: var(--color-text);
    transition:
        border-color 180ms ease,
        box-shadow 180ms ease;
}

.calculation-form__input:hover {
    border-color: #a7a7a7;
}

.calculation-form__input:focus-visible {
    border-color: var(--color-text);
    outline: none;
    box-shadow: 0 0 0 0.25rem rgb(255 224 0 / 55%);
}

.calculation-form__button {
    min-height: 3.25rem;
    padding: 0.75rem 1rem;
    border: 2px solid var(--color-text);
    border-radius: 0.7rem;
    background-color: var(--color-primary);
    color: var(--color-text);
    font-weight: 800;
    cursor: pointer;
    box-shadow: 0 0.35rem 0 var(--color-text);
    transition:
        transform 160ms ease,
        box-shadow 160ms ease,
        background-color 160ms ease;
}

4. Cómo funciona

La altura mínima y el relleno crean objetivos de interacción amplios. El campo cambia de borde al pasar el puntero y recibe un halo amarillo al enfocarse. El botón utiliza el color principal y una sombra que sugiere volumen. Las transiciones suavizan únicamente las propiedades que cambian.

5. Relación con el resto de la aplicación

Los estados visuales acompañan el comportamiento nativo de <input> y <button>. No sustituyen la semántica: la interacción por teclado procede de los elementos HTML, mientras CSS hace visible su estado.

6. Errores habituales

  • eliminar outline sin crear una alternativa de foco claramente visible;
  • aplicar la transición a all, lo que puede animar propiedades no previstas;
  • mostrar cambios solo en :hover y olvidar a quienes navegan con teclado;
  • usar colores de texto y fondo con poco contraste.

7. Conclusión del paso

Los controles comunican visualmente cuándo están disponibles, enfocados o activados, y conservan una superficie de interacción cómoda.

Paso 9. Añadir animaciones, adaptación y reducción de movimiento

1. Objetivo del paso

Introducir movimientos sutiles, reorganizar el contenido en pantallas pequeñas y respetar a las personas que prefieren menos animación.

2. Explicación teórica

@keyframes define etapas de una animación. Una consulta @media aplica reglas solo cuando se cumple una condición. prefers-reduced-motion consulta una preferencia configurada por el usuario en su sistema operativo o navegador.

3. Fragmento exacto relacionado

Ejemplo 14 CSS
@keyframes content-entry {
    from {
        opacity: 0;
        transform: translateY(0.45rem);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* + DISEÑO ADAPTABLE */
@media (max-width: 42rem) {
    .app__content {
        grid-template-columns: 1fr;
    }

    .result__output {
        min-height: 6rem;
    }
}

/* Respeta la preferencia de quienes necesitan reducir el movimiento. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

4. Cómo funciona

content-entry hace aparecer el contenido con un pequeño desplazamiento vertical. Cuando la ventana no supera 42rem, la cuadrícula cambia de dos columnas a una. Si el usuario solicita movimiento reducido, las duraciones pasan prácticamente a cero y las animaciones solo se ejecutan una vez.

5. Relación con el resto de la aplicación

El formulario y el resultado usan content-entry con retrasos distintos. La consulta adaptable modifica .app__content, creada en el paso anterior, sin alterar el orden del HTML: primero continúa apareciendo el formulario y después el resultado.

6. Errores habituales

  • crear una animación y no aplicarla a ningún selector;
  • usar movimientos continuos o intensos para una acción sencilla;
  • resolver el diseño móvil cambiando visualmente el orden lógico del contenido;
  • olvidar prefers-reduced-motion.

7. Conclusión del paso

La presentación es agradable sin depender del movimiento y conserva una distribución legible en pantallas pequeñas.

6. C. Funcionamiento de JavaScript

JavaScript trabaja con el DOM (Document Object Model), la representación en memoria que el navegador crea a partir del HTML. A través del DOM, el programa puede encontrar elementos, escuchar acciones y actualizar contenido.

El flujo completo es el siguiente:

  1. El navegador analiza el HTML.
  2. Gracias a defer, ejecuta el script cuando los elementos ya existen.
  3. El script guarda referencias al formulario, al campo y al resultado.
  4. Registra una función para el evento submit.
  5. El usuario introduce un número y envía el formulario.
  6. La función evita la recarga, obtiene y valida el dato.
  7. Calcula el cuadrado.
  8. Actualiza <output> con un mensaje textual.

Paso 10. Activar el modo estricto y seleccionar elementos

1. Objetivo del paso

Preparar un entorno de ejecución más riguroso y guardar referencias a los elementos que JavaScript necesita.

2. Explicación teórica

'use strict' activa el modo estricto, que convierte algunos errores silenciosos en errores visibles y evita determinados comportamientos antiguos. document.getElementById() busca el elemento cuyo id coincide exactamente con el texto indicado.

3. Fragmento exacto relacionado

Ejemplo 15 JavaScript
'use strict';

/*
 * Referencias a los tres elementos que intervienen en la aplicación.
 * Se obtienen una sola vez para evitar búsquedas repetidas en el DOM.
 */
const formularioCuadrado = document.getElementById('formulario-cuadrado');
const campoNumero = document.getElementById('numero');
const resultado = document.getElementById('resultado');

4. Cómo funciona

Cada llamada devuelve el elemento encontrado o null si no existe. Las referencias se guardan con const porque las variables no se reasignarán a otros elementos. Esto no vuelve inmutable al elemento: JavaScript puede seguir cambiando su valor o contenido.

5. Relación con el resto de la aplicación

Los identificadores deben coincidir con los del HTML. formularioCuadrado servirá para registrar el evento, campoNumero proporcionará el dato y resultado recibirá el mensaje.

6. Errores habituales

  • incluir # en getElementById('#numero'); este método recibe el identificador sin almohadilla;
  • cambiar un id en HTML y no actualizar JavaScript;
  • confundir const con la inmutabilidad total del objeto referenciado;
  • consultar repetidamente el DOM dentro de la función sin necesidad.

7. Conclusión del paso

El programa dispone de referencias estables a los tres elementos que intervienen en el flujo.

Paso 11. Comprobar que el HTML y JavaScript siguen coordinados

1. Objetivo del paso

Detectar inmediatamente si falta alguno de los elementos esenciales.

2. Explicación teórica

Una comprobación defensiva verifica una condición que normalmente debería cumplirse, pero que podría dejar de hacerlo después de modificar el código. El operador ! niega un valor; || significa «o»; throw detiene la ejecución lanzando un error explícito.

3. Fragmento exacto relacionado

Ejemplo 16 JavaScript
/*
 * Esta comprobación facilita detectar un error en el HTML si se cambia o se
 * elimina accidentalmente alguno de los identificadores anteriores.
 */
if (!formularioCuadrado || !campoNumero || !resultado) {
    throw new Error('No se han encontrado todos los elementos necesarios de la aplicación.');
}

4. Cómo funciona

Si al menos una referencia vale null, la condición se cumple y se lanza el error. En lugar de fallar más adelante con un mensaje poco claro, la consola indica la causa general: falta un elemento necesario.

5. Relación con el resto de la aplicación

Solo cuando existen las tres referencias se continúa hasta el registro del evento. defer reduce la posibilidad de un fallo por ejecutar el script demasiado pronto; la comprobación también detecta identificadores eliminados o escritos de forma distinta.

6. Errores habituales

  • usar && y detectar el problema únicamente cuando faltan los tres elementos a la vez;
  • mostrar el error al usuario como si fuera un dato incorrecto introducido por él;
  • continuar la ejecución después de descubrir que falta un elemento esencial;
  • incluir datos sensibles en mensajes de error del cliente.

7. Conclusión del paso

El código falla de forma temprana y comprensible si se rompe la relación entre los archivos.

Paso 12. Registrar y gestionar el envío del formulario

1. Objetivo del paso

Ejecutar el cálculo cuando se envíe el formulario y evitar que la página se recargue.

2. Explicación teórica

Un evento es un suceso detectado por el navegador. Una función manejadora es la función que se ejecuta como respuesta. addEventListener() asocia ambos sin escribir JavaScript dentro del HTML.

3. Fragmento exacto relacionado

Ejemplo 17 JavaScript
function calcularCuadrado(evento) {
    /* Evita que el formulario recargue la página al enviarse. */
    evento.preventDefault();
Ejemplo 18 JavaScript
/*
 * Se pasa calcularCuadrado sin paréntesis porque se entrega una referencia a la
 * función. El navegador la ejecutará únicamente cuando se envíe el formulario.
 *
 * calcularCuadrado   -> referencia a la función
 * calcularCuadrado() -> ejecución inmediata de la función
 */
formularioCuadrado.addEventListener('submit', calcularCuadrado);

4. Cómo funciona

El navegador entrega automáticamente a la función un objeto evento. preventDefault() cancela el comportamiento predeterminado de envío, que en esta aplicación recargaría la página. Al final del archivo se registra calcularCuadrado como manejadora de submit; se pasa sin paréntesis para que no se ejecute de inmediato.

5. Relación con el resto de la aplicación

El evento procede del <form> y no solo del botón. Por eso funciona tanto al hacer clic como al pulsar Enter desde el campo, siempre que la validación nativa permita el envío.

6. Errores habituales

  • escribir addEventListener('submit', calcularCuadrado()) y ejecutar la función durante la carga;
  • usar onclick en el botón y perder la idea de que se está enviando un formulario;
  • olvidar preventDefault() y ver desaparecer el resultado tras la recarga;
  • escribir evento.preventDefault sin los paréntesis, lo que no ejecuta el método.

7. Conclusión del paso

La aplicación responde al envío semántico del formulario y mantiene la página disponible para presentar el resultado.

Paso 13. Obtener, validar y procesar el número

1. Objetivo del paso

Convertir el contenido del campo en un número utilizable, rechazar valores no finitos y calcular el cuadrado.

2. Explicación teórica

La propiedad valueAsNumber devuelve el valor numérico de un campo type="number". Si no puede producir un número válido, devuelve NaN. Number.isFinite() acepta únicamente números finitos: descarta NaN, Infinity y -Infinity. El operador ** realiza una exponenciación.

3. Fragmento exacto relacionado

Ejemplo 19 JavaScript
/* valueAsNumber obtiene directamente el valor numérico del campo. */
const numero = campoNumero.valueAsNumber;

/*
 * La validación nativa impide normalmente llegar aquí con un dato vacío o
 * incorrecto. Esta comprobación adicional hace que la función siga siendo
 * segura si se invoca desde otro código.
 */
if (!Number.isFinite(numero)) {
    resultado.value = 'Introduce un número válido.';
    campoNumero.focus();
    return;
}

const cuadrado = numero ** 2;

/* Evita mostrar Infinity cuando el resultado excede el rango de JavaScript. */
if (!Number.isFinite(cuadrado)) {
    resultado.value = 'El número es demasiado grande para calcular su cuadrado.';
    return;
}

4. Cómo funciona

Primero se obtiene el número. Si no es finito, se escribe un mensaje, se devuelve el foco al campo y return termina la función. Si es correcto, se eleva a 2. Después se comprueba también el resultado, porque un número aceptado puede producir Infinity al elevarse si es extraordinariamente grande.

5. Relación con el resto de la aplicación

HTML aporta una primera validación mediante type="number" y required. JavaScript añade una comprobación defensiva y otra que HTML no puede realizar: verificar que el resultado siga dentro del rango numérico finito.

6. Errores habituales

  • usar campoNumero.value y olvidar que devuelve texto;
  • comprobar solo numero !== NaN, una comparación que nunca detecta correctamente NaN;
  • confundir numero ** 2 con numero * 2;
  • olvidar return y continuar el cálculo tras mostrar un error;
  • validar la entrada pero no el posible desbordamiento del resultado.

7. Conclusión del paso

El programa obtiene un número real de la interfaz, aplica dos controles coherentes y calcula su cuadrado solo cuando puede hacerlo de forma representable.

Paso 14. Formatear y mostrar el resultado de forma segura

1. Objetivo del paso

Crear un mensaje comprensible para números positivos y negativos y escribirlo como texto en <output>.

2. Explicación teórica

El operador condicional condición ? valor1 : valor2 elige entre dos valores. Las plantillas literales, delimitadas con acentos graves, permiten insertar expresiones mediante ${...}. La propiedad value de <output> cambia el resultado mostrado.

3. Fragmento exacto relacionado

Ejemplo 20 JavaScript
const base = numero < 0 ? `(${numero})` : String(numero);

/*
 * value actualiza el elemento <output> como texto. No se usa innerHTML, de
 * modo que el contenido introducido no puede interpretarse como código HTML.
 */
resultado.value = `${base}² = ${cuadrado}`;

4. Cómo funciona

Si el número es negativo, se rodea de paréntesis: (-3)² = 9. Esto evita la ambigüedad visual de -3², que matemáticamente suele interpretarse como -(3²). Si no es negativo, String(numero) crea su representación textual. Finalmente se compone y asigna el mensaje.

5. Relación con el resto de la aplicación

resultado referencia el <output> del HTML. La región activa ya existe cuando se actualiza, por lo que las tecnologías de asistencia pueden detectar el cambio. CSS da al mensaje espacio, contraste y ajuste de línea.

6. Errores habituales

  • omitir los paréntesis al presentar una base negativa;
  • emplear comillas normales con ${base} y esperar interpolación;
  • usar innerHTML para una salida que solo requiere texto;
  • modificar una variable distinta de la que referencia el <output>.

7. Conclusión del paso

La aplicación presenta una expresión matemática clara y mantiene el dato como texto, sin convertirlo en marcado HTML.

7. D. Accesibilidad

La accesibilidad web busca que una página pueda ser percibida, comprendida y utilizada por el mayor número posible de personas, incluidas quienes emplean teclado, lectores de pantalla, ampliación, alto contraste o reducción de movimiento.

7.1. Medidas presentes y beneficio que aportan

Medida presente Fragmento relacionado Beneficio principal
Idioma declarado <html lang="es"> Facilita la pronunciación correcta del contenido.
Contenido principal <main class="app" ...> Permite reconocer el contenido principal como un punto de referencia.
Encabezados ordenados <h1 ...> y <h2 ...> Expresan la estructura de la página.
Etiqueta visible <label ... for="numero"> Da nombre al campo y amplía su zona de activación.
Ayuda asociada aria-describedby="ayuda-numero" Hace que la instrucción adicional se anuncie junto al campo.
Controles nativos <input> y <button type="submit"> Conservan teclado, foco, activación y significado propios del navegador.
Salida semántica <output ...> Identifica el contenido como resultado de una operación.
Actualización anunciable role="status", aria-live="polite" y aria-atomic="true" Facilita el anuncio no intrusivo del mensaje actualizado.
Foco visible .calculation-form__input:focus-visible y .calculation-form__button:focus-visible Permite saber qué control recibirá la siguiente acción del teclado.
Movimiento reducido @media (prefers-reduced-motion: reduce) Evita imponer animaciones a quien ha pedido reducirlas.

7.2. Asociación entre etiqueta, ayuda y campo

La etiqueta y el campo se conectan mediante el mismo identificador:

Ejemplo 21 HTML
<label class="calculation-form__label" for="numero">Introduce un número</label>
Ejemplo 22 HTML
<input
    class="calculation-form__input"
    type="number"
    id="numero"
    name="numero"
    step="any"
    inputmode="decimal"
    aria-describedby="ayuda-numero"
    required
/>

El texto de ayuda tiene el identificador citado por aria-describedby:

Ejemplo 23 HTML
<p id="ayuda-numero" class="calculation-form__help">
    Puedes utilizar números positivos, negativos o decimales.
</p>

No debe sustituirse la etiqueta por un placeholder. El texto de un placeholder puede desaparecer al escribir, suele tener menos contraste y no siempre funciona como nombre accesible fiable.

7.3. Navegación mediante teclado

La aplicación puede utilizarse sin ratón:

  1. La tecla Tab lleva el foco al campo.
  2. Se escribe el número.
  3. Enter envía el formulario, o bien Tab lleva el foco al botón y Enter o la barra espaciadora lo activa.
  4. El foco visible permite seguir la posición actual.

Este funcionamiento es posible porque se usan controles HTML nativos y se escucha submit:

Ejemplo 24 JavaScript
formularioCuadrado.addEventListener('submit', calcularCuadrado);

7.4. Mensaje de resultado

El resultado se actualiza sin trasladar forzosamente el foco:

Ejemplo 25 JavaScript
resultado.value = `${base}² = ${cuadrado}`;

La región utiliza un anuncio cortés, que no debería interrumpir de forma brusca el contenido que ya estuviera leyendo una tecnología de asistencia. Cuando JavaScript detecta una entrada no finita por su comprobación defensiva, devuelve el foco al campo:

Ejemplo 26 JavaScript
if (!Number.isFinite(numero)) {
    resultado.value = 'Introduce un número válido.';
    campoNumero.focus();
    return;
}

En el uso normal, un campo vacío queda detenido antes por la validación nativa de required, y el navegador presenta su propio aviso.

7.5. Contraste, foco y movimiento

Los pares de colores principales presentan un contraste alto: #191919 sobre #ffe000 alcanza aproximadamente 13,32:1, y #5f5f5f sobre blanco aproximadamente 6,39:1. Esto favorece la lectura del botón y del texto de ayuda.

El código solo elimina el contorno nativo del campo después de crear una alternativa visible:

Ejemplo 27 CSS
.calculation-form__input:focus-visible {
    border-color: var(--color-text);
    outline: none;
    box-shadow: 0 0 0 0.25rem rgb(255 224 0 / 55%);
}

Advertencia: outline: none sería un problema si no existieran el borde oscuro y el halo de sustitución.

La reducción de movimiento es global y se aplica también a pseudoelementos:

Ejemplo 28 CSS
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        scroll-behavior: auto !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

7.6. Imágenes y texto alternativo

La aplicación no contiene imágenes. Por tanto, no necesita atributos alt. No tendría sentido añadir texto alternativo donde no existe contenido gráfico.

7.7. Uso de ARIA

ARIA complementa HTML cuando es necesario comunicar relaciones o estados que no quedan suficientemente claros. En esta aplicación se usa para:

  • nombrar regiones mediante aria-labelledby;
  • asociar la ayuda al campo mediante aria-describedby;
  • reforzar el anuncio dinámico del resultado.

Los atributos de la región de resultado son explícitos, aunque <output> ya posee semántica propia. No convierten el código en inválido. En el apartado de revisión crítica se muestra una simplificación opcional basada en el principio «usar primero HTML nativo y añadir ARIA solo cuando aporte algo necesario».

8. E. Usabilidad

La usabilidad estudia hasta qué punto una interfaz resulta fácil de aprender, eficiente y poco propensa a errores. Una página puede ser técnicamente accesible y, aun así, resultar confusa; por eso conviene revisar ambos aspectos por separado.

8.1. Claridad de la tarea

El título, la etiqueta y el botón utilizan expresiones directas:

Ejemplo 29 HTML
<h1 id="titulo-aplicacion" class="app__title">Calcular el cuadrado</h1>
Ejemplo 30 HTML
<label class="calculation-form__label" for="numero">Introduce un número</label>
Ejemplo 31 HTML
<button class="calculation-form__button" type="submit">Calcular el cuadrado</button>

La persona puede entender qué debe introducir y qué ocurrirá sin interpretar iconos ni términos innecesarios.

8.2. Prevención de errores

HTML limita el tipo de dato y evita el envío vacío:

Ejemplo 32 HTML
type="number"
step="any"
inputmode="decimal"
aria-describedby="ayuda-numero"
required

La ayuda visible aclara que se admiten positivos, negativos y decimales. JavaScript añade mensajes específicos para un valor no finito y para un resultado que excede el rango representable.

Matiz importante: la validación nativa se produce antes del evento submit. Por eso, en condiciones normales, un campo vacío muestra el mensaje propio del navegador y no llega al primer mensaje de error de JavaScript.

8.3. Acción principal reconocible

El botón tiene texto descriptivo, color destacado, tamaño cómodo y estados visuales:

Ejemplo 33 CSS
.calculation-form__button:hover {
    background-color: #ffea3d;
    transform: translateY(-0.12rem);
    box-shadow: 0 0.47rem 0 var(--color-text);
}

.calculation-form__button:active {
    transform: translateY(0.18rem);
    box-shadow: 0 0.12rem 0 var(--color-text);
}

El movimiento comunica que el elemento responde a la interacción, pero no es imprescindible para entenderlo.

8.4. Retroalimentación después de la acción

Tras el envío, la zona de resultado cambia inmediatamente:

Ejemplo 34 JavaScript
resultado.value = `${base}² = ${cuadrado}`;

El mensaje incluye el dato de entrada y la operación, no solo una cifra aislada. Para un número negativo, los paréntesis evitan ambigüedad matemática.

8.5. Adaptación a distintos tamaños

En pantallas anchas, formulario y resultado aparecen en dos columnas. En pantallas de hasta 42rem, pasan a una sola:

Ejemplo 35 CSS
@media (max-width: 42rem) {
    .app__content {
        grid-template-columns: 1fr;
    }

    .result__output {
        min-height: 6rem;
    }
}

El orden sigue siendo lógico porque no se altera el DOM: primero entrada y acción; después resultado.

8.6. Coherencia y carga cognitiva

Las variables mantienen colores y radios coherentes. Los bloques están claramente separados, y la interfaz solo contiene una tarea principal. No se añaden opciones que distraigan del objetivo didáctico.

8.7. Limitación numérica que conviene explicar

JavaScript representa la mayoría de números con el formato de coma flotante binaria. Algunos decimales no pueden almacenarse exactamente. Por ello, una operación como el cuadrado de 0.1 puede mostrar más cifras de las esperadas. No es un error de sintaxis ni una vulnerabilidad, sino una limitación del modelo numérico.

La aplicación original no establece una política de redondeo. Añadirla sin saber cuántos decimales se desean podría ocultar precisión o cambiar el comportamiento didáctico. Por ese motivo, el tutorial conserva el cálculo original:

Ejemplo 36 JavaScript
const cuadrado = numero ** 2;

9. F. Seguridad del código

La seguridad web consiste en impedir que los datos o acciones no confiables produzcan efectos no autorizados. En esta aplicación el riesgo es reducido porque no existen cuentas, servidor, almacenamiento ni comunicaciones de red. Aun así, incluye decisiones útiles que sirven para proyectos mayores.

9.1. Validación y tratamiento del dato

La validación se produce en dos capas del cliente:

  1. HTML aplica type="number", step="any" y required.
  2. JavaScript comprueba Number.isFinite(numero) y Number.isFinite(cuadrado).
Ejemplo 37 JavaScript
if (!Number.isFinite(numero)) {
    resultado.value = 'Introduce un número válido.';
    campoNumero.focus();
    return;
}

La comprobación JavaScript no confía únicamente en que el evento siempre proceda de un envío normal; también protege la función si otro código la invoca o manipula el campo.

9.2. Cliente frente a servidor

La validación del cliente mejora la experiencia, pero no constituye una frontera de seguridad: una persona puede modificar el HTML o ejecutar solicitudes sin usar la interfaz. Si el dato se enviase a un servidor, el servidor tendría que volver a validar el tipo, los límites y las reglas de negocio.

En la aplicación proporcionada no existe envío ni servidor. Por tanto, no falta una validación del servidor para su objetivo actual; simplemente sería obligatoria si en el futuro se almacenaran o procesaran datos fuera del navegador.

9.3. innerHTML frente a escritura textual

innerHTML analiza una cadena como marcado HTML. Puede producir una vulnerabilidad de inyección de HTML o XSS si la cadena contiene datos controlados por una persona y no se tratan correctamente. No es inseguro en todos los usos, pero resulta innecesario cuando solo se quiere mostrar texto.

La aplicación utiliza:

Ejemplo 38 JavaScript
resultado.value = `${base}² = ${cuadrado}`;

La propiedad value de <output> actualiza la salida como texto. En un elemento genérico, la alternativa equivalente sería normalmente textContent. En este caso, value es más específica porque el destino es un <output>.

9.4. onclick frente a addEventListener()

El código no incluye un controlador en línea mediante el atributo onclick. En su lugar utiliza este fragmento original:

Ejemplo 39 JavaScript
formularioCuadrado.addEventListener('submit', calcularCuadrado);

Un atributo onclick no crea por sí solo una vulnerabilidad. El riesgo aparece especialmente cuando su contenido se construye concatenando datos no confiables, porque esos datos podrían alterar el código del atributo. Además, los controladores en línea dificultan una Política de Seguridad de Contenidos estricta y mezclan estructura con comportamiento.

addEventListener() ofrece estas ventajas:

  • mantiene JavaScript fuera del HTML;
  • permite registrar varios manejadores sin sobrescribir una propiedad;
  • facilita eliminar o reorganizar los eventos;
  • evita construir código ejecutable dentro de cadenas HTML;
  • en este caso, permite escuchar submit, la acción semántica correcta.

9.5. Separación de responsabilidades

La aplicación mantiene:

  • la estructura en index.html;
  • la presentación en style.css;
  • el comportamiento en script.js.

Esta separación no elimina todas las vulnerabilidades, pero facilita revisar, probar y mantener cada responsabilidad. También evita estilos y eventos en línea.

9.6. Información sensible

Todo archivo enviado al navegador puede ser inspeccionado por quien visite la página. Nunca deben guardarse en HTML, CSS o JavaScript del cliente contraseñas, claves privadas, tokens secretos o credenciales de servidor.

El código proporcionado no contiene datos sensibles. El mensaje de error lanzado por throw new Error(...) describe una incidencia técnica general y no revela credenciales ni información privada.

9.7. Dependencias y superficie de ataque

La aplicación no incorpora bibliotecas de terceros ni realiza peticiones de red. Esto reduce la superficie de ataque y evita riesgos de cadena de suministro asociados a dependencias externas. No significa que toda aplicación sin dependencias sea automáticamente segura, pero sí que aquí hay menos componentes que mantener y auditar.

10. Revisión crítica del código

10.1. Resultado general de la revisión

Se ha comprobado la sintaxis del archivo JavaScript con el analizador de Node.js y no se han detectado errores. La revisión manual conjunta de HTML, CSS y JavaScript tampoco revela errores de sintaxis, fallos lógicos o riesgos de seguridad que impidan cumplir el objetivo declarado.

Los aspectos revisados son:

  • Sintaxis: las estructuras, selectores, bloques y llamadas están correctamente cerrados.
  • Lógica: el flujo impide la recarga, valida entrada y resultado, calcula con ** 2 y presenta la salida.
  • Accesibilidad: hay semántica, etiqueta, ayuda asociada, teclado, foco visible, región de resultado y reducción de movimiento.
  • Usabilidad: instrucciones claras, acción principal reconocible, validación y respuesta inmediata.
  • Seguridad: no se usa innerHTML, no hay controladores en línea, no hay datos sensibles ni dependencias.
  • Organización: los nombres son descriptivos y las clases siguen BEM de manera coherente.
  • Código repetido o innecesario: no existe repetición significativa que justifique una refactorización.

Conclusión crítica: no es necesario reescribir los archivos para que la aplicación funcione correctamente. Las observaciones siguientes son matices didácticos o simplificaciones opcionales, no reparaciones obligatorias.

10.2. ARIA explícito en <output>: simplificación opcional

Código original

Ejemplo 40 HTML
<output
    id="resultado"
    class="result__output"
    name="cuadrado"
    for="numero"
    form="formulario-cuadrado"
    role="status"
    aria-live="polite"
    aria-atomic="true"
>El resultado aparecerá aquí.</output>

Análisis

<output> ya es un elemento semántico destinado a resultados y dispone de semántica accesible propia. Declarar además role="status", aria-live="polite" y aria-atomic="true" hace explícita la intención y puede ser útil en un ejemplo docente o como refuerzo de compatibilidad. No es un error ni un riesgo.

Si se aplica de forma estricta el principio de usar la menor cantidad de ARIA necesaria, se podría simplificar el elemento. Esta decisión debe probarse con los navegadores y tecnologías de asistencia que necesite el proyecto.

Versión simplificada opcional —no sustituye al código original del tutorial—

Ejemplo 41 HTML
<output
    id="resultado"
    class="result__output"
    name="cuadrado"
    for="numero"
    form="formulario-cuadrado"
>El resultado aparecerá aquí.</output>

Justificación

La versión simplificada confía primero en la semántica nativa. La versión original, en cambio, documenta de manera explícita el comportamiento deseado de la región. Ambas conservan la misma finalidad; no se recomienda cambiarla sin realizar pruebas de accesibilidad.

10.3. Precisión de los decimales: limitación, no error lógico

Código original

Ejemplo 42 JavaScript
const cuadrado = numero ** 2;

Análisis

El cálculo es correcto, pero ciertos decimales pueden mostrar una representación extensa debido a la coma flotante binaria de JavaScript. Una corrección concreta requeriría decidir primero una regla que el enunciado no proporciona: número de decimales, redondeo, cifras significativas o conservación de toda la precisión disponible.

Decisión adoptada

Se mantiene el fragmento original. Aplicar toFixed() arbitrariamente cambiaría la presentación y podría redondear resultados que el ejercicio pretende observar. Aquí es más riguroso explicar la limitación que inventar una política.

10.4. Validación HTML y JavaScript

Código original

Ejemplo 43 HTML
<input
    class="calculation-form__input"
    type="number"
    id="numero"
    name="numero"
    step="any"
    inputmode="decimal"
    aria-describedby="ayuda-numero"
    required
/>
Ejemplo 44 JavaScript
if (!Number.isFinite(numero)) {
    resultado.value = 'Introduce un número válido.';
    campoNumero.focus();
    return;
}

Análisis y justificación

Las dos validaciones no son una repetición innecesaria. La primera proporciona respuesta nativa antes del envío; la segunda protege la función ante invocaciones o cambios no previstos. Se conserva este enfoque por ser robusto y fácil de explicar.

10.5. Nombres y mantenimiento

Los nombres formularioCuadrado, campoNumero, resultado, numero, cuadrado y base expresan su función. Las clases BEM reflejan la pertenencia entre bloques y elementos. Los comentarios son más abundantes de lo habitual en una aplicación de producción, pero resultan apropiados para el objetivo docente y no deben considerarse código innecesario en este contexto.

11. Resumen y cierre

La aplicación parte de una estructura HTML semántica, aplica una presentación organizada mediante BEM y añade el comportamiento con JavaScript no intrusivo. El recorrido completo puede resumirse así:

  1. HTML crea el formulario y el resultado.
  2. CSS organiza la interfaz, diferencia estados y adapta la distribución.
  3. JavaScript espera el envío del formulario.
  4. Obtiene el dato con valueAsNumber.
  5. Comprueba que tanto la entrada como el resultado sean finitos.
  6. Calcula con numero ** 2.
  7. Muestra una expresión clara mediante la propiedad value de <output>.

Buenas prácticas aplicadas

  • declarar idioma, codificación y viewport;
  • usar <main>, <header>, <form>, <label>, <button>, <section> y <output> según su significado;
  • mantener una jerarquía h1h2;
  • asociar etiqueta, ayuda, campo, formulario y resultado;
  • utilizar controles nativos compatibles con teclado;
  • mostrar un foco visible;
  • respetar prefers-reduced-motion;
  • organizar CSS mediante variables y nombres BEM;
  • cargar el script con defer;
  • usar constantes descriptivas y comprobar las referencias al DOM;
  • escuchar submit con addEventListener();
  • validar antes de calcular;
  • usar escritura textual en lugar de innerHTML;
  • separar estructura, presentación y comportamiento.

Errores habituales que deben evitarse

  • romper las rutas a CSS o JavaScript;
  • hacer que for, id, aria-describedby, form o aria-labelledby apunten a valores inexistentes;
  • usar encabezados por su apariencia y no por su nivel lógico;
  • olvidar step="any" si se quieren admitir decimales;
  • registrar calcularCuadrado() con paréntesis en addEventListener();
  • olvidar preventDefault();
  • tratar el valor textual del campo como si ya fuera un número;
  • comprobar NaN mediante una comparación directa;
  • eliminar el indicador de foco sin proporcionar otro;
  • insertar datos con innerHTML cuando solo deben mostrarse como texto;
  • confiar en la validación del cliente si algún día se añade un servidor;
  • guardar secretos en archivos que se envían al navegador.

12. Actividad de comprobación

Responde sin consultar el código y después comprueba tus respuestas:

  1. ¿Qué responsabilidad corresponde a HTML, cuál a CSS y cuál a JavaScript?
  2. ¿Por qué for="numero" y id="numero" deben coincidir?
  3. ¿Qué diferencia existe entre type="number", step="any" y required?
  4. ¿Por qué se escucha el evento submit del formulario en lugar de limitarse al clic del botón?
  5. ¿Qué ocurriría si se escribiera calcularCuadrado() como segundo argumento de addEventListener()?
  6. ¿Qué devuelve valueAsNumber cuando el campo no contiene un número utilizable?
  7. ¿Por qué se comprueba también Number.isFinite(cuadrado) después de calcular?
  8. ¿Qué ventaja de seguridad aporta escribir en resultado.value en vez de usar innerHTML?
  9. ¿Qué cambio realiza la consulta @media (max-width: 42rem)?
  10. ¿Para qué sirve prefers-reduced-motion?

Soluciones orientativas

  1. HTML estructura y da significado; CSS presenta; JavaScript gestiona el comportamiento.
  2. Porque for identifica el campo etiquetado; si no coincide, se pierde esa asociación.
  3. type define el tipo de control, step permite cualquier incremento decimal y required impide el envío vacío.
  4. Porque submit representa el envío completo y también funciona con teclado.
  5. La función se ejecutaría inmediatamente durante el registro, en vez de esperar al evento.
  6. Devuelve NaN.
  7. Porque una entrada finita muy grande puede producir Infinity al elevarse al cuadrado.
  8. Evita interpretar el contenido como marcado o código HTML.
  9. Sustituye las dos columnas por una y reduce la altura mínima del resultado.
  10. Para respetar la preferencia de las personas que necesitan reducir o evitar animaciones.

13. Lista de verificación

Antes de dar por terminada la aplicación, comprueba lo siguiente:

Si todos los puntos se cumplen, la aplicación conserva su semántica, resulta utilizable con teclado, responde a distintos tamaños de pantalla y ejecuta correctamente el cálculo previsto.