erp-core/docs/03-requerimientos/RF-users/RF-USER-002.md

315 lines
8.4 KiB
Markdown

# RF-USER-002: Perfil de Usuario
## Identificacion
| Campo | Valor |
|-------|-------|
| **ID** | RF-USER-002 |
| **Modulo** | MGN-002 |
| **Nombre Modulo** | Users - Gestion de Usuarios |
| **Prioridad** | P1 |
| **Complejidad** | Baja |
| **Estado** | Aprobado |
| **Autor** | System |
| **Fecha** | 2025-12-05 |
---
## Descripcion
El sistema debe permitir a cada usuario ver y editar su propio perfil, incluyendo informacion personal, foto de perfil y datos de contacto. A diferencia del CRUD de usuarios (RF-USER-001), el perfil es autoservicio - cada usuario gestiona su propia informacion.
### Contexto de Negocio
El perfil de usuario permite:
- Personalizacion de la experiencia
- Informacion de contacto actualizada
- Identidad visual mediante avatar
- Datos para notificaciones y comunicacion
---
## Criterios de Aceptacion
- [x] **CA-001:** El usuario debe poder ver su perfil completo
- [x] **CA-002:** El usuario debe poder editar nombre y apellido
- [x] **CA-003:** El usuario debe poder editar telefono
- [x] **CA-004:** El usuario debe poder subir foto de perfil (avatar)
- [x] **CA-005:** El usuario NO debe poder cambiar su email directamente
- [x] **CA-006:** El sistema debe validar formato de telefono
- [x] **CA-007:** El sistema debe redimensionar imagenes de avatar automaticamente
- [x] **CA-008:** El perfil debe mostrar informacion de la cuenta (fecha registro, ultimo login)
### Ejemplos de Verificacion
```gherkin
Scenario: Ver perfil propio
Given un usuario autenticado
When accede a GET /api/v1/users/me
Then el sistema retorna su perfil completo
And incluye firstName, lastName, email, phone, avatarUrl
And incluye createdAt, lastLoginAt
And NO incluye passwordHash ni datos sensibles
Scenario: Actualizar nombre
Given un usuario autenticado
When actualiza su nombre a "Carlos"
Then el sistema guarda el cambio
And updated_by se establece con su propio ID
And responde con el perfil actualizado
Scenario: Subir avatar
Given un usuario autenticado
And una imagen JPG de 2MB
When sube la imagen como avatar
Then el sistema redimensiona a 200x200 px
And genera thumbnail de 50x50 px
And almacena en storage (S3/local)
And actualiza avatarUrl en el usuario
Scenario: Imagen muy grande
Given un usuario autenticado
And una imagen de 15MB
When intenta subir como avatar
Then el sistema responde con status 400
And el mensaje es "Imagen excede tamaño maximo (10MB)"
```
---
## Reglas de Negocio
| ID | Regla | Validacion |
|----|-------|------------|
| RN-001 | Solo el propio usuario edita su perfil | user.id == request.userId |
| RN-002 | Email no editable desde perfil | Campo readonly |
| RN-003 | Avatar max 10MB | File size validation |
| RN-004 | Formatos permitidos: JPG, PNG, WebP | MIME type check |
| RN-005 | Avatar redimensionado a 200x200 | Image processing |
| RN-006 | Telefono formato E.164 | Regex +[0-9]{10,15} |
| RN-007 | Nombre min 2, max 100 caracteres | String validation |
### Campos Editables vs No Editables
| Campo | Editable | Notas |
|-------|----------|-------|
| firstName | Si | Min 2 chars |
| lastName | Si | Min 2 chars |
| phone | Si | Formato E.164 |
| avatarUrl | Si | Via upload |
| email | No | Requiere proceso separado |
| status | No | Solo admin |
| roles | No | Solo admin |
| tenantId | No | Inmutable |
---
## Impacto en Capas
### Database
| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Tabla | usar | `users` - ya existe |
| Columna | agregar | `avatar_url` VARCHAR(500) |
| Columna | agregar | `avatar_thumbnail_url` VARCHAR(500) |
| Tabla | crear | `user_avatars` - historial de avatares |
### Backend
| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Controller | agregar | `UsersController.getProfile()` |
| Controller | agregar | `UsersController.updateProfile()` |
| Controller | agregar | `UsersController.uploadAvatar()` |
| Method | crear | `UsersService.getProfile()` |
| Method | crear | `UsersService.updateProfile()` |
| Method | crear | `AvatarService.upload()` |
| Method | crear | `AvatarService.resize()` |
| DTO | crear | `UpdateProfileDto` |
| DTO | crear | `ProfileResponseDto` |
| Endpoint | crear | `GET /api/v1/users/me` |
| Endpoint | crear | `PATCH /api/v1/users/me` |
| Endpoint | crear | `POST /api/v1/users/me/avatar` |
| Endpoint | crear | `DELETE /api/v1/users/me/avatar` |
### Frontend
| Elemento | Accion | Descripcion |
|----------|--------|-------------|
| Pagina | crear | `ProfilePage` |
| Componente | crear | `ProfileForm` |
| Componente | crear | `AvatarUploader` |
| Componente | crear | `AvatarCropper` |
| Service | crear | `profileService` |
---
## Dependencias
### Depende de (Bloqueantes)
| ID | Requerimiento | Estado |
|----|---------------|--------|
| RF-USER-001 | CRUD Usuarios | Tabla users |
| RF-AUTH-001 | Login | Autenticacion |
### Dependencias Externas
| Servicio | Descripcion |
|----------|-------------|
| Storage | S3, MinIO o filesystem para avatares |
| Image Processing | Sharp o similar para resize |
---
## Especificaciones Tecnicas
### Endpoint GET /api/v1/users/me
```typescript
// Response 200
{
"id": "uuid",
"email": "user@example.com",
"firstName": "Juan",
"lastName": "Perez",
"phone": "+521234567890",
"avatarUrl": "https://storage.erp.com/avatars/uuid-200.jpg",
"avatarThumbnailUrl": "https://storage.erp.com/avatars/uuid-50.jpg",
"status": "active",
"emailVerifiedAt": "2025-01-01T00:00:00Z",
"lastLoginAt": "2025-12-05T10:30:00Z",
"createdAt": "2025-01-01T00:00:00Z",
"tenant": {
"id": "tenant-uuid",
"name": "Empresa XYZ"
},
"roles": [
{ "id": "role-uuid", "name": "admin" }
]
}
```
### Endpoint PATCH /api/v1/users/me
```typescript
// Request
{
"firstName": "Carlos",
"lastName": "Lopez",
"phone": "+521234567890"
}
// Response 200
{
// ProfileResponseDto actualizado
}
```
### Endpoint POST /api/v1/users/me/avatar
```typescript
// Request
// Content-Type: multipart/form-data
// Field: avatar (file)
// Response 200
{
"avatarUrl": "https://storage.erp.com/avatars/uuid-200.jpg",
"avatarThumbnailUrl": "https://storage.erp.com/avatars/uuid-50.jpg"
}
```
### Procesamiento de Avatar
```typescript
// avatar.service.ts
async uploadAvatar(userId: string, file: Express.Multer.File): Promise<AvatarUrls> {
// 1. Validar archivo
this.validateFile(file); // size, mime type
// 2. Generar nombres unicos
const filename = `${userId}-${Date.now()}`;
// 3. Procesar imagen
const mainBuffer = await sharp(file.buffer)
.resize(200, 200, { fit: 'cover' })
.jpeg({ quality: 85 })
.toBuffer();
const thumbBuffer = await sharp(file.buffer)
.resize(50, 50, { fit: 'cover' })
.jpeg({ quality: 80 })
.toBuffer();
// 4. Subir a storage
const mainUrl = await this.storage.upload(`avatars/${filename}-200.jpg`, mainBuffer);
const thumbUrl = await this.storage.upload(`avatars/${filename}-50.jpg`, thumbBuffer);
// 5. Eliminar avatar anterior (opcional)
await this.deleteOldAvatar(userId);
// 6. Actualizar usuario
await this.usersRepository.update(userId, {
avatarUrl: mainUrl,
avatarThumbnailUrl: thumbUrl,
});
return { avatarUrl: mainUrl, avatarThumbnailUrl: thumbUrl };
}
```
---
## Datos de Prueba
| Escenario | Entrada | Resultado |
|-----------|---------|-----------|
| Ver perfil | GET /users/me | 200, perfil completo |
| Actualizar nombre | firstName: "Carlos" | 200, actualizado |
| Telefono invalido | phone: "123" | 400, "Formato invalido" |
| Avatar JPG valido | imagen 1MB | 200, URLs generadas |
| Avatar muy grande | imagen 15MB | 400, "Excede limite" |
| Avatar formato invalido | archivo .pdf | 400, "Formato no permitido" |
| Eliminar avatar | DELETE /users/me/avatar | 200, avatar eliminado |
---
## Estimacion
| Capa | Story Points | Notas |
|------|--------------|-------|
| Database | 1 | Columnas avatar |
| Backend | 3 | Profile endpoints + avatar |
| Frontend | 3 | Profile page + avatar uploader |
| **Total** | **7** | |
---
## Notas Adicionales
- Considerar CDN para servir avatares
- Implementar cache de avatares
- Avatar por defecto basado en iniciales (fallback)
- Considerar gravatar como fallback
- Rate limiting en upload de avatares
---
## Historial de Cambios
| Version | Fecha | Autor | Cambios |
|---------|-------|-------|---------|
| 1.0 | 2025-12-05 | System | Creacion inicial |
---
## Aprobaciones
| Rol | Nombre | Fecha | Firma |
|-----|--------|-------|-------|
| Analista | System | 2025-12-05 | [x] |
| Tech Lead | - | - | [ ] |
| Product Owner | - | - | [ ] |