Saltar al contenido
Curso de JavaScript
Todos los cursos Ir al ejercicio →

Proyecto: lista de tareas

  • ⏱ 150 minutos
  • 📶 Nivel: principiante
  • 🧩 Incluye ejercicio

💡 🎯 Al terminar esta lección podrás…

  • Aplicar todo el curso en una aplicación completa.
  • Separar los datos de la interfaz y pintar a partir del estado.
  • Persistir datos con localStorage de forma segura.
  • Revisar la calidad, seguridad y accesibilidad de una app.

1.Qué vas a construir

Vas a crear una aplicación de tareas completa, la «lista de pendientes» que casi todo programador ha hecho alguna vez. Reúne todo lo que has aprendido: variables, decisiones, bucles, funciones, arrays, objetos, el DOM, eventos, formularios, JSON y localStorage.

Función de la appLo que aplicasLección
Guardar las tareas en una lista de objetosArrays y objetos6 y 7
Añadir con un formulario y validarEventos, submit, preventDefault9
Marcar como hechas y borrarDelegación de eventos, closest()9
Filtrar: todas, pendientes, hechasfilter, atributos aria-pressed6 y 8
Contador de pendientesFunciones, plantillas de texto2 y 5
Pintar la lista a partir de los datoscreateElement, textContent8
Guardar y recuperar al recargarlocalStorage, JSON, try/catch7 y 11

💡 Consejo · Una idea que lo cambia todo

La clave de este proyecto es separar los datos (el array tareas) de lo que se ve (el DOM). No cambies la página a mano: cambia los datos y vuelve a pintar. Así la pantalla siempre refleja la realidad y es fácil guardar. Es la idea en la que se basan frameworks como React y Vue.

2.Plan: los datos y las funciones

El modelo de datos

Cada tarea es un objeto
{ id: 1712345678, texto: "Estudiar JavaScript", hecha: false }

// Toda la aplicación vive en este array:
let tareas = [
  { id: 1, texto: "Estudiar JavaScript", hecha: false },
  { id: 2, texto: "Hacer ejercicio",     hecha: true  },
];

// Y en una variable para el filtro actual:
let filtro = "todas";     // "todas" | "pendientes" | "hechas"

Las funciones que necesitas

FunciónResponsabilidad
cargar()Lee el array guardado en localStorage (o [])
guardar()Guarda el array actual como JSON
visibles()Devuelve las tareas que pasan el filtro actual
crearElemento(tarea)Construye el <li> de una tarea
pintar()Vuelve a dibujar la lista, el contador y los filtros
cambiar()guardar() y pintar(): la usarás tras cada cambio
iniciar()Carga los datos y pinta; se llama al arrancar

Además, cuatro manejadores de eventos: enviar el formulario, pulsar dentro de la lista (marcar y borrar, con delegación), pulsar un filtro y pulsar «Borrar completadas».

3.La estructura del <li>

Para que tu código y las pruebas del ejercicio se entiendan, cada tarea debe pintarse con esta estructura:

Cada tarea en el HTML
<li data-id="1712345678" class="hecha">      <!-- la clase "hecha" solo si está completada -->
  <input type="checkbox" class="check" checked aria-label="Marcar como hecha">
  <span class="texto">Estudiar JavaScript</span>
  <button type="button" class="borrar" aria-label="Borrar tarea">✕</button>
</li>

⚠️ Importante · textContent, siempre

El texto de cada tarea lo escribe el usuario. Si lo insertaras con innerHTML, alguien podría escribir <img src=x onerror=alert(1)> y ejecutar código en tu página. Con textContent el texto es siempre texto. Las pruebas del ejercicio lo comprueban.

4.Paso a paso

Paso 1 · Datos y almacenamiento

Cargar y guardar
const CLAVE = "tareas";
let tareas = [];
let filtro = "todas";

function cargar() {
  try { return JSON.parse(localStorage.getItem(CLAVE)) ?? []; }
  catch { return []; }
}
function guardar() {
  localStorage.setItem(CLAVE, JSON.stringify(tareas));
}

Paso 2 · Pintar

pintar() recorre visibles(), crea un <li> por tarea con crearElemento() y los coloca en la lista con lista.replaceChildren(...). Después actualiza el contador ("1 pendiente" o "3 pendientes") y marca con aria-pressed="true" el botón del filtro activo.

Paso 3 · Añadir tareas

Escucha submit en el formulario: preventDefault(), recorta el texto, si está vacío muestra el error en #error; si no, añade { id, texto, hecha: false } al array, vacía el campo y llama a cambiar().

Paso 4 · Marcar y borrar con delegación

Un solo manejador de click en #lista: busca el li con e.target.closest("li"), localiza la tarea por su data-id y, según el elemento pulsado (.check o .borrar), cambia hecha o elimina la tarea. Termina con cambiar().

Paso 5 · Filtros y limpiar

Los botones [data-filtro] cambian la variable filtro y llaman a pintar(). «Borrar completadas» deja en el array solo las pendientes (filter) y llama a cambiar().

Paso 6 · Arrancar

Escribe iniciar() (carga los datos, restablece el filtro y pinta) y llámala al final del script. Prueba: añade tareas, recarga con F5 en tu propia página y comprueba que siguen ahí.

📌 Nota · Un identificador por tarea

No uses la posición en el array como identificador: cambia al borrar o filtrar. Un id propio (por ejemplo Date.now()) no cambia nunca, y es lo que permite localizar la tarea correcta.

5.Revisión de calidad

RevisiónCómo comprobarlo
FuncionaAñadir, marcar, borrar, filtrar y limpiar, sin errores en la consola
PersisteRecarga la página: las tareas siguen ahí (revisa en F12 → Aplicación → Almacenamiento local)
Es seguroAñade una tarea con <b>hola</b> o <script>: debe verse como texto, no ejecutarse
TecladoTodo se puede hacer con Tab, Enter y Espacio
Lector de pantallaEl contador y el error se anuncian (role="status", role="alert")
Casos límiteTexto vacío, solo espacios, un texto larguísimo, borrar la última tarea
CódigoFunciones pequeñas, nombres claros, sin repetición, sin variables globales innecesarias

💡 Consejo · Estilo con CSS

Esta app está pidiendo a gritos el curso de CSS: una tarjeta centrada con Grid o Flexbox, la clase .hecha con texto tachado y atenuado, transiciones al marcar, un tema oscuro con variables y que funcione perfecto en el móvil. Haz esa parte y tendrás una pieza lista para tu portafolio.

6.Retos para ir más lejos

7.Qué sigue en tu camino

Con este curso ya sabes programar en JavaScript y construir interfaces interactivas. El camino hacia el desarrollo full stack tiene estas paradas:

EtapaQué aprenderásDónde empezar
Herramientas modernasnpm, Vite, módulos, depuración avanzadaVite
Un framework de interfazReact (Vue y Svelte son alternativas): componentes y estadoCurso de React de este sitio
Backend con Node.jsServidores, rutas y APIs propiasnodejs.org
Bases de datosSQL (PostgreSQL) o NoSQL (MongoDB)MDN: servidores
TypeScriptJavaScript con tipos para proyectos grandestypescriptlang.org
DesplieguePublicar apps con backend (Render, Railway, Vercel)Documentación de cada plataforma

Y el consejo de siempre: construye cosas. Una calculadora, un juego del ahorcado, un clon de tu app favorita. Cada proyecto te enseña algo que ningún curso puede enseñarte.

8.Errores frecuentes en el proyecto

📒 Apuntes para llevar

  • Separa los datos (el array tareas) de lo que se ve (el DOM): cambia los datos y vuelve a pintar.
  • Cada tarea es un objeto { id, texto, hecha } con un id propio estable.
  • Un conjunto de funciones pequeñas (cargar, guardar, visibles, pintar, cambiar, iniciar) hace el programa fácil de seguir.
  • La delegación en la lista atiende marcar y borrar, incluso en tareas nuevas.
  • textContent (nunca innerHTML) inserta texto del usuario de forma segura.
  • localStorage + JSON persiste los datos; lee con ?? [] y try/catch.
  • La accesibilidad también es JavaScript: aria-label, aria-pressed, role="status" y role="alert".
  • El siguiente paso: un framework, Node.js y bases de datos, rumbo a full stack.

9.Glosario

TérminoSignificado
EstadoLos datos que describen cómo está la aplicación en un momento.
Renderizar / pintarDibujar la interfaz a partir del estado.
Delegación de eventosUn manejador en el contenedor que atiende a sus hijos.
PersistenciaConservar los datos aunque se cierre o recargue la página.
Identificador (id)Valor único que distingue a cada elemento.
Caso límiteSituación extrema o poco habitual que hay que probar.
XSSAtaque que inyecta código en una página mediante datos de usuario.
Full stackDesarrollo tanto del lado del navegador como del servidor.

🧠 Comprueba lo aprendido

Elige una respuesta en cada pregunta. Verás al instante si acertaste y por qué.

¿Cuál es la idea central de este proyecto?

¿Por qué no usar la posición del array como id de una tarea?

Un usuario escribe <b>hola</b> como tarea. ¿Cómo lo muestras de forma segura?

Recargas la página y las tareas desaparecen. ¿Qué falta casi seguro?

¿Para qué sirve la delegación de eventos en la lista de tareas?

Tu progreso se guarda en este navegador y se refleja en el índice del curso.

💡 🚀 ¡Ahora te toca!

La práctica fija lo aprendido. Pasa al ejercicio de esta lección.

Ir al ejercicio