CAPÍTULO 03 / APRENDE LA WEB

Haz que la página responda.

JavaScript lee datos, toma decisiones y reacciona a eventos. Conecta la estructura HTML con las acciones de quien la usa.

11 TEMAS · ESCRIBE · EJECUTA · COMPARA

Aprende JavaScript paso a paso, escribiendo.

Esta ruta empieza por el lenguaje y termina con una petición real. En cada lección carga el ejercicio, escribe tu solución en el laboratorio y prueba el resultado.

LECCIÓN 1 DE 11

La consola y console.log

La consola permite observar valores sin colocarlos en la interfaz. console.log muestra información y ayuda a seguir el programa.

Ahora escribe

Escribe un saludo y el resultado de 2 + 3 en la consola.

Ver pista

console.log puede recibir varios argumentos.

Ver solución explicada
console.log("Hola, JavaScript");
console.log("Resultado:", 2 + 3);

Verás el saludo y el número 5.

LECCIÓN 2 DE 11

Variables y tipos

const conserva la referencia; let permite reasignarla. String, number y boolean representan texto, números y condiciones. typeof permite observar algunos tipos.

Ahora escribe

Declara un nombre, una edad y una condición. Muestra sus valores y tipos.

Ver pista

Pon comillas al texto; los números y booleanos no necesitan comillas.

Ver solución explicada
const nombre = "Ana";
let edad = 20;
const estudia = true;
console.log(nombre, typeof nombre);
console.log(edad, typeof edad);
console.log(estudia, typeof estudia);

String, number y boolean. Recuerda que typeof null es una excepción histórica: devuelve object.

LECCIÓN 3 DE 11

Operadores

Los operadores combinan, comparan o transforman valores: +, -, *, /, %, ===, !==, >, &&, || y !.

Ahora escribe

Calcula el total de tres unidades de un producto y comprueba si supera 50.

Ver pista

Multiplica precio por cantidad y compara el total con >.

Ver solución explicada
const precio = 18;
const cantidad = 3;
const total = precio * cantidad;
console.log("Total:", total);
console.log("Supera 50:", total > 50);
console.log("Comparación estricta:", 5 === "5");

Total 54; supera 50: true. 5 === "5" es false. El + también concatena cadenas.

LECCIÓN 4 DE 11

Condicionales: if / else

if ejecuta un bloque cuando una condición es verdadera; else ejecuta la alternativa.

Ahora escribe

Muestra un mensaje distinto si la edad es mayor o igual que 18. Cambia la edad y vuelve a ejecutar.

Ver pista

Usa >= en la condición y llaves para cada bloque.

Ver solución explicada
const edad = 17;
if (edad >= 18) {
  console.log("Mayor de edad");
} else {
  console.log("Menor de edad");
}

Con 17 verás Menor de edad; con 18 se ejecutará la primera rama.

LECCIÓN 5 DE 11

Funciones

Una función agrupa instrucciones. Los parámetros son entradas y return devuelve un resultado y termina esa ejecución.

Ahora escribe

Escribe sumar(a, b) y muestra el resultado de sumar(4, 6).

Ver pista

Devuelve a + b. Llama a la función para obtener el valor.

Ver solución explicada
function sumar(a, b) {
  return a + b;
}
console.log(sumar(4, 6));

Obtendrás 10. Declarar la función no es lo mismo que ejecutarla.

LECCIÓN 6 DE 11

Arrays

Un array es una colección ordenada. Sus índices empiezan en 0; length cuenta elementos y push añade al final.

Ahora escribe

Crea tres temas, añade uno más y consulta el primero y la cantidad.

Ver pista

Usa [0], push y length.

Ver solución explicada
const temas = ["HTML", "CSS", "JavaScript"];
temas.push("Accesibilidad");
console.log("Primero:", temas[0]);
console.log("Cantidad:", temas.length);
console.log(temas);

El primero es HTML y la cantidad es 4. const permite modificar el array, pero no reasignar su referencia.

LECCIÓN 7 DE 11

Bucles: for y forEach

for controla inicio, condición y actualización. forEach llama una función por cada elemento de un array y devuelve undefined.

Ahora escribe

Recorre los mismos tres temas con for y con forEach.

Ver pista

El índice debe ser menor que length, no menor o igual.

Ver solución explicada
const temas = ["HTML", "CSS", "JavaScript"];
for (let i = 0; i < temas.length; i++) {
  console.log("for:", temas[i]);
}
temas.forEach((tema, indice) => {
  console.log("forEach:", indice, tema);
});

Verás los tres temas dos veces. forEach no admite break ni espera automáticamente callbacks async.

LECCIÓN 8 DE 11

Objetos (y así vienen las APIs)

Un objeto agrupa propiedades. Muchas APIs devuelven JSON, que se interpreta para obtener objetos o arrays de JavaScript. JSON es texto, no un objeto ya construido.

Ahora escribe

Consulta nombre y ciudad en un objeto; interpreta después un JSON de ejemplo.

Ver pista

Usa el punto para propiedades y JSON.parse para el texto.

Ver solución explicada
const persona = { nombre: "Ana", ciudad: "La Paz" };
console.log(persona.nombre, persona.ciudad);
const texto = '{"id":1,"titulo":"Aprender","completada":false}';
const tarea = JSON.parse(texto);
console.log(tarea.titulo, tarea.completada);

Observa las propiedades. En JSON las claves y cadenas usan comillas dobles; no todas las APIs utilizan JSON.

LECCIÓN 9 DE 11

El DOM

El DOM representa el documento como nodos. querySelector consulta uno; textContent modifica texto sin interpretar etiquetas HTML.

Ahora escribe

Selecciona #resultado y cambia su texto al ejecutar el programa.

Ver pista

El elemento ya está en el HTML; usa su id en el selector.

Ver solución explicada
const resultado = document.querySelector("#resultado");
resultado.textContent = "JavaScript cambió este párrafo.";
console.log(resultado.tagName);

El párrafo cambia en la vista previa. La consola muestra P. Un selector sin coincidencia devuelve null.

LECCIÓN 10 DE 11

Eventos

Un listener registra una función para cuando ocurre un evento. Click e input no son instrucciones que se ejecutan al registrar el callback.

Ahora escribe

Haz que #accion cambie el resultado solo cuando lo pulses.

Ver pista

Usa addEventListener con click y una función.

Ver solución explicada
const boton = document.querySelector("#accion");
const resultado = document.querySelector("#resultado");
boton.addEventListener("click", () => {
  resultado.textContent = "Evento recibido.";
  console.log("Click recibido");
});

El texto no cambia hasta pulsar el botón. Prueba también activarlo con teclado.

LECCIÓN 11 DE 11

fetch + async / await

fetch pide un recurso por red. async permite usar await; response.ok comprueba el estado HTTP y response.json interpreta el JSON. try/catch maneja fallos.

Ahora escribe

Al pulsar #accion, consulta una tarea ficticia de JSONPlaceholder y muestra su título.

Ver pista

Usa una función async, comprueba response.ok y captura el error.

Ver solución explicada
async function cargarTarea() {
  const resultado = document.querySelector("#resultado");
  resultado.textContent = "Cargando…";
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/todos/1", { signal: AbortSignal.timeout(10000) });
    if (!response.ok) throw new Error("HTTP " + response.status);
    const tarea = await response.json();
    resultado.textContent = tarea.title;
    console.log("Tarea:", tarea);
  } catch (error) {
    resultado.textContent = "No se pudo cargar. Reintenta.";
    console.log(error.message);
  }
}
document.querySelector("#accion").addEventListener("click", cargarTarea);

Necesitas internet. Solo este ejercicio habilita conexión a JSONPlaceholder; utiliza datos ficticios y no requiere clave. fetch no rechaza por sí solo un HTTP 404.

Documentación de JSONPlaceholder. La consulta se realiza al pulsar el botón del ejemplo.

DESDE EL PRIMER ARCHIVO HASTA EL RESULTADO

Conecta JavaScript a la página que construiste.

Cada paso acumula lo anterior. Lee la explicación, observa el resultado y prueba una modificación. El laboratorio adapta los archivos separados a un marco aislado.

Qué añadimos
Dónde va y en qué orden
Qué deberías ver
¿Qué pasa si…?

Escribe antes de mirar la solución

El editor conserva el programa del paso anterior. Completa la nueva parte siguiendo el objetivo y pulsa «Aplicar mis cambios». Observa la consola y prueba los controles de la página.

Ver una pista de código

No necesitas memorizar un fragmento exacto: puede haber otras soluciones válidas. Al cambiar de paso se carga un nuevo punto de partida y se descartan las ediciones de este paso.

Consola y comportamiento


            
Ver código usado en la variante

El navegador normaliza el HTML al analizarlo. La vista previa incrusta CSS y JavaScript; en tus archivos mantendrás sus enlaces externos.

HTML

CSS

JavaScript

FUNDAMENTOS

De una instrucción a un comportamiento.

¿Qué es JavaScript?

Un lenguaje de programación con tipos dinámicos. En el navegador utiliza APIs web, como DOM y fetch, para interactuar con el documento y la red. Estas APIs no son lo mismo que el lenguaje ECMAScript.

Datos, decisiones y funciones

const nombre = "Ana";
let puntos = 0;

function sumarPunto() {
  puntos += 1;
  return `${nombre}: ${puntos}`;
}

Un programa combina valores, condiciones, repeticiones y funciones. Los nombres deben comunicar su propósito.

Cómo se conecta a la página

<script src="js/app.js" defer></script>

// Dentro de app.js
const boton = document.querySelector("button");
boton.addEventListener("click", () => {
  document.querySelector("p").textContent = "Funcionó";
});

defer permite que el HTML se analice antes de ejecutar el script externo. El evento click no se ejecuta al registrar la función: se ejecuta cuando ocurre la acción.

Errores que ayudan a aprender

Lee la consola, distingue errores de sintaxis y de ejecución, y prueba una modificación cada vez. El código que falla necesita una explicación útil, no una pantalla vacía.

UNA HISTORIA DE INTERACCIÓN

De scripts pequeños a aplicaciones.

  1. 1995

    Nacimiento en Netscape

    Brendan Eich creó el lenguaje que pasó por los nombres Mocha y LiveScript antes de llamarse JavaScript. Java y JavaScript son lenguajes distintos.

  2. 1997

    ECMAScript

    Ecma International publicó la primera edición del estándar del lenguaje.

  3. 2009

    ECMAScript 5

    Añadió herramientas como modo estricto y métodos de arrays ampliamente utilizados.

  4. 2015–hoy

    Módulos y evolución continua

    ES2015 incorporó let, const, clases, promesas y módulos. TC39 continúa desarrollando nuevas características; los motores implementan el estándar.

OBSERVA EL ESTADO

Un evento cambia un valor.

Contador interactivo

0

El valor cambia en JavaScript; CSS conserva el estilo y HTML conserva la estructura.

Qué sucede al pulsar

let contador = 0;

boton.addEventListener("click", () => {
  contador += 1;
  salida.textContent = contador;
});
  1. El navegador detecta el evento.
  2. La función actualiza el estado.
  3. textContent muestra el nuevo valor.

CONSULTA CUANDO LO NECESITES

Tu referencia de JavaScript

Conceptos del lenguaje, métodos habituales y APIs web importantes. No existe una lista de “etiquetas JavaScript”: se estudian declaraciones, operadores, funciones, objetos y métodos.

letBases

Declara una variable que puede recibir otro valor.

Cuándo usarlo
Para estados que cambian.
Cuándo evitarlo
No uses let si la referencia nunca se reasigna.
let contador = 0;
contador += 1;
Consultar documentación
constBases

Declara una referencia que no se puede reasignar.

Cuándo usarlo
Como opción habitual para valores y referencias estables.
Cuándo evitarlo
No significa que un objeto o array sea inmutable.
const persona = { nombre: "Ana" };
persona.nombre = "Luis";
Consultar documentación
varBases

Declaración antigua con alcance de función y hoisting.

Cuándo usarlo
Para comprender código existente.
Cuándo evitarlo
En código nuevo suele ser preferible let o const.
function ejemplo() { var numero = 1; }
Consultar documentación
Tipos de datosBases

Primitivos y objetos definen qué operaciones tienen sentido.

Cuándo usarlo
Distingue string, number, boolean, undefined, null, bigint y symbol.
Cuándo evitarlo
No asumas que typeof null devuelve null: devuelve object.
typeof "hola"; // "string"
typeof 42; // "number"
Consultar documentación
=== y !==Bases

Comparación estricta sin conversión implícita de tipo.

Cuándo usarlo
Para comparaciones predecibles.
Cuándo evitarlo
Dos objetos distintos no son iguales aunque sus propiedades coincidan.
5 === "5"; // false
Consultar documentación
Conversión de tiposBases

Transforma explícitamente valores.

Cuándo usarlo
Al leer entradas de formularios.
Cuándo evitarlo
Number("") es 0; valida también el texto vacío.
const numero = Number("42");
Number.isNaN(numero); // false
Consultar documentación
Template literalsBases

Texto entre acentos graves con interpolación.

Cuándo usarlo
Para construir mensajes.
Cuándo evitarlo
No confundir interpolación con escapar HTML.
const nombre = "Ana";
console.log(`Hola, ${nombre}`);
Consultar documentación
Operadores &&, || y ??Bases

Combinan condiciones o eligen valores alternativos.

Cuándo usarlo
?? conserva 0, false y cadena vacía.
Cuándo evitarlo
|| reemplaza cualquier valor falsy, no solo null.
const cantidad = 0;
console.log(cantidad ?? 10); // 0
Consultar documentación
if / elseControl y funciones

Ejecuta ramas según una condición.

Cuándo usarlo
Para decisiones de lógica.
Cuándo evitarlo
Evita condiciones difíciles de leer o asignaciones accidentales.
if (edad >= 18) { console.log("Mayor"); } else { console.log("Menor"); }
Consultar documentación
switchControl y funciones

Elige una rama entre casos con comparación estricta.

Cuándo usarlo
Cuando hay varios casos discretos.
Cuándo evitarlo
Sin break puede continuar al siguiente caso.
switch (tipo) {
  case "html": console.log("Estructura"); break;
  default: console.log("Otro");
}
Consultar documentación
forControl y funciones

Bucle con inicio, condición y actualización.

Cuándo usarlo
Para repetir con índice.
Cuándo evitarlo
Una condición que no termina bloquea la ejecución.
for (let i = 0; i < 3; i++) console.log(i);
Consultar documentación
for...ofControl y funciones

Recorre valores de un iterable.

Cuándo usarlo
Para arrays, strings y otras colecciones iterables.
Cuándo evitarlo
No sirve directamente para objetos planos.
for (const nombre of ["Ana", "Luis"]) console.log(nombre);
Consultar documentación
whileControl y funciones

Repite mientras una condición siga siendo verdadera.

Cuándo usarlo
Cuando no conoces el número de repeticiones.
Cuándo evitarlo
Actualiza el estado para evitar bucles infinitos.
let n = 0;
while (n < 3) { n++; }
Consultar documentación
function y returnControl y funciones

Declara una operación reutilizable y puede devolver un resultado.

Cuándo usarlo
Para separar responsabilidades.
Cuándo evitarlo
return termina la función; sin retorno explícito devuelve undefined.
function sumar(a, b) { return a + b; }
console.log(sumar(2, 3));
Consultar documentación
Funciones flechaControl y funciones

Sintaxis breve y this léxico.

Cuándo usarlo
Para callbacks y funciones pequeñas.
Cuándo evitarlo
No reemplaza métodos que necesitan su propio this ni constructores.
const duplicar = numero => numero * 2;
Consultar documentación
ScopeControl y funciones

Alcance en que un identificador es accesible.

Cuándo usarlo
Para mantener variables cerca de su uso.
Cuándo evitarlo
let y const dentro de un bloque no existen fuera de él.
{ const secreto = 1; }
// secreto no está disponible aquí.
Consultar documentación
ClosureControl y funciones

Una función conserva acceso al entorno donde se creó.

Cuándo usarlo
Para estado privado y callbacks.
Cuándo evitarlo
No confundas el entorno capturado con una copia congelada del valor.
function crearContador() {
  let n = 0;
  return () => ++n;
}
Consultar documentación
ArrayColecciones

Colección ordenada de valores.

Cuándo usarlo
Para listas, resultados y registros.
Cuándo evitarlo
Los índices empiezan en 0; length no es el último índice.
const temas = ["HTML", "CSS", "JS"];
console.log(temas[0]);
Consultar documentación
map()Colecciones

Devuelve un nuevo array transformando cada elemento.

Cuándo usarlo
Para producir otra colección.
Cuándo evitarlo
Si solo haces efectos secundarios usa forEach.
[1, 2, 3].map(n => n * 2); // [2, 4, 6]
Consultar documentación
filter()Colecciones

Devuelve elementos que pasan una condición.

Cuándo usarlo
Para búsquedas y filtros.
Cuándo evitarlo
No transforma el contenido de cada elemento.
[1, 2, 3, 4].filter(n => n % 2 === 0);
Consultar documentación
find()Colecciones

Devuelve la primera coincidencia o undefined.

Cuándo usarlo
Para encontrar un elemento.
Cuándo evitarlo
No es un array de todas las coincidencias.
[2, 5, 8].find(n => n > 4); // 5
Consultar documentación
reduce()Colecciones

Acumula un resultado recorriendo una colección.

Cuándo usarlo
Para sumas u otras agregaciones claras.
Cuándo evitarlo
No lo uses si un bucle resulta mucho más legible.
[2, 3, 4].reduce((total, n) => total + n, 0);
Consultar documentación
sort()Colecciones

Ordena el array en el lugar.

Cuándo usarlo
Con comparador para números.
Cuándo evitarlo
Sin comparador convierte valores para ordenar como texto.
[10, 2, 30].sort((a, b) => a - b);
Consultar documentación
ObjetoColecciones

Agrupa propiedades con claves.

Cuándo usarlo
Para representar una entidad o configuración.
Cuándo evitarlo
No confundas copia superficial con copia profunda.
const curso = { titulo: "HTML", nivel: 1 };
console.log(curso.titulo);
Consultar documentación
Desestructuración y spreadColecciones

Extrae propiedades o expande colecciones.

Cuándo usarlo
Para código breve con nombres claros.
Cuándo evitarlo
spread copia un nivel; los objetos anidados siguen compartidos.
const { titulo } = curso;
const copia = { ...curso };
Consultar documentación
Map y SetColecciones

Map asocia claves a valores; Set conserva valores únicos.

Cuándo usarlo
Para diccionarios o eliminar duplicados.
Cuándo evitarlo
No son equivalentes directos a objetos o arrays en JSON.
const unicos = [...new Set([1, 1, 2])];
Consultar documentación
JSONColecciones

Formato de datos; parse convierte texto y stringify produce texto.

Cuándo usarlo
Para intercambio y almacenamiento.
Cuándo evitarlo
No uses eval para interpretar JSON; parse puede lanzar errores.
const datos = JSON.parse('{"nombre":"Ana"}');
Consultar documentación
querySelector()DOM y eventos

Busca el primer elemento que coincide con un selector CSS.

Cuándo usarlo
Para conectar una interacción con HTML.
Cuándo evitarlo
Puede devolver null; revisa la existencia antes de usarlo.
const boton = document.querySelector("button");
Consultar documentación
querySelectorAll()DOM y eventos

Devuelve una NodeList estática de coincidencias.

Cuándo usarlo
Para aplicar lógica a varios elementos.
Cuándo evitarlo
No es un array ni se actualiza al insertar elementos nuevos.
document.querySelectorAll("button").forEach(b => console.log(b));
Consultar documentación
textContentDOM y eventos

Lee o asigna texto de un nodo.

Cuándo usarlo
Para insertar texto y datos externos.
Cuándo evitarlo
No interpreta etiquetas HTML; eso suele ser una ventaja.
document.querySelector("p").textContent = "Hola";
Consultar documentación
classListDOM y eventos

Añade, quita o alterna clases de CSS.

Cuándo usarlo
Para reflejar estados sin mezclar estilos extensos en JS.
Cuándo evitarlo
Cambiar una clase no cambia aria-expanded automáticamente.
panel.classList.toggle("activo");
Consultar documentación
addEventListener()DOM y eventos

Registra una función para un evento.

Cuándo usarlo
Para click, input, submit y teclado.
Cuándo evitarlo
Usa elementos nativos y evita registrar duplicados sin necesidad.
boton.addEventListener("click", () => console.log("Click"));
Consultar documentación
event y preventDefault()DOM y eventos

El evento aporta contexto; preventDefault cancela su acción por defecto si es cancelable.

Cuándo usarlo
Para gestionar un formulario con JavaScript.
Cuándo evitarlo
No impide la propagación; eso es otro comportamiento.
form.addEventListener("submit", event => {
  event.preventDefault();
});
Consultar documentación
createElement y appendDOM y eventos

Crean e insertan nodos.

Cuándo usarlo
Cuando necesitas contenido dinámico.
Cuándo evitarlo
Para estructura repetida, una plantilla HTML facilita mantenimiento.
const p = document.createElement("p");
p.textContent = "Resultado";
document.body.append(p);
Consultar documentación
datasetDOM y eventos

Accede a atributos data-* como cadenas.

Cuándo usarlo
Para relacionar controles con información propia.
Cuándo evitarlo
No lo uses para secretos ni olvides convertir números.
const id = boton.dataset.id;
Consultar documentación
PromiseAsincronía y errores

Representa un resultado futuro pendiente, resuelto o rechazado.

Cuándo usarlo
Para operaciones asíncronas.
Cuándo evitarlo
No equivale a ejecutar trabajo pesado en otro hilo.
Promise.resolve(42).then(valor => console.log(valor));
Consultar documentación
async / awaitAsincronía y errores

Permite escribir operaciones asíncronas con una secuencia clara.

Cuándo usarlo
Para esperar promesas dentro de funciones async.
Cuándo evitarlo
await no bloquea todo el navegador, pero las operaciones síncronas pesadas sí pueden hacerlo.
async function cargar() {
  const valor = await Promise.resolve(42);
  return valor;
}
Consultar documentación
fetch()Asincronía y errores

API web que solicita un recurso por red.

Cuándo usarlo
Para APIs y archivos públicos.
Cuándo evitarlo
Un HTTP 404 no rechaza por sí solo la promesa: comprueba response.ok.
const response = await fetch("datos.json");
if (!response.ok) throw new Error("No disponible");
const datos = await response.json();
Consultar documentación
try / catch / finallyAsincronía y errores

Gestiona errores y limpieza.

Cuándo usarlo
Para operaciones que pueden fallar.
Cuándo evitarlo
No ocultes errores sin explicar qué puede hacer el usuario.
try { JSON.parse("incorrecto"); }
catch (error) { console.log(error.message); }
Consultar documentación
setTimeout()Asincronía y errores

Programa un callback tras una demora mínima.

Cuándo usarlo
Para acciones diferidas.
Cuándo evitarlo
No garantiza ejecutar exactamente en ese milisegundo.
setTimeout(() => console.log("Después"), 500);
Consultar documentación
Promise.all()Asincronía y errores

Espera varias promesas y rechaza si una falla.

Cuándo usarlo
Para consultas independientes en paralelo.
Cuándo evitarlo
Si necesitas resultados parciales considera allSettled.
const valores = await Promise.all([Promise.resolve(1), Promise.resolve(2)]);
Consultar documentación
import / exportOrganización

Comparte código entre módulos.

Cuándo usarlo
Para separar funcionalidades grandes.
Cuándo evitarlo
En el navegador requiere script type=module y rutas válidas.
export function sumar(a, b) { return a + b; }
Consultar documentación
classOrganización

Define una estructura para crear objetos con métodos.

Cuándo usarlo
Cuando varias instancias comparten comportamiento.
Cuándo evitarlo
No hace falta una clase para cada función pequeña.
class Contador {
  constructor() { this.valor = 0; }
  sumar() { this.valor++; }
}
Consultar documentación
localStorageOrganización

API web de almacenamiento local como cadenas.

Cuándo usarlo
Para preferencias o estado local sencillo.
Cuándo evitarlo
No para contraseñas; puede fallar o no estar disponible.
localStorage.setItem("tema", "claro");
const tema = localStorage.getItem("tema");
Consultar documentación
console.log()Organización

Muestra información de depuración.

Cuándo usarlo
Para observar valores durante el aprendizaje.
Cuándo evitarlo
No sustituye mensajes de interfaz ni publiques datos sensibles.
console.log("Resultado:", 2 + 3);
Consultar documentación

CAMBIA · EJECUTA · OBSERVA

Tu laboratorio de JavaScript

Edita el ejemplo y pulsa Ejecutar. El resultado y el código permanecen separados.

Código editable

Resultado en el navegador

Vista previa

Consola del ejemplo


          

Solo ejecuta código que entiendas. La vista previa está aislada del sitio. Los cambios del editor no se guardan al cerrar la página.

Una petición también necesita estados.

Al consultar una API, piensa en carga, éxito, ausencia de resultados y error. La interfaz debe explicar cada estado. Este es un patrón de referencia; no necesita una petición externa para practicar el laboratorio.

async function cargarDatos() {
  mensaje.textContent = "Cargando…";
  try {
    const response = await fetch("datos.json");
    if (!response.ok) throw new Error("No disponible");
    const datos = await response.json();
    mensaje.textContent = `${datos.length} resultados`;
  } catch (error) {
    mensaje.textContent = "No se pudo cargar. Inténtalo de nuevo.";
  }
}

Piensa antes de ejecutar.

¿Qué devuelve [1, 2, 3].map(n => n * 2)?