Apuntes DAM
Volver al inicio

Ejercicios resueltos de Desarrollo Web en Entorno Cliente

Los 10 ejercicios de Desarrollo Web en Entorno Cliente de la web, tema a tema: cada uno con su enunciado, los datos que necesitas (código de partida, ejemplos de entrada y salida o la base de datos) y, al final, la solución explicada.

Descargar el PDF

Fundamentos de JavaScript

1. Encuentra el fallo: los turnos que se repiten

Medio · Fundamentos de JavaScript · apuntesdam.com/subject/dwec/topic/fundamentos-javascript

El programa programa tres avisos con setTimeout y cada uno debería decir su número: «Turno 1», «Turno 2» y «Turno 3». En lugar de eso, los tres dicen «Turno 4». Corrígelo cambiando una sola palabra.

Código de partida (javascript)
for (var i = 1; i <= 3; i++) {  setTimeout(() => console.log(`Turno ${i}`), 10 * i);}

Ejemplo: Tres turnos

Salida esperada

Turno 1
Turno 2
Turno 3

2. Encuentra el fallo: el pago que nunca cuadra

Medio · Fundamentos de JavaScript · apuntesdam.com/subject/dwec/topic/fundamentos-javascript

El carrito tiene dos artículos de 0,10 € y 0,20 €, y el cliente paga 0,30 €. El programa dice que falta dinero. Corrige la comparación para que funcione con importes en euros.

Código de partida (javascript)
const carrito = [0.1, 0.2];const total = carrito.reduce((a, b) => a + b, 0);const pagado = 0.3;console.log(total === pagado ? "Pago correcto" : "Falta dinero");

Ejemplo: 0,10 + 0,20 pagando 0,30

Salida esperada

Pago correcto

Objetos Predefinidos y BOM

3. Encuentra el fallo: las notas mal ordenadas

Fácil · Objetos Predefinidos y BOM · apuntesdam.com/subject/dwec/topic/objetos-predefinidos

El programa ordena unas puntuaciones de menor a mayor, pero el 10 y el 25 salen en mal sitio: «10, 2, 25, 7, 9». Corrígelo.

Código de partida (javascript)
const notas = [10, 9, 2, 7, 25];notas.sort();console.log(notas.join(", "));

Ejemplo: Cinco puntuaciones

Salida esperada

2, 7, 9, 10, 25

Funciones, Objetos y Clases

4. Encuentra el fallo: la copia que no es una copia

Fácil · Funciones, Objetos y Clases · apuntesdam.com/subject/dwec/topic/funciones-objetos

El programa hace una copia de la lista para añadir un elemento sin tocar la original. Al final las dos listas tienen 4 elementos, cuando la original debería seguir con 3. Corrígelo.

Código de partida (javascript)
const original = ["lunes", "martes", "miércoles"];const copia = original;copia.push("jueves");console.log(`Original: ${original.length}, copia: ${copia.length}`);

Ejemplo: Longitudes

Salida esperada

Original: 3, copia: 4

5. Encuentra el fallo: el contador que no cuenta

Difícil · Funciones, Objetos y Clases · apuntesdam.com/subject/dwec/topic/funciones-objetos

El método sumarTodos debería acumular en this.cuenta los números recibidos y devolver 6 para [1, 2, 3], pero devuelve 0. Corrígelo.

Código de partida (javascript)
const contador = {  cuenta: 0,  sumarTodos(numeros) {    numeros.forEach(function (n) {      this.cuenta += n;    });    return this.cuenta;  },};console.log(contador.sumarTodos([1, 2, 3]));

Ejemplo: Sumar 1, 2 y 3

Salida esperada

6

Asincronía, AJAX y Frameworks

6. Encuentra el fallo: el total que es una promesa

Medio · Asincronía, AJAX y Frameworks · apuntesdam.com/subject/dwec/topic/asincronia-ajax

precioConIva es una función asíncrona. El programa debería mostrar «Total: 121 €», pero muestra «Total: [object Promise] €». Corrígelo.

Código de partida (javascript)
async function precioConIva(precio) {  return precio * 1.21;} async function main() {  const total = precioConIva(100);  console.log(`Total: ${total} €`);}main();

Ejemplo: 100 € con IVA

Salida esperada

Total: 121 €

Ejercicios largos

7. Calendario de un mes con festivos y puentes

Medio · JavaScript · 50 minutos · apuntesdam.com/ejercicios/javascript/calendario-con-festivos-y-puentes

La intranet de una empresa quiere un calendario sencillo en el que cada departamento marque sus festivos y vea de un vistazo cuántos días se trabajan en el mes y qué puentes se pueden pedir. Antes de dibujarlo en la página, la lógica se escribe y se prueba en la consola con Node.js.

En España las semanas empiezan en lunes, pero getDay() numera los días con el domingo como 0. Además, new Date(2026, 1, 1) crea la fecha en la hora local del ordenador, y según la zona horaria el día puede cambiar al pasarla a UTC. Usar Date.UTC y los métodos getUTC… evita las dos trampas.

Un puente es un día laborable que queda encerrado entre un festivo y el fin de semana: si un festivo cae en martes, el lunes anterior; si cae en jueves, el viernes siguiente.

Requisitos

  • La primera línea es año mes (año de 1900 a 2100, mes de 1 a 12); si no es válida, escribe Fecha no válida y nada más. Cada línea siguiente es un festivo: día nombre. Una línea con un día que no existe en ese mes, o sin nombre, escribe Aviso: línea N ignorada (las líneas vacías no avisan). Si un día se repite, vale el último nombre.

  • Escribe el título (Diciembre de 2026) y la cabecera L M X J V S D. Después, una línea por semana: cada día ocupa 3 caracteres, el número alineado a la derecha en 2 y un * si es festivo o un espacio si no. La primera semana empieza con 3 espacios por cada día del mes anterior. Ninguna línea termina en espacios.

  • Festivos: y, por orden de día, una línea por festivo con dos espacios delante: 8 Inmaculada Concepción (martes). Si no hay ninguno, Festivos: ninguno.

  • Días laborables: N: los días de lunes a viernes que no son festivos.

  • Puentes posibles: lunes 7, viernes 24: los lunes anteriores a un festivo en martes y los viernes posteriores a un festivo en jueves, siempre que sean del mismo mes y no sean festivos, por orden de día y sin repetir. Si no hay ninguno, Puentes posibles: ninguno.

Formato de la entrada

  • Línea 1: año mes. Resto: un festivo por línea, día nombre.

Ejemplo: Diciembre con tres festivos

Entrada

2026 12
6 Día de la Constitución
8 Inmaculada Concepción
25 Navidad

Salida esperada

Diciembre de 2026
 L  M  X  J  V  S  D
    1  2  3  4  5  6*
 7  8* 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25*26 27
28 29 30 31
Festivos:
  6 Día de la Constitución (domingo)
  8 Inmaculada Concepción (martes)
  25 Navidad (viernes)
Días laborables: 21
Puentes posibles: lunes 7

Ejemplo: Mayo con líneas que no valen

Entrada

2026 5
1 Fiesta del Trabajo
2 Comunidad de Madrid
40 Día imposible
abc
15 San Isidro

Salida esperada

Aviso: línea 4 ignorada
Aviso: línea 5 ignorada
Mayo de 2026
 L  M  X  J  V  S  D
             1* 2* 3
 4  5  6  7  8  9 10
11 12 13 14 15*16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Festivos:
  1 Fiesta del Trabajo (viernes)
  2 Comunidad de Madrid (sábado)
  15 San Isidro (viernes)
Días laborables: 19
Puentes posibles: ninguno
Código de partida (javascript)
const lineas = require("fs").readFileSync(0, "utf8").split("\n"); const MESES = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"];const DIAS = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"]; function main() {  const [anio, mes] = (lineas[0] ?? "").trim().split(/\s+/).map(Number);  // TODO: valida la fecha, lee los festivos y dibuja el calendario  // TODO: festivos, días laborables y puentes posibles} main();

8. Reservas de pistas de pádel con clases

Difícil · JavaScript · 70 minutos · apuntesdam.com/ejercicios/javascript/reservas-de-pistas-de-padel

Un club de pádel quiere una web para reservar pistas desde el móvil. Antes de hacer la interfaz, el equipo escribe el «motor» de reservas como una clase que no sabe nada del HTML: recibe órdenes y devuelve textos. Así se puede probar en la consola con Node.js y después usarla tal cual en la página.

El club tiene 3 pistas y nueve turnos de 90 minutos cada día, de las 09:00 a las 22:30. Por la mañana y a primera hora de la tarde (turnos de las 09:00 a las 15:00) es hora valle y la pista cuesta 12 €; desde las 16:30, hora punta, 18 €. Los socios tienen un 30 % de descuento, y para que todo el mundo pueda jugar nadie puede tener más de dos reservas a la vez.

Requisitos

  • Cada línea es una orden; la palabra de la orden puede ir en mayúsculas o minúsculas. Las líneas vacías se ignoran y una orden desconocida escribe Orden desconocida: palabra.

  • SOCIOS Ana,Luis Mora añade socios (nombres separados por comas, sin espacios alrededor) y escribe Socios: N con el total de socios.

  • RESERVA pista hora nombre (el nombre puede tener espacios) comprueba, en este orden: que la pista sea 1, 2 o 3 (Pista no válida: X), que la hora sea uno de los turnos (Hora no válida: X), que la pista esté libre a esa hora (La pista P ya está reservada a las HH:MM) y que esa persona no tenga ya 2 reservas (Nombre ya tiene 2 reservas). Si todo va bien, la reserva recibe un número (1, 2, 3… sin reutilizar los cancelados) y se escribe Reserva N: pista P a las HH:MM para Nombre (X €).

  • CANCELA n escribe Reserva n cancelada y libera la pista, o No existe la reserva n.

  • LIBRES pista escribe Pista P libre a las 09:00, 10:30, … con sus turnos libres en orden, o Pista P sin horas libres, o Pista no válida: X.

  • RESUMEN escribe una línea por pista, Pista P: R de 9 horas (Q %) con el porcentaje redondeado al entero; Recaudación prevista: X € con la suma de las reservas vigentes; y Mejor cliente: Nombre (N reservas) (la persona con más reservas vigentes; si empatan, la primera alfabéticamente; «reserva» si es 1), o Mejor cliente: ninguno.

  • Los importes se escriben con dos decimales y coma decimal: 12,60 €.

Formato de la entrada

  • Una orden por línea: SOCIOS, RESERVA, CANCELA, LIBRES o RESUMEN.

Turnos y precios
TurnosTipoPrecioSocios (−30 %)
09:00, 10:30, 12:00, 13:30, 15:00valle12,00 €8,40 €
16:30, 18:00, 19:30, 21:00punta18,00 €12,60 €

Ejemplo: Una mañana de reservas

Entrada

SOCIOS Ana,Luis Mora
RESERVA 2 18:00 Ana
RESERVA 1 09:00 Pedro Gil
RESERVA 2 18:00 Pedro Gil
RESERVA 3 10:30 Ana
RESERVA 1 12:00 Ana
RESERVA 4 12:00 Eva
RESERVA 1 11:00 Eva
CANCELA 2
CANCELA 9
LIBRES 1
RESERVA 1 21:00 Luis Mora
RESUMEN

Salida esperada

Socios: 2
Reserva 1: pista 2 a las 18:00 para Ana (12,60 €)
Reserva 2: pista 1 a las 09:00 para Pedro Gil (12,00 €)
La pista 2 ya está reservada a las 18:00
Reserva 3: pista 3 a las 10:30 para Ana (8,40 €)
Ana ya tiene 2 reservas
Pista no válida: 4
Hora no válida: 11:00
Reserva 2 cancelada
No existe la reserva 9
Pista 1 libre a las 09:00, 10:30, 12:00, 13:30, 15:00, 16:30, 18:00, 19:30, 21:00
Reserva 4: pista 1 a las 21:00 para Luis Mora (12,60 €)
Pista 1: 1 de 9 horas (11 %)
Pista 2: 1 de 9 horas (11 %)
Pista 3: 1 de 9 horas (11 %)
Recaudación prevista: 33,60 €
Mejor cliente: Ana (2 reservas)

Ejemplo: Órdenes en minúsculas

Entrada

reserva 3 15:00 Marta
libres 3
resumen
borrar 1

Salida esperada

Reserva 1: pista 3 a las 15:00 para Marta (12,00 €)
Pista 3 libre a las 09:00, 10:30, 12:00, 13:30, 16:30, 18:00, 19:30, 21:00
Pista 1: 0 de 9 horas (0 %)
Pista 2: 0 de 9 horas (0 %)
Pista 3: 1 de 9 horas (11 %)
Recaudación prevista: 12,00 €
Mejor cliente: Marta (1 reserva)
Orden desconocida: borrar
Código de partida (javascript)
const lineas = require("fs").readFileSync(0, "utf8").split("\n"); const HORAS = ["09:00", "10:30", "12:00", "13:30", "15:00", "16:30", "18:00", "19:30", "21:00"];const PISTAS = 3; const euros = (centimos) => `${(centimos / 100).toFixed(2).replace(".", ",")} €`; class Reserva {  constructor(id, pista, hora, nombre, precio) {    this.id = id;    this.pista = pista;    this.hora = hora;    this.nombre = nombre;    this.precio = precio;  }} class Club {  #reservas = new Map();   // id → Reserva  #siguiente = 1;  #socios = new Set();   // TODO: anadirSocios, reservar, cancelar, libres y resumen (cada uno devuelve el texto que se escribe)} const club = new Club();for (const linea of lineas) {  // TODO: SOCIOS, RESERVA, CANCELA, LIBRES y RESUMEN}

9. Validador de formularios de registro

Difícil · JavaScript · 65 minutos · apuntesdam.com/ejercicios/javascript/validador-de-formularios-de-registro

Una web de cursos recibe cientos de altas al día. El formulario ya valida en el navegador, pero eso se puede saltar (basta con desactivar JavaScript o enviar la petición a mano), así que el equipo quiere la misma validación en una función que se pueda ejecutar en el cliente y en el servidor con Node.js. La prueba se hace con un fichero de formularios en JSON, uno por línea.

Cada campo tiene sus reglas y el usuario debe recibir un único mensaje por campo, el del primer problema que se encuentre: no tiene sentido decirle a la vez que la contraseña es corta y que le faltan números. Los nombres pueden tener tildes, eñes, apóstrofos (O'Brien) y guiones (Ruiz-Pérez), así que [a-zA-Z] no sirve: hace falta la clase Unicode \p{L}.

Para que los resultados no dependan del día en que se ejecuten, la edad se calcula a una fecha de referencia fija: el 1 de septiembre de 2026.

Requisitos

  • Cada línea no vacía es un formulario en JSON. Si no es JSON válido, o no es un objeto, escribe Formulario N: JSON no válido (N cuenta todas las líneas no vacías). Los valores se convierten a texto y se les quitan los espacios de los extremos; un campo que falta o es null cuenta como vacío.

  • nombre: obligatorio; entre 2 y 40 caracteres; empieza por una letra y solo tiene letras (de cualquier idioma), espacios, apóstrofos y guiones (solo letras, espacios, apóstrofos y guiones).

  • email: obligatorio; algo sin espacios ni arrobas, una @, algo sin espacios ni arrobas, un punto y al menos dos letras al final (formato no válido).

  • password: obligatoria; al menos 8 caracteres; con mayúsculas, minúsculas y números (necesita mayúsculas, minúsculas y números); y si la primera palabra del nombre tiene 3 letras o más, no puede contenerla sin distinguir mayúsculas (no puede contener tu nombre). confirmacion: si hay contraseña, tiene que ser igual (no coincide con la contraseña).

  • nacimiento: obligatoria, con formato AAAA-MM-DD y una fecha que exista (fecha no válida); la persona tiene que haber cumplido 16 años el 1 de septiembre de 2026 (hay que tener al menos 16 años).

  • cp: obligatorio; 5 cifras con las dos primeras entre 01 y 52 (código postal no válido). telefono: opcional; si se escribe, quitando los espacios deben quedar 9 cifras que empiecen por 6, 7, 8 o 9 (teléfono no válido). Un campo vacío obligatorio da obligatorio.

  • Para cada formulario: Formulario N: correcto, o Formulario N: E errores («error» si es uno) seguido de una línea por campo con error, en el orden nombre, email, password, confirmacion, nacimiento, cp, telefono: - campo: mensaje. Las reglas de cada campo se comprueban en el orden en que aparecen aquí y solo se muestra el primer fallo.

  • Al final, Correctos: C de N y Campo con más errores: campo (veces) (si empatan, el primero en el orden de los campos) o Campo con más errores: ninguno.

Formato de la entrada

  • Un formulario por línea, como objeto JSON con los campos nombre, email, password, confirmacion, nacimiento, cp y telefono.

Ejemplo: Tres altas

Entrada

{"nombre": "Lucía Martín-Ruiz", "email": "lucia@correo.es", "password": "Clave2026", "confirmacion": "Clave2026", "nacimiento": "2004-05-17", "cp": "28013", "telefono": "612 345 678"}
{"nombre": "Ana", "email": "ana@correo", "password": "anaPerez99", "confirmacion": "anaperez99", "nacimiento": "2011-02-30", "cp": "53001"}
{"nombre": "O'Brien", "email": "obrien@mail.ie", "password": "Dublin1916", "confirmacion": "Dublin1916", "nacimiento": "2010-09-01", "cp": 8001}

Salida esperada

Formulario 1: correcto
Formulario 2: 5 errores
  - email: formato no válido
  - password: no puede contener tu nombre
  - confirmacion: no coincide con la contraseña
  - nacimiento: fecha no válida
  - cp: código postal no válido
Formulario 3: 1 error
  - cp: código postal no válido
Correctos: 1 de 3
Campo con más errores: cp (2)

Ejemplo: Formularios incompletos o mal escritos

Entrada

{"email": "  ", "password": "corta"}
{nombre: "sin comillas"}
[1, 2, 3]
{"nombre": "Ñuño Álvarez", "email": "nuno@x.com", "password": "abcdefgh", "nacimiento": "2010-08-31", "cp": "01001", "telefono": "91234567"}

Salida esperada

Formulario 1: 6 errores
  - nombre: obligatorio
  - email: obligatorio
  - password: al menos 8 caracteres
  - confirmacion: no coincide con la contraseña
  - nacimiento: obligatorio
  - cp: obligatorio
Formulario 2: JSON no válido
Formulario 3: JSON no válido
Formulario 4: 3 errores
  - password: necesita mayúsculas, minúsculas y números
  - confirmacion: no coincide con la contraseña
  - telefono: teléfono no válido
Correctos: 0 de 4
Campo con más errores: password (2)
Código de partida (javascript)
const lineas = require("fs").readFileSync(0, "utf8").split("\n"); const HOY = new Date(Date.UTC(2026, 8, 1));   // fecha de referencia: 1 de septiembre de 2026const CAMPOS = ["nombre", "email", "password", "confirmacion", "nacimiento", "cp", "telefono"]; /** Errores de un formulario: un objeto campo → mensaje, como mucho uno por campo. */function validar(datos) {  const errores = {};  // TODO: las reglas de cada campo  return errores;} // TODO: lee cada línea como JSON, valida y escribe el resultado y el resumen

10. Cargador de datos asíncrono con reintentos y dependencias

Muy difícil · JavaScript · 90 minutos · apuntesdam.com/ejercicios/javascript/cargador-asincrono-con-reintentos

El panel de control de una tienda online pide al arrancar los usuarios, los productos, los pedidos, las estadísticas… cada dato a una API distinta. Pedirlos uno detrás de otro haría esperar al usuario la suma de todos los tiempos; pedirlos a la vez, solo lo que tarde el más lento.

Pero las redes fallan. Una petición puede devolver un error (y quizá funcione si se repite) o quedarse colgada para siempre: por eso cada petición tiene un tiempo límite de 100 ms y se reintenta hasta 3 veces. Además, algunos datos dependen de otros (los pedidos necesitan los usuarios): esos tienen que esperar a sus dependencias, y si alguna falla, no se piden.

Como no hay APIs reales, se simulan con setTimeout: cada recurso tarda unos milisegundos y falla las primeras veces que se indique. El informe final va en el orden de la entrada, no en el de llegada, para que sea siempre el mismo.

Requisitos

  • Cada línea es nombre ms fallos [dependencias]: lo que tarda cada petición en milisegundos, cuántas de las primeras peticiones fallan con error del servidor y, opcionalmente, los nombres de los recursos de los que depende separados por comas (sin espacios). Las líneas vacías se ignoran. Una línea con otro formato o con un nombre repetido escribe Aviso: línea N ignorada. No hay dependencias circulares.

  • Todos los recursos empiezan a cargarse a la vez, salvo los que tienen dependencias, que esperan a que terminen todas ellas. Si alguna dependencia no se ha cargado (o no existe), el recurso no se pide.

  • Cada intento dura lo que diga ms, pero si pasan 100 ms sin respuesta se da por fallido con tiempo agotado. Se hacen hasta 3 intentos, uno detrás de otro, y el motivo de error que se informa es el del último.

  • Al terminar todo, una línea por recurso en el orden de la entrada: nombre: cargado (N intentos) («intento» si es 1), nombre: error tras 3 intentos (motivo) u nombre: omitido (depende de dep, que no se ha podido cargar) / (depende de dep, que no existe), indicando la primera dependencia de su lista que ha fallado.

  • Para terminar, Cargados: C de T · Peticiones: P, donde P es el número total de peticiones simuladas que se han hecho.

Formato de la entrada

  • Un recurso por línea: nombre ms fallos y, opcionalmente, dep1,dep2.

Ejemplo: Un panel con seis recursos

Entrada

usuarios 30 0
productos 20 2
fotos 150 0
pedidos 10 0 usuarios,productos
galeria 10 0 fotos
informe 10 0 pedidos,stock

Salida esperada

usuarios: cargado (1 intento)
productos: cargado (3 intentos)
fotos: error tras 3 intentos (tiempo agotado)
pedidos: cargado (1 intento)
galeria: omitido (depende de fotos, que no se ha podido cargar)
informe: omitido (depende de stock, que no existe)
Cargados: 3 de 6 · Peticiones: 8

Ejemplo: Errores y líneas no válidas

Entrada

a 10 3
b 10 1
b 20 0
c diez 0
d 5 0 a

Salida esperada

Aviso: línea 3 ignorada
Aviso: línea 4 ignorada
a: error tras 3 intentos (error del servidor)
b: cargado (2 intentos)
d: omitido (depende de a, que no se ha podido cargar)
Cargados: 1 de 3 · Peticiones: 5
Código de partida (javascript)
const lineas = require("fs").readFileSync(0, "utf8").split("\n"); const LIMITE_MS = 100;const INTENTOS = 3;let peticiones = 0; /** Petición simulada: tarda «ms» y falla mientras el intento sea menor o igual que «fallos». */function peticion(recurso, intento) {  peticiones++;  return new Promise((resolve, reject) => {    setTimeout(() => (intento <= recurso.fallos ? reject(new Error("error del servidor")) : resolve(recurso.nombre)), recurso.ms);  });} /** La primera promesa que termine gana: la petición o el temporizador. */function conTiempoLimite(promesa, ms) {  // TODO} async function conReintentos(recurso) {  // TODO} async function main() {  // TODO: lee los recursos, cárgalos respetando las dependencias y escribe el informe} main();