Refactor del componente de configuración de cursos
Caso de estudio del refactor de grades-tab en el Sistema de Carga Horaria: de un componente monolítico basado en Context a una arquitectura escalable con Zustand y Repository Pattern.
Caso de estudio del refactor arquitectónico del componente
grades-tab—la configuración de cursos y asignaciones horarias de docentes— del Sistema de Cálculo de Carga Horaria: cómo pasó de un componente monolítico basado en Context a una arquitectura modular con Zustand y Repository Pattern.
Descarga: Artículo técnico (PDF) — versión 1.0, mayo 2026. Documento original por Vander Luis Catti Idme; esta página es la versión navegable equivalente.
Artículo relacionado: este caso de estudio cubre el frontend (componente de configuración de cursos). Para el backend (motor de cálculo de carga horaria en NestJS) ver Sistema de Cálculo de Carga Horaria.
El componente
El módulo grades-tab permite configurar los cursos de un colegio dentro de un período académico: qué asignaturas tiene cada curso, cuántas horas semanales, y qué docente está asignado a cada asignatura. Es una de las pantallas más usadas y de mayor complejidad de negocio del sistema.
Cada asignatura puede expandirse para ver y editar la asignación de docente (titular) y las horas correspondientes:
1. Introducción
A medida que una aplicación crece, decisiones técnicas que al inicio parecían simples comienzan a mostrar sus límites. Esto ocurre especialmente en módulos con alta complejidad de negocio, muchas interacciones de usuario y requerimientos en evolución constante.
Fue el caso de grades-tab. En su primera versión, el equipo optó por una implementación rápida basada en Context + useReducer, centralizando toda la lógica en un único hook. Funcionó bien en etapas iniciales, pero conforme el producto evolucionó aparecieron nuevos escenarios: filtros dinámicos, modales con estados independientes, sincronización de datos remotos, cálculos derivados, búsquedas reactivas, edición parcial de entidades, dependencias entre selects y múltiples flujos asincrónicos simultáneos.
Con el tiempo, el módulo se convirtió en un punto crítico de mantenimiento y escalabilidad. Este artículo describe el refactor hacia una arquitectura más modular y mantenible usando Zustand para el estado, Repository Pattern para el acceso a datos, separación explícita de responsabilidades y una reorganización estructural por dominio funcional.
2. Problemas de la implementación original
La arquitectura original centralizaba prácticamente toda la lógica en use-grades.ts, un archivo monolítico de más de 550 líneas. Ese hook era responsable de obtener datos desde Apollo, ejecutar mutations, manejar el estado de UI, procesar filtros, controlar modales, almacenar datos derivados, ejecutar lógica de negocio y coordinar actualizaciones entre componentes.
2.1 Alta cohesión accidental
Muchas responsabilidades distintas convivían en el mismo hook:
| Responsabilidad | Ejemplo |
|---|---|
| Estado de negocio | cursos, profesores, subjects |
| Estado visual | modales, loading, filtros |
| Sincronización remota | Apollo queries/mutations |
| Datos | cálculos y mapeos |
| Side effects | refreshes y sincronización |
Cualquier cambio pequeño podía impactar múltiples partes del sistema.
2.2 Complejidad creciente del reducer
La solución usaba useReducer con múltiples acciones centralizadas (SET_GRADES, OPEN_MODAL, UPDATE_FILTER, SET_LOADING…). Con el crecimiento del módulo aumentó la cantidad de actions, se incrementó el acoplamiento y el reducer empezó a comportarse como un “mini framework interno”. El problema de fondo no era useReducer en sí, sino que todo el dominio dependía de un único árbol de estado compartido.
2.3 Re-renders innecesarios
Al usar Context como mecanismo principal de distribución de estado, cualquier actualización disparaba renders amplios, incluso en componentes que no necesitaban esos cambios. Esto era especialmente problemático en las listas y en el cálculo de horas docentes: una asignación afecta a la asignatura, que a su vez afecta al curso.
2.4 Acoplamiento entre UI y capa de datos
Apollo Client estaba integrado directamente dentro del hook principal (useQuery, useMutation). Esto generaba dependencia fuerte del cliente GraphQL, dificultad para testear, poca reutilización y lógica de red mezclada con lógica de UI.
3. Objetivos del refactor
Antes de migrar se definieron objetivos arquitectónicos claros: separar responsabilidades, reducir acoplamiento, mejorar mantenibilidad, facilitar testing, minimizar renders innecesarios, permitir escalabilidad funcional y mejorar legibilidad.
4. Nueva arquitectura
La nueva implementación introduce una arquitectura basada en capas y responsabilidades explícitas.
| Aspecto | Original (grades-tab) | Refactor (grades-tab-refactor) |
|---|---|---|
| Manejo de estado | Context + useReducer | Zustand |
| Organización | Monolítica | Modular |
| Acceso a datos | Apollo directo | Repository Pattern |
| Responsabilidades | Mezcladas | Separadas |
| Renderizado | Global | Selectivo |
| Escalabilidad | Limitada | Alta |
| Testing | Difícil | Más simple |
4.1 Separación del estado con Zustand
Uno de los cambios más importantes fue reemplazar Context por múltiples stores independientes:
stores/
├── grade.store.ts
├── grade-ui.store.ts
├── grade-subject.store.ts
└── selectors-data.store.ts
grade.store.ts— estado de negocio de cursos: listado de grades, CRUD, sincronización de entidades, loading states específicos.grade-subject.store.ts— estado de negocio de asignaturas: asignación de docentes, horas y cálculos derivados.grade-ui.store.ts— estado puramente visual: búsqueda, modales, filtros, estados temporales de interacción. Evita contaminar la lógica de negocio con detalles de presentación.selectors-data.store.ts— información auxiliar: people, subsidies, datos para selects. Separarlos reduce dependencias cruzadas y evita estados gigantes.
4.2 Beneficios obtenidos con Zustand
Renderizado selectivo. Gracias a los selectores y useShallow, los componentes solo reaccionan a cambios relevantes, reduciendo significativamente los renders innecesarios.
Menor boilerplate. Se eliminó gran parte del código de reducers, actions, dispatches y constantes de tipos. El estado se volvió más directo y expresivo.
4.3 Implementación del Repository Pattern
Otro cambio fundamental fue desacoplar el acceso a datos. En la solución original, Apollo estaba embebido en el hook, lo que generaba dependencias difíciles de mockear, lógica repetida y complejidad accidental. Se introdujo una capa de repositories:
repositories/
├── grade.repository.ts
├── grade-subject.repository.ts
└── selectors-data.repository.ts
Los componentes y hooks ya no conocen Apollo directamente; ahora solo consumen métodos del dominio (gradeRepository.updateGrade(data)). Esto permite reutilizar la lógica de acceso a datos desde hooks, servicios, background sync, testing, loaders o futuras migraciones.
Hooks más livianos. En la arquitectura original los hooks eran a la vez state manager, orchestrator, data layer, UI controller y business layer. En el refactor pasan a ser coordinadores ligeros: use-grades.ts (más de 550 líneas, “todo”) se transformó en use-grade.ts (~60 líneas, sincronización mínima).
4.4 Reorganización de componentes
La estructura original mezclaba parcialmente responsabilidades. La nueva sigue dominios funcionales claros:
grades-tab-refactor/
├── components/
│ ├── grade-details/
│ └── modals/
├── hooks/
├── repositories/
├── stores/
└── utils/
4.5 Mejoras técnicas introducidas
- Debounce en búsquedas —
debounce(search, 300)para evitar renders y consultas excesivas. - Selectores optimizados — uso de
useShallow()para minimizar renders.
5. Resultados
- Corrección de rendimiento. Mientras más se usaba la pestaña de grades, se creaban instancias de Context innecesarias, lo que producía un memory leak que obligaba a refrescar toda la página. El refactor lo eliminó.
- Mejor mantenibilidad. Es más fácil localizar responsabilidades.
- Menor acoplamiento. UI, estado y acceso a datos quedaron desacoplados.
- Mejor experiencia de desarrollo. Navegación más simple, archivos pequeños, debugging más claro y testing más sencillo.
- Mayor escalabilidad. Agregar nuevas funcionalidades requiere menos impacto transversal.
6. Conclusión
El refactor de grades-tab representa una evolución arquitectónica importante: de una solución monolítica hacia una arquitectura modular, desacoplada y preparada para crecer. Los cambios principales fueron la migración de Context a Zustand, la separación explícita del estado, la introducción del Repository Pattern, la reducción de hooks gigantes, la reorganización estructural y la optimización del renderizado.
Más allá de la tecnología utilizada, el principal aprendizaje fue entender que el problema no era únicamente el tamaño del componente, sino la mezcla de responsabilidades dentro del mismo flujo. Separar el dominio correctamente permitió transformar un módulo difícil de mantener en una arquitectura mucho más predecible, escalable y extensible.
Documento técnico original por Vander Luis Catti Idme — versión 1.0, mayo de 2026. Descargar PDF.