Skip to content

Calendario

Ruta: /teacher/calendar · Atajo: g c · Sidebar: Calendario

El Calendario es la herramienta principal de programacion del profesor. Permite ver, crear, modificar y completar sesiones con vista semanal interactiva, drag-to-reschedule, y edicion de disponibilidad inline.

Calendar page

Grid de 7 dias (lun-dom) con slots por hora. 60px por hora de altura.

  • Regla horaria (columna izquierda): Handles superior/inferior para ajustar rango visible, ticks a 15 min
  • Indicador de hora actual: Punto rojo + linea en la columna de hoy
  • Columna de hoy: Resaltada con fondo primary claro
  • Bloques no disponibles: Overlay gris
  • Time off: Bloques grises rayados
  • Extra availability: Bloques verdes claros
  • Eventos: Bloques coloreados por estado, arrastrables, clickables
  • Flechas izq/der: Cambiar semana (ArrowLeft/ArrowRight)
  • Label de rango: “15 Ene – 21 Ene” (formato dinamico)
  • Toggle vista: Semana (grid) / Lista (tabla) — atajos 1 y 2

Cada evento tiene dos dimensiones de color:

  1. Color de estado — controla el color del borde izquierdo y el fondo del bloque
Estado Color
hold Amarillo/amber (#f59e0b)
pending_confirmation Amarillo/amber (#f59e0b)
scheduled Azul (primary)
in_progress Azul (primary)
completed Verde (success)
pending_review Verde (success)
cancelled_by_student Gris
cancelled_by_teacher Gris
cancelled_system Gris
no_show_student Rojo
no_show_teacher Rojo
rescheduled Gris
expired Gris
  1. Color de servicio — el campo services.color (hex) se devuelve como serviceColor en la respuesta de /teacher/calendar. El CalendarEvent expone serviceColor?: string | null. El EventBlock usa el color de estado como color principal del bloque; serviceColor esta disponible para estilos adicionales.

El grid semanal detecta eventos solapados usando la funcion findOverlaps():

  • Anillo rojo en bloques que se superponen con otra sesion o time off
  • Banner de advertencia en el formulario de creacion cuando el horario elegido colisiona
  • Banner de advertencia en el dialog de drag-reschedule al soltar en un slot ocupado

Boton en la toolbar con dropdown de formato:

  • iCal (.ics): Salida conforme a RFC 5545 con VCALENDAR/VEVENT. Compatible con Google Calendar, Outlook, Apple Calendar.
  • CSV (.csv): Columnas: fecha, hora inicio, hora fin, alumno, servicio, estado.
  • Parametros: format=ical|csv, start=, end= (rango de fechas).

Toggle de seleccion en la toolbar activa el modo seleccion:

  • Click en cualquier sesion (vista semana o lista) la selecciona/deselecciona
  • Barra flotante inferior muestra cuantas sesiones estan seleccionadas y botones: Completar, Cancelar, No-show
  • Acciones destructivas muestran ConfirmDialog antes de ejecutar
  • Al confirmar, llama a POST /teacher/sessions/bulk-action con los IDs y la accion

Cada bloque de evento muestra:

  • Titulo (nombre del alumno o “[Prueba] Alumno”)
  • Badge de template count (icono BookOpen) si tiene templates vinculados
  • Click abre el Panel de detalle (lateral derecho)
  • Drag-and-drop para reprogramar (threshold 8px, snap a 15 min, cross-week via hot zones)

El drag-to-reschedule soporta navegacion entre semanas. Cuando el profesor arrastra un evento hacia el borde izquierdo o derecho del grid:

  1. Hot zone (48px desde el borde): Se muestra un indicador visual con gradiente + chevron pulsante + etiqueta (“Sem. anterior” / “Sem. siguiente”)
  2. Dwell (400ms): Tras mantener el puntero en la hot zone durante 400ms, la semana navega automaticamente
  3. Cooldown (700ms): Tras cada navegacion, hay un cooldown de 700ms antes de permitir otra navegacion
  4. Ghost preview: El ghost del evento permanece visible durante la transicion — los column refs se actualizan al remontar las nuevas columnas
  5. Drop: Al soltar el evento en la nueva semana, se muestra el ConfirmDialog con la fecha correcta cross-week

Prop: onEdgeNavigate?: (direction: 'prev' | 'next') => void en WeekCalendarGrid. Si no se proporciona, el comportamiento es identico al anterior (sin hot zones ni navegacion).

Por que funciona entre re-renders: eventDragRef es un ref (sobrevive al re-render de weekOffset), pointermove es un listener global, y dayKeyColumnRefs se actualiza automaticamente cuando las nuevas columnas se montan. El callback onEventDragReschedule recibe el CalendarEvent directamente desde el ref (4to argumento) en lugar de buscarlo en el array events — que tras la navegacion contiene los datos de la nueva semana, no de la original.

Al hacer click en un evento, se abre un panel lateral con:

  1. Header: Titulo + avatar del alumno + badge de estado
  2. Hora: Fecha/hora con boton de reprogramar
  3. Participantes (solo grupos): Lista de asistentes + agregar/eliminar
  4. Meet link: Boton para entrar a la videollamada
  5. Templates: Picker multi-template (popover)
  6. Recursos: Picker de recursos con link/unlink
  7. Tags: Multi-select de tags
  8. Notas de sesion (colapsable): Summary, teacher notes y student mood del satellite session_content. Muestra “sin notas” si el campo esta vacio. Datos leidos desde session.sessionContent con fallback a event.summary / event.teacherNotes / event.studentMood (pre-cargados en el calendario).
  9. Acciones: Completar, No-show, Cancelar
  10. Link al perfil del alumno

Sheet con 3 tabs:

Tab 1 — Programar clase:

  • Servicio (dropdown), alumno (individual o multi-select para grupo)
  • Frecuencia: unica, semanal o recurrente
  • Unica: Date picker + input de hora
  • Semanal: Slot picker por dia de la semana + fecha de inicio
  • Recurrente: Crea sesiones con un recurringGroupId compartido que agrupa la serie. Permite editar/cancelar toda la serie (ver “Edicion de clases recurrentes”).
  • Template picker (opcional)
  • Display de creditos disponibles

Tab 2 — Anadir time off:

  • Frecuencia: unico o semanal (8 semanas default)
  • Date picker + hora inicio/fin
  • Razon (textarea opcional)

Tab 3 — Anadir disponibilidad extra:

  • Misma estructura que time off, tipo extra_availability

Toggle con icono de lapiz (e). Cuando activo:

  • Banner “Modo edicion” con conteo de slots
  • Click + drag en columna crea nueva regla de disponibilidad (snap 15 min)
  • Handles superior/inferior para redimensionar reglas existentes
  • Boton X para eliminar regla
  • Validacion de overlap (backend rechaza solapamientos en el mismo dia)

Gestion de plantillas de disponibilidad:

  • Schedule base (siempre presente)
  • Schedules custom con toggle activar/desactivar
  • Acciones: crear, renombrar, duplicar, eliminar
  • Activaciones por rango de fechas (popover): start/end date para cada schedule
  • Badge “Activo ahora” para activaciones vigentes

Al crear un nuevo schedule, se ofrece una seleccion de 4 plantillas predefinidas:

Preset Descripcion
“9 a 5” Lunes a viernes, 09:00 – 17:00
“Tardes” Lunes a viernes, 15:00 – 20:00
“Fines de semana” Sabado y domingo, 09:00 – 14:00
“Horario completo” Lunes a domingo, 08:00 – 22:00

El profesor puede elegir un preset como punto de partida y luego ajustar las reglas individualmente. Si prefiere empezar de cero, puede crear un schedule vacio sin seleccionar ningun preset.

Al crear una sesion en el Action Sheet, se puede sobrescribir la duracion definida por el servicio. Un campo opcional “Duracion” (en minutos) permite programar, por ejemplo, una clase de 30 minutos con un servicio configurado a 60 minutos. El campo solo aparece cuando la sesion es de tipo unico o puntual; las sesiones recurrentes mantienen la duracion del servicio.

Atajo Accion
n Nueva clase
t Ir a hoy (semana actual)
ArrowLeft / ArrowRight Semana anterior / siguiente
1 / 2 Vista semana / lista
c Completar evento seleccionado
Escape Cerrar paneles / deseleccionar

Configuracion del calendario (Settings Sheet)

Section titled “Configuracion del calendario (Settings Sheet)”

Panel de ajustes accesible desde el icono de engranaje en la toolbar. Agrupa cuatro secciones:

GENERAL

  • Vista por defecto: Semana / Lista / Heatmap
  • Scroll / Fit: Modo de altura del grid semanal. scroll = altura fija con scroll (default). fit = comprime el grid al rango horario con sesiones.
  • Modo de completado: Manual (el profesor marca completadas) / Auto (se completan solas al terminar).

LIMITES

  • Sesiones por dia: Slider 1–20 (default 8). Limita cuantas sesiones se pueden programar en un mismo dia. El sistema avisa visualmente si se supera el limite al crear sesiones.

RECORDATORIOS

  • Recordatorio antes de clase: Toggle on/off. Cuando activo, el sistema envia un email al profesor X horas antes de cada sesion programada.
  • Horas de antelacion: Selector entre 1 h · 2 h · 12 h · 24 h · 48 h. Solo visible cuando el toggle esta activo.

El recordatorio lo ejecuta session-reminder-worker.ts (BullMQ cron cada 15 min). Escanea sesiones con reminderSentAt IS NULL, status scheduled/confirmed, que empiecen dentro de una ventana de ±15 min alrededor del momento ahora + hours. Una vez enviado, marca sessions.reminderSentAt = now.

Guardado: El boton “Guardar cambios” envia dos peticiones en paralelo:

  • PATCH /teacher/settings/scheduling — maxSessionsPerDay, sessionReminderHours, completionMode, etc.
  • PATCH /teacher/settings/notifications — notificationPreferences.reminderBefore

Feature Descripcion Estado Implementado
Arrastrar a otra semana Hot zones en los bordes izq/der del grid. Dwell 400ms navega a semana adyacente, cooldown 700ms. Ghost persiste entre re-renders via refs ✅ Batch 5
Colores por servicio services.color se expone como serviceColor en la respuesta del calendario y en el CalendarEvent ✅ Batch 2
Notas de sesion en el panel El panel de detalle ahora muestra summary, teacherNotes y studentMood del satellite session_content en una seccion colapsable ✅ Batch 2
Edicion de clases recurrentes Las sesiones ahora se pueden crear con un recurringGroupId que agrupa la serie ✅ Batch 2
Presets de disponibilidad 4 plantillas predefinidas (“9 a 5”, “Tardes”, “Fines de semana”, “Horario completo”) disponibles al crear un nuevo schedule ✅ Batch 3
Duracion custom de sesion El Action Sheet permite sobrescribir la duracion del servicio para una sesion puntual ✅ Batch 3
Error state en lugar de mock events Cuando la API /teacher/calendar falla, se muestra un estado de error con mensaje y boton “Reintentar”. La UI ya no enmascara el problema con datos falsos ✅ Batch 4
Timezone del profesor en reprogramacion RescheduleSheet ahora usa el timezone del profesor en lugar del timezone del navegador ✅ Batch 4
Creditos por participante en panel de grupo El panel de detalle de evento ahora muestra el credito disponible de cada participante individualmente ✅ Batch 4
Validacion de disponibilidad para slots semanales LessonForm valida cada slot semanal contra las reglas de disponibilidad del profesor. Si un slot cae fuera, se muestra un banner de advertencia y se bloquea el envio ✅ Batch 4
Vista heatmap de actividad Mapa de calor hora×dia para detectar patrones de carga. Periodos: 1 sem / 2 sem / 1 mes / 3 meses ✅ Batch 6
Recordatorios automaticos de sesion Email al profesor X horas antes de cada clase. Toggle + selector de antelacion (1/2/12/24/48 h). BullMQ cron cada 15 min ✅ Batch 6
Limite de sesiones por dia Slider 1–20 en la configuracion del calendario. Aviso visual al superar el limite ✅ Batch 6
Post-class prompt Tarjeta flotante (bottom-right, z-50) que aparece cuando hay sesiones terminadas en las ultimas 2h con status=scheduled. Campos: resumen + deberes. Acciones: Completar, No asistio, Despues. Navegacion entre multiples sesiones pendientes. Descarte via sessionStorage ✅ Google Meet batch

Mejora Descripcion Dificultad Estado Implementado
(sin items pendientes)

Archivo Proposito
apps/app/src/routes/teacher/calendar.lazy.tsx Pagina principal + settings sheet
apps/app/src/components/calendar/week-calendar-grid.tsx Grid semanal
apps/app/src/components/calendar/event-detail-panel.tsx Panel de detalle
apps/app/src/components/calendar/calendar-action-sheet.tsx Formularios de creacion
apps/app/src/components/calendar/calendar-heatmap-view.tsx Vista heatmap hora×dia
apps/app/src/components/calendar/reschedule-sheet.tsx Reprogramacion
apps/app/src/components/calendar/schedule-selector.tsx Schedules de disponibilidad
apps/app/src/components/calendar/calendar-list-view.tsx Vista lista
apps/api/src/services/teacher/calendar-service.ts Servicio de calendario
apps/api/src/services/scheduling/session-service.ts CRUD de sesiones
apps/api/src/services/scheduling/availability-service.ts Reglas de disponibilidad
apps/api/src/jobs/session-reminder-worker.ts Worker BullMQ de recordatorios
Endpoint Metodo Proposito
/teacher/calendar GET Sesiones + overrides del rango
/teacher/sessions POST Crear sesion (unica/semanal)
/teacher/sessions/:id/complete POST Completar sesion
/teacher/sessions/:id/no-show POST Marcar no-show
/teacher/sessions/:id/cancel POST Cancelar (con politica)
/teacher/sessions/:id/reschedule POST Reprogramar
/teacher/sessions/:id/templates PUT Vincular templates
/teacher/sessions/:id/resources PUT Vincular recursos
/teacher/sessions/:id/content PATCH Actualizar tags/notas
/teacher/sessions/:id/participants POST/DELETE Gestionar grupo
/teacher/sessions/bulk-action POST Accion en masa (complete/cancel/no_show)
/teacher/calendar/export GET Exportar sesiones en iCal o CSV
/teacher/availability-overrides POST Crear time off / extra
/teacher/availability-rules POST/PATCH/DELETE Reglas de disponibilidad
/teacher/availability-schedules CRUD Schedules de disponibilidad
/teacher/insights/heatmap GET Datos del heatmap (?days=7|14|30|90)
/teacher/settings/scheduling PATCH Incluye maxSessionsPerDay, sessionReminderHours
/teacher/settings/notifications PATCH Incluye notificationPreferences.reminderBefore
packages/shared/src/schemas/session.ts
export const bulkSessionActionSchema = z.object({
sessionIds: z.array(z.string().uuid()).min(1),
action: z.enum(['complete', 'cancel', 'no_show']),
});
apps/app/src/components/calendar/week-calendar-grid.tsx
export interface CalendarEvent {
id: string;
title: string;
start: string; // ISO 8601 (UTC)
end: string; // ISO 8601 (UTC)
color: string; // hex — color de estado
serviceColor?: string | null; // hex — color del servicio (services.color)
subtitle?: string;
status?: string;
studentId?: string | null;
type?: 'booking' | 'lesson' | 'session' | 'time_off' | 'extra_availability';
templateId?: string | null;
templateName?: string | null;
templates?: Array<{ id: string; name: string }>;
templateCount?: number;
summary?: string | null; // session_content.summary
teacherNotes?: string | null; // session_content.teacherNotes
studentMood?: string | null; // session_content.studentMood
recurringGroupId?: string | null; // agrupa sesiones de la misma serie recurrente
}
useQuery({ queryKey: ['teacher-calendar', start, end], queryFn: ... })
useQuery({ queryKey: ['session-detail', eventId], queryFn: ... })
useQuery({ queryKey: ['teacher-students-for-scheduling'], queryFn: ... })
useQuery({ queryKey: ['teacher-services'], queryFn: ... })
useQuery({ queryKey: ['teacher', 'availability-schedules'], queryFn: ... })
useQuery({ queryKey: ['teacher', 'availability', scheduleId | 'all'], queryFn: ... })
useQuery({ queryKey: ['teacher', 'settings'], queryFn: ... })
useQuery({ queryKey: ['tags'], queryFn: ... })
useQuery({ queryKey: ['reschedule-slots', sessionId, ...], queryFn: ... })
useQuery({ queryKey: ['teacher', 'policies'], queryFn: ... })