Sidebar derecha — Actividad Global (Calendario)
Última actualización: 06-01-2026 09:01 PM
1) Objetivo
Section titled “1) Objetivo”Este módulo implementa un calendario global en la barra lateral derecha para visualizar actividad por día agregada a través de todos los proyectos.
La UI:
- Muestra 12 meses como bloques independientes.
- Cada mes es una grilla de 7 columnas (D–S).
- Cada celda (día) muestra un número: horas/día (máximo 1 decimal).
- El color de cada celda es un degradado verde según intensidad (relativa al máximo del año seleccionado).
- Hace auto-scroll al mes actual (si el año seleccionado es el actual) o al primer mes con actividad (si no).
2) Alcance funcional
Section titled “2) Alcance funcional”2.1 Lo que SÍ hace hoy
Section titled “2.1 Lo que SÍ hace hoy”- Agregación global: suma
minutospor fechaYYYY-MM-DDdesde todos los retos de todos los proyectos. - Selector de año (derivado automáticamente de las fechas presentes).
- Render por meses (12 secciones) con encabezado del mes.
- Grilla del mes con padding inicial/final para alinear semanas.
- Número dentro de cada celda: horas con 1 decimal (redondeo).
- Colores en verdes: 0 neutro; 4 niveles de verde.
- Auto-focus/auto-scroll al mes relevante.
2.2 Lo que NO hace (todavía)
Section titled “2.2 Lo que NO hace (todavía)”- No filtra/selecciona retos al hacer click en un día (mejora opcional).
- No tiene tooltip custom (solo
titleHTML). - No tiene botón “Ir a hoy” (fácil de agregar, opcional).
- No diferencia “solo done” vs “todos” (opcional si se decide).
- No persiste preferencias de UI del calendario (año/scroll/mes seleccionado). Por ahora, la única UI persistida en pAVANTI es el colapsado de grupos (local
pweb_proyectos_ui_v1y export/import v2 víaui.collapsedByProyecto).
3) Contratos y entradas/salidas
Section titled “3) Contratos y entradas/salidas”3.1 Entrada (props)
Section titled “3.1 Entrada (props)”El componente recibe:
proyectos: Proyecto[]
Esto es intencional: la actividad es global, no depende del proyecto seleccionado.
3.2 Salida (UI)
Section titled “3.2 Salida (UI)”- Bloques mensuales con grilla y celdas.
- Cada celda:
- valor numérico
horas/día, - color por intensidad,
- ring/foco para “hoy” cuando aplica.
- valor numérico
4) Fuentes de datos
Section titled “4) Fuentes de datos”4.1 Origen (árbol Proyectos → Nodos)
Section titled “4.1 Origen (árbol Proyectos → Nodos)”La actividad se basa en:
NodoReto.fecha(esperado:YYYY-MM-DD, o string que empiece por eso).NodoReto.minutos(número >= 0).
Reglas:
- Si
fechaes inválida → se ignora esa entrada. - Si
minutosno es número o es negativo → se normaliza a 0 y se ignora si queda 0. - Se suman minutos por día:
YYYY-MM-DD -> minutos_totales.
4.2 Agregación global
Section titled “4.2 Agregación global”La agregación global vive en lib/actividad.ts con:
actividadPorDia(proyecto)actividadGlobalPorDia(proyectos)yearsDesdeActividad(actividad)normalizarFechaISO(fecha)
Este diseño mantiene la lógica:
- sin React,
- testable,
- reutilizable si mañana se construyen reportes, export, gráficos, etc.
5) Diseño del calendario (render mensual)
Section titled “5) Diseño del calendario (render mensual)”5.1 Grilla por mes
Section titled “5.1 Grilla por mes”Cada mes:
- encabezado (nombre del mes + año),
- fila de días de semana:
D L M X J V S, - grilla
grid-cols-7donde:- se insertan celdas vacías antes del día 1 (padding inicial),
- luego celdas de 1..N,
- luego celdas vacías al final para completar la última semana.
Motivo: esto asegura que el layout sea “calendario real” y se lea bien por meses.
5.2 Estabilidad de fecha y DST
Section titled “5.2 Estabilidad de fecha y DST”Se construyen fechas usando “mediodía local” (12:00) para evitar errores típicos por cambios de hora (DST) en algunos entornos.
- Se evita instanciar a las 00:00 donde pueden ocurrir saltos/offset raros.
6) Número dentro de cada celda (horas/día)
Section titled “6) Número dentro de cada celda (horas/día)”6.1 Conversión
Section titled “6.1 Conversión”horas = minutos / 60- redondeo a 1 decimal:
Math.round(horas * 10) / 10 - presentación:
- si es entero:
"3" - si no:
"3.5"
- si es entero:
6.2 Consideraciones de legibilidad
Section titled “6.2 Consideraciones de legibilidad”- La UI es densa por naturaleza (muchas celdas).
- Se usa tipografía pequeña y
tabular-numspara alineación. - Si el “0” visualmente estorba, es un cambio trivial:
- mostrar vacío cuando
minutos === 0. - (Pero hoy se mantiene consistente mostrando
0.)
- mostrar vacío cuando
7) Colores (verde degradado)
Section titled “7) Colores (verde degradado)”7.1 Principio
Section titled “7.1 Principio”- Solo verde (preferencia estética + consistencia).
- 5 estados:
- 0: neutro (gris/oscuro)
- 1..4: verde con intensidad creciente.
7.2 Escala de intensidad
Section titled “7.2 Escala de intensidad”La intensidad se calcula relativa al máximo del año seleccionado:
maxYear = max(minutos_por_dia_en_year)ratio = minutosDia / maxYear- thresholds:
- <= 0.25 → nivel 1
- <= 0.50 → nivel 2
- <= 0.75 → nivel 3
-
0.75 → nivel 4
Ventaja: escala automática. Riesgo: si hay un outlier enorme en un solo día, comprime el resto. Si eso pasa, alternativa (opcional):
- usar percentiles o escala logarítmica.
8) Auto-focus: scroll al mes relevante
Section titled “8) Auto-focus: scroll al mes relevante”8.1 Regla de enfoque
Section titled “8.1 Regla de enfoque”Cuando cambia el año (o al montar):
- Si
year === currentYear→ enfoca el mes actual. - Si
year !== currentYear→ enfoca el primer mes con actividad (si existe), si no enero.
8.2 Implementación
Section titled “8.2 Implementación”- Se guardan referencias a cada
<section>de mes (monthRefs[0..11]). - Se ejecuta
scrollIntoView({ block: "start", behavior: "smooth" }). - Se usa un guard (
scrolledYearRef) para no re-disparar el scroll repetidamente.
9) Performance y complejidad
Section titled “9) Performance y complejidad”9.1 Complejidad
Section titled “9.1 Complejidad”- Agregación global: O(total de nodos/reto en todos los proyectos).
- Render:
- 12 meses,
- ~ (35 a 42) celdas por mes (dependiendo padding),
- total ~ 420–504 celdas/año.
Esto es perfectamente razonable para un sidebar.
9.2 Memoización
Section titled “9.2 Memoización”Se recomienda (y se aplica) useMemo para:
- actividad global,
- años disponibles,
- meses ya construidos,
- máximo anual.
10) Edge cases a tener presentes
Section titled “10) Edge cases a tener presentes”fechacon tiempo (YYYY-MM-DDTHH:mm...) → se recorta a 10 chars.- fechas inválidas:
2025-02-31→ se ignora. minutosundefined/string → se normaliza.- año sin actividad: todo neutro, mensaje “no hay actividad”.
- importaciones: si el import trae fechas fuera de rango (1970..3000), se ignoran.
11) Mejoras opcionales (futuras)
Section titled “11) Mejoras opcionales (futuras)”- Botón “Ir a hoy”
- útil cuando el usuario se pierde en scroll.
- se implementa reusando
scrollIntoViewhacia el mes actual.
- Click en día → filtrar/centrar retos por fecha
- el calendario se vuelve una herramienta de navegación real.
- requiere un contrato hacia
TablaProyectos(p.ej.selectedDateen estado global UI).
- Modo “Solo done”
- togglear si se suma actividad de todos los retos o solo completados.
- Mostrar número solo si > 0
- reduce ruido visual.
- Tooltip bonito
- reemplazar
titlepor popover accesible (sin bloquear scroll).
- Escala robusta
- percentiles o log para evitar outliers.
12) Checklist de mantenimiento
Section titled “12) Checklist de mantenimiento”- Mantener
lib/actividad.tscomo fuente de verdad:- no duplicar agregación en UI.
- Validar que
normalizarFechaISOsiga siendo conservador. - No introducir dependencias “laterales” entre dominios:
- este módulo vive en
proyectos/y consumeproyectos/lib.
- este módulo vive en
13) Archivos implicados
Section titled “13) Archivos implicados”- UI:
/src/proyectos/components/SidebarDerechaProyectos.tsx
- Lógica:
/src/proyectos/lib/actividad.ts
- Orquestación:
/src/proyectos/components/ProyectosApp.tsx(pasaproyectosal sidebar derecho)