Apariencia
Reportes ejecutivos
Dashboard analítico para tomar decisiones de negocio. Agrega datos de todos los módulos.
URL: /admin/reportesQuién: role:admin (o role:recruiter para algunos reportes operativos).
Los 9 reportes del MVP
Todos están implementados en App\Services\ReportsService. Cada uno tiene su endpoint bajo /admin/reports/*.
1. Candidatos registrados
Endpoint: GET /admin/reports/candidates-registered?from=2026-01-01&to=2026-04-19
json
{
"total": 124,
"by_day": [
{"date": "2026-01-05", "count": 3},
{"date": "2026-01-06", "count": 5}
],
"by_state": {
"activo": 87,
"en_registro": 24,
"inactivo": 13
}
}Visualización: LineChart (nuevos por día) + pie por estado.
2. Membresías activas
Endpoint: GET /admin/reports/active-memberships
json
{
"active": 87,
"by_plan": {
"candidate_6m": 87
},
"expiring_soon": 14
}Visualización: BarChart + KPI card.
3. Pagos por periodo
Endpoint: GET /admin/reports/payments?from=...&to=...
json
{
"total_succeeded": 52,
"total_amount": "25948.00",
"total_failed": 3,
"by_day": [{"date": "2026-04-01", "amount": "1497.00"}]
}Visualización: LineChart monto acumulado + tabla.
4. Membresías expirando pronto
Endpoint: GET /admin/reports/expiring-memberships?days=30
Lista de usuarios con expires_at entre hoy y +N días. Útil para campaña de retención.
json
[
{
"user_id": 42,
"name": "Ana Pérez",
"email": "ana@humae.com",
"expires_at": "2026-05-01T00:00:00Z",
"days_remaining": 12
}
]Exportable a CSV.
5. Vacantes por estado
Endpoint: GET /admin/reports/vacancies-by-state
json
{
"borrador": 5,
"activa": 18,
"en_busqueda": 12,
"con_candidatos_asignados": 8,
"entrevistas_en_curso": 4,
"finalista_seleccionado": 2,
"cubierta": 32,
"cancelada": 7
}Visualización: pie con etiquetas.
6. Entrevistas por periodo
Endpoint: GET /admin/reports/interviews?from=...&to=...
json
{
"total_scheduled": 38,
"total_confirmed": 32,
"total_completed": 28,
"total_cancelled": 4,
"total_no_show": 2,
"by_day": [{"date": "2026-04-10", "count": 5}]
}Visualización: BarChart stacked por estado.
7. Efectividad de reclutadores
Endpoint: GET /admin/reports/recruiter-effectiveness
json
[
{
"recruiter_id": 3,
"recruiter_name": "Carla Ruiz",
"total_assignments": 45,
"total_hired": 12,
"total_rejected": 20,
"hired_rate": 0.267,
"avg_time_to_hire_days": 18.5
}
]Visualización: tabla rankeada con filtros.
8. Tiempo de cierre de vacantes
Endpoint: GET /admin/reports/time-to-fill
json
{
"overall_avg_days": 21.3,
"by_category": {
"Desarrollo": 18.2,
"Diseño": 24.5,
"Marketing": 28.7
},
"by_month": [
{"month": "2026-01", "avg_days": 23.1},
{"month": "2026-02", "avg_days": 20.8}
]
}Visualización: LineChart tendencia + barras por categoría.
9. Candidatos más buscados
Endpoint: GET /admin/reports/most-searched-profiles?limit=20
Ranking de candidatos con más:
- Vistas únicas de recruiters.
- Favoritos agregados.
- Descargas de CV.
json
[
{
"candidate_profile_id": 12,
"name": "Luis Díaz",
"favorites_count": 8,
"views_count": 24,
"cv_downloads": 5
}
]KPI cards del dashboard
Arriba del todo, 4 tarjetas ejecutivas:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Candidatos │ │ Membresías │ │ Vacantes │ │ Time-to-fill │
│ 842 │ │ 520 │ │ 23 │ │ 21.3 días │
│ +12% vs mes │ │ +8% vs mes │ │ activas │ │ ↓ 2.1 días │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘Performance
Todos los reportes usan DB::table() y selectRaw con bindings (no Eloquent puro) porque:
- Los agregados (
COUNT,SUM,GROUP BY DATE(created_at)) son más eficientes. - Evita cargar modelos completos cuando solo se necesita el agregado.
- Mantiene compatibilidad SQLite (tests) + MySQL (prod) con detección del driver.
Detección de driver:
php
$dateDiff = DB::getDriverName() === 'sqlite'
? "julianday(end_date) - julianday(start_date)"
: "TIMESTAMPDIFF(DAY, start_date, end_date)";Exportación
- Cada reporte tiene botón "Exportar CSV".
- Los datos se arman en el backend con
response()->streamDownload(). - Los filtros aplicados (fechas, estado) se reflejan en el export.
Schedule de generación
Por ahora son on-demand (se calculan en cada request). Para fase 2:
- Pre-agregar nightly con un job.
- Cachear por 5 min en Redis.
- Webhook a Slack con resumen diario.
Seguridad
- Todos los endpoints requieren
role:adminorole:recruiter(algunos). ReportsController::authorizeStaff()valida el rol.- No se exponen datos de candidatos individuales sin autorización adicional.
Siguiente
Config global: Configuración →

