Skip to content

Gestión de entrevistas (recruiter)

El recruiter es quien agenda, confirma y cierra entrevistas entre candidatos y empresas. Es una capa de coordinación crítica.

Agendar una entrevista

Desde el pipeline

Dentro de una tarjeta de asignación en etapa presented o interviewing, el botón "Agendar entrevista" abre ScheduleInterviewDialog.

Endpoint: POST /api/v1/vacancies/{vid}/assignments/{aid}/interviews

Body:

json
{
  "title": "Primera entrevista técnica",
  "scheduled_at": "2026-04-22T10:00:00-06:00",
  "duration_minutes": 60,
  "timezone": "America/Mexico_City",
  "mode": "online",
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "meeting_provider": "google_meet",
  "meeting_id": "abc-defg-hij",
  "round": 1
}

Validaciones

  • scheduled_at debe estar en el futuro.
  • duration_minutes entre 15 y 480.
  • mode{online, presencial, telefono}.
  • Si mode = online, meeting_url requerido.
  • Si mode = presencial, location requerido.

Qué hace el backend

  1. Verifica que la vacante esté en estado que admita entrevistas (con_candidatos_asignados, entrevistas_en_curso).
  2. Crea la entrevista con state = propuesta.
  3. Calcula round automáticamente si no se provee: max(round existente) + 1.
  4. Dentro de una transacción, si la vacante estaba en con_candidatos_asignados, la mueve a entrevistas_en_curso.
  5. Dispara InterviewScheduledNotification a:
    • El candidato
    • Los owners/managers de la empresa (miembros con rol interno owner o manager)
  6. Responde 201.

Código: App\Services\InterviewService::schedule().

Modificar una entrevista

Confirmar

Endpoint: POST /api/v1/interviews/{id}/confirm

  • Solo desde propuesta.
  • Cambia a confirmada.
  • Idempotente: si ya estaba confirmada, no hace nada.
  • Dispara InterviewConfirmedNotification.

Reprogramar

Endpoint: POST /api/v1/interviews/{id}/reschedule

json
{
  "new_scheduled_at": "2026-04-24T14:00:00-06:00",
  "reason": "Conflicto en agenda del entrevistador",
  "duration_minutes": 45,
  "meeting_url": "https://meet.google.com/new-link"
}
  • Disponible desde propuesta o confirmada.
  • Crea un registro en interview_reschedules con:
    • requested_by (el recruiter)
    • previous_scheduled_at
    • new_scheduled_at
    • reason
  • Actualiza la entrevista: scheduled_at = new_scheduled_at, state = propuesta, y los otros campos opcionales.
  • Notifica a todas las partes.

Este flujo permite iteraciones: candidato propone una nueva fecha, recruiter re-agenda, empresa confirma, etc. El histórico queda completo.

Cancelar

Endpoint: POST /api/v1/interviews/{id}/cancelBody: {"reason": "motivo"}.

  • Disponible desde cualquier no-terminal.
  • Cambia a cancelada.
  • Prepend [cancelado] motivo al recruiter_feedback de la entrevista.
  • Notifica a todas las partes.

Marcar como realizada

Endpoint: POST /api/v1/interviews/{id}/complete

json
{
  "feedback": "Entrevista muy positiva. Demostró solidez técnica...",
  "recommendation": "advance"  // o "reject", "second_interview"
}
  • Solo desde confirmada.
  • Cambia a realizada.
  • Guarda recruiter_feedback, recommendation y rating.
  • No notifica automáticamente al candidato (el feedback es privado).
  • Si la recomendación es reject, sugiere al recruiter mover la asignación a rejected.

Visibilidad del feedback

recruiter_feedback y company_feedback se exponen en InterviewResource sólo a recruiter, admin y company_user. Los candidatos no los ven (es información interna de evaluación). En la UI, la InterviewCard muestra una sección colapsable "Resultado de la entrevista" con la recomendación (badge), el rating (X/10) y el texto del feedback cuando la entrevista está en realizada o cuando hay feedback acumulado en una cancelada con motivo.

Marcar no_asisto

Endpoint: POST /api/v1/interviews/{id}/no-show

  • Solo desde confirmada.
  • Cambia a no_asisto.
  • Impacta negativamente el rating del candidato (campo futuro).
  • Notifica al candidato con oportunidad de explicar.

Vista de agenda

URL: /recruiter/agenda (opcional, fase 2)

Calendario visual con todas las entrevistas del recruiter agrupadas por día. Útil para ver carga semanal.

En el MVP la vista es por-vacante (dentro del pipeline).

Filtros en el listado de entrevistas

Endpoint GET /api/v1/interviews:

FiltroEjemplo
statepropuesta, confirmada, realizada, cancelada, no_asisto, reprogramada
from, torango de fechas (ISO 8601)
assignment_idde una asignación específica

La página /me/entrevistas (recruiter y company_user comparten la misma) expone estos filtros en UI:

  • Tabs Próximas (propuesta/confirmada/reprogramada), Historial (realizada/cancelada/no_asisto), Todas — agrupan estados localmente.
  • Búsqueda — input que filtra cliente-side por nombre del candidato o code/title de la vacante.
  • Estado — select que mapea a ?state= del endpoint (Todos los estados lo omite).
  • Rango de fechasDesde / Hasta que se mandan como from/to.
  • Limpiar filtros — reset rápido cuando hay alguno activo.

Cada cambio dispara una nueva query de TanStack (la queryKey incluye los filtros). El backend pagina a 30 por defecto; si la lista crece más allá de eso, añadir per_page y un botón "Cargar más" en una iteración futura.

Notificaciones disparadas

EventoNotificaciónDestinatarios
Schedule nuevaInterviewScheduledNotificationcandidato + company_user
ConfirmadaInterviewConfirmedNotificationcandidato + company_user + recruiter
ReprogramadaInterviewRescheduledNotificationtodas las partes
CanceladaInterviewCancelledNotificationtodas las partes
Realizada(silenciosa al candidato)
No asistióInterviewNoShowNotificationcandidato

Todas vía email + in-app.

Integración con calendario (Fase 2)

Pendiente: generar archivos .ics para adjuntar al correo de confirmación. Esto permite que el candidato y el company_user agreguen la entrevista a su Google Calendar / Outlook con un click.

Performance y diseño

  • El endpoint GET /interviews usa eager loading de assignment.candidateProfile.user y assignment.vacancy.company.
  • Índices DB: (vacancy_assignment_id, state), scheduled_at, state.
  • Paginación de 20 con per_page máximo 50.

Errores comunes

ErrorHTTPCausa
"Vacante no admite entrevistas"422Estado de vacancy no compatible
"No puedes agendar en el pasado"422scheduled_at < now()
"Entrevista ya realizada"422Intento de modificar una en estado terminal
"Transición no permitida"422FSM rechaza

Siguiente

Cierre del kit del recruiter: Favoritos y notas →

Manual de usuario HUMAE · Uso interno