Skip to content

Máquinas de estado ​

Documentación consolidada de los tres FSM (finite-state machines) que rigen la plataforma. Toda transición pasa por validación del servicio correspondiente.

Vacancy ​

Enum: App\Enums\VacancyStateServicio: App\Services\VacancyStateMachine

borrador
   │
   ▼
activa
   │
   ▼
en_busqueda
   │
   ▼
con_candidatos_asignados
   │
   ▼
entrevistas_en_curso
   │
   ▼
finalista_seleccionado
   │
   ▼
cubierta  (terminal)

[Desde cualquier no-terminal] → cancelada  (terminal)

Transiciones permitidas ​

FromTo permitidos
borradoractiva, cancelada
activaen_busqueda, cancelada
en_busquedacon_candidatos_asignados, cancelada
con_candidatos_asignadosentrevistas_en_curso, cancelada
entrevistas_en_cursofinalista_seleccionado, cancelada
finalista_seleccionadocubierta, cancelada
cubierta(ninguna, terminal)
cancelada(ninguna, terminal)

Significado de cada estado ​

EstadoDescripción
borradorCompany_user está llenando la vacante, aún no publicada
activaPublicada; visible a recruiters HUMAE
en_busquedaRecruiter asignado activamente buscando candidatos
con_candidatos_asignadosHay ≥ 1 VacancyAssignment en la vacante
entrevistas_en_cursoAl menos una entrevista agendada
finalista_seleccionadoHay al menos 1 asignación en stage finalist
cubiertaContratación completada; vacante cerrada exitosamente
canceladaCerrada sin contratación

VacancyAssignment ​

Enum: App\Enums\AssignmentStageServicio: App\Services\AssignmentStageMachine

sourced  ⇄  presented  ⇄  interviewing  ⇄  finalist  ──▶  hired (terminal)
   │             │              │              │
   └─────────────┴──────────────┴──────────────┴───▶  [rejected | withdrawn] (terminales)

Las flechas dobles indican que se puede retroceder un paso entre etapas activas para corregir movimientos erróneos. No se puede retroceder más de un paso ni desde una terminal.

Transiciones ​

FromTo permitidos
sourcedpresented, rejected, withdrawn
presentedsourced, interviewing, rejected, withdrawn
interviewingpresented, finalist, rejected, withdrawn
finalistinterviewing, hired, rejected, withdrawn
hired, rejected, withdrawn(terminales)

Timestamps ​

Cada transición guarda un timestamp específico:

StageCampoSeteo
sourced—(initial)
presentedpresented_atnow()
interviewinginterviewed_atnow()
finalist—(sin timestamp propio)
hiredhired_atnow()
rejectedrejected_atnow()
withdrawnwithdrawn_atnow()

Implementado en AssignmentStageMachine::timestampField().

Retroceder no resetea timestamps

Cuando una asignación retrocede (ej. presented → sourced), los timestamps ya guardados se preservan. presented_at significa "primera vez que llegó a presentado", aunque la asignación esté ahora de vuelta en sourced. Esto mantiene la auditoría intacta.

Significado ​

StageQuién
sourcedRecruiter acaba de asignar al candidato; aún no se muestra a la empresa
presentedRecruiter envía formalmente al candidato a la empresa
interviewingHay entrevistas agendadas/en curso
finalistEntrevistas concluidas, finalista pendiente de confirmar
hiredContratado
rejectedDescartado (con rejection_reason)
withdrawnRetirado voluntariamente o automáticamente

Interview ​

Enum: App\Enums\InterviewStateServicio: App\Services\InterviewStateMachine

propuesta ──┬──▶ confirmada ──┬──▶ realizada   (terminal)
            │                 │
            │                 ├──▶ no_asisto   (terminal)
            │                 │
            │                 ├──▶ reprogramada
            │                 │
            │                 └──▶ cancelada   (terminal)
            │
            ├──▶ reprogramada
            │       │
            │       └──▶ propuesta (loop con nueva fecha)
            │
            └──▶ cancelada   (terminal)

Transiciones ​

FromTo permitidos
propuestaconfirmada, reprogramada, cancelada
confirmadarealizada, reprogramada, cancelada, no_asisto
reprogramadapropuesta (con nueva fecha), cancelada
realizada(terminal)
cancelada(terminal)
no_asisto(terminal)

Significado ​

EstadoQuién puede moverla ahí
propuestaRecruiter agenda
confirmadaCualquiera de las partes confirma
reprogramadaHay nueva fecha pendiente de confirmar
realizadaRecruiter marca tras finalizar + feedback
canceladaCualquiera cancela
no_asistoRecruiter marca si el candidato no llegó

Candidate ​

Enum: App\Enums\CandidateState

El estado del candidato no usa FSM estricta (es informativo; varios flujos lo mueven). Valores:

EstadoCuándo se usa
sin_perfilUsuario recién registrado, no tocó el perfil
en_registroPerfil en progreso
activoPerfil completo, membresía activa, aparece en directorio
en_procesoTiene asignaciones activas
presentado_empresaHay al menos 1 asignación en presented
entrevistadoTiene entrevistas confirmada o realizada
contratadoAsignación hired
rechazadoMuchos rechazos consecutivos (opcional, manual)
retiradoCandidato decidió pausar
inactivoMembresía expirada

El frontend filtra visibilidad en directorio con los estados: {activo, en_proceso, presentado_empresa, entrevistado}.

Estado del usuario (UserStatus) ​

Enum: App\Enums\UserStatus

Atributo de cuenta — no FSM. Define si un User puede iniciar sesión y aparecer como operativo. Se persiste en users.status.

ValorCuándo se usa
activeCuenta operativa. Login OK si además email_verified_at no es nulo.
pending_approvalCuenta auto-registrada (reclutador o empresa) esperando que un admin la apruebe. Login devuelve 403 errors.code=pending_approval.
suspendedBloqueado manualmente por un admin (vía /admin/usuarios). Login devuelve 403 errors.code=account_inactive.
inactiveCuenta dada de baja o rechazada por un admin tras un auto-registro. Login devuelve 403 errors.code=account_inactive.

Los candidatos creados por POST /auth/register quedan directamente en active (sólo necesitan verify-email). Los reclutadores y empresas creados por POST /auth/register/recruiter y POST /auth/register/company quedan en pending_approval hasta que un admin los aprueba o rechaza desde Gestión de usuarios →.

Categoría del candidato (CandidateKind) ​

Enum: App\Enums\CandidateKind

No es un estado de FSM, sino un atributo categórico que el candidato declara en su perfil (PDF cosasfaltanteshumae punto 2). Se persiste en candidate_profiles.candidate_kind.

ValorEtiqueta UI
employeeEmpleado
internPracticante

Si el candidato no marcó nada, queda null y no pasa los filtros estrictos por categoría del directorio.

Tipo de candidato requerido (VacancyTargetKind) ​

Enum: App\Enums\VacancyTargetKind

Atributo categórico de la vacante (PDF cosasfaltanteshumae · clasificación de vacantes). Se persiste en vacancies.target_candidate_kind con default any.

ValorEtiqueta UIComportamiento en matching
employeeEmpleadoexige candidate_kind = employee (25 pts en eje categoría si match)
internPracticanteexige candidate_kind = intern (25 pts si match)
anyCualquiera (default)acepta ambos; 60 % parcial (15 pts) en eje categoría

Detalles del scoring: MatchingService.

Implementación de una FSM ​

Cada servicio tiene la misma firma:

php
class AssignmentStageMachine
{
    public static function graph(): array { ... }
    public static function allowedFrom(Stage $from): array { ... }
    public static function canTransition(Stage $from, Stage $to): bool { ... }
    public static function allowedValuesFrom(Stage $from): array { ... }
}

Uso en controllers ​

php
$newStage = AssignmentStage::from($request->input('stage'));
$current = $assignment->stage;

if (! AssignmentStageMachine::canTransition($current, $newStage)) {
    return $this->error('Transición no permitida', status: 422);
}

Uso en frontend ​

El backend expone allowed_transitions en el recurso para que el frontend sepa qué botones mostrar:

json
{
  "id": 42,
  "stage": "presented",
  "allowed_transitions": ["interviewing", "rejected", "withdrawn"]
}

El frontend solo renderiza esos botones. Si se envía otro, el backend rechaza.

Tests ​

Cada FSM tiene tests unitarios en tests/Unit/Services/*StateMachineTest.php:

  • Happy path completo
  • Rejection / withdrawal desde todas las etapas
  • Bloqueo de saltos de etapa
  • Terminal states no tienen salidas

Siguiente ​

Permisos y autorización consolidados: Matriz de roles y permisos →

Manual de usuario HUMAE · Uso interno