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
+14 -2
View File
@@ -81,6 +81,7 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
.role-pill { display:inline-block; padding:2px 9px; border-radius:14px; font-size:10.5px; font-weight:700;
letter-spacing:.04em; text-transform:uppercase; }
.role-admin { background:#1B3A6B; color:#fff; }
.role-socio { background:#FCE4EC; color:#880E4F; }
.role-abogado { background:#E1F5EE; color:#085041; }
.role-usuario { background:#EEEDE9; color:#5F5E5A; }
@@ -167,6 +168,7 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
<input type="search" id="search" placeholder="Buscar por email o nombre…">
<select id="f-role">
<option value="">— Todos los roles —</option>
<option value="admin">Admin</option>
<option value="socio">Socio</option>
<option value="abogado">Abogado</option>
<option value="usuario">Usuario</option>
@@ -223,6 +225,7 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
<option value="usuario">Usuario (pasante / asistente)</option>
<option value="abogado">Abogado</option>
<option value="socio">Socio (modo Dios)</option>
<option value="admin" id="opt-admin" style="display:none;">Admin (programador, modo Dios oculto)</option>
</select>
</div>
</div>
@@ -251,9 +254,18 @@ tr.is-me td { background:#FFFBEB; font-weight:500; }
<div class="toast" id="toast"></div>
<script>
const ROLES = ['socio','abogado','usuario'];
const ROLE_LABEL = { socio:'Socio', abogado:'Abogado', usuario:'Usuario' };
const ROLES = ['admin','socio','abogado','usuario'];
const ROLE_LABEL = { admin:'Admin', socio:'Socio', abogado:'Abogado', usuario:'Usuario' };
const MY_EMAIL = (window.DONNA && window.DONNA.me) || '';
const I_AM_ADMIN = !!(window.DONNA && window.DONNA.isAdmin);
// Mostrar la opción "admin" en el dropdown solo si el usuario actual es admin
if (I_AM_ADMIN) {
document.addEventListener('DOMContentLoaded', () => {
const opt = document.getElementById('opt-admin');
if (opt) opt.style.display = '';
});
}
let users = [];
let editingId = null;