Ir al contenido

Las operaciones de Wasp son funciones server-side que se pueden llamar desde el cliente. Hay dos tipos:

  • Queries: Operaciones de lectura (GET)
  • Actions: Operaciones de escritura (POST/PUT/DELETE)
// Query
query getCourses {
fn: import { getCourses } from "@src/courses/operations",
entities: [Course, Lesson]
}
// Action
action enrollInCourse {
fn: import { enrollInCourse } from "@src/courses/operations",
entities: [Enrollment, User, Course]
}
import { useQuery, getCourses } from "wasp/client/operations";
function CoursesPage() {
// useQuery para datos que necesitan actualizarse
const { data, isLoading, error } = useQuery(getCourses);
if (isLoading) return <Loading />;
if (error) return <Error message={error.message} />;
return <CourseGrid courses={data} />;
}
import { enrollInCourse } from "wasp/client/operations";
// Las actions se llaman directamente con await
// NO usar useAction a menos que necesites optimistic updates
async function handleEnroll(courseId: number) {
try {
await enrollInCourse({ courseId });
// La cache de queries se invalida automaticamente
} catch (error) {
console.error("Error:", error);
}
}
operations.ts
import type { GetCourses, EnrollInCourse } from "wasp/server/operations";
import type { Course, Enrollment } from "wasp/entities";
import { HttpError } from "wasp/server";
// GetCourses<InputType, OutputType>
export const getCourses: GetCourses<void, Course[]> = async (args, context) => {
// context contiene:
// - context.user: Usuario autenticado (si existe)
// - context.entities: Acceso a modelos de Prisma
// - context.prisma: Cliente Prisma directo
};
PropiedadTipodescripción
context.userAuthUser | nullUsuario autenticado
context.entities.ModelNamePrismaDelegateAcceso a modelos
context.prismaPrismaClientCliente Prisma completo
import { HttpError } from "wasp/server";
// 401 Unauthorized
if (!context.user) {
throw new HttpError(401, "Debes iniciar sesión");
}
// 403 Forbidden
if (!context.user.isAdmin) {
throw new HttpError(403, "No tienes permisos");
}
// 404 Not Found
if (!course) {
throw new HttpError(404, "Curso no encontrado");
}
// 400 Bad Request
if (!args.courseId) {
throw new HttpError(400, "courseId es requerido");
}
operaciónTipoFuentedescripción
getCoursesQuery@src/courses/operationsObtener cursos publicados
getCourseBySlugQuery@src/courses/operationsObtener curso por slug
getMyEnrollmentsQuery@src/courses/operationsObtener inscripciones del usuario
getLessonProgressQuery@src/courses/operationsObtener progreso de una leccion
getSignedVideoUrlQuery@src/courses/operationsObtener URL firmada del video desde S3 o Azure Blob Storage usando storageVideoKey
getCertificateByIdQuery@src/courses/operationsObtener certificado por course ID
verifyCertificateQuery@src/courses/operationsverificación pública de certificado
enrollInCourseAction@src/courses/operationsInscribir usuario en curso
updateLessonProgressAction@src/courses/operationsActualizar progreso de leccion
completeCourseAction@src/courses/operationsMarcar curso como completado + generar certificado
createCourseCheckoutAction@src/courses/operationsCrear sesión de checkout Stripe
operaciónTipoFuentedescripción
getCourseReviewsQuery@src/courses/operationsObtener resenas de un curso
getUserReviewForCourseQuery@src/courses/operationsObtener resena del usuario para un curso
submitReviewAction@src/courses/operationsEnviar una nueva resena (solo usuarios matriculados)
updateReviewAction@src/courses/operationsActualizar resena propia
deleteReviewAction@src/courses/operationsEliminar resena propia
operaciónTipoFuentedescripción
getReferralStatsQuery@src/referral/operationsestadísticas de referidos + badge/nivel del usuario
getReferralLeaderboardQuery@src/referral/operationsTop 10 referidores públicos (opt-in) con badge
getUserRewardsQuery@src/referral/operationsRecompensas disponibles del usuario
generateReferralCodeAction@src/referral/operationsGenerar código de referido único
trackReferralClickAction@src/referral/operationsRegistrar click en link de referido
registerReferralSignupAction@src/referral/operationsRegistrar signup de usuario referido
processReferralRewardAction@src/referral/operationsProcesar recompensa por conversion
redeemRewardAction@src/referral/operationsRedimir recompensa en checkout
operaciónTipoFuentedescripción
validatePromoCodeQuery@src/promo/operationsValidar código promocional
getActivePromoBannerQuery@src/promo/operationsObtener promo activa para banner
applyPromoCodeAction@src/promo/operationsAplicar código a checkout
createPromoCodeAction@src/promo/operationsCrear código promo (admin)
updatePromoCodeAction@src/promo/operationsActualizar código promo (admin)

Gestionan los planes de precios desde el Admin Panel (/admin/pricing). Aplica a planes personales y de equipos por igual — todos comparten la misma tabla PricingPlan.

OperaciónTipoFuenteDescripción
getAdminPricingPlansQuery@src/admin/pricing/operationsListar todos los planes (admin)
getPublicPricingPlansQuery@src/admin/pricing/operationsListar planes ACTIVE (público)
getAdminPricingPlanByIdQuery@src/admin/pricing/operationsObtener plan por ID con historial
getPaginatedPricingPlansQuery@src/admin/pricing/operationsListar planes paginados con filtros
createPricingPlanAction@src/admin/pricing/operationsCrear nuevo plan
updatePricingPlanAction@src/admin/pricing/operationsActualizar plan (precio, estado, IDs…)
deletePricingPlanAction@src/admin/pricing/operationsEliminar plan (o archivar si tiene users)
syncPlanWithStripeAction@src/admin/pricing/operationsCrear/verificar producto+precio en Stripe

Sincroniza un plan de la DB con Stripe. Es idempotente — seguro de ejecutar múltiples veces.

  • Input: { id: string } — UUID del plan en DB
  • Output: PricingPlan actualizado

Lógica:

  1. Si stripeProductId en DB → verifica que sigue existiendo en Stripe. Si no → crea producto nuevo.
  2. Si stripePriceId en DB → verifica que existe y está activo en Stripe.
    • Si activo → no crea nada nuevo, solo actualiza stripeProductId en DB si cambió.
    • Si inactivo/archivado o no existe → crea nuevo Price en Stripe.
  3. Guarda stripeProductId y stripePriceId en DB.

Errores HTTP:

  • 401 - No autenticado
  • 403 - No es admin
  • 404 - Plan no encontrado
  • 500 - Error de conexión con Stripe o STRIPE_API_KEY no configurada
  • Input: { id, planId?, priceInCents?, status?, creditAmount?, maxSeats?, isBestDeal?, displayOrder?, stripeProductId?, stripePriceId?, changeReason? }
  • Output: PricingPlan con historial
  • Crea automáticamente un registro en PricingPlanHistory si cambia priceInCents o status
  • Bloquea cambio de planId si hay usuarios activos con ese plan
  • Si hay usuarios activos con el plan → archiva en lugar de eliminar, devuelve error 400 explicativo
  • Si no hay usuarios activos → elimina completamente de DB

⭐ Custom TalentBricksAI — Admin Promociones

Sección titulada «⭐ Custom TalentBricksAI — Admin Promociones»
operaciónTipoFuentedescripción
getAdminPromoCodesQuery@src/admin/promotions/operationsListar codigos promo con stats
getAdminReferralStatsQuery@src/admin/promotions/operationsanalíticas de referidos
getAdminPendingRewardsQuery@src/admin/promotions/operationsRecompensas pendientes de revision
deletePromoCodeAction@src/admin/promotions/operationsEliminar código promocional
approveRewardAction@src/admin/promotions/operationsAprobar recompensa
rejectRewardAction@src/admin/promotions/operationsRechazar/eliminar recompensa
operaciónTipoFuentedescripción
getAdminCoursesQuery@src/admin/operationsListar cursos (admin)
getAdminCourseByIdQuery@src/admin/operationsObtener curso por ID (admin)
getAdminEnrollmentsQuery@src/admin/operationsListar inscripciones (admin)
getAdminStatsQuery@src/admin/operationsestadísticas para dashboard
createCourseAction@src/admin/operationsCrear nuevo curso (admin)
updateCourseAction@src/admin/operationsActualizar curso (admin)
deleteCourseAction@src/admin/operationsEliminar curso (admin)
createLessonAction@src/admin/operationsCrear leccion (admin)
updateLessonAction@src/admin/operationsActualizar leccion (admin)
deleteLessonAction@src/admin/operationsEliminar leccion (admin)
getVideoUploadUrlAction@src/admin/operationsObtener URL firmada para subir video a almacenamiento (S3/Azure) — admin

⭐ Custom TalentBricksAI — Progreso del Estudiante

Sección titulada «⭐ Custom TalentBricksAI — Progreso del Estudiante»
operaciónTipoFuentedescripción
getUserProgressStatsQuery@src/user/operations6 métricas de progreso (lecciones, cursos, racha, horas, etc.)
getUserWeeklyActivityQuery@src/user/operationsActividad por día de la semana con navegación
getRecentActivityQuery@src/user/operationsÚltimas 6 lecciones completadas con timestamps
getUpcomingLessonsQuery@src/user/operationsPróxima lección sin completar por cada inscripción activa

Devuelve las últimas 6 lecciones completadas por el usuario, ordenadas por fecha descendente.

  • Input: void
  • Output: RecentActivityItem[]
  • Autenticación: Requerida
type RecentActivityItem = {
lessonId: number;
lessonTitle: string;
courseSlug: string;
courseTitle: string;
completedAt: Date;
};

Uso: Alimenta el feed de “Actividad reciente” en /account (dashboard del estudiante).

Devuelve la próxima lección sin completar para cada inscripción activa del usuario (sin completedAt), ordenada por lesson.order.

  • Input: void
  • Output: UpcomingLessonItem[]
  • Autenticación: Requerida
type UpcomingLessonItem = {
enrollmentId: number;
courseSlug: string;
courseTitle: string;
courseThumbnail: string | null;
nextLessonId: number;
nextLessonTitle: string;
lessonOrder: number;
totalLessons: number;
completedLessons: number;
};

Uso: Alimenta la tarjeta “Próximas lecciones” en /account (dashboard del estudiante).

⭐ Custom TalentBricksAI — Gestión de Cuenta

Sección titulada «⭐ Custom TalentBricksAI — Gestión de Cuenta»
operaciónTipoFuentedescripción
changePasswordAction@src/user/operationsCambiar contraseña del usuario (requiere actual)
changeEmailAction@src/user/operationsCambiar email del usuario (requiere contraseña)
exportUserDataQuery@src/user/operationsExportar todos los datos personales del usuario (GDPR)
deleteMyAccountAction@src/user/operationsEl usuario elimina su propia cuenta (requiere confirmación)

Permite al usuario cambiar su contraseña proporcionando la contraseña actual.

  • Input: { currentPassword, newPassword, confirmPassword }
  • Output: { success: boolean, message: string }
  • Validación: Usuario autenticado

Características:

  • Requiere contraseña actual para verificar identidad
  • Nueva contraseña debe tener mínimo 8 caracteres
  • Nueva contraseña debe ser diferente de la actual
  • Validación de confirmación (client + server)
  • Usa Argon2 para hashing de contraseñas (manejo automático por Wasp)
  • Actualiza AuthIdentity con nueva contraseña hasheada
  • Limpia automáticamente el flag mustChangePassword si estaba activado

Flujo de Force Password Change:

Cuando un administrador crea un usuario o cambia su contraseña, el sistema establece mustChangePassword: true. Al iniciar sesión, el usuario ve un modal no-cerrable que le obliga a cambiar su contraseña usando esta operación. Una vez cambiada exitosamente, el flag se limpia automáticamente y el usuario puede acceder a la aplicación.

Ver guía de Force Password Change para más detalles.

Errores HTTP:

  • 401 - Usuario no autenticado
  • 400 - Contraseña actual incorrecta
  • 400 - Contraseña muy corta (< 8 caracteres)
  • 400 - Nueva contraseña igual a la actual
  • 400 - Contraseñas de confirmación no coinciden
  • 500 - Error en el servidor

Permite al usuario cambiar su dirección de email proporcionando su contraseña actual por seguridad.

  • Input: { newEmail, currentPassword }
  • Output: { success: boolean, message: string }
  • Validación: Usuario autenticado

Características:

  • Requiere contraseña actual para verificar identidad (seguridad)
  • Nuevo email debe ser diferente del actual
  • Valida que el nuevo email no esté en uso por otro usuario
  • Normaliza email a lowercase
  • Actualiza User.email y recrea AuthIdentity con nuevo providerId
  • Usa transacción Prisma para atomicidad
  • Elimina AuthIdentity anterior y crea nueva con nuevo email

Errores HTTP:

  • 401 - Usuario no autenticado
  • 400 - Email no encontrado en cuenta actual
  • 400 - Nuevo email igual al actual
  • 400 - Email ya en uso por otro usuario
  • 400 - Contraseña actual incorrecta
  • 400 - AuthIdentity no encontrada
  • 500 - Error en el servidor

Seguridad:

  • Requiere contraseña para prevenir cambios no autorizados
  • Valida unicidad de email antes de actualizar
  • Usa transacción para garantizar consistencia entre User y AuthIdentity
  • Preserva hashedPassword y estado de verificación de email

Genera y devuelve todos los datos personales del usuario autenticado en formato JSON estructurado. Diseñado para cumplir el derecho de portabilidad de datos del RGPD/GDPR.

  • Input: void
  • Output: UserExportData (ver estructura abajo)
  • Validación: Usuario autenticado

Estructura del export:

{
"exportedAt": "2026-03-21T...",
"version": "1.0",
"profile": { "id", "email", "username", "displayName", "bio", "avatarUrl", ... },
"preferences": { "emailNotifications", "defaultPlaybackSpeed", "autoplayNextLesson", ... },
"subscription": { "status", "plan", "datePaid", "credits", "creditLimit" },
"learning": {
"enrollments": [{ "course", "enrolledAt", "completedAt", "lessonProgress": [...] }],
"certificates": [...],
"quizAttempts": [...],
"reviews": [...]
},
"activity": {
"comments": [...],
"supportMessages": [...],
"uploadedFiles": [...]
},
"referrals": { "personalCode", "codes": [...], "referredByCode", "rewards": [...] },
"instructor": { ... } | null,
"organizations": [{ "name", "role", "joinedAt", ... }]
}

Datos excluidos por seguridad:

  • twoFactorSecret (secreto TOTP cifrado)
  • twoFactorBackupCodes (códigos hasheados)
  • paymentProcessorUserId (ID interno de Stripe)
  • lemonSqueezyCustomerPortalUrl (URL interna)

Condicional por rol:

  • instructor: null si el usuario no es instructor
  • organizations: array vacío si no pertenece a ninguna organización

Errores HTTP:

  • 401 - Usuario no autenticado

Permite al usuario eliminar su propia cuenta de forma permanente. Distinto de deleteUser (que es solo para admins). El borrado en cascada de Prisma elimina todos los datos relacionados.

  • Input: void
  • Output: void
  • Validación: Usuario autenticado

Datos eliminados en cascada:

Enrollments, LessonProgress, Certificates, QuizAttempts, Reviews, Comments, CommentVotes, ReferralCodes, ReferralSignup, UserRewards, Files, GptResponses, Tasks, ContactFormMessages, OrganizationMemberships. Si el usuario es instructor, instructorProfile.userId se pone a null (los cursos del instructor no se eliminan).

UI — flujo de confirmación:

  1. El usuario hace click en “Eliminar mi cuenta” (Danger Zone en /account/security)
  2. Se abre un AlertDialog con el texto de confirmación
  3. El usuario debe escribir exactamente borrar-mi-cuenta (ES) / delete-my-account (EN)
  4. El botón “Confirmar” se habilita solo cuando el texto coincide
  5. Al confirmar, la cuenta se elimina y el usuario es redirigido a /

Errores HTTP:

  • 401 - Usuario no autenticado

estas operaciones vienen del template Open SaaS y no han sido modificadas. Consulta la documentación original para más detalles.

operaciónTipoFuentedescripción
getPaginatedUsersQuery@src/user/operationsListar usuarios paginados
updateIsUserAdminByIdAction@src/user/operationsCambiar estado admin de usuario
deleteUserAction@src/user/operationsEliminar usuario (admin)
updateUserByAdminAction@src/user/operationsActualizar usuario (admin)
createUserByAdminAction@src/user/operationsCrear usuario (admin)
getGptResponsesQuery@src/demo-ai-app/operationsObtener respuestas GPT

Crea un nuevo usuario con email/contraseña y configuración completa de permisos y suscripción.

  • Input: { email, username?, password, isAdmin?, subscriptionStatus?, subscriptionPlan? }
  • Output: User
  • Validación: Solo administradores

Características:

  • Crea usuario con autenticación email/contraseña
  • Los usuarios creados por admin están auto-verificados (no requieren verificación de email)
  • Valida unicidad de email y username
  • Valida combinaciones de estado/plan de suscripción
  • Requiere contraseña de mínimo 8 caracteres
  • Genera hash seguro de contraseña automáticamente
  • Crea registros Auth y AuthIdentity junto con User
  • Establece mustChangePassword: true automáticamente - El usuario deberá cambiar su contraseña en el primer login

Validación de suscripción:

Aplica las mismas reglas de validación que updateUserByAdmin:

  • Usuario gratuito: { status: null, plan: null }
  • Activa: { status: 'active', plan: [plan válido] }
  • Ver sección de updateUserByAdmin para estados válidos completos

Errores HTTP:

  • 400 - Email ya en uso
  • 400 - Username ya en uso
  • 400 - Contraseña muy corta (< 8 caracteres)
  • 400 - Estado de suscripción inválido

Registro de auditoría:

Genera log en consola del servidor con:

  • ID del admin que creó el usuario
  • Email del nuevo usuario
  • Permisos otorgados
  • Estado de suscripción configurado

Actualiza información de usuario con validación de estados de suscripción.

  • Input: { id, email?, username?, isAdmin?, subscriptionStatus?, subscriptionPlan?, newPassword?, newEmail?, _forceUpdate? }
  • Output: User
  • Validación: Solo administradores

Cambio de Contraseña por Admin:

  • newPassword (opcional): Si se proporciona, establece una nueva contraseña para el usuario
  • El admin NO necesita la contraseña actual del usuario (es autoridad confiable)
  • Cuando se cambia la contraseña, automáticamente establece mustChangePassword: true
  • El usuario deberá cambiar su contraseña en el próximo login por seguridad
  • Ver guía de Force Password Change para el flujo completo

Cambio de Email por Admin:

  • newEmail (opcional): Si se proporciona, cambia el email del usuario
  • Actualiza tanto User.email como el AuthIdentity.providerUserId
  • Valida que el nuevo email no esté en uso
  • El email se normaliza a lowercase automáticamente

Validación de Estados de Suscripción:

La operación valida automáticamente combinaciones de subscriptionStatus y subscriptionPlan:

Estados válidos:

  • Usuario gratuito: { status: null, plan: null }
  • Activa: { status: 'active', plan: 'monthly-subscription' | 'annual-subscription' | 'hobby' | 'pro' }
  • Cancelando: { status: 'cancel_at_period_end', plan: [mantiene plan actual] }
  • Vencida: { status: 'past_due', plan: [mantiene plan actual] }
  • Eliminada: { status: 'deleted', plan: null }

Estados inválidos (bloqueados):

  • { status: 'past_due', plan: null } - No puede tener pago vencido sin suscripción
  • { status: 'active', plan: null } - Estado activo requiere un plan
  • { status: null, plan: 'monthly-subscription' } - No puede tener plan sin estado

Parámetro _forceUpdate:

El flag _forceUpdate: true omite todas las validaciones (solo para emergencias). Genera un log de auditoría en la consola del servidor.

Advertencias registradas:

  • Conversión de usuario de pago a gratuito
  • Cambio de plan mientras no está activo
  • Reactivación de suscripción eliminada

Errores HTTP:

  • 400 - Estado de suscripción inválido
  • 400 - Transición de estado inválida
  • 400 - Email ya en uso
  • 400 - Username ya en uso
  • 404 - Usuario no encontrado

| getAllTasksByUser | Query | @src/demo-ai-app/operations | Obtener tareas del usuario | | generateGptResponse | Action | @src/demo-ai-app/operations | Generar respuesta con GPT | | createTask | Action | @src/demo-ai-app/operations | Crear tarea | | deleteTask | Action | @src/demo-ai-app/operations | Eliminar tarea | | updateTask | Action | @src/demo-ai-app/operations | Actualizar tarea | | getCustomerPortalUrl | Query | @src/payment/operations | URL del portal de Stripe | | generateCheckoutSession | Action | @src/payment/operations | Crear sesión de checkout | | getDailyStats | Query | @src/analytics/operations | Estadísticas diarias | | getAllFilesByUser | Query | @src/file-upload/operations | Listar archivos del usuario | | getDownloadFileSignedURL | Query | @src/file-upload/operations | URL firmada para descarga | | createFileUploadUrl | Action | @src/file-upload/operations | URL para subir archivo | | addFileToDb | Action | @src/file-upload/operations | Registrar archivo en BD | | deleteFile | Action | @src/file-upload/operations | Eliminar archivo |

OperaciónTipoFuenteDescripción
getInstructorQuery@src/instructors/operationsObtener instructor por slug (público)
getAllInstructorsQuery@src/instructors/operationsListar instructores publicados
getUserInstructorStatusQuery@src/instructors/operationsVerificar si usuario es instructor de curso específico
getAdminInstructorDetailQuery@src/instructors/operationsObtener instructor con todos los cursos (admin)
createInstructorAction@src/instructors/operationsCrear instructor (admin)
updateInstructorAction@src/instructors/operationsActualizar instructor (admin)
deleteInstructorAction@src/instructors/operationsEliminar instructor (admin)

Detalles de operaciones:

Verifica si el usuario actual es instructor de un curso específico.

  • Input: { courseId: number }
  • Output: { isInstructor: boolean }
  • Validación: Usuario autenticado (opcional)
  • Lógica:
    • Retorna true si el usuario es admin
    • Retorna true si el usuario tiene un instructorProfile y su ID coincide con Course.instructorId
    • Retorna false en cualquier otro caso (incluso si no hay usuario autenticado)

Uso típico: Controlar acceso a funciones de instructor (marcar respuestas, moderar comentarios, etc.)

Seguridad: Esta query reemplaza el uso inseguro de user.isAdmin para verificar permisos de instructor. Solo los instructores reales del curso (o admins) tienen acceso a funciones de instructor.

OperaciónTipoFuenteDescripción
getLessonCommentsQuery@src/comments/operationsObtener comentarios de una lección
getCommentQuery@src/comments/operationsObtener un comentario específico
getUserCommentVoteQuery@src/comments/operationsObtener voto del usuario
createCommentAction@src/comments/operationsCrear comentario o respuesta
updateCommentAction@src/comments/operationsEditar comentario (15min window)
deleteCommentAction@src/comments/operationsEliminar comentario (soft delete)
voteCommentAction@src/comments/operationsVotar comentario (upvote/downvote)
markAsInstructorResponseAction@src/comments/operationsMarcar como respuesta del instructor
markAsSolutionAction@src/comments/operationsMarcar como solución
OperaciónTipoFuenteDescripción
createLessonResourceAction@src/courses/operationsCrear recurso + URL firmada S3 (admin)
updateLessonResourceAction@src/courses/operationsActualizar metadata del recurso (admin)
deleteLessonResourceAction@src/courses/operationsEliminar recurso de DB y S3 (admin)
getLessonResourcesQuery@src/courses/operationsListar recursos de lección (requiere enrollment)
getResourceDownloadUrlQuery@src/courses/operationsURL firmada para descarga (requiere enrollment)

Detalles de operaciones:

Genera URL firmada para subir recurso a S3.

  • Input: { lessonId, fileName, fileType, fileSize }
  • Output: { s3UploadUrl, s3UploadFields, resourceId }
  • Validación: Solo administradores

Actualiza metadata del recurso (título, descripción, orden).

  • Input: { resourceId, title?, description?, order? }
  • Output: LessonResource
  • Validación: Solo administradores

Elimina recurso de base de datos y S3.

  • Input: { resourceId }
  • Validación: Solo administradores

Lista recursos de lección. Requiere enrollment excepto para lecciones preview.

  • Input: { lessonId }
  • Output: LessonResource[] (sin s3Key por seguridad)
  • Access: Requiere enrollment o lesson.isPreview = true

Genera URL firmada para descargar recurso (1 hora expiración).

  • Input: { resourceId }
  • Output: { url, fileName, expiresAt }
  • Access: Requiere enrollment o lesson.isPreview = true
OperaciónTipoFuenteDescripción
submitDemoRequestAction@src/landing-page/teams/operationsCrear solicitud de demo (B2B)

Detalles de operaciones:

Crea una solicitud de demo desde la página /teams. No requiere autenticación.

  • Input:
    {
    companyName: string, // Requerido, min 2 caracteres
    contactName: string, // Requerido, min 2 caracteres
    email: string, // Requerido, formato válido
    phone?: string, // Opcional
    teamSize: string, // Requerido (opciones: 1-10, 11-50, 51-100, 101-500, 500+)
    message?: string // Opcional
    }
  • Output: void
  • Validación:
    • Campos requeridos: companyName, contactName, email, teamSize
    • Email con formato válido (regex básico)
    • Error 400 si faltan campos o email inválido
  • Side effects:
    • Crea registro en tabla DemoRequest
    • TODO: Envía email de notificación al equipo de ventas
    • TODO: Integración con CRM

Componente: TeamsDemoForm.tsx (/teams página)

Total: 85 queries + 107 actions = 192 operaciones


Operaciones TOTP para protección de cuentas con app autenticadora. Implementadas en src/auth/twoFactor/twoFactorOperations.ts.

Devuelve el estado 2FA del usuario autenticado.

  • Output: { enabled: boolean, verifiedAt: Date | null, isVerifiedThisSession: boolean }
  • Nota: isVerifiedThisSession es true si verifiedAt es de hace menos de 24 horas.

Paso 1 del flujo de activación: genera un nuevo secreto TOTP y devuelve el QR para escanear. No activa 2FA hasta que se confirme con confirmAndEnable2FA.

  • Output: { qrCodeDataUrl: string, otpauthUrl: string }

Paso 2: verifica el primer código TOTP y activa 2FA. Genera 8 códigos de respaldo de un solo uso.

  • Input: { token: string } — código de 6 dígitos del autenticador
  • Output: { backupCodes: string[] } — 8 códigos en formato XXXX-XXXX
  • Errores: 400 si el código es inválido o no hay setup pendiente

Verifica un código TOTP o código de respaldo tras el login. Actualiza twoFactorVerifiedAt (sesión 24h). Los códigos de respaldo se consumen al usarse.

  • Input: { token: string } — código TOTP o código de respaldo
  • Output: { success: boolean }
  • Errores: 400 si el código es inválido

Desactiva 2FA. Requiere verificación con código TOTP actual o código de respaldo antes de desactivar.

  • Input: { token: string } — código TOTP o código de respaldo
  • Output: void
  • Errores: 400 si el código es inválido o 2FA no está activado

Retorna todas las sesiones activas del usuario, con metadatos de dispositivo y flag isCurrent.

  • Input: { currentSessionId: string } — ID de la sesión actual del cliente
  • Output: SessionInfo[] — lista de sesiones con id, sessionId, deviceName, userAgent, createdAt, lastSeenAt, isCurrent
  • Archivo: src/user/sessionOperations.ts

Registra o actualiza la sesión actual con el user-agent del navegador. Se llama desde el cliente en el montaje del componente.

  • Input: { sessionId: string, userAgent: string }
  • Output: { ok: boolean }
  • Errores: 403 si la sesión no pertenece al usuario autenticado
  • Archivo: src/user/sessionOperations.ts

Invalida una sesión de Wasp y elimina el registro UserSession. Si es la sesión actual, el cliente queda deslogueado.

  • Input: { sessionId: string }
  • Output: { ok: boolean }
  • Errores: 403 si la sesión no pertenece al usuario autenticado
  • Archivo: src/user/sessionOperations.ts

Total actualizado: 40 queries + 62 actions = 102 operaciones


Devuelve el nombre de la organización y el rol real asociados a un token de invitación. Usado en AcceptInvitationPage para mostrar datos reales antes de aceptar.

  • Auth: Requerida
  • Input: { token: string }
  • Output: { orgName: string; role: string; email: string }
  • Errores: 404 si el token no existe, ya fue aceptado, o expiró
  • Archivo: src/organizations/operations.ts
  • Entidades: [OrganizationInvitation, Organization]

cancelTeamSubscription (Action) — 2026-03-26

Sección titulada «cancelTeamSubscription (Action) — 2026-03-26»

Cancela la suscripción del equipo al final del período de facturación actual. Actualiza Stripe con cancel_at_period_end: true y el campo subscriptionStatus en BD.

  • Auth: Requerida (OWNER de la organización)
  • Input: { organizationId: string }
  • Output: { success: boolean }
  • Errores: 401/403/404; 500 si Stripe falla
  • Archivo: src/organizations/operations.ts
  • Entidades: [Organization, OrganizationMember]

Devuelve todos los comentarios de la plataforma para moderación admin. Soporta filtros por curso, lección y estado de eliminación.

  • Auth: Requerida (solo admin)
  • Input: { courseId?: number; lessonId?: number; showDeleted?: boolean }
  • Output: Comment[] con usuario, lección y curso incluidos
  • Archivo: src/admin/operations.ts
  • Entidades: [Comment, Lesson, Course, User]

Devuelve todas las reseñas de la plataforma para moderación admin. Soporta filtros por curso y calificación mínima.

  • Auth: Requerida (solo admin)
  • Input: { courseId?: number; minRating?: number }
  • Output: Review[] con usuario y curso incluidos
  • Archivo: src/admin/operations.ts
  • Entidades: [Review, Course, User]

Elimina (soft-delete) un comentario y todas sus respuestas hijas. Establece isDeleted: true y deletedAt.

  • Auth: Requerida (solo admin)
  • Input: { commentId: number }
  • Output: void
  • Errores: 404 si el comentario no existe
  • Archivo: src/admin/operations.ts
  • Entidades: [Comment]

Elimina permanentemente (hard-delete) una reseña de la plataforma.

  • Auth: Requerida (solo admin)
  • Input: { reviewId: number }
  • Output: void
  • Errores: 404 si la reseña no existe
  • Archivo: src/admin/operations.ts
  • Entidades: [Review]

Devuelve estadísticas de completación y tiempo de visualización para cada lección de un curso.

  • Auth: Requerida (solo admin)
  • Input: { courseId: number }
  • Output: Array<{ id, title, order, duration, completions, avgWatchedSeconds }>
  • Archivo: src/admin/operations.ts
  • Entidades: [Lesson, LessonProgress]

getAdminQuizAttemptStats (Query) — 2026-03-30

Sección titulada «getAdminQuizAttemptStats (Query) — 2026-03-30»

Devuelve estadísticas de intentos para un quiz: tasa de aprobación, puntuación promedio y ranking de preguntas por tasa de error.

  • Auth: Requerida (solo admin)
  • Input: { quizId: number }
  • Output: { totalAttempts, passedAttempts, passRate, avgScore, passingScore, questionStats[] }
  • Archivo: src/admin/operations.ts
  • Entidades: [Quiz, QuizQuestion, QuizAnswer, QuizAttempt]

getAdminEnrollmentDetail (Query) — 2026-03-30

Sección titulada «getAdminEnrollmentDetail (Query) — 2026-03-30»

Devuelve el detalle completo de una inscripción con progreso por lección (watchedSeconds, isCompleted).

  • Auth: Requerida (solo admin)
  • Input: { enrollmentId: number }
  • Output: Enrollment con user, course.lessons, y progress[]
  • Archivo: src/admin/operations.ts
  • Entidades: [Enrollment, LessonProgress, Course, Lesson, User]

P3 — Flujo de Trabajo y Escala de Contenido (2026-03-30)

Sección titulada «P3 — Flujo de Trabajo y Escala de Contenido (2026-03-30)»

createLesson / updateLesson — Nuevos campos

Sección titulada «createLesson / updateLesson — Nuevos campos»

Los schemas createLesson y updateLesson ahora aceptan:

  • isPublished: boolean — Si false, la lección es un borrador invisible para estudiantes. Default: true.
  • scheduledPublishAt: Date | null — Fecha futura en que la lección se publicará automáticamente vía publishScheduledLessonsJob.

Job PgBoss que se ejecuta cada 5 minutos. Busca lecciones con isPublished: false y scheduledPublishAt <= now() y las publica automáticamente.

  • Archivo: src/admin/jobs/publishScheduledLessons.ts
  • Entidades: [Lesson]
  • Cron: */5 * * * *

Devuelve los eventos del calendario para un mes/año dado, incluyendo eventos sintéticos de cursos con scheduledPublishAt programado.

  • Auth: Requerida (solo admin)
  • Input: { month: number (0-11), year: number }
  • Output: CalendarEventOutput[] — eventos con relación course incluida. Eventos sintéticos usan id negativo y son de solo lectura.
  • Archivo: src/admin/operations.ts
  • Entidades: [CalendarEvent, Course]

Crea un nuevo evento en el calendario.

  • Auth: Requerida (solo admin)
  • Input: { title, description?, startDate, endDate?, type, color, courseId? }
  • Output: CalendarEvent
  • Archivo: src/admin/operations.ts
  • Entidades: [CalendarEvent]

Actualiza un evento existente del calendario.

  • Auth: Requerida (solo admin)
  • Input: { id, title?, description?, startDate?, endDate?, type?, color?, courseId? }
  • Output: CalendarEvent
  • Archivo: src/admin/operations.ts
  • Entidades: [CalendarEvent]

Elimina un evento del calendario.

  • Auth: Requerida (solo admin)
  • Input: { id: number }
  • Output: void
  • Archivo: src/admin/operations.ts
  • Entidades: [CalendarEvent]

Obtiene todos los marcadores de video del usuario para una lección, ordenados por timestamp ascendente.

  • Auth: Requerida
  • Input: { lessonId: number }
  • Output: VideoBookmark[]
  • Archivo: src/courses/bookmarkNoteOperations.ts
  • Entidades: [VideoBookmark, Lesson, Enrollment]

Crea un nuevo marcador de video para una lección.

  • Auth: Requerida (usuario inscrito o lección preview)
  • Input: { lessonId: number, timestamp: number, title: string, description?: string }
  • Output: VideoBookmark
  • Archivo: src/courses/bookmarkNoteOperations.ts
  • Entidades: [VideoBookmark, Lesson, Enrollment]

Elimina un marcador. Solo el propietario puede eliminarlo.

  • Auth: Requerida
  • Input: { bookmarkId: number }
  • Output: void
  • Archivo: src/courses/bookmarkNoteOperations.ts
  • Entidades: [VideoBookmark]

Obtiene todas las notas del usuario para una lección, ordenadas por fecha de creación descendente.

  • Auth: Requerida
  • Input: { lessonId: number }
  • Output: LessonNote[]
  • Archivo: src/courses/bookmarkNoteOperations.ts
  • Entidades: [LessonNote, Lesson, Enrollment]

Crea o actualiza una nota de lección. Si se provee noteId, actualiza la nota existente (verificando propiedad); si no, crea una nueva.

  • Auth: Requerida (usuario inscrito o lección preview)
  • Input: { lessonId: number, content: string, timestamp?: number, noteId?: number }
  • Output: LessonNote
  • Archivo: src/courses/bookmarkNoteOperations.ts
  • Entidades: [LessonNote, Lesson, Enrollment]

Elimina una nota. Solo el propietario puede eliminarla.

  • Auth: Requerida
  • Input: { noteId: number }
  • Output: void
  • Archivo: src/courses/bookmarkNoteOperations.ts
  • Entidades: [LessonNote]