Apariencia
Directorio de talento
El directorio es el corazón operativo del reclutador HUMAE: un buscador potente de todos los candidatos con membresía activa, con filtros avanzados y vista detallada.
URL y acceso
URL: /recruiter/directorioEndpoint principal: GET /api/v1/directory/candidatesQuién puede usarlo: role:recruiter o role:admin.
Qué candidatos aparecen
Por defecto, el directorio muestra candidatos que cumplen todas estas condiciones:
- Membresía activa —
memberships.status = 'active'yexpires_at > now(). - Estado visible —
CandidateProfile.state ∈ {activo, en_proceso, presentado_empresa, entrevistado}. - Perfil no
soft-deleted.
Esto se puede modificar con filtros (un admin puede ver candidatos sin membresía con ?has_active_membership=0).
Filtros disponibles
El sidebar de filtros incluye (implementado en DirectorySearchService):
| Filtro | Query param | Tipo | Ejemplo |
|---|---|---|---|
| Búsqueda de texto | q | string | "product manager" |
| Estado del candidato | state | enum | activo |
| Categoría (empleado/practicante) | candidate_kind | enum | intern |
| País / Estado / Ciudad | country_id, state_id, city_id | FK | 32 (México) |
| Nivel de carrera | career_level_id | FK | 5 (Senior) |
| Área funcional (legacy single) | functional_area_id | FK | 3 (Producción) |
| Áreas de interés (OR) | functional_area_ids[] | array FK | [4, 7, 12] |
| Área principal del candidato | primary_functional_area_id | FK | 4 |
| Posición específica | position_id | FK | 17 (Backend Developer) |
| Años de experiencia | years_exp_min, years_exp_max | int | 3 / 10 |
| Salario máximo ofertado | salary_max | number | 50000 |
| Disponibilidad | availability | string | inmediata |
| Abierto a remoto | open_to_remote | bool | 1 |
| Abierto a reubicarse | open_to_relocation | bool | 1 |
| Skills (AND) | skills[] | array FK | [1, 5, 12] |
| Idiomas (AND) | languages[] | array FK | [1, 2] |
| Membresía activa | has_active_membership | bool | 1 (default) |
Categoría (empleado / practicante)
Tres botones en el sidebar:
Categoría
[ Todos ] [ Empleado ] [ Practicante ]Al elegir "Practicante" se envía candidate_kind=intern. Los candidatos con candidate_kind = null no pasan este filtro (el campo es estricto). En las cards, los practicantes muestran un badge ámbar; los empleados, badge color marca.
Áreas de interés (OR semántico)
A diferencia de skills/languages que usan AND, las áreas de interés usan OR: si pasas functional_area_ids[]=4&functional_area_ids[]=7, aparecen candidatos que tengan alguna de esas dos áreas en su perfil. Implementación:
php
$query->whereHas('functionalAreas', fn (Builder $q) =>
$q->whereIn('functional_areas.id', $areaIds));Si el reclutador necesita un candidato cuya área principal sea una específica (no secundaria), usa primary_functional_area_id:
php
$query->whereHas('functionalAreas', fn (Builder $q) =>
$q->where('functional_areas.id', $primaryId)
->where('candidate_functional_areas.is_primary', true));Semántica AND en skills/languages
Si pasas skills[]=1&skills[]=5&skills[]=12, solo aparecen candidatos con las 3 skills. Implementación en DirectorySearchService:
php
foreach ($skillIds as $skillId) {
$query->whereHas('skills', fn ($q) => $q->where('skills.id', $skillId));
}Salario esperado vs máximo que la empresa paga
El filtro salary_max busca candidatos cuyo expected_salary_min sea menor o igual a salary_max. Si el candidato no puso salario esperado, también aparece (da beneficio de la duda).
php
$query->where(function ($q) use ($max) {
$q->whereNull('expected_salary_min')
->orWhere('expected_salary_min', '<=', $max);
});Búsqueda de texto
q busca en first_name, last_name, headline, summary con LIKE %term%. No hace full-text search (fase 2 con Scout + Algolia/Meilisearch).
Paginación
- Default: 20 por página.
- Configurable con
per_page(máximo 50 por seguridad). - Respuesta incluye
metacontotal,current_page,last_page.
Listado (vista de tarjetas)
Cada candidato se muestra como DirectoryCandidateCard:
┌──────────────────────────────────────────────┐
│ [Foto] Ana Pérez │
│ Backend Engineer Senior │
│ [Empleado] 8 años exp. │
│ CDMX · $50K-$70K │
│ │
│ 🏷️ React Node.js PostgreSQL AWS │
│ 🌐 Español (nativo) Inglés (C1) │
│ │
│ [Ver perfil] [Añadir a favoritos ⭐] │
└──────────────────────────────────────────────┘El badge [Empleado] o [Practicante] aparece junto a los años de experiencia. Si el candidato no marcó categoría, el badge se omite.
Vista detallada
URL: /recruiter/directorio/{id}Endpoint: GET /api/v1/directory/candidates/{id}
Trae el candidato con todas sus relaciones cargadas (con eager loading explícito para evitar N+1):
- Datos básicos + ubicación
- Experiencias (cronológicas)
- Educación
- Cursos + certificaciones
- Referencias (con contacto)
- Categoría (empleado/practicante) y áreas de interés con la principal destacada con ⭐
- Skills con nivel
- Idiomas con nivel MCER
- Documentos adjuntos
- Psicométricos completados con scores
- Asignaciones activas e históricas
- Notas internas del recruiter
Acciones disponibles en el detalle
| Botón | Efecto |
|---|---|
| Añadir a favoritos | POST /recruiter/favorites con candidate_profile_id |
| Asignar a vacante | Abre modal con selector de vacantes activas → POST /vacancies/{id}/assignments |
| Descargar CV | GET /recruiter/candidates/{id}/cv.pdf |
| Añadir nota interna | Campo libre, solo visible a recruiters + admin |
| Enviar mensaje (Fase 2) | WhatsApp o email |
Ordenamiento
Por defecto: ORDER BY updated_at DESC (los perfiles más recientemente actualizados primero).
En fase 2:
- Ranking por match contra una vacante específica.
- Ordenar por psicométricos (score global).
- Ordenar por última vez que aparecieron en pipeline.
Performance
- Eager loading de
user,skills,languagesevita N+1. - Índices DB en
state,(country_id, state_id, city_id),functional_area_id,career_level_idsoportan los filtros típicos. - Para búsquedas con muchos filtros, la query tarda 30–80 ms en dev (SQLite) y se espera similar en prod con MySQL + índices apropiados.
Privacidad
- El candidato NO sabe cuántas veces fue visto en el directorio.
- El candidato NO sabe quién descargó su CV o quién lo añadió a favoritos.
- Empresas cliente no acceden al directorio directamente — solo ven candidatos que HUMAE les presenta en su panel.
Reportes relacionados
Un admin puede consultar los candidatos más buscados en reportes:
- Endpoint:
GET /admin/reports/most-searched-profiles?limit=20 - Métrica: candidatos con más aperturas de detalle por parte de recruiters en los últimos 30 días.
- Útil para ajustar la curaduría y evitar sesgos.
Caso de uso: buscar un practicante de Sistemas
"Una empresa pidió un practicante de Sistemas para apoyar al área de TI. ¿A quién tengo?"
- Sidebar → Categoría: Practicante.
- Sidebar → Áreas de interés → buscar y marcar
Sistemas. - Confirmar "Solo con membresía activa" (default).
- La lista muestra a María Torres con badge ámbar de Practicante y chip ⭐ Sistemas. Click para abrir detalle.
- Desde el detalle: Asignar a vacante → seleccionar la vacante "Practicante de Sistemas". Movido al pipeline en columna
sourced.
Si después una empresa solicita un Auxiliar de Almacén · Empleado:
- Sidebar → Categoría: Empleado.
- Áreas: Almacén + Logística (multi-select OR — aparece quien tenga cualquiera).
- Sofía aparece con su área principal Logística (estrella) y secundaria Almacén.
Siguiente
Ya con candidatos identificados, el recruiter los mueve al pipeline kanban para gestionar su progreso. Pipeline kanban →

