Módulo: Invitaciones
Scope
| Tipo de organización | Acceso |
|---|---|
| Empresa | ✅ |
| Agencia | ✅ |
| Reclutador independiente | ❌ |
Permisos por rol
| Acción | Disponible | Permiso |
|---|---|---|
| Ver invitaciones | ✅ | memberships:read |
| Crear invitación | ✅ | memberships:invite |
| Revocar invitación | ✅ | memberships:revoke-invite |
| Reenviar invitación | ✅ | memberships:invite |
¿Qué es?
Gestión de invitaciones pendientes de la organización. Permite ver todas las invitaciones enviadas, filtrar por estado, revocar invitaciones pendientes, y reenviar el email de invitación.
¿Qué puede hacer el usuario?
- Ver lista de invitaciones con filtro por estado (chips)
- Filtrar: Todas, Pendientes, Aceptadas, Revocadas, Expiradas
- Revocar invitaciones pendientes (con motivo opcional)
- Reenviar invitaciones pendientes (genera nuevo token + email)
- Pull-to-refresh para actualizar la lista
Flujo del usuario
Listar invitaciones
1. Abre "Equipo" → tab "Invitaciones"
2. Ve lista con email, rol, estado y fechas
3. Filtra con chips de estado
4. Pull-to-refresh para actualizar
Revocar invitación
1. En una invitación PENDIENTE, presiona menú ⋮
2. Selecciona "Revocar"
3. Se muestra diálogo con campo de motivo (opcional)
4. Confirma → invitación revocada, enlace deja de funcionar
5. Lista se recarga automáticamente
Reenviar invitación
1. En una invitación PENDIENTE, presiona menú ⋮
2. Selecciona "Reenviar"
3. El API genera nuevo token, actualiza expiración, y envía nuevo email
4. Snackbar confirma el reenvío
Reglas de negocio
- Solo invitaciones con status PENDING pueden revocarse o reenviarse
- Revocar requiere confirmación con motivo opcional
- Reenviar genera un token completamente nuevo (el anterior se invalida)
- La expiración se renueva al reenviar (7 días desde el momento)
- Las invitaciones EXPIRED se marcan automáticamente por el backend
- El token de invitación nunca se almacena en texto plano (solo hash SHA-256)
- El email de invitación incluye un link con el token para aceptar
Estados de invitación
| Estado | Descripción | Acciones disponibles |
|---|---|---|
| PENDING | Esperando aceptación | Revocar, Reenviar |
| ACCEPTED | Miembro creado | Ninguna |
| REVOKED | Enlace invalidado | Ninguna |
| EXPIRED | Pasó fecha de expiración | Ninguna |
Arquitectura
lib/features/team/
├── application/
│ ├── invitations_cubit.dart # loadInvitations, createInvitation, revokeInvitation, resendInvitation
│ └── invitations_state.dart # InvitationsStatus enum, invitations list, statusFilter
├── data/
│ ├── datasources/
│ │ └── team_remote_datasource.dart # listInvitations, createInvitation, revokeInvitation, resendInvitation
│ ├── models/
│ │ └── invitation_model.dart # InvitationModel, InvitationsResponseModel
│ └── repositories/
│ └── team_repository_impl.dart
├── domain/
│ ├── entities/
│ │ └── invitation.dart # Invitation (Equatable) con helpers isPending, isAccepted, etc.
│ └── repositories/
│ └── team_repository.dart
└── presentation/
└── tabs/
└── invitations_tab.dart # BlocProvider + BlocBuilder, filter chips, revoke dialog
Endpoints API
| Método | Endpoint | Permiso | Descripción |
|---|---|---|---|
| GET | /memberships/invitations | memberships:read | Lista invitaciones (paginado, status filter) |
| POST | /memberships/invitations | memberships:invite | Crear invitación (email + roleId) |
| PATCH | /memberships/invitations/:id/revoke | memberships:revoke-invite | Revocar invitación (reason?) |
| POST | /memberships/invitations/:id/resend | memberships:invite | Reenviar invitación |
Respuesta de invitación
{
"id": "uuid",
"organizationId": "uuid",
"email": "ana@example.com",
"status": "PENDING",
"role": { "id": "uuid", "key": "recruiter", "name": "Recruiter", ... },
"invitedByUserId": "uuid",
"expiresAt": "2026-05-18T16:00:00.000Z",
"acceptUrl": "https://portal.contratta.mx/es/invitations/accept?token=...",
"createdAt": "2026-05-11T16:00:00.000Z"
}
Registro DI
- Cubit:
Factory(nueva instancia por pantalla)
getIt<InvitationsCubit>()
Localización
✅ Completa — claves con prefijo invitations en ambos ARB:
- Filtros:
invitationsFilterAll,invitationsFilterPending,invitationsFilterAccepted,invitationsFilterRevoked,invitationsFilterExpired - Status:
invitationsStatusPending,invitationsStatusAccepted,invitationsStatusExpired,invitationsStatusRevoked - Acciones:
invitationsResendButton,invitationsRevokeButton,invitationsRevoked,invitationsResent - Diálogo:
invitationsRevokeTitle,invitationsRevokeMessage,invitationsRevokeReasonHint - Empty states:
invitationsEmpty,invitationsEmptyMessage,invitationsNoResults,invitationsNoFilterResults - Parametrizado:
invitationsExpires({date})