La última lección del módulo 9 lo anunció con estas palabras exactas: "queda una única lección, la última del curso: 'Proyecto: Construyendo TaskFlow', donde no se presenta ningún concepto nuevo, sino que se recorre la aplicación de principio a fin". Esa "única lección" es, en realidad, este módulo completo, dividido en cuatro partes por comodidad de lectura, pero pensado como una sola unidad narrativa: el proyecto final. Esta primera parte no escribe todavía ningún componente; traza el mapa completo de TaskFlow tal como ha quedado tras nueve módulos —su árbol de componentes, cómo viajan los datos entre ellos, y la estructura de carpetas final del proyecto— para que las tres lecciones siguientes tengan un plano claro sobre el que construir, en lugar de reconstruir esa imagen de conjunto sobre la marcha.
Contenido
- Qué es este módulo y qué no es
- El árbol de componentes de TaskFlow, completo
- Responsabilidad de una línea, por componente
- Flujo de datos: propiedades hacia abajo
- Flujo de datos: eventos hacia arriba
- La excepción deliberada: el contexto de filtro
- Estructura de carpetas final del proyecto
- De la carpeta a la lección: dónde se explicó cada pieza
- Hacia la construcción de los componentes
- Qué es este módulo y qué no es
Conviene fijar esto antes de escribir una sola línea de código: este módulo no introduce ningún concepto de Lit que no se haya explicado ya entre los módulos 1 y 9. Plantillas, propiedades reactivas, estilos con Shadow DOM, eventos personalizados, ciclo de vida, controladores, mixins, directivas, contexto compartido, integración con el resto del ecosistema y buenas prácticas: todo eso ya está cubierto. Lo que sí falta, y es exactamente el objetivo de las cuatro lecciones de este módulo, es verlo todo unido, como un único proyecto coherente, en lugar de como una sucesión de fragmentos de código repartidos entre treinta y seis lecciones distintas, cada uno centrado en la técnica que le correspondía explicar en su momento.
Esta primera lección se limita a la planificación: el árbol de componentes final, el flujo de datos que los conecta, y la estructura de carpetas. Las lecciones 10-02 y 10-03 escriben el código completo de cada componente y cada pieza de infraestructura compartida, y la 10-04 cierra con pruebas, empaquetado y el propio cierre del curso.
- El árbol de componentes de TaskFlow, completo
TaskFlow ha crecido, a lo largo del curso, hasta convertirse en cinco componentes personalizados, organizados en un árbol de cuatro niveles de profundidad:
<task-board> (raíz de la aplicación)
├── <task-filter> (hermano de <task-list>)
└── <task-list>
└── <task-card> (una por tarea, repetida con repeat + key)
└── <user-avatar>Ningún componente de este árbol es nuevo: <task-board> apareció en el módulo 5 (05-04) como ancestro común de <task-list>; <user-avatar> en el módulo 4 (04-04), como hijo de <task-card>; y <task-filter>, mencionado desde el propio módulo 5 sin implementar, no cobró vida real hasta el módulo 7 (07-04), de la mano de @lit/context. Lo único que aporta esta lección es dibujar, por fin, el árbol completo de una sola vez, algo que ninguna lección anterior necesitaba hacer porque cada una se centraba en la pieza que estaba construyendo en ese momento.
- Responsabilidad de una línea, por componente
Cada componente de TaskFlow tiene una responsabilidad que se puede resumir en una sola frase, sin necesidad de enumerar todos los detalles de su implementación:
| Componente | Responsabilidad en una línea |
|---|---|
<task-board> |
Orquestador raíz: mantiene el array tareas, publica el contexto de filtro, y gestiona la carga inicial y los cambios de estado que suben desde sus descendientes. |
<task-filter> |
Ofrece los controles de búsqueda por texto y por estado, y escribe los cambios directamente en el contexto de filtro compartido. |
<task-list> |
Consume el contexto de filtro, decide qué tareas cumplen el criterio activo, y las renderiza de forma eficiente con repeat y una clave estable. |
<task-card> |
Muestra los datos de una única tarea, permite cambiar su estado, y avisa visualmente cuando esa tarea está cerca de su fecha límite. |
<user-avatar> |
Muestra a la persona asignada a una tarea, con una imagen real o con sus iniciales como alternativa, mediante contenido distribuido. |
Esta tabla, aunque parezca trivial, es la prueba de que la descomposición de TaskFlow en cinco piezas, hecha a lo largo del curso, sigue el criterio explicado en la lección "Patrones y Anti-patrones Comunes" (09-04, apartado 6): cada componente cambia por un motivo distinto al de los demás, y ninguno necesita la conjunción "y" para describirse.
- Flujo de datos: propiedades hacia abajo
El primer eje de comunicación de TaskFlow, establecido desde el módulo 3 y perfeccionado en el módulo 5, es el flujo de propiedades reactivas de un componente hacia sus hijos directos en la plantilla:
| Desde | Hasta | Qué baja |
|---|---|---|
<task-board> |
<task-list> |
.tareas (el array completo, ya filtrado no; sin filtrar: el filtrado ocurre dentro de <task-list>, a partir del contexto) |
<task-list> |
<task-card> |
.titulo, .estado, .prioridad, .urgente, .fechaLimite, asignado-a, asignado-imagen, una instancia por cada tarea del array, con tarea.id como clave de repeat |
<task-card> |
<user-avatar> |
El atributo nombre, y opcionalmente una imagen distribuida mediante <slot> en lugar de una propiedad |
Ninguna fila de esta tabla es nueva: la primera se estableció en la lección "Patrones de Comunicación entre Componentes Hermanos" (05-04), la segunda en "Comunicación de Padre a Hijo con Propiedades" (05-03) y se refinó con repeat en "Contexto Compartido con @lit/context" (07-04), y la tercera en "Slots y Estilizado de Contenido Distribuido" (04-04). Lo que aporta esta lección es, de nuevo, verlas juntas como un único flujo descendente continuo, desde la raíz del árbol hasta su hoja más profunda.
- Flujo de datos: eventos hacia arriba
El segundo eje, simétrico al anterior, es el de los eventos personalizados que suben desde donde ocurre una interacción hasta quien tiene autoridad para decidir qué hacer con ella:
<task-card> --tarea-cambiada (detail: { nuevoEstado })-->
<task-list> --tarea-cambiada (detail: { idTarea, nuevoEstado })-->
<task-board>Este reenvío en dos saltos, con el idTarea añadido explícitamente en el segundo evento porque <task-board> no tiene acceso al cierre sobre tarea.id que sí tiene <task-list>, se estableció íntegramente en las lecciones "Eventos Personalizados: Comunicación de Hijo a Padre" (05-02) y "Patrones de Comunicación entre Componentes Hermanos" (05-04). <task-board> es, en todo momento, el único componente de TaskFlow que modifica realmente el array tareas; todos los demás se limitan a anunciar hechos ocurridos sobre sí mismos, nunca a modificar directamente el estado de un ancestro.
- La excepción deliberada: el contexto de filtro
Los dos ejes anteriores —propiedades hacia abajo, eventos hacia arriba— cubren toda la comunicación de TaskFlow salvo una relación: la que conecta <task-filter> con <task-list>, dos componentes hermanos sin relación directa de padre a hijo. Como se explicó en la lección "Contexto Compartido con @lit/context" (07-04), resolver esa relación mediante el patrón de los dos apartados anteriores obligaría a <task-board> a reenviar manualmente, en cada tecla pulsada en el filtro, una propiedad hacia <task-list> que el propio <task-board> no necesita para nada por sí mismo.
<task-board> (ContextProvider de filtroContext)
↑↓ ↑↓
<task-filter> <task-list>
(ContextConsumer, escribe) (ContextConsumer, lee)<task-board> publica el contexto una única vez, en su constructor; <task-filter> lo consume para leer y para escribir (a través de la función actualizar incluida en el propio valor del contexto); <task-list> lo consume solo para leer, y decide con ese valor qué tareas cumplen el filtro antes de pasarlas, ya como propiedad normal, hacia cada <task-card>. Ninguno de los dos hermanos conoce al otro en ningún momento: ambos hablan únicamente con el contexto que <task-board> publica, exactamente el mecanismo estudiado en el módulo 7.
- Estructura de carpetas final del proyecto
Con el árbol de componentes y los dos ejes de comunicación ya trazados, la estructura de carpetas de TaskFlow, tal como queda al final del curso, organiza cada pieza según el tipo de responsabilidad que cumple, no según el módulo en que se introdujo:
taskflow/
├── index.html
├── package.json
├── vite.config.js
├── web-test-runner.config.js
├── src/
│ ├── components/
│ │ ├── task-board.js
│ │ ├── task-filter.js
│ │ ├── task-list.js
│ │ ├── task-card.js
│ │ └── user-avatar.js
│ ├── controllers/
│ │ └── contador-tiempo-restante-controller.js
│ ├── mixins/
│ │ └── con-estado-carga.js
│ ├── context/
│ │ └── filtro-context.js
│ ├── directives/
│ │ └── resaltar-si-urgente.js
│ ├── services/
│ │ └── tareas-service.js
│ └── styles/
│ └── shared-styles.js
└── test/
├── task-card.test.js
└── task-filter.test.jssrc/components/ reúne los cinco elementos personalizados del árbol del apartado 2; src/controllers/ y src/mixins/ separan, siguiendo el criterio de la lección "Mixins y Composición de Comportamiento" (06-04), la lógica con estado propio y ciclo de vida (ContadorTiempoRestanteController) de la lógica que se integra directamente en la API pública de un componente (ConEstadoCarga); src/context/ contiene la clave de contexto compartida entre <task-board>, <task-filter> y <task-list>; src/directives/ guarda la directiva personalizada resaltarSiUrgente; src/services/ aísla la simulación de carga de datos externa a cualquier componente concreto; y src/styles/ mantiene los estilos compartidos entre varios componentes. test/ reúne la batería de pruebas construida en el módulo 9.
- De la carpeta a la lección: dónde se explicó cada pieza
Esta tabla final sirve de índice inverso: dado un fichero de la estructura anterior, indica en qué lección del curso se explicó por primera vez la técnica que ese fichero aplica, útil como referencia rápida durante las tres lecciones siguientes de este módulo.
| Fichero o carpeta | Lección donde se explicó la técnica |
|---|---|
components/task-board.js (orquestador, contexto, carga con until) |
05-04, 06-04, 07-03, 07-04 |
components/task-filter.js (contexto, accesibilidad) |
07-04, 09-02 |
components/task-list.js (repeat, contexto consumidor) |
02-04, 07-04, 09-03 |
components/task-card.js (propiedades, eventos, controlador, directiva, accesibilidad) |
03-01 a 03-04, 05-02, 06-03, 07-02, 09-02 |
components/user-avatar.js (slots, variables CSS) |
04-03, 04-04 |
controllers/contador-tiempo-restante-controller.js |
06-03 |
mixins/con-estado-carga.js |
06-04 |
context/filtro-context.js |
07-04 |
directives/resaltar-si-urgente.js |
07-02 |
services/tareas-service.js |
07-03 |
styles/shared-styles.js |
04-02 |
test/*.test.js |
09-01, 09-02 |
- Hacia la construcción de los componentes
Con el árbol, los dos ejes de comunicación, la excepción del contexto y la estructura de carpetas ya fijados, esta lección ha cumplido su función de mapa. Las tres lecciones siguientes se limitan a recorrerlo: primero los componentes que muestran datos, después los que orquestan el estado, y por último las pruebas, el empaquetado y el cierre del curso.
Errores Comunes y Consejos
- Organizar las carpetas por módulo del curso en lugar de por tipo de responsabilidad: una estructura como
modulo-06/,modulo-07/reflejaría el orden en que se aprendió cada técnica, no cómo se organiza un proyecto real;components/,controllers/,mixins/,context/agrupan el código según su papel dentro de la aplicación, que es el criterio que sobrevive una vez terminado el curso. - Olvidar que el contexto de filtro es una excepción, no la regla general: como se ha explicado en el apartado 6, la inmensa mayoría de la comunicación de TaskFlow sigue el patrón de propiedades hacia abajo y eventos hacia arriba;
@lit/contextse reserva para el único caso —hermanos sin relación directa— que ese patrón no resuelve bien por sí solo. - Pensar que
<task-board>conoce la estructura interna de<task-card>:<task-board>solo conoce a<task-list>(su hijo directo) y, mediante el contexto, a<task-filter>; nunca ha tenido, ni necesita tener, ninguna referencia directa a una<task-card>concreta, gracias al reenvío de eventos explicado en el apartado 5. - Esperar que esta lección contenga código ejecutable: deliberadamente, no lo contiene; es un mapa para las tres lecciones siguientes, no una implementación.
Ejercicios
- Sin mirar las lecciones anteriores, dibuja de memoria el árbol de componentes del apartado 2 y, junto a cada flecha, escribe si esa relación se resuelve con una propiedad, con un evento, o con el contexto de filtro. Comprueba después el resultado contra los apartados 4, 5 y 6.
- Explica, basándote en el apartado 5, por qué
<task-list>necesita añadiridTareaaldetaildel eventotarea-cambiadaque reenvía, cuando<task-card>, el origen del evento, nunca incluyó ese dato en el suyo. - Un compañero de equipo propone mover
contador-tiempo-restante-controller.jsasrc/components/, argumentando que solo lo usa<task-card>. Explica, basándote en el apartado 7 y en el criterio de la lección "Mixins y Composición de Comportamiento" (06-04), por quésrc/controllers/sigue siendo la ubicación más adecuada aunque, por ahora, solo un componente lo use.
Soluciones
- El árbol correcto es el del apartado 2:
<task-board>con dos hijos,<task-filter>y<task-list>;<task-list>con una<task-card>por cada tarea; cada<task-card>con un<user-avatar>. Las relaciones son:<task-board>→<task-list>(propiedad.tareas),<task-list>→<task-card>(propiedades por tarea),<task-card>→<user-avatar>(atributo y contenido distribuido),<task-card>→<task-list>→<task-board>(eventotarea-cambiadaen dos saltos), y<task-board>↔<task-filter>/<task-board>↔<task-list>(contexto de filtro, en ambas direcciones para<task-filter>, solo lectura para<task-list>). <task-card>no conoce el concepto de "identificador de tarea": es un componente aislado que solo sabe de sí mismo, y el evento que despacha (tarea-cambiada, con solonuevoEstadoen sudetail) refleja exactamente esa ignorancia deliberada.<task-list>, en cambio, sí conoce la estructura del arraytareas(gracias al cierre detarea.idcapturado al recorrer el array conrepeat), y es la única que puede añadir esa información antes de que el evento siga subiendo; si no la añadiera,<task-board>, que no tiene ningún cierre sobre qué tarjeta concreta disparó el evento, no podría saber qué elemento del array debe actualizar.ContadorTiempoRestanteControllerno es, en sí mismo, parte de la API pública ni de la plantilla de ningún componente: es un objeto con estado propio y ciclo de vida, pensado deliberadamente (como se explicó en 06-03) para ser reutilizable por cualquier host futuro que exponga una propiedadfechaLimite, no solo por<task-card>. Colocarlo ensrc/components/sugeriría que es, él mismo, un elemento personalizado registrado concustomElements.define, lo cual no es el caso;src/controllers/refleja correctamente su naturaleza —una pieza de comportamiento reutilizable, desacoplada de cualquier componente concreto— independientemente de cuántos hosts lo usen hoy.
Conclusión
Esta lección ha trazado el mapa completo de TaskFlow sin escribir ningún componente nuevo: el árbol de cinco componentes con su responsabilidad de una línea cada uno, los dos ejes de comunicación —propiedades hacia abajo, eventos hacia arriba— que resuelven casi toda la aplicación, la excepción deliberada del contexto de filtro para el único par de hermanos sin relación directa, y la estructura de carpetas final que organiza cada pieza según su papel, no según el módulo en que se aprendió.
Con este mapa ya trazado, la siguiente lección, "Construyendo los Componentes Principales", empieza a rellenarlo de verdad: el código completo y consolidado de <user-avatar>, <task-card> y <task-filter>, uniendo en un solo fichero por componente todas las piezas que este mismo curso fue añadiendo por partes, módulo a módulo.
Curso de Lit
Módulo 1: Introducción a Lit y Web Components
- ¿Qué son los Web Components y por qué Lit?
- Configuración del Entorno de Desarrollo
- Tu Primer Componente Lit
- Anatomía de un Componente Lit
Módulo 2: Plantillas Reactivas y Renderizado
- El Motor de Plantillas de Lit
- Expresiones e Interpolación en Plantillas
- Renderizado Condicional
- Renderizado de Listas
- El Ciclo de Renderizado
Módulo 3: Propiedades y Estado Reactivo
- Propiedades Reactivas
- Estado Interno con @state
- Tipos de Propiedades y Conversores Personalizados
- Atributos vs Propiedades y Reflexión
Módulo 4: Estilos en Componentes Lit
- CSS Encapsulado con Shadow DOM
- Estilos Compartidos entre Componentes
- Variables CSS Personalizadas y Theming
- Slots y Estilizado de Contenido Distribuido
Módulo 5: Eventos y Comunicación entre Componentes
- Manejo de Eventos DOM en Plantillas
- Eventos Personalizados: Comunicación Hijo a Padre
- Comunicación de Padre a Hijo con Propiedades
- Patrones de Comunicación entre Componentes Hermanos
Módulo 6: Ciclo de Vida y Comportamiento Avanzado
- Callbacks del Ciclo de Vida
- Hooks Reactivos: willUpdate, updated y firstUpdated
- Controladores Reactivos
- Mixins y Composición de Comportamiento
Módulo 7: Directivas y Funcionalidades Avanzadas de Plantillas
- Directivas Incorporadas: classMap, styleMap e ifDefined
- Directivas Personalizadas
- Renderizado Asíncrono con until
- Contexto Compartido con @lit/context
Módulo 8: Integración, Interoperabilidad y Despliegue
- Usar Componentes Lit en HTML Plano
- Integrar Lit con React, Vue y Angular
- Renderizado en el Servidor con @lit-labs/ssr
- Empaquetado, Publicación y TypeScript
Módulo 9: Pruebas y Buenas Prácticas
- Pruebas Unitarias con Web Test Runner
- Accesibilidad en Web Components
- Rendimiento y Optimización
- Patrones y Anti-patrones Comunes
