Ir al contenido

Los recursos de lección son archivos adjuntos que el administrador sube a cada lección: PDFs, presentaciones, archivos de código, hojas de referencia, etc. Son distintos de los videos de lección.


Todos los archivos de la plataforma (recursos, videos y archivos de usuario) conviven en el mismo bucket S3, separados por prefijos:

{bucket}/
├── lessons/
│ ├── {lessonId}/
│ │ ├── resources/
│ │ │ └── {uuid}.{ext} ← recursos de lección (PDFs, docs, etc.)
│ │ └── videos/
│ │ └── {uuid}.{ext} ← videos de lección (MP4, WebM, MOV)
└── {userId}/
└── {uuid}.{ext} ← archivos de usuario (avatares, uploads)

La clave S3 para un recurso se genera en resourceS3Utils.ts:

// lessons/{lessonId}/resources/{randomUUID()}.{ext}
function getResourceS3Key(fileName: string, lessonId: number) {
const ext = path.extname(fileName).slice(1);
return `lessons/${lessonId}/resources/${randomUUID()}.${ext}`;
}

El componente ResourceUploadSection gestiona todo el proceso en tres pasos:

┌─────────────────────────────────────────────────────────────────────┐
│ FLUJO DE SUBIDA │
│ │
│ Admin Servidor (Wasp) Proveedor S3/Azure │
│ ────── ────────────── ───────────────── │
│ │
│ 1. Selecciona │
│ archivo ──────────────▶ │
│ createLessonResource │
│ (valida admin, tamaño, │
│ tipo; crea registro DB) │
│ │
│ ◀────────────── { s3UploadUrl, │
│ s3UploadFields, │
│ resourceId, │
│ uploadMethod } │
│ │
│ 2. PUT / POST │
│ directo ────────────────────────────▶ S3 / Azure Blob │
│ al cloud (sin pasar por servidor) │
│ │
│ 3. UI │
│ actualiza │
│ lista local │
└─────────────────────────────────────────────────────────────────────┘
ProveedorMétodoNotas
AWS S3POST (multipart)Requiere s3UploadFields adicionales en el FormData
Azure BlobPUT con header x-ms-blob-type: BlockBlobSin campos adicionales

El cliente detecta el método con la propiedad uploadMethod devuelta por createLessonResource.


Cuando un estudiante accede a los recursos de una lección:

┌──────────────────────────────────────────────────────────────────┐
│ FLUJO DE DESCARGA │
│ │
│ Estudiante Servidor (Wasp) S3 directo / Azure Blob │
│ ────────── ────────────── ────────────────────── │
│ │
│ 1. Solicita ──────────────▶ │
│ recurso getResourceDownloadSignedURL │
│ (verifica matrícula / suscripción) │
│ │
│ getStorageProvider() │
│ .getDownloadUrl(s3Key) ──────▶ genera URL │
│ firmada (1h) │
│ AWS: GetObjectCommand + getSignedUrl │
│ Azure: SAS URL (permisos solo lectura) │
│ │
│ ◀─────────────────────────────── URL firmada │
│ │
│ 2. Descarga ──────────────────────────────▶ archivo │
│ directo │
└──────────────────────────────────────────────────────────────────┘

TipoLímiteFormatos permitidos
Recursos de lección50 MBPDF, DOCX, DOC, PPTX, PPT, ZIP, RAR, 7Z, JSON, texto/código, imágenes (JPEG, PNG, GIF), MP4, MOV
Videos de lección5 GBMP4, WebM, MOV
Archivos de usuario5 MBImágenes (JPEG, PNG, GIF)

Los tipos MIME válidos se definen en app/src/file-upload/validation.ts:

export const ALLOWED_FILE_TYPES = [
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"application/vnd.ms-powerpoint", // .ppt
"application/vnd.openxmlformats-officedocument.presentationml.presentation", // .pptx
"application/msword", // .doc
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
"application/zip",
"application/x-rar-compressed",
"application/x-7z-compressed",
"text/*",
"application/json",
"video/quicktime",
"video/mp4",
];

Las mismas variables de almacenamiento utilizadas para videos se aplican también a los recursos. Ver la guía Subir Videos → Variables de Entorno para la configuración completa.

En resumen:

# Proveedor activo (aws por defecto)
STORAGE_PROVIDER=aws
# AWS S3 (requerido para STORAGE_PROVIDER=aws)
AWS_S3_IAM_ACCESS_KEY=AKIA...
AWS_S3_IAM_SECRET_KEY=...
AWS_S3_FILES_BUCKET=nombre-del-bucket
AWS_S3_REGION=us-east-1
# Azure (alternativa a AWS)
# STORAGE_PROVIDER=azure
# AZURE_STORAGE_ACCOUNT_NAME=talentbricksai
# AZURE_STORAGE_ACCOUNT_KEY=...
# AZURE_STORAGE_CONTAINER_NAME=archivos

  1. Ir al Panel de AdminCursos → seleccionar el curso.
  2. Dentro del curso, hacer clic en la lección deseada para editarla.
  3. En la sección Recursos, hacer clic en “Agregar recurso”.
  4. Seleccionar el archivo en el explorador (máx. 50 MB, formatos válidos según la tabla anterior).
  5. Esperar a que aparezca el indicador de carga — el archivo se sube directamente al almacenamiento en la nube.
  6. El recurso aparece en la lista con el nombre del archivo como título por defecto.
  1. En la lista de recursos, hacer clic en “Editar” junto al recurso.
  2. Modificar el título (visible para el estudiante) y la descripción (opcional).
  3. Hacer clic en “Guardar”.

El título y la descripción se actualizan solo en la base de datos — el archivo en S3 no se mueve.

  1. Hacer clic en el ícono de papelera junto al recurso.
  2. Confirmar la eliminación en el diálogo.

  • Sin acceso público al bucket: el bucket debe configurarse como privado (sin ACLs públicas).
  • URLs firmadas con expiración de 1 hora:
    • AWS: GetObjectCommand + getSignedUrl({ expiresIn: 3600 }) → URL de S3 firmada.
    • Azure: SAS con BlobSASPermissions.parse("r") (solo lectura) y expiresOn = ahora + 1h.
  • SAS de subida con permisos mínimos (Azure): la URL de subida usa BlobSASPermissions.parse("w") (solo escritura), distinta de la URL de descarga (solo lectura).
  • Límite de tamaño aplicado en AWS, validado en cliente para Azure:
    • AWS: el presigned POST incluye Conditions: [["content-length-range", 0, maxBytes]] — S3 rechaza archivos que superen el límite aunque el cliente lo intente.
    • Azure: la SAS no admite restricciones de tamaño. El límite de 50 MB se aplica únicamente mediante validación en el cliente (ResourceUploadSection) antes de iniciar la subida.
  • Verificación de acceso: antes de generar la URL de descarga, el servidor comprueba que el usuario tenga matrícula activa en el curso o una suscripción vigente.
  • Subida directa al cloud: los archivos se suben desde el navegador del admin directamente a S3/Azure, sin pasar por el servidor de la aplicación. Esto reduce la carga del servidor y evita almacenar temporalmente archivos grandes en memoria.

Ambos proveedores implementan la misma interfaz definida en app/src/storage/types.ts:

export interface StorageProvider {
// Genera una URL firmada para subir un archivo al bucket
getUploadUrl(params: {
key: string; // clave S3 / blob name
contentType: string;
maxBytes: number; // aplicado en AWS; ignorado en Azure
}): Promise<UploadUrlResult>;
// Genera una URL firmada para descargar un archivo (1 hora)
getDownloadUrl(key: string): Promise<string>;
// Elimina un archivo del bucket
deleteFile(key: string): Promise<void>;
// Comprueba si un archivo existe (usado en validaciones internas)
checkFileExists(key: string): Promise<boolean>;
}
MétodoAWSAzure
getUploadUrlPresigned POST (createPresignedPost)SAS URL con permisos "w"
getDownloadUrlGetObjectCommand + getSignedUrlSAS URL con permisos "r"
deleteFileDeleteObjectCommandblockBlobClient.delete()
checkFileExistsHeadObjectCommandblockBlobClient.exists()

En entorno de desarrollo no es necesario configurar S3 ni Azure para trabajar con el código de la aplicación. Sin embargo, la funcionalidad de subida de recursos sí requiere credenciales de almacenamiento (no existe modo simulado para recursos, a diferencia de los videos de desarrollo que usan URLs públicas W3C).

Para probar recursos en local:

  1. Configura las variables de entorno de AWS o Azure en .env.server.
  2. Usa un bucket de desarrollo separado (p. ej. talentbricksai-dev).
  3. Ejecuta wasp start normalmente.