Skip to content

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:admin o role:recruiter (algunos).
  • ReportsController::authorizeStaff() valida el rol.
  • No se exponen datos de candidatos individuales sin autorización adicional.

Siguiente

Config global: Configuración →

Manual de usuario HUMAE · Uso interno