Apariencia
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:
- Login como admin
/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:
/admin/catalogos/skills→ "Nueva"- Nombre:
Rust, categoría:language, descripción - Guardar
Resultado esperado:
slugauto-generado:rust- Aparece en la lista activa
- Candidatos pueden agregar
Rustdesde su perfil
Variaciones:
- Slug duplicado (crear
reactotra 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:
- Skill → botón "Desactivar"
- 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:
/admin/catalogos→ tab "Áreas funcionales".- Verificar que el header muestra "21 totales · 21 activos · 0 desactivados".
- Buscador → escribir "Cal" → la lista filtra a "Calidad".
- Click en botón "+ Agregar".
- Llenar el modal con
code = comercio_exterior,name = Comercio Exterior,description = Importaciones, exportaciones, aduanas. Guardar. - Buscar "Comercio" → aparece la nueva área.
- Click ✏ en "Comercio Exterior" → cambiar
nameaComercio Exterior y Aduanas→ guardar. - 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).
- Volver a
/admin/catalogos→ click ✏ en "Comercio Exterior y Aduanas" → desmarcar "Activo" → guardar. - En el form del candidato, refrescar — el área ya no aparece en el listado (solo activos en
GET /api/v1/catalogs/functional-areas). - 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 conis_active=false.DELETE /api/v1/admin/catalogs/functional-areas/{id}→ 204. Las FKsfunctional_area_idencandidate_profiles,candidate_experiencesyvacanciesquedan enNULL(constraintnullOnDelete); las filas en el pivotecandidate_functional_areasse borran en cascada (cascadeOnDelete).- Toda la operación es idempotente y disparable solo por el rol
admin(Spatie permissioncatalogs.manage).
Variaciones:
- Como recruiter → 403 al intentar entrar a
/admin/catalogos. - Code duplicado (ej. crear con
code = qualityya existente) → 422errors.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:
/admin/catalogos/paises→ "Nuevo"- Crear "Argentina"
/admin/catalogos/estados→ "Nuevo" seleccionando Argentina- Crear "Buenos Aires"
- 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:
/admin/usuarios- Filtrar por rol
candidate+ estadoactive
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:
- Detalle de user → "Cambiar rol"
- Nuevo rol:
recruiter - Confirmar
Resultado esperado:
model_has_rolesactualizado- 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:
- Detalle → "Suspender"
- Motivo: "Comportamiento inapropiado"
- 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és →
status=active, puede loguearse de nuevo
TC-ADM-008 · Cerrar todas las sesiones de un user
Severidad: 🟠 Alta
Pasos:
- Detalle → "Cerrar todas las sesiones"
- Confirmar
Resultado esperado:
- Tabla
personal_access_tokensborra 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:
- Abrir MailHog (
http://localhost:8025) o el inbox del admin. - Buscar el correo "Solicitud de registro pendiente: Reclutador" (o "…Usuario de empresa").
- 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_namede laCompany. - El CTA abre
https://app.humae.com.mx/admin/usuarios?status=pending_approvaly la pantalla precarga el filtro "Pendientes de aprobación". - En la lista aparece la solicitud con badge ámbar
Pendiente de aprobacióny botones Aprobar / Rechazar.
Variaciones:
- Hay 3 admins en el sistema → los 3 reciben copia del correo. Verificar
Notification::send()recorreUser::role('admin')->get(). - No hay admins → el
AuthServiceno 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:
/admin/usuarios?status=pending_approval.- En la fila del solicitante, click en Aprobar.
- Esperar el toast "{nombre} fue aprobado".
Resultado esperado:
POST /api/v1/admin/users/{user}/approve→ 200.users.statuspasa depending_approvalaactive.- La fila ya no aparece bajo el filtro
pending_approval(se invalida la query["admin","users"]). - Llega correo
AccountApprovedNotificational usuario con CTA al login. - El usuario puede iniciar sesión y aterriza en su home según rol (
/recruiter/directorioo/me/empresa/vacantes).
Variaciones:
- Doble click rápido / aprobar a un user que ya estaba en
active→ backend devuelve409 "Esta cuenta no está pendiente de aprobación."(toast de error). - Llamar al endpoint como recruiter →
403 "Solo admin". - El usuario aún NO verificó su correo → la aprobación funciona igual (queda
active), pero el login seguirá devolviendo403 errors.code=email_unverifiedhasta que verifique.
TC-ADM-USR-015 · Rechazar un auto-registro con motivo
Severidad: 🔴 Crítica
Precondiciones: Hay un usuario con status=pending_approval.
Pasos:
/admin/usuarios?status=pending_approval.- En la fila del solicitante, click en Rechazar (botón outline destructivo).
- Se abre el
RejectUserDialog. Escribir motivo: "Información incompleta — vuelve a registrarte con datos válidos.". - Confirmar.
Resultado esperado:
POST /api/v1/admin/users/{user}/rejectcon{reason: "Información incompleta…"}→ 200.users.statuspasa ainactive. La fila desaparece depending_approvaly se ve bajo el filtroInactivos.- Llega correo
AccountRejectedNotificational 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
maxLengthdel textarea ya lo bloquea desde el frontend. Si se ataca el endpoint directo, devuelve422. - 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:
/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:
/admin/reportes→ "Candidatos registrados"- Filtros: últimos 30 días
- "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:
/admin/configuracion→membership.expiring_warning_days- Cambiar de 15 → 30
- Guardar
Resultado esperado:
settingstable 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:
/admin/email-templates→welcome- "Previsualizar" → ver HTML con datos fake
- "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.logmuestrastatus=sentpara ese correo

