Saltar al contenido

Lección 19 de 20 · Lectura y práctica

Desarrollar el Proyecto: Aplicación de Gestión de Tareas Avanzada

Tipo de lección: VideoConcepto claveEn arquitecturas frontend complejas, los patrones genéricos y el tipado profundo transforman TypeScript de una herramienta de verificación estática a un sistema de diseño que previene errores.

Del concepto a la práctica

Taller de esta lección

El núcleo del proyecto puede ser una transición explícita: completar una tarea cambia su estado, pero no altera el resto de registros. Trabajar con datos locales hace el resultado reproducible.

Ejemplo resuelto

ts
type Task = { id: string; title: string; done: boolean };
function complete(tasks: Task[], id: string): Task[] {
  return tasks.map(t => t.id === id ? { ...t, done: true } : t);
}
const input = [{ id: "a", title: "Diseñar", done: false }, { id: "b", title: "Probar", done: false }];
console.log(complete(input, "b"));

La salida conserva a pendiente y marca b como completada.

Pruébalo

  1. Ejecuta la función con los dos registros.

  2. Comprueba que input[1].done sigue siendo false.

  3. Completa un identificador inexistente.

  4. Compara longitudes y títulos.

Comprueba tu resultado

La nueva lista conserva dos tareas y solo b.done es true; la entrada original no cambia.

Error frecuente: modificar t.done = true dentro de forEach. Corrígelo con map y propagación, preservando inmutabilidad y referencias no afectadas.

La función acepta identificadores duplicados y completaría todas las coincidencias; el repositorio del capstone debe impedir duplicados al crear. Una llamada repetida sobre una tarea ya completada conserva su valor final, aunque cree otro objeto para esa fila. Comprueba longitudes, títulos, entrada intacta y referencia de la fila a que no cambió. Si necesitas distinguir id inexistente de una operación correcta, devuelve un Result en vez de una lista indistinguible.

Diagrama de decisión con ramas Sí y No y un bucle que procesa cada elemento de una lista.
CursaloMapa de práctica
Cómo avanza un programa

Una condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Tipo de lección: Video

Concepto clave

En arquitecturas frontend complejas, los patrones genéricos y el tipado profundo transforman TypeScript de una herramienta de verificación estática a un sistema de diseño que previene errores en tiempo de compilación. Imagina construir un puente: los genéricos son los planos parametrizados que se adaptan a diferentes tipos de carga (datos), mientras que el tipado profundo es la inspección estructural que garantiza que cada pieza encaje perfectamente antes de montarla.

En una aplicación de gestión de tareas avanzada, esto significa definir contratos de tipo que modelen el dominio empresarial con precisión. Por ejemplo, una tarea no es solo un objeto con title y completed, sino una entidad con estados transicionales, dependencias y reglas de negocio. Los genéricos permiten crear componentes reutilizables como DataTable<Task> o Form<TaskInput> que se tipan automáticamente según el contexto, reduciendo duplicación y aumentando la seguridad.

"El tipado profundo no es sobre ser estricto, sino sobre capturar la intención del dominio en el código." — Principio de diseño TypeScript avanzado

Cómo funciona en la práctica

Vamos a implementar un sistema de filtros genérico para nuestra aplicación de tareas. Primero, definimos tipos base:

type TaskStatus = 'pending' | 'in-progress' | 'completed' | 'archived';
type Priority = 'low' | 'medium' | 'high';

interface Task {
    id: string;
    title: string;
    description: string;
    status: TaskStatus;
    priority: Priority;
    dueDate: Date;
    dependencies: Task[];
}

Luego, creamos un filtro genérico que acepte cualquier tipo de dato y permita combinaciones:

type FilterCondition<T> = (item: T) => boolean;

class GenericFilter<T> {
    private conditions: FilterCondition<T>[] = [];

    addCondition(condition: FilterCondition<T>): this {
        this.conditions.push(condition);
        return this;
    }

    apply(items: T[]): T[] {
        return items.filter(item => this.conditions.every(cond => cond(item)));
    }
}

// Uso con Task
const taskFilter = new GenericFilter<Task>()
    .addCondition(task => task.status === 'pending')
    .addCondition(task => task.priority === 'high');
const filteredTasks = taskFilter.apply(tasksArray);

Este patrón permite construir filtros complejos de forma tipada y reutilizable.

Caso de estudio

Implementemos un gestor de estado para tareas con tipado profundo. Creamos un TaskManager que use genéricos para acciones y reducers:

type Action<T, P = void> = P extends void ? { type: T } : { type: T; payload: P };

type TaskAction = Action<'ADD_TASK', Task> | Action<'UPDATE_TASK', { id: string; updates: Partial<Task> }> | Action<'DELETE_TASK', string>;

class TaskStore {
    private tasks: Task[] = [];

    dispatch(action: TaskAction): void {
        switch (action.type) {
            case 'ADD_TASK':
                this.tasks.push(action.payload);
                break;
            case 'UPDATE_TASK':
                const index = this.tasks.findIndex(t => t.id === action.payload.id);
                if (index !== -1) {
                    this.tasks[index] = { ...this.tasks[index], ...action.payload.updates };
                }
                break;
            case 'DELETE_TASK':
                this.tasks = this.tasks.filter(t => t.id !== action.payload);
                break;
        }
    }

    getTasksByStatus(status: TaskStatus): Task[] {
        return this.tasks.filter(task => task.status === status);
    }
}

Este enfoque garantiza que cada acción tenga el payload correcto, evitando errores comunes como pasar un string donde se espera un objeto.

Errores comunes

  • Over-engineering con genéricos: Crear genéricos demasiado complejos que dificultan la lectura. Solución: Usa genéricos solo cuando haya verdadera reutilización, y documenta con ejemplos.
  • Tipado superficial en uniones: Definir type Status = string en lugar de type Status = 'pending' | 'completed'. Esto pierde seguridad. Solución: Siempre usa uniones literales para dominios finitos.
  • Ignorar inferencia de tipos: Escribir tipos explícitos innecesarios, como const x: number = 5. Solución: Deja que TypeScript infiera cuando sea obvio, y solo anota en límites de módulo o APIs públicas.
  • No validar datos en runtime: Asumir que los datos de API coinciden con tus tipos. Solución: Usa librerías como Zod o io-ts para validación en runtime con tipado.
  • Abusar de any o as: Usarlos para silenciar errores en lugar de corregir el tipo. Solución: Refactoriza para mantener la integridad del tipado, usando unknown y type guards.

Checklist de dominio

  1. ¿Puedes crear un componente genérico que funcione con al menos tres tipos de datos diferentes sin duplicar código?
  2. ¿Has implementado un sistema de filtros o búsqueda con tipado estricto que prevenga errores de propiedad?
  3. ¿Usas uniones discriminadas para modelar estados de dominio, como tareas con transiciones válidas?
  4. ¿Validas datos en runtime (ej., respuestas de API) y los conviertes a tus tipos TypeScript?
  5. ¿Evitas el uso de any en código de producción, reemplazándolo con unknown y type guards?
  6. ¿Documentas tus genéricos con ejemplos de uso para mejorar la mantenibilidad?
  7. ¿Aplicas tipado profundo en al menos un módulo complejo, como gestión de estado o formularios?

Implementar un sistema de formularios genérico para tareas

En este ejercicio, crearás un sistema de formularios genérico que pueda manejar diferentes tipos de tareas con validación tipada. Sigue estos pasos:

  1. Define un tipo TaskForm que extienda Task pero haga opcionales algunos campos como id y dependencies para creación.
  2. Crea una clase genérica GenericForm<T> con métodos para agregar campos, validar y enviar. Usa un tipo FieldConfig<T> para configurar cada campo con validadores.
  3. Implementa validadores específicos para tareas, como verificar que dueDate sea futura y que priority sea uno de los valores permitidos.
  4. Integra el formulario en un componente de React o Vue usando TypeScript, mostrando errores de tipo en tiempo de compilación.
  5. Prueba el formulario con dos tipos de tareas: una básica y una subtarea con dependencias, asegurando la reutilización.
Pistas
  • Usa tipos mapeados en TypeScript para crear FieldConfig basado en las claves de T.
  • Considera usar uniones discriminadas en los validadores para manejar diferentes tipos de campos.
  • Para la integración con React, usa genéricos en props para tipar los datos del formulario.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con TypeScript Avanzado: Patrones Genéricos y Tipado Profundo para Arquitecturas Complejas

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.