Saltar al contenido
Curso de JavaScript
Todos los cursos

Ejercicio: Proyecto: lista de tareas

  • ⏱ 120 minutos
  • 📶 Nivel: principiante

Objetivo

Construir la aplicación de tareas completa. Las pruebas ejecutan tu código sobre la página dada: simulan el uso real (añadir, marcar, borrar, filtrar, recargar) y comprueban el resultado.

La página de partida

Esta es la página sobre la que trabaja tu código. No la modifiques: escribe solo el JavaScript.

pagina.html
<main class="app">
  <h1>Mis tareas</h1>
  <form id="form-tarea">
    <label for="texto">Nueva tarea</label>
    <input id="texto" type="text" autocomplete="off">
    <button type="submit">Añadir</button>
  </form>
  <p id="error" role="alert"></p>
  <div class="filtros" role="group" aria-label="Filtrar tareas">
    <button type="button" data-filtro="todas" aria-pressed="true">Todas</button>
    <button type="button" data-filtro="pendientes" aria-pressed="false">Pendientes</button>
    <button type="button" data-filtro="hechas" aria-pressed="false">Hechas</button>
  </div>
  <ul id="lista"></ul>
  <p id="contador" role="status"></p>
  <button type="button" id="borrar-hechas">Borrar completadas</button>
</main>
▶ Prueba tu código aquí (opcional)
Consola

Pasos

Marca cada paso a medida que lo completes. Tu avance se guarda en este navegador.

💡 Pista 1: no sé por dónde empezar

Empieza solo por cargar, guardar, crearElemento y pintar, y prueba en el editor con unas tareas a mano. Cuando se vean bien, añade los eventos uno a uno.

💡 Pista 2: ids de tipo texto

li.dataset.id es texto y tu id guardado es número. Compara con Number(li.dataset.id) o guarda el id como texto en ambos lados.

🔎 Comprueba tu trabajo

Pega tu código y pulsa Comprobar. La revisión ocurre en tu navegador; nada se envía a ningún sitio. Cada punto en rojo incluye una pista.

    ✅ Ver una solución completa de ejemplo

    Intenta resolverlo antes de mirar. Tu versión puede ser distinta y también correcta.

    app.js
    const CLAVE = "tareas";
    let tareas = [];
    let filtro = "todas";
    
    const form = document.getElementById("form-tarea");
    const campo = document.getElementById("texto");
    const error = document.getElementById("error");
    const lista = document.getElementById("lista");
    const contador = document.getElementById("contador");
    
    // ----- Almacenamiento -----
    function cargar() {
      try {
        return JSON.parse(localStorage.getItem(CLAVE)) ?? [];
      } catch {
        return [];
      }
    }
    
    function guardar() {
      localStorage.setItem(CLAVE, JSON.stringify(tareas));
    }
    
    // ----- Pintar la página a partir de los datos -----
    function visibles() {
      if (filtro === "pendientes") return tareas.filter((t) => !t.hecha);
      if (filtro === "hechas") return tareas.filter((t) => t.hecha);
      return tareas;
    }
    
    function crearElemento(tarea) {
      const li = document.createElement("li");
      li.dataset.id = tarea.id;
      li.classList.toggle("hecha", tarea.hecha);
    
      const check = document.createElement("input");
      check.type = "checkbox";
      check.className = "check";
      check.checked = tarea.hecha;
      check.setAttribute("aria-label", "Marcar como hecha");
    
      const texto = document.createElement("span");
      texto.className = "texto";
      texto.textContent = tarea.texto;          // textContent: seguro con cualquier texto
    
      const borrar = document.createElement("button");
      borrar.type = "button";
      borrar.className = "borrar";
      borrar.setAttribute("aria-label", "Borrar tarea");
      borrar.textContent = "✕";
    
      li.append(check, texto, borrar);
      return li;
    }
    
    function pintar() {
      lista.replaceChildren(...visibles().map(crearElemento));
    
      const pendientes = tareas.filter((t) => !t.hecha).length;
      contador.textContent = pendientes === 1 ? "1 pendiente" : `${pendientes} pendientes`;
    
      document.querySelectorAll("[data-filtro]").forEach((b) => {
        b.setAttribute("aria-pressed", String(b.dataset.filtro === filtro));
      });
    }
    
    function cambiar() {      // cada cambio: guardar y volver a pintar
      guardar();
      pintar();
    }
    
    // ----- Eventos -----
    form.addEventListener("submit", (e) => {
      e.preventDefault();
      const texto = campo.value.trim();
      if (texto === "") {
        error.textContent = "Escribe una tarea";
        return;
      }
      error.textContent = "";
      tareas.push({ id: Date.now() + Math.random(), texto, hecha: false });
      campo.value = "";
      cambiar();
    });
    
    lista.addEventListener("click", (e) => {
      const li = e.target.closest("li");
      if (!li) return;
      const id = Number(li.dataset.id);
      const tarea = tareas.find((t) => t.id === id);
      if (!tarea) return;
    
      if (e.target.classList.contains("check")) {
        tarea.hecha = e.target.checked;
        cambiar();
      } else if (e.target.classList.contains("borrar")) {
        tareas = tareas.filter((t) => t.id !== id);
        cambiar();
      }
    });
    
    document.querySelector(".filtros").addEventListener("click", (e) => {
      const boton = e.target.closest("[data-filtro]");
      if (!boton) return;
      filtro = boton.dataset.filtro;
      pintar();
    });
    
    document.getElementById("borrar-hechas").addEventListener("click", () => {
      tareas = tareas.filter((t) => !t.hecha);
      cambiar();
    });
    
    // ----- Arranque -----
    function iniciar() {
      tareas = cargar();
      filtro = "todas";
      pintar();
    }
    
    iniciar();

    💡 🏁 Reto extra (opcional)

    Añade edición: al hacer doble clic sobre el texto de una tarea, conviértelo en un <input>; al pulsar Enter se guarda y con Escape se cancela. Pruébalo con el teclado.