Usuario de plataforma streaming/cine
API RESTful construida con NestJS, MongoDB y arquitectura DDD (Domain-Driven Design) que separa la autenticación (User) del dominio de negocio (Profiles).
- Framework: NestJS
- Base de datos: MongoDB con Mongoose
- Autenticación: JWT (JSON Web Tokens)
- Validación: class-validator, class-transformer
- Documentación: Swagger/OpenAPI
- Rate Limiting: @nestjs/throttler
- Upload de archivos: Multer + Sharp (thumbnails)
Separación de dominios:
- Dominio de Autenticación:
User(email, password, role) - Dominio de Negocio:
Profiles(datos específicos de cada rol)
Factory Pattern: El servicio de autenticación crea automáticamente el Profile correspondiente según el rol del usuario durante el registro.
- USUARIO
- ADMIN
- UsuarioProfile (rol: USUARIO)
- Pelicula
- Licencia
- Entrada
- Suscripcion
- Node.js 18+
- npm o yarn
- MongoDB 4.4+
- Clonar el repositorio
git clone <repository-url>
cd cineplus-api- Instalar dependencias
npm install- Configurar variables de entorno
Crear archivo .env en la raíz del proyecto:
# MongoDB
MONGODB_URI=mongodb://localhost:27017/cineplus_db
# JWT
JWT_SECRET=tu_secreto_super_seguro_aqui_cambiar_en_produccion
JWT_EXPIRES_IN=7d
# Puerto
PORT=3010
# Node Environment
NODE_ENV=developmentPara producción, crear .env.production:
MONGODB_URI=mongodb+srv://usuario:password@cluster.mongodb.net/cineplus_db
JWT_SECRET=otro_secreto_diferente_para_produccion
JWT_EXPIRES_IN=7d
PORT=3010
NODE_ENV=production- Compilar el proyecto
npm run buildnpm run start:devnpm run build
npm run start:prodEl servidor estará disponible en: http://localhost:3010
Una vez iniciado el servidor, accede a la documentación interactiva:
URL: http://localhost:3010/api
Swagger proporciona:
- Lista completa de endpoints
- Modelos de datos
- Posibilidad de probar endpoints directamente
- Ejemplos de requests y responses
POST /api/auth/register
Content-Type: application/json
{
"email": "usuario@example.com",
"password": "password123",
"role": "USUARIO",
"nombre": "Juan Pérez",
"telefono": "+51 987654321"
}Respuesta exitosa:
{
"user": {
"_id": "507f1f77bcf86cd799439011",
"email": "usuario@example.com",
"role": "USUARIO",
"isActive": true,
"emailVerified": false,
"createdAt": "2024-01-01T00:00:00.000Z"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Roles disponibles para registro:
USUARIO
POST /api/auth/login
Content-Type: application/json
{
"email": "usuario@example.com",
"password": "password123"
}Respuesta exitosa:
{
"user": {
"_id": "507f1f77bcf86cd799439011",
"email": "usuario@example.com",
"role": "USUARIO"
},
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}GET /api/auth/profile
Authorization: Bearer {access_token}Authorization: Bearer {access_token}Rol asociado: USUARIO
Endpoints disponibles:
GET /api/usuario-profile/me
Authorization: Bearer {token}Respuesta:
{
"_id": "507f1f77bcf86cd799439011",
"user": "507f1f77bcf86cd799439012",
"nombre": "Valor de ejemplo",
"telefono": "Valor de ejemplo",
"preferencias": [
"ejemplo1",
"ejemplo2"
]
,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}PUT /api/usuario-profile/me
Authorization: Bearer {token}
Content-Type: application/json
{
"nombre": "Valor de ejemplo",
"telefono": "Valor de ejemplo",
"preferencias": [
"ejemplo1",
"ejemplo2"
]
}GET /api/usuario-profile
Authorization: Bearer {token_admin}GET /api/usuario-profile/{userId}
Authorization: Bearer {token_admin}La imagen NO es requerida al crear un pelicula. Puedes:
- ✅ Crear el pelicula SIN imagen
- ✅ Agregar la imagen DESPUÉS usando el endpoint de upload
POST /api/pelicula
Authorization: Bearer {{token}}
Content-Type: application/json
{
"titulo": "El Padrino",
"director": "Francis Ford Coppola",
"anio": 1972,
"duracion": 175,
"genero": "Drama"
}Respuesta exitosa:
{
"success": true,
"message": "Pelicula creado exitosamente",
"data": {
"_id": "507f1f77bcf86cd799439011",
"nombre": "El Padrino",
"imagen": null,
"imagenThumbnail": null,
...
}
}💡 Nota: Guarda el _id del pelicula creado, lo necesitarás para subir la imagen.
POST /api/pelicula/507f1f77bcf86cd799439011/upload-image
Authorization: Bearer {{token}}
Content-Type: multipart/form-data
file: [imagen.jpg]Respuesta exitosa:
{
"success": true,
"message": "Imagen subida y asociada exitosamente",
"data": {
"pelicula": {
"_id": "507f1f77bcf86cd799439011",
"nombre": "El Padrino",
"imagen": "uploads/1700000000000-imagen.jpg",
"imagenThumbnail": "uploads/thumbnails/thumb-1700000000000-imagen.jpg"
},
"upload": {
"url": "uploads/1700000000000-imagen.jpg",
"thumbnailUrl": "uploads/thumbnails/thumb-1700000000000-imagen.jpg"
}
}
}- Abrir la colección de Postman del proyecto
- Ir a:
Auth→Login - Ejecutar el login y copiar el
access_token - Ir a:
Pelicula→Create Pelicula - Configurar el token en Headers:
Authorization: Bearer {{access_token}} - En el Body (JSON): Pegar el siguiente JSON:
{
"titulo": "El Padrino", "director": "Francis Ford Coppola", "anio": 1972, "duracion": 175, "genero": "Drama" }
7. **Enviar** la petición
8. **Copiar** el `_id` del pelicula creado
---
#### Paso 2: Subir Imagen
1. **Ir a:** `Upload por Entidad` → `Upload Pelicula Image`
2. **Reemplazar** `{{id}}` en la URL con el ID copiado:
http://localhost:3010/api/pelicula/507f1f77bcf86cd799439011/upload-image
3. **Configurar** Headers:
Authorization: Bearer {{access_token}}
4. **En el Body:**
- Seleccionar tipo: `form-data`
- Agregar key: `file`
- Tipo: `File`
- Seleccionar tu imagen (JPG, PNG, etc.)
5. **Enviar** la petición
**✅ ¡Listo!** Tu pelicula ahora tiene imagen.
---
### 🖼️ Verificar la Imagen
Una vez subida, puedes ver la imagen en el navegador:
**Imagen original:**
http://localhost:3010/uploads/1700000000000-imagen.jpg
**Thumbnail (miniatura):**
http://localhost:3010/uploads/thumbnails/thumb-1700000000000-imagen.jpg
---
### 💻 Ejemplo Completo con curl
```bash
# 1. Login para obtener token
TOKEN=$(curl -X POST http://localhost:3010/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"productor@example.com","password":"password123"}' \
| jq -r '.access_token')
# 2. Crear Pelicula
PRODUCTO_ID=$(curl -X POST http://localhost:3010/api/pelicula \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "titulo": "El Padrino", "director": "Francis Ford Coppola", "anio": 1972, "duracion": 175, "genero": "Drama" }' \
| jq -r '.data._id')
echo "Pelicula creado con ID: $PRODUCTO_ID"
# 3. Subir imagen
curl -X POST http://localhost:3010/api/pelicula/$PRODUCTO_ID/upload-image \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/ruta/a/tu/imagen.jpg"
echo "✅ Imagen subida exitosamente!"
| Característica | Detalle |
|---|---|
| Formatos soportados | JPG, JPEG, PNG, GIF, WEBP |
| Tamaño máximo | 5 MB por imagen |
| Thumbnail | Se genera automáticamente (200x200px) |
| Ubicación | /uploads/ para originales, /uploads/thumbnails/ para thumbnails |
| Permisos | Solo usuarios autenticados (según rol) |
Causa: No se envió el archivo o el campo no se llama file
Solución:
- En Postman: Asegúrate de que el key sea exactamente
file - En curl: Verifica que uses
-F "file=@/ruta/imagen.jpg"
Causa: El archivo no es una imagen válida
Solución:
- Verifica que el archivo sea JPG, PNG, GIF o WEBP
- Verifica que el archivo no esté corrupto
Causa: Token JWT inválido o expirado
Solución:
- Haz login nuevamente
- Copia el nuevo access_token
- Actualiza el header Authorization
Causa: La ruta de la imagen es incorrecta o el servidor no está sirviendo archivos estáticos
Solución:
- Verifica que el servidor esté corriendo
- Verifica que la URL sea exactamente la devuelta por el endpoint de upload
- La URL debe empezar con
http://localhost:3010/uploads/
El API implementa rate limiting para proteger contra abuso:
- short: 3 requests por segundo
- medium: 20 requests por 10 segundos
- long: 100 requests por minuto
Si excedes el límite, recibirás un error 429 Too Many Requests.
Después de login o registro, recibirás un access_token. Úsalo en las peticiones que requieren autenticación:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Todos los endpoints excepto /auth/register y /auth/login requieren autenticación.
- Usuario autenticado: Puede acceder a sus propios datos (endpoints
/me) - ADMIN: Puede acceder a todos los datos del sistema
curl -X POST http://localhost:3010/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "cliente@example.com",
"password": "password123",
"role": "USUARIO",
"nombre": "Juan Pérez",
"telefono": "+51 987654321"
}'curl -X POST http://localhost:3010/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "cliente@example.com",
"password": "password123"
}'Copiar el access_token de la respuesta.
curl -X GET http://localhost:3010/api/usuario-profile/me \
-H "Authorization: Bearer {access_token}"curl -X PUT http://localhost:3010/api/usuario-profile/me \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"telefono": "+51 999888777",
"direccion": "Nueva dirección"
}'Importa la colección de Postman incluida en el proyecto:
Archivo: cineplus-api.postman_collection.json
La colección incluye:
- Todos los endpoints de Auth
- Todos los endpoints de Profiles
- Endpoints de Upload
- Variables de entorno preconfiguradas
- Ejemplos de requests
# Tests unitarios
npm run test
# Tests e2e
npm run test:e2e
# Coverage
npm run test:covcineplus-api/
├── src/
│ ├── auth/ # Módulo de autenticación
│ │ ├── dto/ # DTOs (register, login)
│ │ ├── schemas/ # Schema de User
│ │ ├── guards/ # Guards JWT y Roles
│ │ ├── decorators/ # Decoradores personalizados
│ │ └── auth.service.ts # Lógica de autenticación
│ │
│ ├── usuario-profile/ # Profile USUARIO
│ │ ├── dto/
│ │ ├── schemas/
│ │ ├── usuario-profile.controller.ts
│ │ ├── usuario-profile.service.ts
│ │ └── usuario-profile.module.ts
│ │
│ ├── upload/ # Módulo de uploads
│ ├── app.module.ts # Módulo principal
│ └── main.ts # Entry point
│
├── uploads/ # Imágenes subidas
│ └── thumbnails/ # Thumbnails generados
│
├── .env # Variables de entorno (development)
├── .env.production # Variables de entorno (production)
├── cineplus-api.postman_collection.json
└── package.json
Error: MongooseError: The 'uri' parameter to 'openUri()' must be a string
Solución: Verifica que la variable MONGODB_URI esté configurada en .env
Error: EADDRINUSE: address already in use :::3000
Solución: Cambia el puerto en .env o detén el proceso que está usando el puerto
Error: 401 Unauthorized
Solución: Verifica que el token esté bien formado y no haya expirado. Genera uno nuevo haciendo login.
Error: 400 Bad Request - validation failed
Solución: Revisa que todos los campos requeridos estén presentes y tengan el formato correcto. Consulta Swagger para ver los campos requeridos.
El sistema crea automáticamente un usuario ADMIN al iniciar:
Email: admin@sistema.com
Password: Admin123456
Role: ADMIN
MONGODB_URI=<mongodb_connection_string>
JWT_SECRET=<secret_key>
JWT_EXPIRES_IN=7d
PORT=3010
NODE_ENV=production- Crear nuevo proyecto en Railway
- Conectar repositorio
- Agregar MongoDB (Add Plugin → MongoDB)
- Configurar variables de entorno
- Deploy automático
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3010
CMD ["npm", "run", "start:prod"]Para contribuir al proyecto:
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/nueva-funcionalidad) - Commit tus cambios (
git commit -m 'Agregar nueva funcionalidad') - Push a la rama (
git push origin feature/nueva-funcionalidad) - Abre un Pull Request
Este proyecto es parte de un ejercicio académico.
Para preguntas o soporte, contacta al equipo de desarrollo.
Generado: 2025-11-22 Version: 1.0.0 Framework: NestJS Base de datos: MongoDB