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
EstadoColor
holdAmarillo/amber (#f59e0b)
pending_confirmationAmarillo/amber (#f59e0b)
scheduledAzul (primary)
in_progressAzul (primary)
completedVerde (success)
pending_reviewVerde (success)
cancelled_by_studentGris
cancelled_by_teacherGris
cancelled_systemGris
no_show_studentRojo
no_show_teacherRojo
rescheduledGris
expiredGris
  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:

PresetDescripcion
”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.

AtajoAccion
nNueva clase
tIr a hoy (semana actual)
ArrowLeft / ArrowRightSemana anterior / siguiente
1 / 2Vista semana / lista
cCompletar evento seleccionado
EscapeCerrar paneles / deseleccionar

FeatureDescripcionEstadoImplementado
Arrastrar a otra semanaHot zones en los bordes izq/der del grid. Dwell 400ms navega a semana adyacente, cooldown 700ms. Ghost persiste entre re-renders via refsBatch 5
Colores por servicioservices.color se expone como serviceColor en la respuesta del calendario y en el CalendarEventBatch 2
Notas de sesion en el panelEl panel de detalle ahora muestra summary, teacherNotes y studentMood del satellite session_content en una seccion colapsableBatch 2
Edicion de clases recurrentesLas sesiones ahora se pueden crear con un recurringGroupId que agrupa la serieBatch 2
Presets de disponibilidad4 plantillas predefinidas (“9 a 5”, “Tardes”, “Fines de semana”, “Horario completo”) disponibles al crear un nuevo scheduleBatch 3
Duracion custom de sesionEl Action Sheet permite sobrescribir la duracion del servicio para una sesion puntualBatch 3
Error state en lugar de mock eventsCuando 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 falsosBatch 4
Timezone del profesor en reprogramacionRescheduleSheet ahora usa el timezone del profesor en lugar del timezone del navegadorBatch 4
Creditos por participante en panel de grupoEl panel de detalle de evento ahora muestra el credito disponible de cada participante individualmenteBatch 4
Validacion de disponibilidad para slots semanalesLessonForm 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 envioBatch 4

MejoraDescripcionDificultadEstadoImplementado
(sin items pendientes)

ArchivoProposito
apps/web/src/routes/teacher/calendar.lazy.tsxPagina principal (1282 lineas)
apps/web/src/components/calendar/week-calendar-grid.tsxGrid semanal (1164 lineas)
apps/web/src/components/calendar/event-detail-panel.tsxPanel de detalle (550 lineas)
apps/web/src/components/calendar/calendar-action-sheet.tsxFormularios de creacion (1218 lineas)
apps/web/src/components/calendar/reschedule-sheet.tsxReprogramacion (325 lineas)
apps/web/src/components/calendar/schedule-selector.tsxSchedules de disponibilidad (597 lineas)
apps/web/src/components/calendar/calendar-list-view.tsxVista lista (124 lineas)
apps/api/src/services/teacher/calendar-service.tsServicio de calendario (173 lineas)
apps/api/src/services/scheduling/session-service.tsCRUD de sesiones
apps/api/src/services/scheduling/availability-service.tsReglas de disponibilidad
EndpointMetodoProposito
/teacher/calendarGETSesiones + overrides del rango
/teacher/sessionsPOSTCrear sesion (unica/semanal)
/teacher/sessions/:id/completePOSTCompletar sesion
/teacher/sessions/:id/no-showPOSTMarcar no-show
/teacher/sessions/:id/cancelPOSTCancelar (con politica)
/teacher/sessions/:id/reschedulePOSTReprogramar
/teacher/sessions/:id/templatesPUTVincular templates
/teacher/sessions/:id/resourcesPUTVincular recursos
/teacher/sessions/:id/contentPATCHActualizar tags/notas
/teacher/sessions/:id/participantsPOST/DELETEGestionar grupo
/teacher/sessions/bulk-actionPOSTAccion en masa (complete/cancel/no_show)
/teacher/calendar/exportGETExportar sesiones en iCal o CSV
/teacher/availability-overridesPOSTCrear time off / extra
/teacher/availability-rulesPOST/PATCH/DELETEReglas de disponibilidad
/teacher/availability-schedulesCRUDSchedules de disponibilidad
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/web/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: ... })