Demo • Arquitectura • Por qué Vercel • API • Flujos • Requisitos
|
|
|
Note
El frontend usa Vercel mientras que el backend está 100% en AWS. Ver Por qué Vercel para más detalles.
🔍 Ver diagrama de arquitectura completo
flowchart TB
subgraph Internet
U["👤 Usuario"]
end
subgraph Vercel ["☁️ Vercel (Frontend)"]
V_EDGE["🌐 Edge Network"]
V_SSR["⚡ Next.js SSR"]
V_STATIC["📄 Static Assets"]
end
subgraph AWS ["☁️ AWS (Backend)"]
subgraph Security ["🛡️ Security Layer"]
WAF["WAF v2"]
end
subgraph Compute ["⚡ Compute"]
APIGW["API Gateway"]
Lambda["Lambda x16"]
end
subgraph Data ["🗄️ Data Layer"]
DDB[("DynamoDB")]
Cognito["Cognito"]
end
subgraph AI ["🤖 AI"]
Bedrock["Bedrock Nova"]
end
subgraph Events ["📨 Events"]
EB["EventBridge"]
SNS["SNS"]
SQS["SQS DLQ"]
end
subgraph Monitoring ["📊 Observability"]
CW["CloudWatch"]
XRAY["X-Ray"]
end
end
U --> V_EDGE
V_EDGE --> V_SSR
V_EDGE --> V_STATIC
V_SSR --> WAF
WAF --> APIGW
APIGW --> Lambda
Lambda --> DDB
Lambda --> Cognito
Lambda --> Bedrock
Lambda --> EB
EB --> SNS
EB --> SQS
Lambda -.-> CW
Lambda -.-> XRAY
Important
S3 no es compatible con Next.js 15 usando App Router.
S3 únicamente sirve contenido estático, mientras que Next.js 15 requiere ejecutar código del lado servidor.
Este frontend utiliza características que necesitan un runtime activo:
- Server Components – Renderizado en el servidor
- Server Actions – Lógica ejecutada desde la UI
- Rendering dinámico – Generación de páginas por request
- Middleware – Ejecución previa a cada solicitud
S3 no puede ejecutar código ni manejar lógica en tiempo de ejecución, por lo que no lo usamos ya que no cumple con nuestras necesidades en nuestro caso.
Vercel ofrece soporte nativo para Next.js y provee los runtimes necesarios sin configuración adicional:
| Requisito | S3 | Vercel |
|---|---|---|
| Server Components | ❌ | ✅ |
| Server Actions | ❌ | ✅ |
| Rendering dinámico | ❌ | ✅ |
| Middleware | ❌ | ✅ |
Alternativas dentro de AWS
Es posible implementar Next.js en AWS, pero con mayor complejidad operativa:
- AWS Amplify – Soporta SSR con ciertas limitaciones
- Lambda@Edge + S3 – Arquitectura compleja y difícil de mantener
- ECS / Fargate – Requiere administrar infraestructura adicional
Vercel simplifica este proceso al manejar todo el entorno automáticamente.
Ver recursos en producción
Tip
Usamos Single-Table Design para minimizar costos y maximizar rendimiento.
📊 Ver diagrama ER de entidades
erDiagram
WORKSHOPS ||--o{ REGISTRATIONS : tiene
USERS ||--o{ REGISTRATIONS : hace
WORKSHOPS {
string PK
string SK
string nombre
string descripcion
int cupo_maximo
int inscritos
date fecha
string categoria
}
USERS {
string PK
string SK
string nombre
string role
date created_at
}
REGISTRATIONS {
string PK
string SK
string workshop_nombre
date fecha_registro
}
| Entidad | PK | SK |
|---|---|---|
| 🎓 Taller | WORKSHOP#{uuid} |
META |
| 👤 Usuario | USER#{email} |
META |
| 📝 Inscripción | USER#{email} |
REGISTRATION#{workshop_id} |
🔍 Ver diagrama de GSIs
flowchart LR
subgraph GSI1 ["GSI1: Por Fecha"]
G1_PK["PK: WORKSHOP#ALL"]
G1_SK["SK: fecha"]
end
subgraph GSI2 ["GSI2: Por Categoría"]
G2_PK["PK: CATEGORY#nombre"]
G2_SK["SK: fecha"]
end
subgraph GSI3 ["GSI3: Inscripciones"]
G3_PK["PK: WORKSHOP#id"]
G3_SK["SK: fecha_registro"]
end
GSI1 --> |"Listar todos"| Q1["GET /workshops"]
GSI2 --> |"Filtrar"| Q2["GET /workshops?category=X"]
GSI3 --> |"Inscritos"| Q3["GET /workshops/{id}/students"]
| GSI | Partition Key | Sort Key | Uso |
|---|---|---|---|
| GSI1 | WORKSHOP#ALL |
fecha |
Listar todos por fecha |
| GSI2 | CATEGORY#{nombre} |
fecha |
Filtrar por categoría |
| GSI3 | WORKSHOP#{id} |
fecha_registro |
Listar inscritos |
📝 Ejemplo de Items en DynamoDB
// 🎓 Taller
{
"PK": "WORKSHOP#abc-123",
"SK": "META",
"GSI1PK": "WORKSHOP#ALL",
"GSI1SK": "2024-02-15",
"GSI2PK": "CATEGORY#desarrollo",
"nombre": "Docker para Developers",
"descripcion": "Aprende contenedores desde cero",
"cupo_maximo": 30,
"inscritos": 12,
"fecha": "2024-02-15T10:00:00Z"
}
// 👤 Usuario
{
"PK": "USER#juan@email.com",
"SK": "META",
"nombre": "Juan Pérez",
"role": "student",
"created_at": "2024-01-10T08:30:00Z"
}
// 📝 Inscripción
{
"PK": "USER#juan@email.com",
"SK": "REGISTRATION#abc-123",
"GSI3PK": "WORKSHOP#abc-123",
"GSI3SK": "2024-01-20T14:00:00Z",
"workshop_nombre": "Docker para Developers",
"workshop_fecha": "2024-02-15T10:00:00Z"
}Base URL:
https://qt6hwpaad0.execute-api.us-east-1.amazonaws.com/dev
| Método | Endpoint | Descripción |
|---|---|---|
/workshops |
Listar talleres | |
/workshops/{id} |
Detalle de taller | |
/workshops/categories |
Categorías | |
/stats |
Estadísticas | |
/auth/login |
Iniciar sesión | |
/auth/register |
Registrarse |
🎓 = Student 👑 = Admin
📝 Ejemplos de Request/Response
Login:
curl -X POST https://qt6hwpaad0.execute-api.us-east-1.amazonaws.com/dev/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "usuario@mail.com", "password": "MiPassword123!"}'Response:
{
"token": "eyJhbGciOiJSUzI1NiIs...",
"user": {
"email": "usuario@mail.com",
"role": "student",
"nombre": "Usuario Demo"
}
}Inscribirse a taller:
curl -X POST .../workshops/abc-123/register \
-H "Authorization: Bearer {token}"Response:
{
"message": "Inscripción exitosa",
"workshop": "Docker para Developers",
"fecha": "2024-02-15T10:00:00Z"
}🔐 Flujo de Autenticación
sequenceDiagram
autonumber
participant U as 👤 Usuario
participant V as ☁️ Vercel
participant WAF as 🛡️ WAF
participant API as 🚪 API Gateway
participant L as ⚡ Lambda
participant C as 🔐 Cognito
U->>V: Email + Password
V->>WAF: POST /auth/login
WAF->>WAF: Validar rate-limit
WAF->>API: Forward request
API->>L: Invoke auth/login
L->>C: AdminInitiateAuth
C->>C: Validar credenciales
C-->>L: JWT Tokens
L-->>API: {token, user}
API-->>V: 200 OK
V->>V: localStorage.setItem('token')
V-->>U: Redirect /dashboard
📝 Flujo de Inscripción a Taller
sequenceDiagram
autonumber
participant U as 👤 Usuario
participant API as 🚪 API Gateway
participant Auth as 🔐 JWT Authorizer
participant L as ⚡ Lambda
participant DB as 🗄️ DynamoDB
participant EB as 📨 EventBridge
participant SNS as 📧 SNS
U->>API: POST /workshops/{id}/register
API->>Auth: Validar JWT
Auth-->>API: ✅ Token válido
API->>L: Invoke
L->>DB: GetItem (workshop)
DB-->>L: Workshop data
alt ✅ Hay cupos disponibles
L->>DB: TransactWriteItems
Note over DB: 1. Crear registro<br/>2. Incrementar inscritos
DB-->>L: Success
L->>EB: PutEvent (STUDENT_REGISTERED)
EB->>SNS: Trigger notification
SNS-->>U: 📧 Email confirmación
L-->>U: 201 Created
else ❌ Sin cupos
L-->>U: 409 Conflict
end
🤖 Flujo del Asistente IA (Bedrock)
sequenceDiagram
autonumber
participant U as 👤 Usuario
participant L as ⚡ Lambda
participant DB as 🗄️ DynamoDB
participant B as 🤖 Bedrock
U->>L: POST /ai/assistant
Note right of U: {message: "¿Qué talleres hay de Python?"}
L->>DB: Query workshops
DB-->>L: Lista de talleres
L->>L: Construir contexto
Note over L: System prompt +<br/>datos de talleres +<br/>pregunta usuario
L->>B: InvokeModel (Nova Micro)
B->>B: Procesar con contexto
B-->>L: Respuesta generada
L-->>U: {response: "Tenemos 3 talleres..."}
📊 Sistema de Eventos (EventBridge)
flowchart TB
subgraph Triggers ["🎯 Eventos"]
E1["STUDENT_REGISTERED"]
E2["WORKSHOP_CREATED"]
E3["WORKSHOP_UPDATED"]
E4["REMINDER_24H"]
end
subgraph EventBridge ["📨 EventBridge"]
EB["Event Bus"]
R1["Rule: Notifications"]
R2["Rule: Reminders"]
end
subgraph Targets ["🎯 Targets"]
SNS["📧 SNS"]
L["⚡ Lambda Processor"]
DLQ["🗑️ SQS DLQ"]
end
E1 & E2 & E3 --> EB
E4 --> EB
EB --> R1 --> SNS
EB --> R2 --> L
L -.->|On Error| DLQ
⏰ Recordatorios Automáticos
flowchart LR
subgraph Scheduler ["📅 EventBridge Scheduler"]
CRON["⏰ Cada hora"]
end
subgraph Process ["⚡ Lambda"]
CHECK["Buscar talleres próximos"]
FILTER["Filtrar 24h antes"]
SEND["Enviar recordatorios"]
end
subgraph Notify ["📨 Notificaciones"]
SNS["SNS Topic"]
EMAIL["📧 Email"]
end
CRON --> CHECK --> FILTER --> SEND --> SNS --> EMAIL
|
|
📦 SkillsForge
├── 🎨 app/ # Next.js 15 App Router
│ ├── admin/ # Panel administrador
│ ├── estudiantes/ # Portal estudiantes
│ ├── api/ # API Routes (proxy)
│ └── (pages)/ # Páginas públicas
│
├── 🧩 frontend/
│ ├── components/ # React Components
│ │ ├── admin/ # Formularios admin
│ │ ├── ai/ # Chat IA, Insights
│ │ ├── shared/ # UI compartida
│ │ └── workshops/ # Cards, botones
│ ├── lib/ # Utilidades, hooks
│ └── types/ # TypeScript types
│
├── ⚡ backend-services/
│ ├── functions/ # Lambda Handlers
│ │ ├── auth/ # 🔐 login, register, refresh
│ │ ├── workshops/ # 📚 CRUD, stats, categories
│ │ ├── registrations/ # 📝 register, unregister, list
│ │ ├── events/ # 📨 processor, reminder
│ │ ├── students/ # 👥 list, delete
│ │ └── ai/ # 🤖 assistant (Bedrock)
│ └── shared/ # Lambda Layer (boto3, jwt)
│
└── 🏗️ infrastructure/
└── lib/stacks/ # CDK Stacks
├── api-stack.ts # API Gateway + Lambdas
├── auth-stack.ts # Cognito
├── data-stack.ts # DynamoDB
├── events-stack.ts # EventBridge + SNS
├── frontend-stack.ts # (Legacy S3/CF)
├── monitoring-stack.ts # CloudWatch + Alarms
└── security-stack.ts # WAF + IAM
Requisitos previos
- Node.js 18+
- Python 3.11
- AWS CLI configurado
- Cuenta de Vercel
# 1. Clonar repositorio
git clone https://github.com/Venganza-de-Python-II/SkillsForge.git
cd skillsforge
# 2. Instalar dependencias
npm install
cd infrastructure && npm install
# 3. Configurar variables
cp .env.example .env
# Editar .env con tus valores
# 4. Desplegar a AWS
cdk bootstrap
cdk deploy --all# Opción 1: Deploy automático
# Conectar repo en vercel.com → Import Project
# Opción 2: CLI
npm i -g vercel
vercel --prod# Frontend
npm run dev # localhost:3000
# Variables de entorno necesarias
NEXT_PUBLIC_API_URL=https://qt6hwpaad0.execute-api.us-east-1.amazonaws.com/dev🛡️ Ver diagrama de capas de seguridad
flowchart TB
subgraph Internet
USER["👤 Usuario"]
ATTACKER["🦹 Atacante"]
end
subgraph Layer1 ["Capa 1: Edge"]
WAF["🛡️ AWS WAF v2"]
end
subgraph Layer2 ["Capa 2: Auth"]
COGNITO["🔐 Cognito"]
JWT["JWT Authorizer"]
end
subgraph Layer3 ["Capa 3: API"]
THROTTLE["⏱️ Throttling"]
CORS["🌐 CORS"]
VALIDATE["✅ Validación"]
end
subgraph Layer4 ["Capa 4: Data"]
IAM["🔑 IAM Roles"]
ENCRYPT["🔒 Encryption"]
end
USER --> WAF
ATTACKER --> WAF
WAF -->|"❌ SQLi/XSS"| BLOCK["🚫 Blocked"]
WAF -->|"✅ Clean"| COGNITO
COGNITO --> JWT
JWT --> THROTTLE --> CORS --> VALIDATE
VALIDATE --> IAM --> ENCRYPT
| Capa | Protección | Configuración |
|---|---|---|
| WAF | Rate limit, SQLi, XSS | 2000 req/5min, AWS Managed Rules |
| Cognito | Autenticación | JWT RS256, refresh tokens |
| API Gateway | Throttling | 1000 req/s burst, 500 req/s steady |
| IAM | Autorización | Least privilege, sin wildcards |
| DynamoDB | Datos | Encryption at rest (AES-256) |
📊 Ver diagrama de observabilidad
flowchart LR
subgraph Sources ["📊 Fuentes"]
L["Lambda Logs"]
API["API Gateway"]
DDB["DynamoDB"]
end
subgraph CloudWatch ["☁️ CloudWatch"]
LOGS["📝 Log Groups"]
METRICS["📊 Metrics"]
DASH["📈 Dashboard"]
ALARMS["🚨 Alarms"]
end
subgraph XRay ["🔍 X-Ray"]
TRACES["Traces"]
MAP["Service Map"]
end
subgraph Alerts ["🔔 Alertas"]
SNS["📧 SNS"]
EMAIL["Email Admin"]
end
L & API & DDB --> LOGS --> METRICS --> DASH
METRICS --> ALARMS --> SNS --> EMAIL
L & API --> TRACES --> MAP
Warning
Las alarmas notifican automáticamente cuando:
- Errores 5XX > 10 en 5 minutos
- Lambda errors > 5%
- DLQ con mensajes pendientes
- Throttling activado
Note
Todo el proyecto opera dentro del AWS Free Tier actualmente
| Servicio | Uso Mensual Free | Nuestro Uso |
|---|---|---|
| Lambda | 1M requests | ~10K ✅ |
| API Gateway | 1M requests | ~10K ✅ |
| DynamoDB | 25 GB | ~50 MB ✅ |
| Cognito | 50K MAU | ~10 ✅ |
| EventBridge | 14M events | ~1K ✅ |
| SNS | 1M publish | ~100 ✅ |
| CloudWatch | 10 metrics | ~5 ✅ |
| Vercel | 100GB bandwidth | ~1GB ✅ |
| Bedrock Nova | Free tier | ✅ |
Note
Escenario: uso moderado en producción (≈100K usuarios/mes, tráfico constante)
| Servicio | Supuesto de Uso en Producción | Costo Estimado Mensual (USD) |
|---|---|---|
| AWS Lambda | ~2M requests | ~$0.40 |
| API Gateway (REST) | ~2M requests | ~$7.00 |
| DynamoDB (On-Demand) | ~5 GB + lecturas/escrituras | ~$3.00 |
| Amazon Cognito | ~20K MAU | ~$11.00 |
| EventBridge | ~2M eventos | ~$2.00 |
| SNS | ~50K notificaciones | ~$1.00 |
| CloudWatch | Logs + métricas | ~$2.00 |
| AWS WAF | Web ACL + reglas | ~$6.00 |
| Amazon Bedrock (Nova) | Uso ligero | ~$5.00 |
| Vercel (Pro) | Tráfico alto | ~$20.00 |
| Total Aproximado | ~$57 USD / mes |
# 1. Fork del repositorio
# 2. Crear branch
git checkout -b feature/nueva-funcionalidad
# 3. Hacer cambios y commit
git commit -m "feat: agregar nueva funcionalidad"
# 4. Push y crear PR
git push origin feature/nueva-funcionalidadEste proyecto está bajo la licencia Apache License 2.0. Ver [LICENSE](Apache License 2.0) para más detalles.