Scope
| Tipo de organización | Acceso |
|---|
| Empresa | ✅ |
| Agencia | ✅ |
| Reclutador independiente | ✅ |
Disponible para los tres tipos de organización. Cada tipo tiene su propio proceso de validación para poder publicar.
Permisos por rol
Empresa (COMPANY)
| Acción | Disponible | Condición |
|---|
| Ver perfil público | ✅ | — |
| Editar descripción y sitio web | ✅ | — |
| Subir/eliminar imagen de perfil | ✅ | — |
| Subir/eliminar portada | ✅ | — |
| Publicar perfil | ✅ | Requiere activación completa + validación fiscal |
Agencia (AGENCY)
| Acción | Disponible | Condición |
|---|
| Ver perfil público | ✅ | — |
| Editar descripción y sitio web | ✅ | — |
| Subir/eliminar imagen de perfil | ✅ | — |
| Subir/eliminar portada | ✅ | — |
| Publicar perfil | ✅ | Requiere activación completa + validación operativa |
Reclutador independiente (RECRUITER)
| Acción | Disponible | Condición |
|---|
| Ver perfil público | ✅ | — |
| Editar descripción y sitio web | ✅ | — |
| Subir/eliminar imagen de perfil | ✅ | — |
| Subir/eliminar portada | ✅ | — |
| Publicar perfil | ✅ | Requiere activación completa + validación de identidad |
¿Qué es?
El Perfil Público es la página visible de una organización dentro de la plataforma Contratta. Funciona como un "escaparate" donde la empresa, agencia o reclutador muestra su información para atraer candidatos o socios.
Este módulo es accesible para los tres tipos de organización:
- Empresa — muestra su perfil corporativo para atraer talento
- Agencia — presenta sus servicios de reclutamiento a empresas y candidatos
- Reclutador independiente — muestra su experiencia y especialización
Cada tipo tiene su propio proceso de validación para poder publicar, pero la experiencia de edición y funcionalidades son las mismas para todos.
¿Qué puede hacer el usuario?
Gestionar su imagen
- Subir una foto de perfil (desde cámara o galería)
- Subir una imagen de portada con recorte personalizado
- Eliminar foto o portada
- Ver la foto en pantalla completa
- Escribir una descripción pública con formato enriquecido (negritas, cursivas, títulos, listas, enlaces, bloques de código, citas)
- Alternar entre modo edición y vista previa del texto
- Agregar un sitio web (con validación de URL)
Publicar/Ocultar el perfil
- Activar o desactivar la visibilidad pública del perfil
- Ver si cumple los requisitos mínimos para publicar (ej. activación completa, datos fiscales, etc.)
- Si no cumple requisitos, se muestra una tarjeta con los pasos pendientes y acceso directo a completarlos
Flujo del usuario
1. Abre "Perfil público" desde el menú lateral
2. Ve su portada, avatar y estado actual
3. Si no puede publicar → ve tarjeta de pendientes → puede ir a completar activación
4. Si puede publicar → activa el switch de publicación (con confirmación)
5. Edita la descripción con el editor rich text
6. Agrega su sitio web (solo el dominio, el https:// es fijo)
7. Presiona "Guardar" → se envía al servidor
8. Recibe confirmación visual de éxito o error localizado
Reglas de negocio
- El perfil solo se puede publicar si
canPublish == true (determinado por el backend según el tipo de organización y su estado de validación)
- La descripción se almacena como HTML en el backend
- El sitio web siempre se guarda con prefijo
https://
- Al togglear publicación se pide confirmación antes de ejecutar
- Al eliminar imágenes se pide confirmación antes de ejecutar
- Los errores del backend se interceptan y se muestran en el idioma activo (es/en)
Tipos de organización soportados
| Tipo | Endpoint de validación |
|---|
| RECRUITER | /recruiters/validation-status |
| AGENCY | /agencies/validation-status |
| COMPANY | /companies/validation-status |
El módulo adapta automáticamente el endpoint según el tipo de organización del usuario logueado.
Arquitectura
Sigue Clean Architecture con estructura feature-first:
lib/features/profile/
├── application/ # Cubit + State
├── data/
│ ├── datasources/ # Llamadas HTTP (Dio)
│ ├── models/ # Serialización JSON
│ └── repositories/ # Implementación del contrato
├── domain/
│ ├── entities/ # Entidades inmutables
│ ├── repositories/ # Contrato abstracto
│ └── usecases/ # Operaciones de negocio
└── presentation/
├── pages/ # Página principal
└── widgets/ # Componentes UI reutilizables
Flujo de datos
UI (Page) → PublicProfileCubit → UseCase → PublicProfileRepository → RemoteDatasource → API
Los errores se propagan como Failure tipados desde el datasource hasta la UI, donde se muestran con AppNotification.
Entidades
PublicProfile
| Campo | Tipo | Descripción |
|---|
| id | String | ID del perfil |
| displayName | String | Nombre visible de la organización |
| publicProfileEnabled | bool | Si el perfil está publicado |
| canPublish | bool | Si la organización cumple requisitos para publicar |
| slug | String? | Slug único para la URL pública |
| description | String? | Descripción en HTML (rich text) |
| websiteUrl | String? | URL del sitio web |
| imageUrl | String? | URL de la imagen de perfil |
| coverImageUrl | String? | URL de la imagen de portada |
ValidationStatus
| Campo | Tipo | Descripción |
|---|
| thresholds | Map<String, bool> | Umbrales de validación cumplidos |
| missingRequirements | List<MissingRequirement> | Requisitos faltantes con severidad |
UseCases
| UseCase | Parámetros | Retorno | Descripción |
|---|
| GetPublicProfileUseCase | - | PublicProfile | Obtiene el perfil actual |
| UpdatePublicProfileUseCase | publicProfileEnabled, description?, websiteUrl? | PublicProfile | Actualiza descripción y sitio web |
| TogglePublishUseCase | bool enabled | PublicProfile | Publica/despublica el perfil |
| UploadProfileImageUseCase | File, filename | void | Sube imagen de perfil |
| DeleteProfileImageUseCase | - | void | Elimina imagen de perfil |
| UploadCoverImageUseCase | File, filename | void | Sube imagen de portada |
| DeleteCoverImageUseCase | - | void | Elimina imagen de portada |
Estado (PublicProfileState)
enum PublicProfileStatus { initial, loading, success, error }
class PublicProfileState {
final PublicProfileStatus status;
final PublicProfile? profile;
final ValidationStatus? validationStatus;
final Failure? failure;
final bool isSaving;
final bool isUploadingImage;
final bool isUploadingCover;
}
Endpoints API
| Método | Endpoint | Descripción |
|---|
| GET | /public-profiles/me | Obtener perfil |
| PATCH | /public-profiles/me | Actualizar perfil |
| POST | /public-profiles/me/image | Subir imagen (multipart) |
| DELETE | /public-profiles/me/image | Eliminar imagen |
| POST | /public-profiles/me/cover | Subir portada (multipart) |
| DELETE | /public-profiles/me/cover | Eliminar portada |
| GET | /recruiters/validation-status | Estado de validación (reclutador) |
| GET | /agencies/validation-status | Estado de validación (agencia) |
| GET | /companies/validation-status | Estado de validación (empresa) |
| Widget | Responsabilidad |
|---|
ProfileCoverAndAvatar | Portada + avatar con acciones de edición |
ProfileEditSection | Sección con toggle de publicación, editor rich text y campo de sitio web |
RichTextDescriptionEditor | Editor de texto enriquecido (flutter_quill) con toolbar y preview |
WebsiteField | Campo de URL con prefijo https:// fijo y validación |
ProfileInfoBanner | Banner con info del slug/URL pública |
ProfilePendingCard | Card que muestra requisitos pendientes para publicar |
ProfileSaveButton | Botón de guardar con estado de loading |
ProfileAvatarOptionsSheet | Bottom sheet con opciones de imagen (cámara, galería, eliminar) |
CoverCropScreen | Pantalla de recorte de portada |
PublicProfileSkeleton | Skeleton loader durante la carga |
Dependencias
flutter_quill — Editor de texto enriquecido
vsc_quill_delta_to_html — Conversión Delta → HTML
flutter_quill_delta_from_html — Conversión HTML → Delta (transitiva)
image_picker — Selección de imágenes
Registro DI (get_it)
- Datasource:
LazySingleton
- Repository:
LazySingleton
- UseCases:
LazySingleton
- Cubit:
FactoryParam<String> (recibe orgType como parámetro)
getIt<PublicProfileCubit>(param1: orgType)
Manejo de errores
- Datasource — deja propagar
DioException sin catch
- Repository — captura
DioException y AppException, mapea a Failure tipados via NetworkErrorHandler
- Cubit — emite
failure en el estado
- UI —
BlocConsumer.listener muestra AppNotification.error con el mensaje localizado
Validación local de URL
El campo de sitio web valida localmente antes de enviar al backend. Si el backend devuelve un error de validación de URL, se intercepta y se muestra el mensaje localizado en vez del mensaje raw del servidor.
Localización
Todas las cadenas visibles usan context.l10n.keyName. Las claves relevantes están en lib/l10n/app_es.arb y lib/l10n/app_en.arb con prefijo profile*.