Filtros reactivos con Observer Pattern en JS

Resumen

Conectar una interfaz gráfica con un motor de filtros de múltiple criterio se vuelve mucho más limpio cuando aplicas el observer pattern en JavaScript. Aquí verás cómo unir el FilterState, el HabitService y el renderizado reactivo para que la UI responda automáticamente a cada cambio, sin llamadas manuales al filtro.

Cómo conectar el HabitService con el FilterState

El primer paso es desacoplar dependencias. En lugar de que el HabitService cree su propia instancia del estado de filtros, la recibe por constructor. Esa inyección de dependencia mantiene el servicio flexible y facilita las pruebas [3:20].

Dentro del servicio se agregan dos métodos clave:

  • listFilteredHabits: obtiene la lista actual de hábitos y aplica los filtros vigentes.
  • applyFilters (privado): recibe los hábitos y los filtros, y ejecuta el motor multicriterio.
  • getStateFilters: expone el estado actual del FilterState cuando algún consumidor lo necesita.

Con esto, el servicio ya no decide cuándo filtrar, solo cómo. La decisión de disparar el filtrado se delega al patrón observador.

¿Qué hace el observer pattern en una app con filtros? Permite que varios elementos de la UI se suscriban a un estado central y reaccionen automáticamente cuando cambia, sin que tú tengas que llamar al render manualmente.

Por qué el listener del formulario solo debe hacer setFilter

Antes, el listener del filterForm capturaba los valores y ejecutaba directamente el filtrado. Con el patrón implementado, esa responsabilidad desaparece [5:10].

El nuevo flujo es más simple:

  1. El usuario envía el formulario.
  2. El listener toma los valores y llama a filterState.setFilter(values).
  3. El estado notifica a sus suscriptores.
  4. El render se ejecuta solo.

Este desacoplamiento es la ganancia real: la UI no sabe nada del motor de filtros y el motor no sabe nada de la UI. Ambos hablan a través del estado.

Cómo suscribir el render a los cambios de estado

En el punto donde se instancia FilterState, agregas la suscripción:

js filterState.subscribe((currentFilters) => { renderHabits(); });

Y aquí viene lo interesante: puedes tener múltiples subscribe en distintos puntos de la interfaz. Un contador, un badge, una lista, todos reaccionan al mismo cambio sin que tengas que orquestarlos manualmente.

Cómo adaptar el formulario HTML al estado real de filtros

El FilterState maneja más campos que los dos originales del HTML (frecuencia y fecha). En realidad espera cuatro: frecuencia, status, racha mínima y búsqueda por texto [8:45].

En lugar de reescribir todo el HTML a mano, puedes apoyarte en vibe coding con Cursor, VS Code, Claude Code, Gemini o ChatGPT. La clave está en dar contexto acotado: selecciona las líneas específicas de tu archivo y pásalas como referencia al prompt.

Un prompt útil sería:

Modifica el filterForm en el HTML para que sus campos coincidan con el estado de filtros definido en este contexto.

¿Por qué dar contexto acotado al LLM mejora el resultado? Porque limita la superficie de código que el modelo interpreta, evita alucinaciones y produce cambios quirúrgicos alineados al estado real de tu app.

Cómo funciona el motor applyFilters con múltiples criterios

El método applyFilters recorre cada hábito y descarta los que no cumplen el estado actual. La lógica combina programación orientada a objetos con búsquedas por texto:

  • Filtra por frecuencia comparando contra el campo del hábito.
  • Filtra por status usando métodos del objeto hábito.
  • Filtra por racha mínima cuando el estado lo exige.
  • Aplica includes para búsquedas por coincidencia de texto.

Si no hay filtros activos, cada criterio devuelve true y la lista completa pasa sin recortes. Ese fallback mantiene el comportamiento por defecto sano.

Cuál es el error más común al implementar Observer Pattern

Al probar el flujo en el navegador, el console.log mostraba los valores del formulario, pero el subscribe nunca se ejecutaba. La lista no se re renderizaba [15:30].

El culpable era una línea faltante dentro de setFilter: el notify(). Sin esa llamada, el estado cambia pero nadie avisa a los suscriptores.

js setFilter(newFilters) { this.filters = { ...this.filters, ...newFilters }; this.notify(); }

Esa misma notificación debe aparecer en cualquier método que modifique el estado, incluido un reset que limpie todos los filtros.

¿Qué hace notify en el observer pattern? Recorre la lista de suscriptores y ejecuta cada callback registrado, pasándoles el estado actualizado. Sin notify, el patrón queda a medias y deja de ser reactivo.

Cómo validar que la reactividad ya funciona

Con el notify en su lugar, la prueba es directa:

  • Registras un hábito diario y otro semanal.
  • Filtras por daily y solo aparece el diario.
  • Cambias a weekly y solo aparece el semanal.
  • Buscas por una cadena de texto y solo queda la coincidencia.
  • Escribes algo que no existe y la lista queda vacía.

Cada acción dispara un solo setFilter, y el render responde solo. Esa es la señal de que el patrón está completo.

El siguiente paso natural es sumar persistencia con localStorage para que los hábitos sobrevivan a un hard reload del navegador. ¿Qué otros elementos de tu UI suscribirías a este mismo FilterState? Cuéntame en los comentarios.