Potencia tu proyecto de hábitos con polimorfismo en JavaScript: dos clases especializadas calculan rachas diarias y semanales con el mismo método, mientras una estrategia según la frecuencia decide qué lógica ejecutar. Verás cómo organizar logs, normalizar fechas, evitar errores comunes y preparar la UI en HTML y CSS para mostrar el resultado.
¿Cómo aplicar polimorfismo en JavaScript para rachas de hábitos?
El enfoque usa una tabla de mapeo por frecuencia que instancia calculators y un método en Habit que selecciona la estrategia correcta. Ambas clases comparten el método calculate, lo que habilita el polimorfismo: mismo mensaje, comportamientos distintos.
Mapa de estrategias con streakCalculators: daily y weekly.
Selección dinámica según this.frequency del hábito.
Manejo de fecha actual con new Date() como today.
Retorno seguro: si no hay estrategia, racha en 0.
// Mapa de estrategias por frecuenciaconst streakCalculators ={daily:newDailyStreakCalculator(),weekly:newWeeklyStreakCalculator(),};// En la clase HabitcalculateStreak(today =newDate()){const calculator = streakCalculators[this.frequency];if(!calculator)return0;// sin estrategia, no hay rachareturn calculator.calculate(this,this.getLogs(), today);}
¿Qué estructura usa calculateStreak con frecuencia y today?
El método recibe today, busca el calculator por frecuencia y llama a calculate(habit, logs, today). Aquí ocurre el polimorfismo: DailyStreakCalculator y WeeklyStreakCalculator comparten firma, pero su lógica interna difiere.
Parámetros clave: habit, logs de fechas, today.
Validación inicial: si no hay registros, racha 0.
Consistencia de interfaz: ambos calculators exponen calculate.
¿Qué hacen DailyStreakCalculator y WeeklyStreakCalculator?
Ambas clases calculan rachas con enfoques distintos. La diaria compara fechas día a día. La semanal agrupa por semana y recorre semanas consecutivas. Se usan utilidades internas (métodos privados) para formatear fechas y agrupar semanas.
DailyStreakCalculator:
Ordena logs del más reciente al más antiguo.
Normaliza today sin horas, minutos, segundos ni milisegundos.
Compara contra la fecha esperada en formato año-mes-día.
Si no coincide, rompe el ciclo y retorna la racha.
classDailyStreakCalculator{calculate(habit, logs, today){if(!logs || logs.length===0)return0;const sorted =[...logs].sort((a, b)=> b - a);// reciente → antiguoconst current =newDate(today.getFullYear(), today.getMonth(), today.getDate());let streak =0;for(const log of sorted){const expected =newDate(current); expected.setDate(current.getDate()- streak);if(this.#toKey(log)===this.#toKey(expected)) streak++;elsebreak;// racha interrumpida}return streak;}// método privado: formatea fecha a clave estable (YYYY-M-D)#toKey(date){return`${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`;}}
WeeklyStreakCalculator:
Agrupa logs por semana con una utilería interna.
Ordena semanas con Object.keys de más reciente a más antigua.
Obtiene la semana actual y avanza comparando con la semana esperada.
Si la semana no coincide, rompe el bucle y retorna la racha.
classWeeklyStreakCalculator{calculate(habit, logs, today){if(!logs || logs.length===0)return0;const byWeek =this.#groupByWeek(logs);const weeks =Object.keys(byWeek).sort((a, b)=> b.localeCompare(a));const currentWeek =this.#weekKey(today);let streak =0;let expected = currentWeek;for(const wk of weeks){if(wk === expected){ streak++; expected =this.#prevWeek(expected);}else{break;// racha interrumpida}}return streak;}// utilería: agrupa fechas por clave de semana#groupByWeek(dates){return dates.reduce((acc, d)=>{const key =this.#weekKey(d);(acc[key]||=[]).push(d);return acc;},{});}// clave de semana y cálculo de semana previa (implementación consistente en tu código)#weekKey(date){/* ... */}#prevWeek(weekKey){/* ... */}}
¿Qué considerar al trabajar con fechas en JavaScript?
Las fechas requieren atención al detalle: normalización, conversión y consistencia. JavaScript ofrece múltiples formas de transformar fechas; cuida el formato de claves y el cálculo de número de semana y semana previa para evitar desajustes.
Normaliza la fecha para comparaciones estables.
Evita dependencias implícitas de zona horaria.
Sé consistente en las claves de día y semana.
¿Cómo depurar y probar con consola y UI?
La inicialización crea hábitos de prueba y muestra resultados en consola con live server. Un detalle importante: olvidar ejecutar un método puede romper la racha. La técnica del patito de hule ayuda a leer el código en voz alta y detectar errores.
Demo: hábito “leer” daily con 3 registros. Hábito “ejercicio” weekly con 2 registros.
Salida: toDisplayString() y calculateStreak() en console.log.
Observación: rachas en 0 por pasar this.getLogs en vez de this.getLogs().
Corrección: agregar paréntesis para ejecutar la función.
¿Qué error común con getLogs y cómo evitarlo?
Pasar una referencia a función no la ejecuta.
Usa this.getLogs() al invocar calculate.
Valida con console.log los parámetros antes del cálculo.
¿Qué reto de interfaz con HTML y CSS?
Renderiza la racha diaria y semanal con el retorno de calculate.
Usa la estructura visual ya definida en HTML y CSS.
Integra el valor en componentes de la UI de hábitos.
¿Tienes una propuesta de UI o mejoras a la lógica de rachas? Compártelas en comentarios y armemos un buen debate técnico.
La única propuesta que se me ocurre es añadir un logo de fuego en cada hábito y que se incremente por cada vez que le das check.
Esto motivaría al usuario en seguir realizando su hábito.
Algo clave del código es su estabilidad. Aunque la app no funcione bien por un error lógico, el hecho de que no se rompa ni mande errores a consola dice mucho de lo bien estructurado que está el manejo de excepciones. Muy limpio.