Ir al contenido

TalentBricksAI soporta dos proveedores de almacenamiento de video. La variable STORAGE_PROVIDER en el servidor controla cuál se usa (aws por defecto).

Opción A — AWS S3 + CloudFront:

┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Usuario │────▶│ CloudFront │────▶│ S3 Bucket │
│ (Video Player)│ │ (CDN) │ │ (Storage) │
└────────────────┘ └────────────────┘ └────────────────┘
CloudFront Signed URLs
(Acceso Temporizado)

Opción B — Azure Blob Storage:

┌────────────────┐ ┌──────────────────────┐
│ Usuario │────▶│ Azure Blob Storage │
│ (Video Player)│ │ (Container privado) │
└────────────────┘ └──────────────────────┘
SAS URLs Temporales
(Acceso Temporizado)

Configuración — Opción A: AWS S3 + CloudFront

Sección titulada «Configuración — Opción A: AWS S3 + CloudFront»
Ventana de terminal
# Crear bucket
aws s3 mb s3://talentbricksai-videos --región us-east-1
# Configurar como privado (por defecto)
aws s3api put-public-access-block \
--bucket talentbricksai-videos \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
talentbricksai-videos/
├── courses/
│ ├── fundamentos-data-engineering/
│ │ ├── 01-introduccion.mp4
│ │ ├── 02-conceptos.mp4
│ │ └── thumbnail.jpg
│ └── ml-básico/
│ ├── 01-intro.mp4
│ └── thumbnail.jpg
└── assets/
└── common/

Crear un usuario IAM con permisos minimos:

{
"versión": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::talentbricksai-videos/*"
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::talentbricksai-videos"
}
]
}
Ventana de terminal
# Subir un video
aws s3 cp ./video.mp4 s3://talentbricksai-videos/courses/mi-curso/01-intro.mp4
# Subir carpeta completa
aws s3 sync ./videos/ s3://talentbricksai-videos/courses/mi-curso/
  1. Ir a S3 en la consola de AWS
  2. Seleccionar el bucket
  3. Click en “Upload”
  4. Arrastrar archivos

La aplicación incluye funcionalidad de upload usando presigned URLs:

// El admin panel genera URLs de subida
const { uploadUrl, fileUrl } = await generateUploadUrl({
fileName: "video.mp4",
contentType: "video/mp4",
});
// Subir directamente a S3
await fetch(uploadUrl, {
method: "PUT",
body: videoFile,
headers: { "Content-Type": "video/mp4" },
});

Configuración — Opción B: Azure Blob Storage

Sección titulada «Configuración — Opción B: Azure Blob Storage»

Ver instrucciones detalladas en Fase 3.B del despliegue.

Ventana de terminal
# Instalar Azure CLI si no lo tienes (macOS)
brew install azure-cli
# Iniciar sesión
az login
# Subir un video
az storage blob upload \
--account-name talentbricksai \
--container-name videos \
--name courses/mi-curso/01-intro.mp4 \
--file ./video.mp4 \
--auth-mode key
# Subir carpeta completa
az storage blob upload-batch \
--account-name talentbricksai \
--destination videos \
--source ./videos \
--pattern "courses/mi-curso/*" \
--auth-mode key

Descargar Azure Storage Explorer — interfaz visual para gestionar blobs sin línea de comandos.

El admin panel usa la misma interfaz que con AWS — el proveedor activo se detecta automáticamente según STORAGE_PROVIDER:

// La app usa getStorageProvider() automáticamente
const { uploadUrl, fileUrl } = await generateUploadUrl({
fileName: "video.mp4",
contentType: "video/mp4",
});
// Subir con PUT (tanto AWS como Azure usan PUT)
await fetch(uploadUrl, {
method: "PUT",
body: videoFile,
headers: { "Content-Type": "video/mp4" },
});

Agregar a .env.server:

# Proveedor activo
STORAGE_PROVIDER=azure
# Azure Blob Storage
AZURE_STORAGE_ACCOUNT_NAME=talentbricksai
AZURE_STORAGE_ACCOUNT_KEY=tu-account-key-base64
AZURE_STORAGE_CONTAINER_NAME=archivos

En la consola de AWS CloudFront:

  1. Click “Create Distribution”
  2. Origin Domain: talentbricksai-videos.s3.us-east-1.amazonaws.com
  3. Origin Access: “Origin access control settings (recommended)”
  4. Crear nuevo OAC (Origin Access Control)
SettingValor
Viewer Protocol PolicyRedirect HTTP to HTTPS
Allowed HTTP MethodsGET, HEAD
Cache PolicyCachingOptimized
Compress ObjectsYes

Para contenido privado, usar URLs firmadas:

server/s3Utils.ts
import { getSignedUrl } from "@aws-sdk/cloudfront-signer";
export function generateVideoUrl(videoPath: string): string {
const url = `https://d123abc.cloudfront.net/${videoPath}`;
return getSignedUrl({
url,
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID!,
privateKey: process.env.CLOUDFRONT_PRIVATE_KEY!,
dateLessThan: new Date(Date.now() + 3600 * 1000).toISOString(), // 1 hora
});
}

Permitir acceso desde CloudFront:

{
"versión": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::talentbricksai-videos/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
SettingValor Recomendado
FormatoMP4 (H.264)
Resolucion1080p (1920x1080)
Bitrate5-8 Mbps
AudioAAC, 128-192 kbps
Frame Rate30 fps
Ventana de terminal
# Comprimir video para web
ffmpeg -i input.mov \
-c:v libx264 -preset slow -crf 22 \
-c:a aac -b:a 128k \
-movflags +faststart \
output.mp4
ServicioCosto
S3 Storage~$0.023/GB/mes
S3 Requests~$0.0004/1000 GET
CloudFront Transfer~$0.085/GB (primeros 10TB)
CloudFront Requests~$0.0075/10000

Ejemplo: 100GB de videos, 1000 visualizaciones/mes

Sección titulada «Ejemplo: 100GB de videos, 1000 visualizaciones/mes»
  • Storage: 100GB × $0.023 = $2.30/mes
  • Transfer: ~50GB × $0.085 = $4.25/mes
  • Total: ~$6.55/mes

Agregar a .env.server:

# Proveedor activo (valor por defecto — puede omitirse)
STORAGE_PROVIDER=aws
# AWS S3
AWS_S3_IAM_ACCESS_KEY=AKIA...
AWS_S3_IAM_SECRET_KEY=...
AWS_S3_FILES_BUCKET=talentbricksai-videos
AWS_S3_REGION=us-east-1
# CloudFront (para URLs firmadas)
CLOUDFRONT_DOMAIN=d123abc.cloudfront.net
CLOUDFRONT_KEY_PAIR_ID=K123...
CLOUDFRONT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."

Agregar a .env.server:

# Proveedor activo
STORAGE_PROVIDER=azure
# Azure Blob Storage
AZURE_STORAGE_ACCOUNT_NAME=talentbricksai
AZURE_STORAGE_ACCOUNT_KEY=tu-account-key-base64
AZURE_STORAGE_CONTAINER_NAME=archivos

El componente VideoPlayer usa las URLs firmadas:

courses/components/VideoPlayer.tsx
export function VideoPlayer({ lesson }: { lesson: Lesson }) {
const { data: videoUrl } = useQuery(getSignedVideoUrl, {
lessonId: lesson.id,
});
return <video src={videoUrl} controls onTimeUpdate={handleProgress} />;
}