SkillsForge es una plataforma integral para la gestión y participación en talleres de formación profesional. Desarrollado con Next.js (frontend) y Flask (backend) con MongoDB como base de datos.
🎯 Prototipo de Prueba
- 🎯 Cumplimiento de Lineamientos
- ✨ Características
- 🛠️ Tecnologías
- 🚀 Instalación
- ⚙️ Configuración
- 📖 Uso
- 🛡️ Seguridad y Rate Limiting
- 🔌 API Endpoints
- 📁 Estructura del Proyecto
- 🚀 Despliegue
- 🔧 Solución de Problemas
- 📑 Documentación Técnica
- 🏗️ Arquitectura
- 🗄️ Base de Datos
- 🔐 Seguridad y Rate Limiting
- 🔌 API Documentacion
- 🚀 Despliegue
- 📝 Requisitos Solicitados / Distribucion
Requisito | Estado | Implementación | Código |
---|---|---|---|
Ver lista de talleres | ✅ Completo | Lista con nombre, fecha, hora, lugar y tipo | app/estudiantes/talleres/page.tsx |
Registrar un taller (admin) | ✅ Completo | Formulario con todos los campos requeridos | components/formulario-taller.tsx |
Modificar un taller (admin) | ✅ Completo | Edición completa con validaciones | app/admin/page.tsx |
Cancelar un taller (admin) | ✅ Completo | Eliminación con confirmación | app/admin/page.tsx |
Registrarse a un taller (estudiante) | ✅ Completo | Inscripción con control de cupos | components/boton-inscripcion.tsx |
Endpoint | Método | Estado | Implementación | Código |
---|---|---|---|---|
/workshops |
GET | ✅ Completo | Obtener todos los talleres | backend/app.py |
/workshops/{id} |
GET | ✅ Completo | Obtener taller específico | backend/app.py |
/workshops |
POST | ✅ Completo | Crear nuevo taller (admin) | backend/app.py |
/workshops/{id} |
PUT | ✅ Completo | Modificar taller (admin) | backend/app.py |
/workshops/{id} |
DELETE | ✅ Completo | Eliminar taller (admin) | backend/app.py |
/workshops/{id}/register |
POST | ✅ Completo | Registrar estudiante | backend/app.py |
Componente | Estado | Descripción | Código |
---|---|---|---|
Portal Estudiantes | ✅ Completo | Ver talleres y registrarse | app/estudiantes/talleres/page.tsx |
Panel Administradores | ✅ Completo | Gestión completa de talleres | app/admin/page.tsx |
Formularios React | ✅ Completo | Crear/editar talleres | components/formulario-taller.tsx |
Tablas React | ✅ Completo | Listado de talleres | components/tabla-talleres.tsx |
Tecnología | Estado | Versión | Implementación |
---|---|---|---|
Backend: Flask | ✅ Completo | 3.0.3 | backend/app.py |
Frontend: React | ✅ Completo | Next.js 15 | app/ |
Base de datos: MongoDB | ✅ Completo | 6.0 | docker-compose.yml |
Autenticación: JWT | ✅ Completo | PyJWT 2.9.0 | backend/app.py |
Docker | ✅ Completo | Docker Compose | docker-compose.yml |
PyMongo | ✅ Completo | 4.8.0 | backend/requirements.txt |
Extra | Estado | Descripción | Código | Comando |
---|---|---|---|---|
Documentación Técnica | ✅ Completo | Arquitectura, BD y despliegue | docs/documentacion-tecnica.md |
- |
Rate Limiting | ✅ Completo | Protección anti-spam | backend/app.py:35-55 |
- |
Refresh Tokens | ✅ Completo | Renovación automática de tokens | backend/app.py:470-550 |
- |
- 200 OK: Operaciones exitosas -
backend/app.py:604
- 201 Created: Recursos creados -
backend/app.py:659
- 400 Bad Request: Datos inválidos -
backend/app.py:625
- 401 Unauthorized: No autenticado -
backend/app.py:210
- 403 Forbidden: Sin permisos -
backend/app.py:215
- 404 Not Found: Recurso no encontrado -
backend/app.py:880
- 409 Conflict: Conflicto de recursos -
backend/app.py:740
- 429 Too Many Requests: Rate limit -
backend/app.py:860
Todas las respuestas de la API están en formato JSON - backend/app.py:15
- Administradores:
backend/app.py:200-230
- Estudiantes:
backend/app.py:232-280
- Refresh Tokens:
backend/app.py:470-550
- Backend: Generación y validación -
backend/app.py:470-550
- Cliente API: Retry inteligente -
lib/api.ts:80-120
- Configuración: Redis + Flask-Limiter -
backend/app.py:35-55
- Límites por Endpoint: Diferentes límites según uso -
backend/app.py:543+
- Manejo de Errores: Respuestas 429 con retry-after -
backend/app.py:860-870
- Headers de Seguridad: X-Frame-Options, X-XSS-Protection -
backend/app.py:850-860
- Validación de Entrada: Sanitización y validación -
backend/app.py:620-640
- Retry Automático: En rate limits y errores de red -
lib/api.ts:45-80
- Manejo de Tokens: Renovación automática -
lib/api.ts:120-150
- Timeout Configurable: Prevención de requests colgados -
lib/api.ts:25-45
- Ver lista de talleres: Lista completa con nombre, fecha, hora, lugar y tipo de actividad
- Registrar un taller (solo administradores): Crear talleres con todos los campos requeridos
- Modificar un taller (solo administradores): Editar detalles de talleres existentes
- Cancelar un taller (solo administradores): Eliminar talleres que no se realizarán
- Registrarse a un taller (solo estudiantes): Inscripción con validación de cupos
GET /workshops
→ Obtener todos los talleres disponiblesGET /workshops/{id}
→ Obtener detalles de un taller específicoPOST /workshops
→ Crear un nuevo taller (solo administradores)PUT /workshops/{id}
→ Modificar un taller existente (solo administradores)DELETE /workshops/{id}
→ Eliminar un taller (solo administradores)POST /workshops/{id}/register
→ Registrar a un estudiante en un taller
- Interfaz para estudiantes: Ver talleres y registrarse
- Panel para administradores: Gestionar creación, edición y cancelación
- Formularios y tablas: Implementados con React y componentes reutilizables
- 📝 Registro y autenticación con validación de email
- 🔍 Búsqueda y filtrado avanzado por categoría, fecha y texto
- 📅 Inscripción a talleres con control de cupos en tiempo real
- 📊 Panel personal para gestionar inscripciones activas
- ❌ Cancelación de inscripciones con actualización automática
- 🔐 Panel de administración con autenticación JWT segura
- ➕ CRUD completo de talleres (crear, leer, actualizar, eliminar)
- 👥 Gestión de estudiantes y visualización de inscripciones
- 📈 Estadísticas del sistema en tiempo real
- 📋 Control de cupos y validaciones de negocio
- 🔒 Autenticación JWT con roles diferenciados (admin/estudiante)
- 🛡️ Rate limiting inteligente por endpoint y IP
- 🔄 Cliente API robusto con retry automático y manejo de errores
- 📱 Diseño responsive optimizado para móviles y desktop
- 🎨 Interfaz moderna con componentes accesibles
- 🐳 Containerización completa con Docker Compose
- 🗃️ Base de datos optimizada con índices y consultas eficientes
- 📚 Documentación: Arquitectura, base de datos y despliegue detallados
- 🔧 Rate limiting: Protección contra spam y ataques
- 🚀 Cliente API inteligente: Manejo automático de tokens y errores
erDiagram
TALLERES {
ObjectId _id PK "Clave primaria"
string nombre "Nombre del taller"
string descripcion "Descripción detallada"
string fecha "Fecha en formato YYYY-MM-DD"
string hora "Hora en formato HH:MM"
string lugar "Ubicación física"
string categoria "Categoría del taller"
string tipo "Tipo de actividad"
string instructor "Nombre del instructor"
float rating "Calificación 0.0-5.0"
int cupo "Capacidad máxima"
string creado_en "Timestamp ISO 8601"
string actualizado_en "Timestamp ISO 8601"
array inscripciones "Subdocumentos embebidos"
}
ESTUDIANTES {
ObjectId _id PK "Clave primaria"
string nombre "Nombre completo"
string email UK "Email único"
string hash "Hash bcrypt contraseña"
string creado_en "Timestamp ISO 8601"
}
INSCRIPCIONES {
string estudiante_id FK "Referencia a ESTUDIANTES._id"
string nombre "Nombre del estudiante (desnormalizado)"
string email "Email del estudiante (desnormalizado)"
string registrado_en "Timestamp de inscripción"
}
TALLERES ||--o{ INSCRIPCIONES : "contiene"
ESTUDIANTES ||--o{ INSCRIPCIONES : "se_inscribe_en"
Campo | Tipo | Restricciones | Descripción |
---|---|---|---|
_id |
ObjectId | PK, Auto-generado | Identificador único del taller |
nombre |
String | Requerido, 1-200 chars | Nombre descriptivo del taller |
descripcion |
String | Requerido, 1-1000 chars | Descripción detallada del contenido |
fecha |
String | Requerido, formato YYYY-MM-DD | Fecha de realización |
hora |
String | Requerido, formato HH:MM | Hora de inicio |
lugar |
String | Requerido, 1-100 chars | Ubicación física o virtual |
categoria |
String | Requerido | tecnologia, emprendimiento, habilidades-blandas |
tipo |
String | Requerido | curso técnico, capacitacion, programa |
instructor |
String | Opcional, 1-100 chars | Nombre del instructor asignado |
rating |
Float | Opcional, 0.0-5.0 | Calificación promedio del taller |
cupo |
Integer | Requerido, ≥ 0 | Capacidad máxima de estudiantes |
creado_en |
String | Auto-generado, ISO 8601 | Timestamp de creación |
actualizado_en |
String | Auto-actualizado, ISO 8601 | Timestamp de última modificación |
inscripciones |
Array | Subdocumentos embebidos | Lista de estudiantes inscritos |
Campo | Tipo | Restricciones | Descripción |
---|---|---|---|
_id |
ObjectId | PK, Auto-generado | Identificador único del estudiante |
nombre |
String | Requerido, 1-100 chars | Nombre completo del estudiante |
email |
String | UK, Requerido, formato email | Correo electrónico único |
hash |
String | Requerido, bcrypt | Hash seguro de la contraseña |
creado_en |
String | Auto-generado, ISO 8601 | Timestamp de registro |
Campo | Tipo | Restricciones | Descripción |
---|---|---|---|
estudiante_id |
String | FK → estudiantes._id | Referencia al estudiante |
nombre |
String | Desnormalizado | Nombre del estudiante (cache) |
email |
String | Desnormalizado | Email del estudiante (cache) |
registrado_en |
String | Auto-generado, ISO 8601 | Timestamp de inscripción |
// Índices para optimización de consultas
db.talleres.createIndex({ "fecha": 1, "hora": 1 }) // Búsquedas por fecha/hora
db.talleres.createIndex({ "categoria": 1 }) // Filtros por categoría
db.talleres.createIndex({ "rating": -1 }) // Ordenamiento por rating
db.talleres.createIndex({ "creado_en": -1 }) // Talleres más recientes
db.estudiantes.createIndex({ "email": 1 }, { unique: true }) // Email único
db.estudiantes.createIndex({ "creado_en": -1 }) // Estudiantes más recientes
Consulta | Índice Utilizado | Complejidad |
---|---|---|
Listar talleres por fecha | fecha_1_hora_1 |
O(log n) |
Filtrar por categoría | categoria_1 |
O(log n) |
Talleres mejor valorados | rating_-1 |
O(log n) |
Buscar estudiante por email | email_1 |
O(log n) |
Verificar inscripción | Scan del array | O(m) donde m = inscripciones |
-
Talleres ↔ Estudiantes: Relación Many-to-Many
- Implementada mediante subdocumentos embebidos
- Desnormalización intencional para optimizar consultas
- Referencia por
estudiante_id
para mantener integridad
-
Integridad Referencial:
- Cascada en eliminación: Al eliminar estudiante, se remueven sus inscripciones
- Validación de existencia: Verificación de estudiante antes de inscripción
- Control de cupos: Validación de capacidad antes de permitir inscripción
¿Por qué subdocumentos embebidos?
- ✅ Rendimiento: Una sola consulta obtiene taller + inscripciones
- ✅ Atomicidad: Inscripción y actualización de cupo en una operación
- ✅ Simplicidad: No requiere JOINs complejos
- ❌ Limitación: Máximo 16MB por documento (no problemático con cupos típicos)
¿Por qué desnormalización?
- ✅ Velocidad: Evita consultas adicionales para mostrar nombres
- ✅ Consistencia: Datos históricos se mantienen aunque cambie el estudiante
- ❌ Redundancia: Duplicación controlada de datos
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"nombre": "Introducción a Python",
"descripcion": "Curso básico de programación en Python para principiantes",
"fecha": "2024-03-15",
"hora": "10:00",
"lugar": "Aula 101",
"categoria": "tecnologia",
"tipo": "curso técnico",
"instructor": "Ana García",
"rating": 4.8,
"cupo": 25,
"creado_en": "2024-01-15T10:30:00.000Z",
"actualizado_en": "2024-01-16T14:20:00.000Z",
"inscripciones": [
{
"estudiante_id": "507f1f77bcf86cd799439012",
"nombre": "Juan Pérez",
"email": "[email protected]",
"registrado_en": "2024-01-16T09:15:00.000Z"
},
{
"estudiante_id": "507f1f77bcf86cd799439013",
"nombre": "María López",
"email": "[email protected]",
"registrado_en": "2024-01-16T11:30:00.000Z"
}
]
}
{
"_id": ObjectId("507f1f77bcf86cd799439012"),
"nombre": "Juan Pérez",
"email": "[email protected]",
"hash": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPj/RK.s5uDfS",
"creado_en": "2024-01-15T08:00:00.000Z"
}
- Next.js 15 - Framework React con App Router
- TypeScript - Tipado estático
- Tailwind CSS - Framework CSS utilitario
- Radix UI - Componentes accesibles
- React Hook Form - Gestión de formularios
- Zod - Validación de esquemas
- Flask 3.0 - Framework web Python
- MongoDB - Base de datos NoSQL
- Redis - Cache y rate limiting
- PyMongo - Driver MongoDB para Python
- PyJWT - Manejo de tokens JWT
- Flask-CORS - Soporte CORS
- Flask-Limiter - Rate limiting
- Werkzeug - Utilidades de seguridad
- Docker & Docker Compose - Containerización
- Mongo Express - Interfaz web para MongoDB
- Node.js 18+ y npm/pnpm
- Docker y Docker Compose
- Python 3.9+ (para desarrollo local del backend)
- Clonar el repositorio
git clone https://github.com/Venganza-de-Python-II/Proyecto-Final
cd proyecto-final
- Levantar servicios con Docker
docker compose up -d --build
- Instalar dependencias del frontend
npm install
# o
pnpm install
- Iniciar el frontend
npm run dev
# o
pnpm dev
cd backend
python -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activate
pip install -r requirements.txt
# Configurar variables de entorno
export FLASK_ENV=development
export JWT_SECRET=tu-secreto-jwt
export ADMIN_USER=admin
export ADMIN_PASSWORD=admin123
export MONGO_URI=mongodb://admin:admin123@localhost:27017/?authSource=admin
export MONGO_DB_NAME=talleresdb
python app.py
npm install
npm run dev
FLASK_ENV=development
JWT_SECRET=supersecreto-cambiar-en-produccion
ADMIN_USER=admin
ADMIN_PASSWORD=admin123
MONGO_URI=mongodb://admin:admin123@localhost:27017/?authSource=admin
MONGO_DB_NAME=talleresdb
CORS_ORIGINS=*
NEXT_PUBLIC_API_URL=http://localhost:5001
- Frontend: http://localhost:3000
- Backend API: http://localhost:5001
- MongoDB: localhost:27017
- Redis: localhost:6379
- Mongo Express: http://localhost:8081
- Ir a http://localhost:3000
- Registrarse en
/estudiantes/registro
- Iniciar sesión en
/estudiantes/login
- Explorar talleres en
/estudiantes/talleres
- Ver inscripciones en
/estudiantes/mis-registros
- Ir a http://localhost:3000/admin/login
- Usar credenciales:
admin
/admin123
- Gestionar talleres y estudiantes desde el panel
- Administrador crea talleres con información detallada
- Estudiantes se registran en el sistema
- Estudiantes buscan y se inscriben a talleres
- Administrador monitorea inscripciones y estadísticas
- Rate Limiting: Límites por IP y endpoint para prevenir spam
- Authorization Headers: Validación de tokens JWT en formato Bearer
- Headers de Seguridad: X-Content-Type-Options, X-Frame-Options, etc.
- Validación de Tokens: Verificación de expiración e integridad
- Retry Automático: Manejo inteligente de rate limits
Endpoint | Límite | Descripción |
---|---|---|
/auth/login |
5/min | Login de administrador |
/auth/estudiantes/login |
10/min | Login de estudiante |
/auth/estudiantes/registro |
3/min | Registro de estudiante |
/workshops |
60/min | Listar talleres |
/workshops/{id} |
30/min | Ver taller específico |
/workshops/{id}/register |
10/min | Inscribirse/desinscribirse |
/stats |
20/min | Estadísticas |
/health |
60/min | Health check |
Global | 100/hora | Límite por IP |
Authorization: Bearer <jwt_token>
Content-Type: application/json
# Rate Limiting
REDIS_URL=redis://localhost:6379
RATELIMIT_DEFAULT=100 per hour
POST /auth/login
Content-Type: application/json
{
"usuario": "admin",
"contrasena": "admin123"
}
# Registro
POST /auth/estudiantes/registro
Content-Type: application/json
{
"nombre": "Juan Pérez",
"email": "[email protected]",
"contrasena": "password123"
}
# Login
POST /auth/estudiantes/login
Content-Type: application/json
{
"email": "[email protected]",
"contrasena": "password123"
}
# Perfil
GET /auth/estudiantes/me
Authorization: Bearer <token>
# Listar talleres (público)
GET /workshops?q=python&categoria=tecnologia&limit=10
# Obtener taller específico
GET /workshops/{id}
# Crear taller (admin)
POST /workshops
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"nombre": "Introducción a Python",
"descripcion": "Curso básico de Python",
"fecha": "2024-03-15",
"hora": "10:00",
"lugar": "Aula 101",
"categoria": "tecnologia",
"tipo": "curso técnico",
"instructor": "Ana García",
"cupo": 25
}
# Actualizar taller (admin)
PUT /workshops/{id}
Authorization: Bearer <admin-token>
# Eliminar taller (admin)
DELETE /workshops/{id}
Authorization: Bearer <admin-token>
# Inscribirse a taller
POST /workshops/{id}/register
Authorization: Bearer <student-token>
# Cancelar inscripción
DELETE /workshops/{id}/register
Authorization: Bearer <student-token>
# Ver mis inscripciones
GET /registrations/me
Authorization: Bearer <student-token>
# Estadísticas del sistema
GET /stats
# Categorías disponibles
GET /categories
# Estado de salud
GET /health
# Especificación OpenAPI
GET /openapi.json
proyecto-final/
├── app/ # Páginas Next.js (App Router)
│ ├── admin/ # Panel de administración
│ ├── estudiantes/ # Páginas de estudiantes
│ ├── globals.css # Estilos globales
│ ├── layout.tsx # Layout principal
│ └── page.tsx # Página de inicio
├── backend/ # API Flask
│ ├── app.py # Aplicación principal
│ ├── config.py # Configuración
│ ├── Dockerfile # Imagen Docker
│ └── requirements.txt # Dependencias Python
├── components/ # Componentes React
│ ├── ui/ # Componentes base (Radix UI)
│ ├── boton-inscripcion.tsx # Botón de inscripción
│ ├── filtros-talleres.tsx # Filtros de búsqueda
│ ├── formulario-taller.tsx # Formulario de talleres
│ ├── navbar.tsx # Barra de navegación
│ └── tabla-talleres.tsx # Tabla de talleres
├── hooks/ # Hooks personalizados
├── lib/ # Utilidades
│ ├── api.ts # Cliente API
│ └── utils.ts # Funciones utilitarias
├── public/ # Archivos estáticos
├── docker-compose.yml # Configuración Docker
├── package.json # Dependencias Node.js
├── types.ts # Tipos TypeScript
└── README.md # Este archivo
# Levantar todos los servicios
docker compose up -d --build
# Ver logs
docker compose logs -f
# Parar servicios
docker compose down
FLASK_ENV=production
JWT_SECRET=clave-super-secreta-de-produccion
ADMIN_PASSWORD=contraseña-segura
MONGO_URI=mongodb://usuario:password@host:puerto/database
CORS_ORIGINS=https://tu-dominio.com
# Construir imágenes
docker compose -f docker-compose.prod.yml build
# Desplegar
docker compose -f docker-compose.prod.yml up -d
# Configurar proxy reverso (nginx)
# Configurar SSL/TLS
# Configurar backups de MongoDB
Síntomas: Error "port already in use" o "EADDRINUSE"
Solución automática:
# Opción 1: Script de setup (incluye limpieza)
./scripts/setup.sh # Linux/Mac
scripts\setup.bat # Windows
# Opción 2: Script específico de limpieza
./scripts/cleanup-ports.sh # Linux/Mac
scripts\cleanup-ports.bat # Windows
Solución manual:
Windows:
# Ver procesos usando puertos
netstat -ano | findstr :5001
netstat -ano | findstr :3000
# Matar proceso por PID
taskkill /PID <PID> /F
Linux/Mac:
# Ver procesos usando puertos
lsof -ti:5001
lsof -ti:3000
# Matar procesos
kill -9 $(lsof -ti:5001)
kill -9 $(lsof -ti:3000)
Puertos utilizados por SkillsForge:
- 3000: Frontend Next.js
- 5001: API Flask
- 27017: MongoDB
- 6379: Redis
- 8081: Mongo Express (admin)
# Verificar que MongoDB esté corriendo
docker ps | grep mongo
# Reiniciar servicios
docker compose restart mongo api
# Ver logs de MongoDB
docker compose logs mongo
- Verificar que
CORS_ORIGINS
esté configurado correctamente - En desarrollo usar
CORS_ORIGINS=*
- En producción especificar dominios exactos
El proyecto usa el puerto 5001 por defecto. Si necesitas cambiarlo:
# docker-compose.yml
ports:
- "PUERTO_DESEADO:5000"
- Verificar que
JWT_SECRET
sea consistente - Comprobar que los tokens no hayan expirado (8 horas por defecto)
- Verificar headers de autorización:
Bearer <token>
# Ver logs del backend
docker compose logs -f api
# Ver logs de MongoDB
docker compose logs -f mongo
# Acceder al contenedor del backend
docker exec -it api-talleres bash
# Verificar base de datos
# Ir a http://localhost:8081 (Mongo Express)
El archivo next.config.mjs es la configuración principal de un proyecto Next.js, escrita en formato ECMAScript Module (.mjs).
/** @type {import('next').NextConfig} */
Esta línea es una anotación de tipo para TypeScript. Ayuda a los editores como VS Code a ofrecer autocompletado y validación al configurar Next.js.
const nextConfig = { ... }
export default nextConfig
Define y exporta la configuración para que Next.js la utilice al compilar y ejecutar el proyecto.
Este archivo define los tipos de datos utilizados en el sistema de gestión de talleres, facilitando la validación y el manejo estructurado de la información en TypeScript.
Este tipo se utiliza para almacenar y manipular los datos relacionados con cada registro.
export type Inscripcion = {
/** ID único del estudiante inscrito (opcional) */
estudiante_id?: string
/** Nombre completo del estudiante */
nombre: string
/** Correo electrónico del estudiante */
email: string
/** Fecha y hora de registro en formato ISO (opcional) */
registrado_en?: string
}
✔️ Inscripción
|
✔️ Taller
|
✔️ Estudiante
|
Este proyecto está bajo la Licencia MIT. Ver el archivo LICENSE
para más detalles.
✅ Base de Datos (MongoDB)
|
✅ Diagramas de Base de Datos
|
✅ Frontend
|
✅ Backend
|
✅ Docker
|
✅ Documentación del Código
|
✅ README.md
|
✅ Autenticación con JWT
|
- Radix UI por los componentes accesibles
- Tailwind CSS por el framework CSS
- Flask y Next.js por los frameworks base