erp-core/docs/01-fase-foundation/MGN-002-users/requerimientos/RF-USER-002.md

8.4 KiB

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

  • CA-001: El usuario debe poder ver su perfil completo
  • CA-002: El usuario debe poder editar nombre y apellido
  • CA-003: El usuario debe poder editar telefono
  • CA-004: El usuario debe poder subir foto de perfil (avatar)
  • CA-005: El usuario NO debe poder cambiar su email directamente
  • CA-006: El sistema debe validar formato de telefono
  • CA-007: El sistema debe redimensionar imagenes de avatar automaticamente
  • CA-008: El perfil debe mostrar informacion de la cuenta (fecha registro, ultimo login)

Ejemplos de Verificacion

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

// 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

// Request
{
  "firstName": "Carlos",
  "lastName": "Lopez",
  "phone": "+521234567890"
}

// Response 200
{
  // ProfileResponseDto actualizado
}

Endpoint POST /api/v1/users/me/avatar

// 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

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