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.
Que hay
Section titled “Que hay”Vistas
Section titled “Vistas”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
Tabla con columnas: fecha, hora, alumno, tipo de clase, estado, clase vinculada. Mismos datos que la vista semanal pero en formato tabular.
Navegacion
Section titled “Navegacion”- Flechas izq/der: Cambiar semana (
ArrowLeft/ArrowRight) - Label de rango: “15 Ene – 21 Ene” (formato dinamico)
- Toggle vista: Semana (grid) / Lista (tabla) — atajos
1y2
Colores por estado y servicio
Section titled “Colores por estado y servicio”Cada evento tiene dos dimensiones de color:
- 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 |
- Color de servicio — el campo
services.color(hex) se devuelve comoserviceColoren la respuesta de/teacher/calendar. ElCalendarEventexponeserviceColor?: string | null. ElEventBlockusa el color de estado como color principal del bloque;serviceColoresta disponible para estilos adicionales.
Advertencia de doble booking
Section titled “Advertencia de doble booking”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
Exportar calendario
Section titled “Exportar calendario”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).
Acciones en masa
Section titled “Acciones en masa”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
ConfirmDialogantes de ejecutar - Al confirmar, llama a
POST /teacher/sessions/bulk-actioncon los IDs y la accion
Eventos interactivos
Section titled “Eventos interactivos”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)
Drag-to-reschedule cross-week (hot zones)
Section titled “Drag-to-reschedule cross-week (hot zones)”El drag-to-reschedule soporta navegacion entre semanas. Cuando el profesor arrastra un evento hacia el borde izquierdo o derecho del grid:
- Hot zone (48px desde el borde): Se muestra un indicador visual con gradiente + chevron pulsante + etiqueta (“Sem. anterior” / “Sem. siguiente”)
- Dwell (400ms): Tras mantener el puntero en la hot zone durante 400ms, la semana navega automaticamente
- Cooldown (700ms): Tras cada navegacion, hay un cooldown de 700ms antes de permitir otra navegacion
- Ghost preview: El ghost del evento permanece visible durante la transicion — los column refs se actualizan al remontar las nuevas columnas
- Drop: Al soltar el evento en la nueva semana, se muestra el
ConfirmDialogcon 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.
Panel de detalle de evento
Section titled “Panel de detalle de evento”Al hacer click en un evento, se abre un panel lateral con:
- Header: Titulo + avatar del alumno + badge de estado
- Hora: Fecha/hora con boton de reprogramar
- Participantes (solo grupos): Lista de asistentes + agregar/eliminar
- Meet link: Boton para entrar a la videollamada
- Templates: Picker multi-template (popover)
- Recursos: Picker de recursos con link/unlink
- Tags: Multi-select de tags
- Notas de sesion (colapsable): Summary, teacher notes y student mood del satellite
session_content. Muestra “sin notas” si el campo esta vacio. Datos leidos desdesession.sessionContentcon fallback aevent.summary/event.teacherNotes/event.studentMood(pre-cargados en el calendario). - Acciones: Completar, No-show, Cancelar
- Link al perfil del alumno
Crear sesiones (Action Sheet)
Section titled “Crear sesiones (Action Sheet)”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
recurringGroupIdcompartido 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
Modo edicion de disponibilidad
Section titled “Modo edicion de disponibilidad”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)
Selector de schedules
Section titled “Selector de schedules”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
Presets de disponibilidad
Section titled “Presets de disponibilidad”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.
Duracion custom de sesion
Section titled “Duracion custom de sesion”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.
Atajos de teclado
Section titled “Atajos de teclado”| 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 |
Que falta
Section titled “Que falta”| 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 |
Que cambiaria
Section titled “Que cambiaria”| Mejora | Descripcion | Dificultad | Estado | Implementado |
|---|---|---|---|---|
| (sin items pendientes) |
Referencia tecnica
Section titled “Referencia tecnica”Archivos clave
Section titled “Archivos clave”| Archivo | Proposito |
|---|---|
apps/web/src/routes/teacher/calendar.lazy.tsx | Pagina principal (1282 lineas) |
apps/web/src/components/calendar/week-calendar-grid.tsx | Grid semanal (1164 lineas) |
apps/web/src/components/calendar/event-detail-panel.tsx | Panel de detalle (550 lineas) |
apps/web/src/components/calendar/calendar-action-sheet.tsx | Formularios de creacion (1218 lineas) |
apps/web/src/components/calendar/reschedule-sheet.tsx | Reprogramacion (325 lineas) |
apps/web/src/components/calendar/schedule-selector.tsx | Schedules de disponibilidad (597 lineas) |
apps/web/src/components/calendar/calendar-list-view.tsx | Vista lista (124 lineas) |
apps/api/src/services/teacher/calendar-service.ts | Servicio de calendario (173 lineas) |
apps/api/src/services/scheduling/session-service.ts | CRUD de sesiones |
apps/api/src/services/scheduling/availability-service.ts | Reglas de disponibilidad |
| 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 |
Schemas Zod relevantes
Section titled “Schemas Zod relevantes”export const bulkSessionActionSchema = z.object({ sessionIds: z.array(z.string().uuid()).min(1), action: z.enum(['complete', 'cancel', 'no_show']),});Interfaz CalendarEvent
Section titled “Interfaz CalendarEvent”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}Queries (TanStack Query)
Section titled “Queries (TanStack Query)”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: ... })