# 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 | - | - | [ ] |
