Si ya dominás variables, condicionales, ciclos, funciones y arrays, estos 20 ejercicios avanzados de JavaScript te permiten practicar problemas más cercanos a aplicaciones reales.
La guía combina manipulación de datos, reduce(), ordenamiento, paginación, asincronía, fetch(), Promises, localStorage, patrones como debounce y throttle, y un pequeño proyecto final.
Antes de empezar, podés repasar 20 ejercicios resueltos de JavaScript para principiantes y 20 ejercicios resueltos de arrays en JavaScript.
Índice de ejercicios
- Contar la frecuencia de palabras en un texto
- Agrupar objetos por una propiedad con reduce()
- Ordenar objetos por múltiples criterios
- Crear una búsqueda con varios filtros
- Implementar paginación de un array
- Crear un carrito de compras
- Calcular subtotal, descuento e impuestos
- Transformar datos anidados de una API
- Eliminar objetos duplicados por id
- Comparar dos arrays y detectar cambios
- Crear una función debounce()
- Crear una función throttle()
- Ejecutar tareas asíncronas en secuencia
- Ejecutar varias Promises en paralelo
- Manejar errores con async/await
- Consumir una API y filtrar resultados
- Reintentar una petición fallida
- Guardar objetos complejos en localStorage
- Crear un sistema de eventos personalizado
- Mini proyecto: buscador de usuarios desde una API
1. Contar la frecuencia de palabras en un texto
Enunciado: Contar cuántas veces aparece cada palabra ignorando mayúsculas y signos básicos.
const texto = "JavaScript es potente. JavaScript es flexible."; const palabras = texto .toLowerCase() .replace(/[.,]/g, "") .split(" "); const frecuencia = {}; for (const palabra of palabras) { frecuencia[palabra] = (frecuencia[palabra] || 0) + 1; } console.log(frecuencia);
toLowerCase() normaliza el texto, replace() elimina signos simples y split() crea un array de palabras. Después usamos un objeto como tabla de frecuencias.
2. Agrupar objetos por una propiedad con reduce()
Enunciado: Agrupar productos por categoría utilizando reduce().
const productos = [ { nombre: "Mouse", categoria: "tecnologia" }, { nombre: "Silla", categoria: "muebles" }, { nombre: "Teclado", categoria: "tecnologia" } ]; const agrupados = productos.reduce(function (resultado, producto) { const categoria = producto.categoria; if (!resultado[categoria]) { resultado[categoria] = []; } resultado[categoria].push(producto); return resultado; }, {}); console.log(agrupados);
reduce() transforma el array completo en una única estructura. En este caso, el resultado es un objeto cuyas propiedades son las categorías.
3. Ordenar objetos por múltiples criterios
Enunciado: Ordenar personas primero por edad y, en caso de empate, por nombre.
const personas = [ { nombre: "Luis", edad: 30 }, { nombre: "Ana", edad: 25 }, { nombre: "Carlos", edad: 30 } ]; personas.sort(function (a, b) { if (a.edad !== b.edad) { return a.edad - b.edad; } return a.nombre.localeCompare(b.nombre); }); console.log(personas);
sort() permite definir múltiples criterios. localeCompare() compara cadenas respetando reglas lingüísticas. Recordá que sort() modifica el array original.
4. Crear una búsqueda con varios filtros
Enunciado: Filtrar productos por texto, categoría y precio máximo.
const productos = [ { nombre: "Mouse gamer", categoria: "tecnologia", precio: 40 }, { nombre: "Teclado", categoria: "tecnologia", precio: 70 }, { nombre: "Silla", categoria: "muebles", precio: 120 } ]; const texto = "mouse"; const categoria = "tecnologia"; const precioMaximo = 100; const resultado = productos.filter(function (producto) { return producto.nombre.toLowerCase().includes(texto.toLowerCase()) && producto.categoria === categoria && producto.precio <= precioMaximo; }); console.log(resultado);
filter() crea un nuevo array con los elementos que cumplen todas las condiciones. Este patrón aparece mucho en buscadores y paneles administrativos.
5. Implementar paginación de un array
Enunciado: Obtener una página concreta de resultados.
function paginar(elementos, pagina, porPagina) { const inicio = (pagina - 1) * porPagina; const fin = inicio + porPagina; return elementos.slice(inicio, fin); } const datos = [1,2,3,4,5,6,7,8,9,10]; console.log(paginar(datos, 2, 3));
slice() devuelve una porción del array sin modificar el original. Para la página 2 con 3 elementos por página devuelve los elementos 4, 5 y 6.
6. Crear un carrito de compras
Enunciado: Calcular el total de un carrito con cantidades y precios.
const carrito = [ { producto: "Mouse", precio: 25, cantidad: 2 }, { producto: "Teclado", precio: 60, cantidad: 1 } ]; const total = carrito.reduce(function (acumulador, item) { return acumulador + item.precio * item.cantidad; }, 0); console.log(total);
El acumulador suma el subtotal de cada producto. Este patrón se usa constantemente en e-commerce y sistemas de facturación.
7. Calcular subtotal, descuento e impuestos
Enunciado: Aplicar un descuento y luego calcular impuestos.
const subtotal = 1000; const descuentoPorcentaje = 10; const impuestoPorcentaje = 21; const descuento = subtotal * descuentoPorcentaje / 100; const base = subtotal - descuento; const impuesto = base * impuestoPorcentaje / 100; const total = base + impuesto; console.log({ subtotal, descuento, impuesto, total });
Separar cada etapa en variables hace que el cálculo sea más legible y fácil de auditar.
8. Transformar datos anidados de una API
Enunciado: Convertir una estructura anidada en una lista más simple.
const respuesta = { usuarios: [ { id: 1, perfil: { nombre: "Ana", ciudad: "Santa Rosa" } }, { id: 2, perfil: { nombre: "Luis", ciudad: "Córdoba" } } ] }; const usuarios = respuesta.usuarios.map(function (usuario) { return { id: usuario.id, nombre: usuario.perfil.nombre, ciudad: usuario.perfil.ciudad }; }); console.log(usuarios);
map() es ideal para adaptar respuestas de APIs a la estructura que necesita nuestra interfaz.
9. Eliminar objetos duplicados por id
Enunciado: Conservar un solo objeto por cada id.
const usuarios = [ { id: 1, nombre: "Ana" }, { id: 2, nombre: "Luis" }, { id: 1, nombre: "Ana duplicada" } ]; const mapa = new Map(); for (const usuario of usuarios) { mapa.set(usuario.id, usuario); } const unicos = [...mapa.values()]; console.log(unicos);
Map permite usar el id como clave. Cuando se repite una clave, el valor anterior se reemplaza por el nuevo.
10. Comparar dos arrays y detectar cambios
Enunciado: Obtener elementos agregados y eliminados entre dos listas.
const anterior = [1, 2, 3, 4]; const actual = [2, 3, 4, 5]; const agregados = actual.filter(function (id) { return !anterior.includes(id); }); const eliminados = anterior.filter(function (id) { return !actual.includes(id); }); console.log({ agregados, eliminados });
Comparar listas de esta forma es útil cuando necesitamos detectar cambios entre un estado anterior y uno nuevo.
11. Crear una función debounce()
Enunciado: Evitar ejecutar una función en cada pulsación y esperar a que el usuario deje de escribir.
function debounce(funcion, demora) { let temporizador; return function (...args) { clearTimeout(temporizador); temporizador = setTimeout(function () { funcion(...args); }, demora); }; } const buscar = debounce(function (texto) { console.log("Buscando:", texto); }, 500); buscar("java"); buscar("javascript");
debounce() reinicia el temporizador cada vez que se llama. Es muy útil en buscadores, autocompletados y validaciones mientras el usuario escribe.
12. Crear una función throttle()
Enunciado: Limitar cuántas veces puede ejecutarse una función dentro de un intervalo.
function throttle(funcion, intervalo) { let habilitado = true; return function (...args) { if (!habilitado) { return; } habilitado = false; funcion(...args); setTimeout(function () { habilitado = true; }, intervalo); }; } const informarScroll = throttle(function () { console.log("Scroll detectado"); }, 1000);
throttle() permite una ejecución cada cierto tiempo. Suele utilizarse con eventos frecuentes como scroll o resize.
13. Ejecutar tareas asíncronas en secuencia
Enunciado: Ejecutar varias operaciones respetando un orden.
function esperar(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } async function ejecutar() { console.log("Paso 1"); await esperar(500); console.log("Paso 2"); await esperar(500); console.log("Paso 3"); } ejecutar();
await pausa la función async hasta que la Promise termina. Esto permite expresar procesos secuenciales de forma clara.
14. Ejecutar varias Promises en paralelo
Enunciado: Esperar varias tareas independientes al mismo tiempo.
function tarea(nombre, tiempo) { return new Promise(function (resolve) { setTimeout(function () { resolve(nombre); }, tiempo); }); } async function ejecutar() { const resultados = await Promise.all([ tarea("A", 500), tarea("B", 300), tarea("C", 700) ]); console.log(resultados); } ejecutar();
Promise.all() espera que todas las Promises terminen y devuelve sus resultados en el mismo orden en que fueron enviadas.
API utilizada en los ejercicios
En los ejemplos que utilizan fetch() vamos a trabajar con JSONPlaceholder, una API REST gratuita pensada para pruebas, aprendizaje y prototipos.
La API proporciona datos ficticios de usuarios, publicaciones, comentarios, tareas y otros recursos. Esto permite practicar peticiones HTTP, Promises, async/await y procesamiento de JSON sin necesitar un backend propio.
En estos ejercicios usamos principalmente el recurso de usuarios:
https://jsonplaceholder.typicode.com/users
Los usuarios devueltos son datos de prueba: no corresponden a personas reales ni a información de producción.
15. Manejar errores con async/await
Enunciado: Capturar errores de una operación asíncrona.
async function cargarDatos() { try { const respuesta = await fetch("https://jsonplaceholder.typicode.com/users"); if (!respuesta.ok) { throw new Error("Error HTTP: " + respuesta.status); } const datos = await respuesta.json(); console.log(datos); } catch (error) { console.log("No se pudieron cargar los datos:", error.message); } } cargarDatos();
try/catch permite capturar errores. También conviene revisar response.ok, porque fetch() no rechaza automáticamente todos los errores HTTP.
16. Consumir una API y filtrar resultados
Enunciado: Descargar usuarios y conservar sólo aquellos que cumplan una condición.
async function cargarUsuarios() { const respuesta = await fetch("https://jsonplaceholder.typicode.com/users"); const usuarios = await respuesta.json(); const filtrados = usuarios.filter(function (usuario) { return usuario.name.toLowerCase().includes("a"); }); console.log(filtrados); } cargarUsuarios();
Este ejercicio combina fetch(), JSON y filter(), una secuencia muy habitual en interfaces que consumen APIs.
17. Reintentar una petición fallida
Enunciado: Crear una función que intente varias veces antes de devolver un error.
async function fetchConReintentos(url, intentos = 3) { let ultimoError; for (let i = 1; i <= intentos; i++) { try { const respuesta = await fetch(url); if (!respuesta.ok) { throw new Error("HTTP " + respuesta.status); } return await respuesta.json(); } catch (error) { ultimoError = error; } } throw ultimoError; } fetchConReintentos("https://jsonplaceholder.typicode.com/users") .then(function (datos) { console.log(datos); }) .catch(function (error) { console.log(error.message); });
Los reintentos pueden ser útiles ante fallos temporales de red. En sistemas reales suele agregarse una espera progresiva entre intentos.
18. Guardar objetos complejos en localStorage
Enunciado: Persistir un objeto convirtiéndolo a JSON.
const configuracion = { tema: "oscuro", idioma: "es", filtros: ["activos", "recientes"] }; localStorage.setItem( "configuracion", JSON.stringify(configuracion) ); const recuperada = JSON.parse( localStorage.getItem("configuracion") ); console.log(recuperada);
localStorage guarda cadenas. Por eso usamos JSON.stringify() al guardar y JSON.parse() al recuperar.
19. Crear un sistema de eventos personalizado
Enunciado: Implementar métodos on, emit y off.
class EventBus { constructor() { this.eventos = {}; } on(nombre, callback) { if (!this.eventos[nombre]) { this.eventos[nombre] = []; } this.eventos[nombre].push(callback); } emit(nombre, datos) { const callbacks = this.eventos[nombre] || []; for (const callback of callbacks) { callback(datos); } } off(nombre, callback) { const callbacks = this.eventos[nombre] || []; this.eventos[nombre] = callbacks.filter(function (item) { return item !== callback; }); } } const bus = new EventBus(); function saludar(nombre) { console.log("Hola", nombre); } bus.on("saludo", saludar); bus.emit("saludo", "Ana"); bus.off("saludo", saludar);
Este patrón desacopla componentes: quien emite un evento no necesita conocer directamente a quien lo escucha.
20. Mini proyecto: buscador de usuarios desde una API
Enunciado: Crear una búsqueda que cargue usuarios, filtre por texto y maneje estados de carga y error.
const estado = { usuarios: [], filtro: "" }; async function cargarUsuarios() { try { console.log("Cargando..."); const respuesta = await fetch("https://jsonplaceholder.typicode.com/users"); if (!respuesta.ok) { throw new Error("No se pudieron cargar los usuarios"); } estado.usuarios = await respuesta.json(); renderizar(); } catch (error) { console.log(error.message); } } function obtenerFiltrados() { const texto = estado.filtro.toLowerCase(); return estado.usuarios.filter(function (usuario) { return usuario.name.toLowerCase().includes(texto) || usuario.email.toLowerCase().includes(texto); }); } function renderizar() { const usuarios = obtenerFiltrados(); console.log(usuarios); } function buscar(texto) { estado.filtro = texto; renderizar(); } cargarUsuarios();
El ejemplo separa estado, carga de datos, filtrado y renderizado. Esa separación facilita escalar luego hacia una interfaz DOM completa.
Qué conceptos avanzados practicamos
Estos ejercicios trabajan transformación de datos, composición de filtros, estructuras Map, funciones de orden superior, asincronía, Promises, APIs, persistencia local y patrones para controlar eventos frecuentes.
En varios casos existe más de una solución válida. El objetivo no es memorizar código, sino reconocer qué herramienta resulta apropiada para cada problema y entender si una operación modifica los datos originales o devuelve una nueva estructura.
Seguí aprendiendo JavaScript
- Guía completa de JavaScript
- 20 ejercicios resueltos de JavaScript para principiantes
- 20 ejercicios resueltos de arrays en JavaScript
- JavaScript asíncrono: Promises, async/await, fetch y APIs
- Proyecto final JavaScript: aplicación de tareas con DOM, eventos y localStorage
¿Te sirvió esta guía? ☕
Si este contenido te ayudó a aprender JavaScript, podés apoyar a Club Programador. Cada aporte ayuda a mantener el sitio online y a seguir publicando material gratuito.
🇦🇷 Apoyar desde Argentina
☕ Invitame un café con Mercado Pago
🌎 Apoyar desde otro país
Descubre más desde Club Programador
Suscríbete y recibe las últimas entradas en tu correo electrónico.