Operaciones (Queries y Actions)
Sección titulada «Operaciones (Queries y Actions)»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)
Declaración en main.wasp
Sección titulada «Declaración en main.wasp»// Queryquery getCourses { fn: import { getCourses } from "@src/courses/operations", entities: [Course, Lesson]}
// Actionaction enrollInCourse { fn: import { enrollInCourse } from "@src/courses/operations", entities: [Enrollment, User, Course]}Uso en el Cliente
Sección titulada «Uso en el Cliente»Queries
Sección titulada «Queries»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} />;}Actions
Sección titulada «Actions»import { enrollInCourse } from "wasp/client/operations";
// Las actions se llaman directamente con await// NO usar useAction a menos que necesites optimistic updatesasync function handleEnroll(courseId: number) { try { await enrollInCourse({ courseId }); // La cache de queries se invalida automaticamente } catch (error) { console.error("Error:", error); }}implementación Server-Side
Sección titulada «implementación Server-Side»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};Context
Sección titulada «Context»| Propiedad | Tipo | descripción |
|---|---|---|
context.user | AuthUser | null | Usuario autenticado |
context.entities.ModelName | PrismaDelegate | Acceso a modelos |
context.prisma | PrismaClient | Cliente Prisma completo |
Errores HTTP
Sección titulada «Errores HTTP»import { HttpError } from "wasp/server";
// 401 Unauthorizedif (!context.user) { throw new HttpError(401, "Debes iniciar sesión");}
// 403 Forbiddenif (!context.user.isAdmin) { throw new HttpError(403, "No tienes permisos");}
// 404 Not Foundif (!course) { throw new HttpError(404, "Curso no encontrado");}
// 400 Bad Requestif (!args.courseId) { throw new HttpError(400, "courseId es requerido");}Lista Completa de Operaciones
Sección titulada «Lista Completa de Operaciones»⭐ Custom TalentBricksAI — Cursos
Sección titulada «⭐ Custom TalentBricksAI — Cursos»| operación | Tipo | Fuente | descripción |
|---|---|---|---|
getCourses | Query | @src/courses/operations | Obtener cursos publicados |
getCourseBySlug | Query | @src/courses/operations | Obtener curso por slug |
getMyEnrollments | Query | @src/courses/operations | Obtener inscripciones del usuario |
getLessonProgress | Query | @src/courses/operations | Obtener progreso de una leccion |
getSignedVideoUrl | Query | @src/courses/operations | Obtener URL firmada del video desde S3 o Azure Blob Storage usando storageVideoKey |
getCertificateById | Query | @src/courses/operations | Obtener certificado por course ID |
verifyCertificate | Query | @src/courses/operations | verificación pública de certificado |
enrollInCourse | Action | @src/courses/operations | Inscribir usuario en curso |
updateLessonProgress | Action | @src/courses/operations | Actualizar progreso de leccion |
completeCourse | Action | @src/courses/operations | Marcar curso como completado + generar certificado |
createCourseCheckout | Action | @src/courses/operations | Crear sesión de checkout Stripe |
⭐ Custom TalentBricksAI — Reviews
Sección titulada «⭐ Custom TalentBricksAI — Reviews»| operación | Tipo | Fuente | descripción |
|---|---|---|---|
getCourseReviews | Query | @src/courses/operations | Obtener resenas de un curso |
getUserReviewForCourse | Query | @src/courses/operations | Obtener resena del usuario para un curso |
submitReview | Action | @src/courses/operations | Enviar una nueva resena (solo usuarios matriculados) |
updateReview | Action | @src/courses/operations | Actualizar resena propia |
deleteReview | Action | @src/courses/operations | Eliminar resena propia |
⭐ Custom TalentBricksAI — Referidos
Sección titulada «⭐ Custom TalentBricksAI — Referidos»| operación | Tipo | Fuente | descripción |
|---|---|---|---|
getReferralStats | Query | @src/referral/operations | estadísticas de referidos + badge/nivel del usuario |
getReferralLeaderboard | Query | @src/referral/operations | Top 10 referidores públicos (opt-in) con badge |
getUserRewards | Query | @src/referral/operations | Recompensas disponibles del usuario |
generateReferralCode | Action | @src/referral/operations | Generar código de referido único |
trackReferralClick | Action | @src/referral/operations | Registrar click en link de referido |
registerReferralSignup | Action | @src/referral/operations | Registrar signup de usuario referido |
processReferralReward | Action | @src/referral/operations | Procesar recompensa por conversion |
redeemReward | Action | @src/referral/operations | Redimir recompensa en checkout |
⭐ Custom TalentBricksAI — Promociones
Sección titulada «⭐ Custom TalentBricksAI — Promociones»| operación | Tipo | Fuente | descripción |
|---|---|---|---|
validatePromoCode | Query | @src/promo/operations | Validar código promocional |
getActivePromoBanner | Query | @src/promo/operations | Obtener promo activa para banner |
applyPromoCode | Action | @src/promo/operations | Aplicar código a checkout |
createPromoCode | Action | @src/promo/operations | Crear código promo (admin) |
updatePromoCode | Action | @src/promo/operations | Actualizar código promo (admin) |
⭐ Custom TalentBricksAI — Admin Precios
Sección titulada «⭐ Custom TalentBricksAI — Admin Precios»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ón | Tipo | Fuente | Descripción |
|---|---|---|---|
getAdminPricingPlans | Query | @src/admin/pricing/operations | Listar todos los planes (admin) |
getPublicPricingPlans | Query | @src/admin/pricing/operations | Listar planes ACTIVE (público) |
getAdminPricingPlanById | Query | @src/admin/pricing/operations | Obtener plan por ID con historial |
getPaginatedPricingPlans | Query | @src/admin/pricing/operations | Listar planes paginados con filtros |
createPricingPlan | Action | @src/admin/pricing/operations | Crear nuevo plan |
updatePricingPlan | Action | @src/admin/pricing/operations | Actualizar plan (precio, estado, IDs…) |
deletePricingPlan | Action | @src/admin/pricing/operations | Eliminar plan (o archivar si tiene users) |
syncPlanWithStripe | Action | @src/admin/pricing/operations | Crear/verificar producto+precio en Stripe |
syncPlanWithStripe (Action, Admin)
Sección titulada «syncPlanWithStripe (Action, Admin)»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:
PricingPlanactualizado
Lógica:
- Si
stripeProductIden DB → verifica que sigue existiendo en Stripe. Si no → crea producto nuevo. - Si
stripePriceIden DB → verifica que existe y está activo en Stripe.- Si activo → no crea nada nuevo, solo actualiza
stripeProductIden DB si cambió. - Si inactivo/archivado o no existe → crea nuevo Price en Stripe.
- Si activo → no crea nada nuevo, solo actualiza
- Guarda
stripeProductIdystripePriceIden DB.
Errores HTTP:
401- No autenticado403- No es admin404- Plan no encontrado500- Error de conexión con Stripe oSTRIPE_API_KEYno configurada
updatePricingPlan (Action, Admin)
Sección titulada «updatePricingPlan (Action, Admin)»- Input:
{ id, planId?, priceInCents?, status?, creditAmount?, maxSeats?, isBestDeal?, displayOrder?, stripeProductId?, stripePriceId?, changeReason? } - Output:
PricingPlancon historial - Crea automáticamente un registro en
PricingPlanHistorysi cambiapriceInCentsostatus - Bloquea cambio de
planIdsi hay usuarios activos con ese plan
deletePricingPlan (Action, Admin)
Sección titulada «deletePricingPlan (Action, Admin)»- 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ón | Tipo | Fuente | descripción |
|---|---|---|---|
getAdminPromoCodes | Query | @src/admin/promotions/operations | Listar codigos promo con stats |
getAdminReferralStats | Query | @src/admin/promotions/operations | analíticas de referidos |
getAdminPendingRewards | Query | @src/admin/promotions/operations | Recompensas pendientes de revision |
deletePromoCode | Action | @src/admin/promotions/operations | Eliminar código promocional |
approveReward | Action | @src/admin/promotions/operations | Aprobar recompensa |
rejectReward | Action | @src/admin/promotions/operations | Rechazar/eliminar recompensa |
⭐ Custom TalentBricksAI — Admin
Sección titulada «⭐ Custom TalentBricksAI — Admin»| operación | Tipo | Fuente | descripción |
|---|---|---|---|
getAdminCourses | Query | @src/admin/operations | Listar cursos (admin) |
getAdminCourseById | Query | @src/admin/operations | Obtener curso por ID (admin) |
getAdminEnrollments | Query | @src/admin/operations | Listar inscripciones (admin) |
getAdminStats | Query | @src/admin/operations | estadísticas para dashboard |
createCourse | Action | @src/admin/operations | Crear nuevo curso (admin) |
updateCourse | Action | @src/admin/operations | Actualizar curso (admin) |
deleteCourse | Action | @src/admin/operations | Eliminar curso (admin) |
createLesson | Action | @src/admin/operations | Crear leccion (admin) |
updateLesson | Action | @src/admin/operations | Actualizar leccion (admin) |
deleteLesson | Action | @src/admin/operations | Eliminar leccion (admin) |
getVideoUploadUrl | Action | @src/admin/operations | Obtener URL firmada para subir video a almacenamiento (S3/Azure) — admin |
⭐ Custom TalentBricksAI — Progreso del Estudiante
Sección titulada «⭐ Custom TalentBricksAI — Progreso del Estudiante»| operación | Tipo | Fuente | descripción |
|---|---|---|---|
getUserProgressStats | Query | @src/user/operations | 6 métricas de progreso (lecciones, cursos, racha, horas, etc.) |
getUserWeeklyActivity | Query | @src/user/operations | Actividad por día de la semana con navegación |
getRecentActivity | Query | @src/user/operations | Últimas 6 lecciones completadas con timestamps |
getUpcomingLessons | Query | @src/user/operations | Próxima lección sin completar por cada inscripción activa |
getRecentActivity (Query, Usuario)
Sección titulada «getRecentActivity (Query, Usuario)»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).
getUpcomingLessons (Query, Usuario)
Sección titulada «getUpcomingLessons (Query, Usuario)»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ón | Tipo | Fuente | descripción |
|---|---|---|---|
changePassword | Action | @src/user/operations | Cambiar contraseña del usuario (requiere actual) |
changeEmail | Action | @src/user/operations | Cambiar email del usuario (requiere contraseña) |
exportUserData | Query | @src/user/operations | Exportar todos los datos personales del usuario (GDPR) |
deleteMyAccount | Action | @src/user/operations | El usuario elimina su propia cuenta (requiere confirmación) |
changePassword (Action, Usuario)
Sección titulada «changePassword (Action, Usuario)»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
mustChangePasswordsi 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 autenticado400- Contraseña actual incorrecta400- Contraseña muy corta (< 8 caracteres)400- Nueva contraseña igual a la actual400- Contraseñas de confirmación no coinciden500- Error en el servidor
changeEmail (Action, Usuario)
Sección titulada «changeEmail (Action, Usuario)»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 autenticado400- Email no encontrado en cuenta actual400- Nuevo email igual al actual400- Email ya en uso por otro usuario400- Contraseña actual incorrecta400- AuthIdentity no encontrada500- 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
exportUserData (Query, Usuario) — GDPR
Sección titulada «exportUserData (Query, Usuario) — GDPR»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:nullsi el usuario no es instructororganizations: array vacío si no pertenece a ninguna organización
Errores HTTP:
401- Usuario no autenticado
deleteMyAccount (Action, Usuario) — GDPR
Sección titulada «deleteMyAccount (Action, Usuario) — GDPR»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:
- El usuario hace click en “Eliminar mi cuenta” (Danger Zone en
/account/security) - Se abre un
AlertDialogcon el texto de confirmación - El usuario debe escribir exactamente
borrar-mi-cuenta(ES) /delete-my-account(EN) - El botón “Confirmar” se habilita solo cuando el texto coincide
- Al confirmar, la cuenta se elimina y el usuario es redirigido a
/
Errores HTTP:
401- Usuario no autenticado
Heredadas del Template Open SaaS
Sección titulada «Heredadas del Template Open SaaS»estas operaciones vienen del template Open SaaS y no han sido modificadas. Consulta la documentación original para más detalles.
| operación | Tipo | Fuente | descripción |
|---|---|---|---|
getPaginatedUsers | Query | @src/user/operations | Listar usuarios paginados |
updateIsUserAdminById | Action | @src/user/operations | Cambiar estado admin de usuario |
deleteUser | Action | @src/user/operations | Eliminar usuario (admin) |
updateUserByAdmin | Action | @src/user/operations | Actualizar usuario (admin) |
createUserByAdmin | Action | @src/user/operations | Crear usuario (admin) |
getGptResponses | Query | @src/demo-ai-app/operations | Obtener respuestas GPT |
createUserByAdmin (Action, Admin)
Sección titulada «createUserByAdmin (Action, Admin)»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: trueautomá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
updateUserByAdminpara estados válidos completos
Errores HTTP:
400- Email ya en uso400- Username ya en uso400- 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
updateUserByAdmin (Action, Admin)
Sección titulada «updateUserByAdmin (Action, Admin)»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.emailcomo elAuthIdentity.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álido400- Transición de estado inválida400- Email ya en uso400- Username ya en uso404- 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 |
Instructores
Sección titulada «Instructores»| Operación | Tipo | Fuente | Descripción |
|---|---|---|---|
getInstructor | Query | @src/instructors/operations | Obtener instructor por slug (público) |
getAllInstructors | Query | @src/instructors/operations | Listar instructores publicados |
getUserInstructorStatus | Query | @src/instructors/operations | Verificar si usuario es instructor de curso específico |
getAdminInstructorDetail | Query | @src/instructors/operations | Obtener instructor con todos los cursos (admin) |
createInstructor | Action | @src/instructors/operations | Crear instructor (admin) |
updateInstructor | Action | @src/instructors/operations | Actualizar instructor (admin) |
deleteInstructor | Action | @src/instructors/operations | Eliminar instructor (admin) |
Detalles de operaciones:
getUserInstructorStatus (Query, Usuario)
Sección titulada «getUserInstructorStatus (Query, Usuario)»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
truesi el usuario es admin - Retorna
truesi el usuario tiene uninstructorProfiley su ID coincide conCourse.instructorId - Retorna
falseen cualquier otro caso (incluso si no hay usuario autenticado)
- Retorna
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.
Comentarios y Q&A
Sección titulada «Comentarios y Q&A»| Operación | Tipo | Fuente | Descripción |
|---|---|---|---|
getLessonComments | Query | @src/comments/operations | Obtener comentarios de una lección |
getComment | Query | @src/comments/operations | Obtener un comentario específico |
getUserCommentVote | Query | @src/comments/operations | Obtener voto del usuario |
createComment | Action | @src/comments/operations | Crear comentario o respuesta |
updateComment | Action | @src/comments/operations | Editar comentario (15min window) |
deleteComment | Action | @src/comments/operations | Eliminar comentario (soft delete) |
voteComment | Action | @src/comments/operations | Votar comentario (upvote/downvote) |
markAsInstructorResponse | Action | @src/comments/operations | Marcar como respuesta del instructor |
markAsSolution | Action | @src/comments/operations | Marcar como solución |
Recursos Descargables de Lecciones
Sección titulada «Recursos Descargables de Lecciones»| Operación | Tipo | Fuente | Descripción |
|---|---|---|---|
createLessonResource | Action | @src/courses/operations | Crear recurso + URL firmada S3 (admin) |
updateLessonResource | Action | @src/courses/operations | Actualizar metadata del recurso (admin) |
deleteLessonResource | Action | @src/courses/operations | Eliminar recurso de DB y S3 (admin) |
getLessonResources | Query | @src/courses/operations | Listar recursos de lección (requiere enrollment) |
getResourceDownloadUrl | Query | @src/courses/operations | URL firmada para descarga (requiere enrollment) |
Detalles de operaciones:
createLessonResource (Action, Admin)
Sección titulada «createLessonResource (Action, Admin)»Genera URL firmada para subir recurso a S3.
- Input:
{ lessonId, fileName, fileType, fileSize } - Output:
{ s3UploadUrl, s3UploadFields, resourceId } - Validación: Solo administradores
updateLessonResource (Action, Admin)
Sección titulada «updateLessonResource (Action, Admin)»Actualiza metadata del recurso (título, descripción, orden).
- Input:
{ resourceId, title?, description?, order? } - Output:
LessonResource - Validación: Solo administradores
deleteLessonResource (Action, Admin)
Sección titulada «deleteLessonResource (Action, Admin)»Elimina recurso de base de datos y S3.
- Input:
{ resourceId } - Validación: Solo administradores
getLessonResources (Query, Estudiante)
Sección titulada «getLessonResources (Query, Estudiante)»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
getResourceDownloadUrl (Query, Estudiante)
Sección titulada «getResourceDownloadUrl (Query, Estudiante)»Genera URL firmada para descargar recurso (1 hora expiración).
- Input:
{ resourceId } - Output:
{ url, fileName, expiresAt } - Access: Requiere enrollment o lesson.isPreview = true
Solicitudes de Demo (B2B/Teams)
Sección titulada «Solicitudes de Demo (B2B/Teams)»| Operación | Tipo | Fuente | Descripción |
|---|---|---|---|
submitDemoRequest | Action | @src/landing-page/teams/operations | Crear solicitud de demo (B2B) |
Detalles de operaciones:
submitDemoRequest (Action, Público)
Sección titulada «submitDemoRequest (Action, Público)»Crea una solicitud de demo desde la página /teams. No requiere autenticación.
- Input:
{companyName: string, // Requerido, min 2 caracterescontactName: string, // Requerido, min 2 caracteresemail: string, // Requerido, formato válidophone?: string, // OpcionalteamSize: 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
- Crea registro en tabla
Componente: TeamsDemoForm.tsx (/teams página)
Total: 85 queries + 107 actions = 192 operaciones
Autenticación de Dos Factores (2FA)
Sección titulada «Autenticación de Dos Factores (2FA)»Operaciones TOTP para protección de cuentas con app autenticadora. Implementadas en
src/auth/twoFactor/twoFactorOperations.ts.
get2FAStatus (Query)
Sección titulada «get2FAStatus (Query)»Devuelve el estado 2FA del usuario autenticado.
- Output:
{ enabled: boolean, verifiedAt: Date | null, isVerifiedThisSession: boolean } - Nota:
isVerifiedThisSessionestruesiverifiedAtes de hace menos de 24 horas.
setup2FA (Action)
Sección titulada «setup2FA (Action)»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 }
confirmAndEnable2FA (Action)
Sección titulada «confirmAndEnable2FA (Action)»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 formatoXXXX-XXXX - Errores: 400 si el código es inválido o no hay setup pendiente
verify2FACode (Action)
Sección titulada «verify2FACode (Action)»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
disable2FA (Action)
Sección titulada «disable2FA (Action)»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
getUserSessions (Query)
Sección titulada «getUserSessions (Query)»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 conid,sessionId,deviceName,userAgent,createdAt,lastSeenAt,isCurrent - Archivo:
src/user/sessionOperations.ts
registerCurrentSession (Action)
Sección titulada «registerCurrentSession (Action)»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
revokeSession (Action)
Sección titulada «revokeSession (Action)»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
getInvitationDetails (Query) — 2026-03-26
Sección titulada «getInvitationDetails (Query) — 2026-03-26»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]
getAdminComments (Query) — 2026-03-30
Sección titulada «getAdminComments (Query) — 2026-03-30»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]
getAdminReviews (Query) — 2026-03-30
Sección titulada «getAdminReviews (Query) — 2026-03-30»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]
adminDeleteComment (Action) — 2026-03-30
Sección titulada «adminDeleteComment (Action) — 2026-03-30»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]
adminDeleteReview (Action) — 2026-03-30
Sección titulada «adminDeleteReview (Action) — 2026-03-30»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]
getAdminLessonStats (Query) — 2026-03-30
Sección titulada «getAdminLessonStats (Query) — 2026-03-30»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:
Enrollmentconuser,course.lessons, yprogress[] - 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— Sifalse, 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íapublishScheduledLessonsJob.
publishScheduledLessonsJob (Job)
Sección titulada «publishScheduledLessonsJob (Job)»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 * * * *
Calendario Admin (2026-03-31)
Sección titulada «Calendario Admin (2026-03-31)»getCalendarEvents (Query)
Sección titulada «getCalendarEvents (Query)»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óncourseincluida. Eventos sintéticos usanidnegativo y son de solo lectura. - Archivo:
src/admin/operations.ts - Entidades:
[CalendarEvent, Course]
createCalendarEvent (Action)
Sección titulada «createCalendarEvent (Action)»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]
updateCalendarEvent (Action)
Sección titulada «updateCalendarEvent (Action)»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]
deleteCalendarEvent (Action)
Sección titulada «deleteCalendarEvent (Action)»Elimina un evento del calendario.
- Auth: Requerida (solo admin)
- Input:
{ id: number } - Output:
void - Archivo:
src/admin/operations.ts - Entidades:
[CalendarEvent]
Marcadores y Notas de Lección
Sección titulada «Marcadores y Notas de Lección»getLessonBookmarks (Query)
Sección titulada «getLessonBookmarks (Query)»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]
createBookmark (Action)
Sección titulada «createBookmark (Action)»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]
deleteBookmark (Action)
Sección titulada «deleteBookmark (Action)»Elimina un marcador. Solo el propietario puede eliminarlo.
- Auth: Requerida
- Input:
{ bookmarkId: number } - Output:
void - Archivo:
src/courses/bookmarkNoteOperations.ts - Entidades:
[VideoBookmark]
getLessonNotes (Query)
Sección titulada «getLessonNotes (Query)»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]
upsertLessonNote (Action)
Sección titulada «upsertLessonNote (Action)»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]
deleteLessonNote (Action)
Sección titulada «deleteLessonNote (Action)»Elimina una nota. Solo el propietario puede eliminarla.
- Auth: Requerida
- Input:
{ noteId: number } - Output:
void - Archivo:
src/courses/bookmarkNoteOperations.ts - Entidades:
[LessonNote]