JOSE MARIA SUAREZ GIESE
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.
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.
streakCalculators: daily y weekly.this.frequency del hábito.new Date() como today.// Mapa de estrategias por frecuencia const streakCalculators = { daily: new DailyStreakCalculator(), weekly: new WeeklyStreakCalculator(), }; // En la clase Habit calculateStreak(today = new Date()) { const calculator = streakCalculators[this.frequency]; if (!calculator) return 0; // sin estrategia, no hay racha return 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.
habit, logs de fechas, today.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.
today sin horas, minutos, segundos ni milisegundos.class DailyStreakCalculator { calculate(habit, logs, today) { if (!logs || logs.length === 0) return 0; const sorted = [...logs].sort((a, b) => b - a); // reciente → antiguo const current = new Date(today.getFullYear(), today.getMonth(), today.getDate()); let streak = 0; for (const log of sorted) { const expected = new Date(current); expected.setDate(current.getDate() - streak); if (this.#toKey(log) === this.#toKey(expected)) streak++; else break; // 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()}`; } }
Object.keys de más reciente a más antigua.class WeeklyStreakCalculator { calculate(habit, logs, today) { if (!logs || logs.length === 0) return 0; 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.
¿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.
daily con 3 registros. Hábito “ejercicio” weekly con 2 registros.toDisplayString() y calculateStreak() en console.log.this.getLogs en vez de this.getLogs().¿Qué error común con getLogs y cómo evitarlo?
this.getLogs() al invocar calculate.console.log los parámetros antes del cálculo.¿Qué reto de interfaz con HTML y CSS?
calculate.¿Tienes una propuesta de UI o mejoras a la lógica de rachas? Compártelas en comentarios y armemos un buen debate técnico.
Implementación de tarjetas de rachas
Implementé el renderizado de las tarjetas de rachas para mostrar la información de cada hábito.
Cambios realizados
createStreakCard(habit), que construye y devuelve una tarjeta sin modificar el contenedor.renderStreaks() ahora recorre todos los hábitos, crea una tarjeta por cada uno y las inserta juntas usando replaceChildren.frequencyLabels para convertir:
daily en “Diario” y “días”.weekly en “Semanal” y “semanas”.habit.namehabit.calculateStreak()habit.getLogs().lengthstreak-active cuando la racha es mayor que cero.streak-inactive cuando la racha es cero.renderStreaks() después de crear hábitos, registrar check-ins y marcar o desmarcar casillas.Decisiones tomadas
Use createElement y textContent para construir la interfaz en lugar de concatenar HTML. Esto evita que un nombre de hábito que contenga etiquetas HTML sea interpretado por el navegador.
Use un objeto indexado por frecuencia en lugar de varios if/else, siguiendo el mismo patrón de streakCalculators.
Mapa de frecuencia
const frequencyLabels = { daily: { nombre: 'Diario', unidad: 'días', }, weekly: { nombre: 'Semanal', unidad: 'semanas', }, };
Función que crea una tarjeta
Este es el fragmento principal para demostrar la separación de responsabilidades:
function createStreakCard(habit) { const habitStreak = habit.calculateStreak(); const card = document.createElement('div'); if (habitStreak > 0) { card.className = 'streak-card streak-active'; } else { card.className = 'streak-card streak-inactive'; } const header = document.createElement('div'); header.className = 'streak-header'; const habitName = document.createElement('p'); habitName.className = 'streak-habit-name'; habitName.textContent = habit.name; const frequencyTags = document.createElement('p'); frequencyTags.className = 'streak-frequency'; frequencyTags.textContent = frequencyLabels[habit.frequency].nombre; const count = document.createElement('span'); count.className = 'streak-count'; count.textContent = habitStreak; const label = document.createElement('span'); label.className = 'streak-label'; label.textContent = frequencyLabels[habit.frequency].unidad; const checkCounts = document.createElement('p'); checkCounts.className = 'check-count'; checkCounts.textContent = `Check-ins totales: ${habit.getLogs().length}`; // El resto de elementos se agregan a sus respectivos contenedores. return card; }
Renderizado de todas las carta
Este fragmento muestra como se crea una tarjeta por cada hábito:
function renderStreaks() { const streaksContainer = document.getElementById('streaksContainer'); if (habits.length === 0) { const emptyState = document.createElement('p'); emptyState.className = 'empty-state'; emptyState.textContent = 'Las rachas aparecerán aquí cuando registres hábitos.'; streaksContainer.replaceChildren(emptyState); return; } const cards = habits.map((habit) => { return createStreakCard(habit); }); streaksContainer.replaceChildren(...cards); }
JOSE MARIA SUAREZ GIESE
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.
Cristhian Daza
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.