Mejora el rendimiento y la claridad de un habit tracker con filtros reactivos, un estado de filtros desacoplado y el observer pattern. Aquí verás cómo inyectar dependencias en tu habit service, suscribirte a cambios con subscribe y activar un engine multicriterio que filtra por frecuencia, status, racha mínima y texto con include. Además, se muestra el error que impedía la reactividad y cómo se resolvió con un simple notify.
¿Cómo se desacopla habit service con filtered state?
El primer paso es que el servicio reciba dos dependencias: repository y filtered state. Así se mantiene desacoplada la lógica de negocio del estado de filtros. Se introducen métodos como list filtered habits, un privado apply filters y un get state filters para exponer el estado cuando se necesite.
Inyección de dependencias para mantener bajo acoplamiento.
list filtered habits aplica filtros y retorna la lista correcta.
apply filters centraliza el filtrado y permite un engine multicriterio.
get state filters devuelve el estado actual de filtros.
¿Qué métodos nuevos organizan la lógica de filtros?
Se añade un método público para listar con filtros y uno privado para aplicarlos. Inicialmente, apply filters puede devolver los hábitos sin cambios mientras se construye el engine.
// Esqueleto orientativoclassHabitService{constructor(repository, filteredState){this.repository= repository;this.filteredState= filteredState;}listFilteredHabits(){// 1) obtener hábitos actuales del repository.// 2) leer filtros desde filteredState.// 3) aplicar filtros y retornar.returnthis.applyFilters(/*habits*/,/*filters*/);}// privado: motor de filtrado (temporalmente sin lógica)applyFilters(habits, filters){return habits;// luego: lógica multicriterio.}getStateFilters(){returnthis.filteredState.getState();}}
¿Cómo se usa el formulario de filtros sin filtrar ahí?
El formulario solo hace set del estado en filtered state. No filtra. La reacción ocurre después, gracias a la suscripción. Para ajustar la UI, se pidió al asistente (Cursor) modificar el form según el estado real de filtros: frequency, status, racha mínima y campo de texto, en lugar de un ejemplo de fecha.
Formulario limpio: solo set de filtros.
Adaptación de campos a frequency, status, racha y texto.
Petición con contexto de App.js para cambios precisos.
¿Cómo se implementa el observer pattern con subscribe y notify?
La clave está en el observer pattern: tras cada cambio en el estado de filtros, se notifica a los suscriptores y se renderiza la lista. Inicialmente no reaccionaba porque faltaba el notify dentro de set filters y reset. Al agregarlo, todo fue reactivo.
Suscriptores múltiples que reaccionan al mismo estado.
notify tras cada cambio o reset.
Subscribe dispara el render de hábitos.
// Esqueleto del estado observableclassFilteredState{constructor(){this.subscribers=[];this.state={frequency:'all',status:'all',minStreak:0,text:''};}subscribe(fn){this.subscribers.push(fn);}notify(){this.subscribers.forEach(fn=>fn(this.state));}setFilters(values){this.state={...this.state,...values };this.notify();// faltaba esta línea.}reset(){this.state={frequency:'all',status:'all',minStreak:0,text:''};this.notify();}getState(){returnthis.state;}}// En la capa de vistafilteredState.subscribe((current)=>{renderHabits();// reacción automática al cambio de filtros.});
¿Dónde falló la reactividad y cómo se corrigió?
El subscribe no se ejecutaba. La causa: no se llamaba a notify tras set filters. Al añadir notify en cada actualización y en reset, la UI volvió a renderizar hábitos de forma inmediata.
¿Cómo opera el motor multicriterio y el render de hábitos?
El engine de apply filters descarta por frecuencia, status, racha mínima y búsqueda por texto usando include. Si no hay filtros activos, se retorna toda la lista. Luego, render habits consume la lista ya filtrada.
Frecuencia: compara daily, weekly u otros valores.
Status: incluye activos o completados según se pida.
Racha mínima: descarta si la racha es menor.
Texto: filtra por coincidencia con include en nombre o descripción.
// Ejemplo orientativo de un motor multicriteriofunctionapplyFilters(habits, f){if(!habits)return[];return habits
.filter(h=> f.frequency&& f.frequency!=='all'? h.frequency=== f.frequency:true).filter(h=> f.status&& f.status!=='all'? h.status=== f.status:true).filter(h=> f.minStreak? h.streak>= f.minStreak:true).filter(h=> f.text?(h.name||'').toLowerCase().includes(f.text.toLowerCase()):true);}functionrenderHabits(){const filtered = habitService.listFilteredHabits();// pintar la lista usando filtered.}
¿Cómo se probó el filtrado en la UI?
Se crearon dos hábitos: uno diario y otro semanal. Al activar el filtro por daily, solo apareció el hábito diario. Al filtrar por texto, coincidieron los que contenían la cadena buscada; con una cadena inexistente, no se mostró ninguno. El patrón demostró ser limpio y escalable.
Prueba por frecuencia: diario y semanal.
Prueba por texto con include.
Prueba sin coincidencias: lista vacía intencional.
¿Te gustaría que se añada persistencia con localStorage y ver el flujo completo guardando y restaurando filtros y hábitos? Comparte en comentarios qué campos te interesa conservar y cómo quisieras visualizar los resultados.