Skip to content

Servicios

Ruta: /teacher/services · Atajo: g v · Sidebar: Servicios

La pagina de Servicios gestiona el catalogo completo del profesor. Cada servicio se define por 3 dimensiones ortogonales: deliveryMode x groupType x structure. Incluye KPIs del mes, dos modos de vista, y workflow de estados.

Services page
  1. Catalogo (default) — Gestion de servicios
  2. Legal — Gestion de documentos legales (contratos, politicas)

Grid responsive con metricas del mes actual vs. anterior:

KPIDatosTendencia
Ingresos del mesTotal revenue del mes en curso% vs mes anterior
Enrollments activosCount de enrollments activos
Enrollments del mesNuevos enrollments este mes% vs mes anterior
Sesiones del mesSesiones completadas/programadas% vs mes anterior

API: GET /teacher/services/stats

  • Busqueda por nombre: Input con debounce 300ms. Filtro client-side sobre el nombre del servicio. Muestra empty state si no hay coincidencias.
  • Pills de estado: Todos, Active, Draft, Paused, Archived (seleccion unica)
  • Date Range Picker: Presets (7 dias, 30 dias, 3 meses) o rango custom
  • Toggle de vista: Cards View vs Analytics View

Tabla de 14 columnas en desktop:

ColumnaContenido
GripHandle de arrastre para reordenar (dnd-kit)
CheckboxSeleccion multiple para acciones en masa
ToggleSwitch activo/pausado (inline, optimista)
Iconos3 badges: delivery mode, group type, structure
NombreNombre + badges (Trial, Unlisted, Private)
MenuTres puntos (Editar, Duplicar, Archivar, Eliminar)
PrecioFormateado segun pricing model
VendidosTotal enrollments historicos
ActivosEnrollments activos actuales
RevenueIngresos totales (lifetime)
ImpartidasSesiones completadas
RestantesCreditos pendientes
PerdidasSesiones forfeited (rojo si mayor que 0)
Este periodoSesiones en el rango seleccionado

Fila de totales al final si hay 2+ servicios. Columnas ordenables (click en header).

Cada fila en la Vista Cards tiene un grip handle (icono GripVertical) a la izquierda. El profesor puede arrastrar y soltar para cambiar el orden de sus servicios.

Implementacion:

  • DndContext + SortableContext con verticalListSortingStrategy envuelven la lista de servicios
  • Cada fila es un SortableServiceRow que usa useSortable de dnd-kit
  • PointerSensor con activationConstraint: { distance: 5 } evita activacion accidental
  • Al soltar (handleDragEnd): se usa arrayMove para reordenar el estado local (localOrder) de forma optimista y se llama PATCH /teacher/services/:id con el nuevo sortOrder para cada servicio movido
  • Si el servidor falla, se resetea localOrder a null y se muestra un toast de error
  • DragOverlay muestra una copia semitransparente de la fila durante el arrastre

API: PATCH /teacher/services/:id con { sortOrder: number }

Cuando hay servicios en la Vista Cards, se puede seleccionar uno o varios con los checkboxes de la columna izquierda. Al seleccionar al menos uno, aparece una barra de acciones fija en la parte superior de la lista con:

  • Contador de seleccionados (“N seleccionados”)
  • Publicar todos — llama a POST /teacher/services/:id/publish para cada servicio seleccionado via Promise.all. Solo aplica a servicios en draft o paused.
  • Pausar todos — llama a POST /teacher/services/:id/pause para cada servicio seleccionado via Promise.all. Solo aplica a servicios active.
  • X — cancela la seleccion

El header de la tabla incluye un checkbox de “seleccionar todo” que alterna entre seleccionar todos los servicios visibles y deseleccionar todos.

Estado: selectedIds: Set<string> + isBulkPublishing: boolean + isBulkPausing: boolean

El campo URL del servicio (slug) en el formulario de edicion (y creacion) incluye validacion en tiempo real:

  1. Formato: Regex /^[a-z0-9-]+$/. Si el slug contiene caracteres invalidos, muestra error “Solo letras minusculas, numeros y guiones” inmediatamente.
  2. Disponibilidad: Con un debounce de 400ms, llama a GET /teacher/services/slug-check?slug=...&excludeId=... para verificar conflictos.
  3. Indicadores visuales:
    • Comprobando — spinner Loader2 + borde neutro
    • Disponible — CheckCircle2 verde + borde verde
    • En uso — AlertCircle rojo + borde rojo
    • Invalido — AlertCircle rojo + borde rojo

El parametro excludeId excluye el propio servicio al editar (su slug actual siempre es “disponible” para si mismo).

API: GET /teacher/services/slug-check?slug=xxx&excludeId=yyy

Servicios agrupados en 4 secciones colapsables por estructura:

  1. Single — Clases sueltas
  2. Package — Paquetes multi-sesion
  3. Course — Cursos con secuencia ordenada
  4. Subscription — Suscripciones recurrentes

Cada grupo muestra badge de conteo, subtotales, y barra de total general.

El menu de tres puntos incluye la opcion “Duplicar” (icono Copy). Llama a POST /teacher/services/:id/duplicate. Copia todos los campos del servicio excepto id, status (queda en draft), name (agrega ” (copia)”) y slug.

Flujo de 2 pasos:

  1. Selector de preset (26 presets agrupados por delivery + group) o modo custom
  2. Formulario de configuracion con todos los campos. Incluye TagMultiSelect para asignar tags al servicio.

Schedule Items — editor de secuencia para cursos

Section titled “Schedule Items — editor de secuencia para cursos”

Cuando se edita un servicio con structure=course, aparece la seccion Schedule Items dentro del sheet de edicion.

Permite definir la secuencia ordenada de eventos del curso:

Tipo de itemIconoColor
live_sessionVideoAzul
async_contentFileTextAmbar
milestoneFlagEsmeralda

Cada item tiene: numero de secuencia, tipo, titulo, descripcion, duracion (minutos), flag isOptional.

Drag-and-drop con dnd-kit: los items son reordenables arrastrando el grip handle. Al soltar, se hace una actualizacion optimista del cache (queryClient.setQueryData) y se llama a POST /teacher/services/:id/schedule/reorder con el array de ids en el nuevo orden.

Formulario de creacion/edicion en un Sheet secundario con: selector de tipo, input de titulo, textarea de descripcion, duracion (oculto para milestones), checkbox opcional.

API usada:

  • GET /teacher/services/:id/schedule — lista items
  • POST /teacher/services/:id/schedule — crear item
  • PATCH /teacher/services/:id/schedule/:itemId — editar item
  • DELETE /teacher/services/:id/schedule/:itemId — borrar item (con ConfirmDialog)
  • POST /teacher/services/:id/schedule/reorder — reordenar

Query key: ['teacher', 'service-schedule', serviceId]

Todos los servicios en edicion muestran la seccion Roster de alumnos al final del sheet. Lista los enrollments del servicio via GET /teacher/enrollments?serviceId=:id.

Columnas de la tabla:

ColumnaDatos
AlumnoNombre + email
EstadoBadge de color por status de enrollment
CompletadassessionsCompleted
RestantessessionsTotal - sessionsCompleted - sessionsScheduled - sessionsForfeited
InscritoFecha de enrolledAt o createdAt

Estados de enrollment con colores: pending_payment (ambar), active (esmeralda), completed (azul), cancelled/expired (gris), suspended (ambar).

Query key: ['teacher', 'service-enrollments', serviceId]

TagMultiSelect disponible en el wizard de creacion y en el formulario de edicion. Las tags se sincronizan via la junction table service_tags. El campo tags ya estaba soportado en el backend.

DimensionOpciones
Delivery Modelive (video), async (self-paced), hybrid (mixto)
Group Typeindividual (1:1), group (limitado), open (ilimitado)
Structuresingle, package, course, subscription
draft → active (publish)
active → paused (pause)
paused → active (publish)
active/paused → archived (archive)
draft → deleted (soft-delete, solo draft)

FeatureDescripcionEstadoImplementado
Drill-down de metricasClick en revenue/enrollments deberia mostrar detalleImplementado ✅
KPIs por servicioTendencias MoM por servicio individual, no solo agregadoImplementado ✅

BugDescripcionEstadoCorregido
Moneda hardcodeada en preciosEl formato de precio usaba currency del servicio pero el wizard podia defaultear a EUR. Ahora usa teacher.defaultCurrencyBatch 4
Servicios inactivos al 50% de opacidadLos servicios pausados/archivados se mostraban con opacity: 50%, dificultando leer las metricas. FIXED: opacity-50 removed. Status badges (Pausado/Archivado) added to ServiceNameCell component instead, preserving metric readabilityBatch 4

MejoraDescripcionDificultadEstadoImplementado
Confirmacion al pausar servicios con enrollmentsConfirmDialog aparece cuando service.activeEnrollments > 0 al pausar, mostrando el conteo de inscripciones activas afectadasFacilBatch 4

ArchivoProposito
apps/web/src/routes/teacher/services.lazy.tsxPagina completa (catalogo + KPIs + vistas + schedule items + roster)
apps/web/src/components/services/service-wizard.tsxWizard de creacion (3 dimensiones)
apps/api/src/services/scheduling/service-catalog.tsCRUD + stats aggregation
apps/api/src/services/scheduling/service-schedule.tsCRUD de schedule items
apps/api/src/routes/teacher/services.tsRutas HTTP
packages/shared/src/schemas/service.tsSchemas Zod

Componentes internos del archivo de servicios

Section titled “Componentes internos del archivo de servicios”
ComponenteProposito
ServicesPagePagina principal — tabs, KPIs, filtros, vistas, DnD, bulk actions
SortableServiceRowFila arrastrable de servicio (grip + checkbox + datos). Usa useSortable de dnd-kit
ServiceFormSheet de edicion/creacion de servicio (incluye validacion de slug en tiempo real)
ScheduleItemsSectionEditor DnD de items de secuencia (solo cursos)
SortableScheduleItemItem individual arrastrable (dnd-kit useSortable)
StudentRosterSectionTabla de enrollments del servicio
ServiceMenuPopover de 3 puntos (editar, archivar, borrar)
ServiceIconsCell3 badges de dimensiones (delivery, group, structure)
ServiceNameCellNombre + badges (Trial, Unlisted, Private)
SortableHeaderHeader de columna ordenable con icono de direccion
PriceCellPrecio formateado segun pricing model
EndpointMetodoProposito
/teacher/servicesGETLista con stats embebidas
/teacher/services/statsGETKPIs del mes
/teacher/services/slug-check?slug=&excludeId=GETVerifica disponibilidad de slug (excluye el propio servicio al editar)
/teacher/services/:idGET/PATCH/DELETECRUD individual (PATCH acepta sortOrder para reordenar)
/teacher/servicesPOSTCrear servicio
/teacher/services/:id/duplicatePOSTDuplicar servicio (crea draft con todos los campos)
/teacher/services/:id/publishPOSTPublicar (draft/paused → active)
/teacher/services/:id/pausePOSTPausar (active → paused)
/teacher/services/:id/archivePOSTArchivar
/teacher/services/:id/scheduleGET/POSTItems de schedule (cursos)
/teacher/services/:id/schedule/:itemIdPATCH/DELETEItem individual
/teacher/services/:id/schedule/reorderPOSTReordenar items
// Lista de servicios con stats (filtros de status y rango de fechas)
useQuery({ queryKey: ['teacher', 'services', filterStatus, startDateStr, endDateStr], queryFn: ... })
// KPIs globales del mes
useQuery({ queryKey: ['teacher', 'services', 'stats'], queryFn: ... })
// Detalle de un servicio (para el form de edicion)
useQuery({ queryKey: ['teacher', 'service-detail', serviceId], queryFn: ... })
// Schedule items de un curso
useQuery({ queryKey: ['teacher', 'service-schedule', serviceId], queryFn: ... })
// Enrollments de un servicio (roster)
useQuery({ queryKey: ['teacher', 'service-enrollments', serviceId], queryFn: ... })