feat(auth): nuevo rol 'admin' (programador, modo Dios oculto)

JERARQUÍA DE ROLES
  admin   = programador. Modo Dios. Oculto de dropdowns y del Panel
            de Control para non-admins. Acceso total.
  socio   = modo Dios visible. Acceso al Panel, ve y edita todo.
  abogado = crea casos, ve los suyos + donde es involucrado.
  usuario = solo ve/edita donde es involucrado.

* Migración 008: agrega 'admin' al ENUM users.role. Promueve a
  ozerpa@aal-law.com como admin.
* Auth.php: isAdmin() nuevo. isSocio() y isAbogado() ahora incluyen
  admin (admin hereda todos los poderes de socio). canCreate/canDelete
  también heredan.
* Lookups.php:
  - abogadoNames() ya filtraba a role IN (socio, abogado) → admins no
    aparecen en el dropdown "Abogado Solicitante".
  - involucrablesUsers() agrega filtro role != 'admin' → admins no
    aparecen en el picker de Involucrados.
  - allUsers($includeAdmins) parametrizada: socios reciben la lista
    sin admins, admins reciben todo.
* Bootstrap.php: window.DONNA.isAdmin disponible al frontend.
* admin/api.php:
  - list_users pasa Auth::isAdmin() para filtrar.
  - create_user / update_user: rechazan role='admin' si quien llama no
    es admin. Solo admins pueden editar a otros admins.
  - delete_user: protege admins (extends al check existente de socio).
  - Self-protection extendida: admin no puede demoverse por debajo de
    socio (queda en god-mode tier).
* admin.html: opción 'admin' en role dropdown solo visible si el
  usuario actual es admin. Role pill navy/blanca para admin.
This commit is contained in:
Soporte ZH Consultores
2026-06-05 00:27:54 +00:00
parent 25f3d7461b
commit 510304872c
6 changed files with 80 additions and 23 deletions
+27 -7
View File
@@ -10,7 +10,7 @@ header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
const ROLES = ['socio','abogado','usuario'];
const ROLES = ['admin','socio','abogado','usuario'];
function ok($data = null): void {
echo json_encode(['ok' => true, 'data' => $data, 'user' => Auth::user()], JSON_UNESCAPED_UNICODE);
@@ -32,7 +32,8 @@ $pdo = Db::pdo();
try {
if ($method === 'GET' && $action === 'list_users') {
ok(Lookups::allUsers());
// Los socios no ven a los admins; los admins ven todo
ok(Lookups::allUsers(Auth::isAdmin()));
}
if ($method === 'POST' && $action === 'create_user') {
@@ -46,6 +47,11 @@ try {
$activo = (int)($b['activo'] ?? 1) === 1 ? 1 : 0;
$orden = (int)($b['orden'] ?? 0);
// Solo admins pueden crear nuevos admins
if ($role === 'admin' && !Auth::isAdmin()) {
err('Solo los admins pueden crear nuevos admins', 403);
}
$exists = $pdo->prepare('SELECT id FROM users WHERE email = :e');
$exists->execute([':e' => $email]);
if ($exists->fetch()) err('Ya existe un usuario con ese email', 409);
@@ -82,10 +88,21 @@ try {
$newRole = in_array(($b['role'] ?? $existing['role']), ROLES, true) ? $b['role'] : $existing['role'];
$newActivo = isset($b['activo']) ? ((int)$b['activo'] === 1 ? 1 : 0) : (int)$existing['activo'];
// Salvaguardas: el socio no puede demoverse ni desactivarse a sí mismo
// Solo admins pueden editar a otros admins
if ($existing['role'] === 'admin' && !Auth::isAdmin()) {
err('Solo los admins pueden editar a otros admins', 403);
}
// Solo admins pueden asignar el rol admin
if ($newRole === 'admin' && !Auth::isAdmin()) {
err('Solo los admins pueden asignar el rol admin', 403);
}
// Salvaguardas: no puedes quitarte tu modo Dios ni desactivarte
if ((int)$existing['id'] === $me) {
if ($newRole !== 'socio') err('No puedes quitarte el rol de Socio a ti mismo', 400);
if ($newActivo === 0) err('No puedes desactivarte a ti mismo', 400);
if (!in_array($newRole, ['admin','socio'], true)) {
err('No puedes quitarte el modo Dios a ti mismo (debe quedar Admin o Socio)', 400);
}
if ($newActivo === 0) err('No puedes desactivarte a ti mismo', 400);
}
$newEmail = isset($b['email']) ? strtolower(trim((string)$b['email'])) : $existing['email'];
@@ -128,8 +145,11 @@ try {
$sel->execute([':id' => $id]);
$u = $sel->fetch();
if (!$u) err('Usuario no encontrado', 404);
if ($u['role'] === 'socio') {
err('No puedes eliminar a otro Socio. Cambia su rol a Abogado o Usuario primero.', 400);
if ($u['role'] === 'admin' && !Auth::isAdmin()) {
err('Solo los admins pueden eliminar a otros admins', 403);
}
if (in_array($u['role'], ['admin','socio'], true)) {
err('No puedes eliminar a un Admin o Socio. Cambia su rol a Abogado o Usuario primero.', 400);
}
// CASCADE de tramites_involucrados limpia los vínculos automáticamente.