Skip to content

Flujo del administrador

12 casos del rol admin: catálogos, usuarios, reportes, configuración global y auditoría.

Duración estimada: 40 minutos.

Catálogos

TC-ADM-001 · Listar skills

Severidad: 🟡 Media

Pasos:

  1. Login como admin
  2. /admin/catalogos/skills

Resultado esperado:

  • Lista con ~150 skills del seed
  • Columnas: nombre, slug, categoría, is_active, orden
  • Buscador por nombre + filtro por is_active
  • Paginación (20/página)

TC-ADM-002 · Crear nueva skill

Severidad: 🟡 Media

Pasos:

  1. /admin/catalogos/skills → "Nueva"
  2. Nombre: Rust, categoría: language, descripción
  3. Guardar

Resultado esperado:

  • slug auto-generado: rust
  • Aparece en la lista activa
  • Candidatos pueden agregar Rust desde su perfil

Variaciones:

  • Slug duplicado (crear react otra vez) → 422 "Slug ya existe"
  • Nombre vacío → 422

TC-ADM-003 · Desactivar skill

Severidad: 🟡 Media

Precondiciones: Skill con candidatos que ya la usan

Pasos:

  1. Skill → botón "Desactivar"
  2. Confirmar

Resultado esperado:

  • is_active = false
  • Candidatos que ya la tenían: la conservan visible en su perfil
  • Nuevos candidatos: NO pueden seleccionarla en el picker
  • Recruiters: NO aparece en filtro del directorio

Variaciones:

  • Eliminar hard (DELETE) → 422 si tiene FK references; 204 si no

TC-ADM-003b · Gestionar el catálogo de áreas funcionales

Severidad: 🔴 Crítica (PDF cosasfaltanteshumae · catálogo administrable)

Precondiciones: User admin logueado. Seeder JobTaxonomySeeder corrido (21 áreas pre-cargadas).

Pasos:

  1. /admin/catalogos → tab "Áreas funcionales".
  2. Verificar que el header muestra "21 totales · 21 activos · 0 desactivados".
  3. Buscador → escribir "Cal" → la lista filtra a "Calidad".
  4. Click en botón "+ Agregar".
  5. Llenar el modal con code = comercio_exterior, name = Comercio Exterior, description = Importaciones, exportaciones, aduanas. Guardar.
  6. Buscar "Comercio" → aparece la nueva área.
  7. Click ✏ en "Comercio Exterior" → cambiar name a Comercio Exterior y Aduanas → guardar.
  8. En otra pestaña, abrir el form de perfil de un candidato y verificar que aparece en el multi-select de áreas (cache de TanStack Query invalidado).
  9. Volver a /admin/catalogos → click ✏ en "Comercio Exterior y Aduanas" → desmarcar "Activo" → guardar.
  10. En el form del candidato, refrescar — el área ya no aparece en el listado (solo activos en GET /api/v1/catalogs/functional-areas).
  11. Volver al admin → click 🗑 (eliminar) en la fila → confirmar.

Resultado esperado:

  • POST /api/v1/admin/catalogs/functional-areas → 201 con la nueva área.
  • PATCH /api/v1/admin/catalogs/functional-areas/{id} → 200 con is_active=false.
  • DELETE /api/v1/admin/catalogs/functional-areas/{id} → 204. Las FKs functional_area_id en candidate_profiles, candidate_experiences y vacancies quedan en NULL (constraint nullOnDelete); las filas en el pivote candidate_functional_areas se borran en cascada (cascadeOnDelete).
  • Toda la operación es idempotente y disparable solo por el rol admin (Spatie permission catalogs.manage).

Variaciones:

  • Como recruiter → 403 al intentar entrar a /admin/catalogos.
  • Code duplicado (ej. crear con code = quality ya existente) → 422 errors.code.
  • Code con caracteres inválidos (Comercio Exterior) → 422 (regex ^[a-z0-9_-]+$).
  • Borrar un área que tiene 5 candidatos asignados → DELETE pasa, perfil de esos candidatos muestra área null (no rompe nada). El admin puede inactivar en lugar de borrar si prefiere preservar la referencia histórica.

TC-ADM-004 · Ubicaciones en cascada (país → estado → ciudad)

Severidad: 🟡 Media

Pasos:

  1. /admin/catalogos/paises → "Nuevo"
  2. Crear "Argentina"
  3. /admin/catalogos/estados → "Nuevo" seleccionando Argentina
  4. Crear "Buenos Aires"
  5. Ciudad "CABA" bajo Buenos Aires

Resultado esperado: Jerarquía se respeta. Intentar borrar Argentina con estados hijos → 422 "Tiene estados asociados"

Usuarios

TC-ADM-005 · Listar y filtrar usuarios

Severidad: 🟠 Alta

Pasos:

  1. /admin/usuarios
  2. Filtrar por rol candidate + estado active

Resultado esperado: Lista muestra solo candidatos activos. Columnas: nombre, email, rol, estado, última conexión, creado.


TC-ADM-006 · Cambiar rol de un user

Severidad: 🔴 Crítica

Pasos:

  1. Detalle de user → "Cambiar rol"
  2. Nuevo rol: recruiter
  3. Confirmar

Resultado esperado:

  • model_has_roles actualizado
  • El user en su próximo login ve el panel de recruiter
  • Permisos recalculados inmediatamente en sesiones activas

Variaciones:

  • Cambiar a rol del mismo user que hace el cambio → warning "Estás modificando tu propio rol"
  • Quitar último admin → 422 "Debe haber al menos 1 admin"

TC-ADM-007 · Suspender usuario

Severidad: 🔴 Crítica

Pasos:

  1. Detalle → "Suspender"
  2. Motivo: "Comportamiento inapropiado"
  3. Confirmar (con input de confirmación del email)

Resultado esperado:

  • users.status = suspended
  • Sus tokens Sanctum revocados → logout forzado
  • Login devuelve 403 "Cuenta suspendida"
  • Email al user afectado (opcional)

Variaciones:

  • Reactivar despuésstatus=active, puede loguearse de nuevo

TC-ADM-008 · Cerrar todas las sesiones de un user

Severidad: 🟠 Alta

Pasos:

  1. Detalle → "Cerrar todas las sesiones"
  2. Confirmar

Resultado esperado:

  • Tabla personal_access_tokens borra todos los tokens del user
  • User queda deslogueado en todos sus dispositivos
  • Tendrá que loguearse de nuevo

TC-ADM-USR-013 · Recibir correo de auto-registro pendiente

Severidad: 🔴 Crítica

Precondiciones: Existir como admin (admin@test.humae). Un reclutador o empresa nueva acaba de registrarse desde /register/reclutador o /register/empresa.

Pasos:

  1. Abrir MailHog (http://localhost:8025) o el inbox del admin.
  2. Buscar el correo "Solicitud de registro pendiente: Reclutador" (o "…Usuario de empresa").
  3. Click en el botón "Ir a usuarios pendientes".

Resultado esperado:

  • El correo incluye nombre, email del solicitante y motivo (si lo llenó).
  • Para empresas también incluye el legal_name de la Company.
  • El CTA abre https://app.humae.com.mx/admin/usuarios?status=pending_approval y la pantalla precarga el filtro "Pendientes de aprobación".
  • En la lista aparece la solicitud con badge ámbar Pendiente de aprobación y botones Aprobar / Rechazar.

Variaciones:

  • Hay 3 admins en el sistema → los 3 reciben copia del correo. Verificar Notification::send() recorre User::role('admin')->get().
  • No hay admins → el AuthService no truena, simplemente no se envía nada.

TC-ADM-USR-014 · Aprobar un auto-registro pendiente

Severidad: 🔴 Crítica

Precondiciones: Hay al menos un usuario con status=pending_approval y email_verified_at no nulo.

Pasos:

  1. /admin/usuarios?status=pending_approval.
  2. En la fila del solicitante, click en Aprobar.
  3. Esperar el toast "{nombre} fue aprobado".

Resultado esperado:

  • POST /api/v1/admin/users/{user}/approve → 200.
  • users.status pasa de pending_approval a active.
  • La fila ya no aparece bajo el filtro pending_approval (se invalida la query ["admin","users"]).
  • Llega correo AccountApprovedNotification al usuario con CTA al login.
  • El usuario puede iniciar sesión y aterriza en su home según rol (/recruiter/directorio o /me/empresa/vacantes).

Variaciones:

  • Doble click rápido / aprobar a un user que ya estaba en active → backend devuelve 409 "Esta cuenta no está pendiente de aprobación." (toast de error).
  • Llamar al endpoint como recruiter403 "Solo admin".
  • El usuario aún NO verificó su correo → la aprobación funciona igual (queda active), pero el login seguirá devolviendo 403 errors.code=email_unverified hasta que verifique.

TC-ADM-USR-015 · Rechazar un auto-registro con motivo

Severidad: 🔴 Crítica

Precondiciones: Hay un usuario con status=pending_approval.

Pasos:

  1. /admin/usuarios?status=pending_approval.
  2. En la fila del solicitante, click en Rechazar (botón outline destructivo).
  3. Se abre el RejectUserDialog. Escribir motivo: "Información incompleta — vuelve a registrarte con datos válidos.".
  4. Confirmar.

Resultado esperado:

  • POST /api/v1/admin/users/{user}/reject con {reason: "Información incompleta…"} → 200.
  • users.status pasa a inactive. La fila desaparece de pending_approval y se ve bajo el filtro Inactivos.
  • Llega correo AccountRejectedNotification al usuario con el motivo en el cuerpo.
  • El siguiente intento de login del usuario devuelve 403 errors.code=account_inactive.

Variaciones:

  • Sin motivo (textarea vacía) → la operación pasa, el correo va sin línea de motivo.
  • Motivo > 500 caracteres → el maxLength del textarea ya lo bloquea desde el frontend. Si se ataca el endpoint directo, devuelve 422.
  • Cancelar el dialog → cierra sin llamar al endpoint, el user sigue pendiente.

Reportes

TC-ADM-009 · Dashboard de reportes carga sin error

Severidad: 🔴 Crítica

Pasos:

  1. /admin/reportes

Resultado esperado:

  • KPIs cards cargan (Candidatos, Membresías, Vacantes, Time-to-fill)
  • Charts renderizan (LineChart, BarChart, PieChart)
  • Sin errores en consola del browser
  • Duración < 3 segundos

Variaciones:

  • DB vacía → charts muestran empty state, no crash
  • Rango de fechas muy amplio (1 año+) → debe seguir funcionando

TC-ADM-010 · Exportar reporte a CSV

Severidad: 🟡 Media

Pasos:

  1. /admin/reportes → "Candidatos registrados"
  2. Filtros: últimos 30 días
  3. "Exportar CSV"

Resultado esperado:

  • Descarga candidatos-registrados-YYYY-MM-DD.csv
  • Columnas correctas, datos coinciden con UI
  • Caracteres especiales (acentos, ñ) codificados en UTF-8 BOM

Configuración global

TC-ADM-011 · Cambiar setting

Severidad: 🟡 Media

Pasos:

  1. /admin/configuracionmembership.expiring_warning_days
  2. Cambiar de 15 → 30
  3. Guardar

Resultado esperado:

  • settings table actualizada
  • Al recargar el dashboard de un candidato con membresía que expira en 25 días → ahora aparece el warning (antes no)

Variaciones:

  • Intentar guardar valor inválido (string donde se espera int) → 422

TC-ADM-012 · Plantilla de email: preview y enviar prueba

Severidad: 🟡 Media

Pasos:

  1. /admin/email-templateswelcome
  2. "Previsualizar" → ver HTML con datos fake
  3. "Enviar prueba a mi email"

Resultado esperado:

  • Preview muestra el correo renderizado correctamente
  • Email de prueba llega al admin con marker "[TEST]"
  • /var/log/mail.log muestra status=sent para ese correo

Siguiente

Integraciones externas →

Manual de usuario HUMAE · Uso interno