Saltar al contenido principal

Superadmin e impersonación

🗄️ Instalación de Base de Datos​

1. Ejecutar Migraciones​

Ejecuta los siguientes scripts SQL en tu base de datos Supabase:

# 1. Crear tablas y campos necesarios
psql -h [HOST] -U [USER] -d [DATABASE] -f database/migrations/001_superadmin_impersonation.sql

# 2. Crear funciones RPC
psql -h [HOST] -U [USER] -d [DATABASE] -f database/functions/superadmin_functions.sql

O usa el editor SQL de Supabase para ejecutar los archivos:

  1. Tabla de impersonación y campos en tenants:

    • Archivo: database/migrations/001_superadmin_impersonation.sql
    • Crea: impersonation_logs, agrega campos suspended a tenants
  2. Funciones RPC:

    • Archivo: database/functions/superadmin_functions.sql
    • Funciones: is_superadmin, tenant_list_accessible (todos los tenants si eres superadmin), create_tenant_with_admin_user, etc.
    • superadmin_list_all_tenants nunca existió en la base; el listado usa tenant_list_accessible.

👤 Configurar Primer Superadmin​

Método 1: SQL Directo​

Para marcar un usuario existente como superadmin, ejecuta en Supabase SQL Editor:

-- Reemplaza '[email protected]' con el email del usuario
UPDATE auth.users
SET raw_user_meta_data = raw_user_meta_data || '{"is_super_admin": true}'::jsonb
WHERE email = '[email protected]';

Método 2: Función de Supabase​

O crea una función helper:

CREATE OR REPLACE FUNCTION public.make_superadmin(p_email text)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
UPDATE auth.users
SET raw_user_meta_data = raw_user_meta_data || '{"is_super_admin": true}'::jsonb
WHERE email = p_email;
END;
$$;

-- Usar:
SELECT make_superadmin('[email protected]');

Verificar​

-- Verificar que el usuario es superadmin
SELECT
id,
email,
raw_user_meta_data->>'is_super_admin' as is_super_admin
FROM auth.users
WHERE email = '[email protected]';

🚀 Uso del Sistema​

Acceder al Panel de Tenants​

  1. Inicia sesión con un usuario superadmin
  2. Navega a: /admin/tenants
  3. Verás la lista de todos los tenants del sistema

Crear un Nuevo Tenant​

  1. Click en "Nuevo Tenant"
  2. Completa el formulario:
    • Nombre del Tenant: Nombre de la empresa
    • Plan: free, starter, pro, enterprise
    • Sector (opcional): pre-siembra módulos y la plantilla del bot
    • Email del Administrador: correo del futuro admin del tenant
    • Nombre del Admin: Nombre completo

Ya no se pide contraseña. El alta crea el usuario con una clave aleatoria que nadie ve y, al terminar, muestra el enlace de configuración para enviárselo al cliente (copiar o WhatsApp). Con ese enlace el administrador elige su contraseña, acepta las políticas y entra directo a los 3 pasos del onboarding (ADR 0007).

  • El enlace es de un solo uso y caduca a los 7 días.
  • Se puede volver a generar desde el menú "..." de la fila del tenant → Enlace de configuración. Generar uno nuevo invalida el anterior.
  • Detalle técnico: tabla tenant_onboarding_invites (solo se guarda el hash del token, cerrada a anon/authenticated), server actions en frontend/src/actions/onboarding-invite.ts, pantalla pública en /invite/setup/[token]. La comprobación de superadmin se hace por sesión (user_is_superadmin), nunca con lo que llegue en la petición.

Nota: El admin se crea en Auth en el mismo paso (createTenantWithAdmin) con user_metadata.invited_flow: true, para que el trigger de signup no le abra un tenant personal extra. Detalle: Alta de usuarios e invited_flow.

Impersonar un Tenant​

Hay dos formas de acceder como administrador de un tenant:

Opción 1: Desde la Lista​

  1. En la lista de tenants, click en el ícono de login (🔓)
  2. Serás redirigido al dashboard como si fueras el admin del tenant
  3. Aparecerá arriba el banner de impersonación, con el ambiente en un distintivo y el banner entero de su color (ver Ambiente en el banner)

Opción 2: Desde el Menú​

  1. Click en el botón "..." en la fila del tenant
  2. Selecciona "Acceder como Admin"

Salir de la Impersonación​

  1. Click en el botón "Salir" del banner de impersonación
  2. Serás redirigido de vuelta al panel de tenants

Ambiente en el banner​

Para no cambiar nada en el ambiente equivocado, el banner de impersonación y el de "Entrar al tenant" muestran un distintivo con el ambiente. En la impersonación, además, el banner entero toma su color:

DistintivoColorCuándo
PRODUCCIÓN (pulsa)Rojoapp.xenpia.com, cualquier host desconocido, o cualquier host cuya NEXT_PUBLIC_SUPABASE_URL sea la base de producción
TESTÁmbarHost con test, qa o staging como etiqueta (test.xenpia.com, cliente-test…)
LOCALVerdelocalhost, 127.*, *.localhost o IP privada

Si el host y la base no coinciden, el distintivo lo dice (p. ej. Local · app en Local, base de Test). Un frontend local apuntando a la base de producción sale en rojo: los cambios caen en producción. La regla está en frontend/src/utils/app-environment.ts (con test); si cambia el ref de un proyecto Supabase, hay que actualizarlo ahí.

Entrar al tenant (sin impersonar)​

Distinto de impersonar: el JWT sigue siendo el del superadmin y no hay membership en ese tenant. El menú se carga elevado; las mutaciones de usuarios van por RPC SECURITY DEFINER que aceptan is_superadmin():

  • cambiar rol → update_membership_role
  • quitar del tenant → remove_user_from_tenant (migración 20261008000001)

Si la baja falla con toast genérico y la migración no está aplicada, el superadmin elevated no tiene permiso (user_has_permission exige membership).


🎯 Funcionalidades​

Panel de Gestión de Tenants​

Vista Principal (/admin/tenants):

  • ✅ Lista de todos los tenants
  • ✅ Búsqueda y filtros
  • ✅ Ver cantidad de usuarios y workspaces por tenant
  • ✅ Ver estado (activo/suspendido)
  • ✅ Ver plan actual (sale de la suscripción espejada de Odoo, no de tenants)

Acciones Disponibles:

  • ✅ Crear nuevo tenant
  • ✅ Editar tenant (nombre) y su plan (tarjeta Plan → se guarda en Odoo)
  • ✅ Suspender/reactivar tenant
  • ✅ Acceder como admin (impersonación)

Sistema de Impersonación​

Características:

  • ✅ Banner visual cuando estás impersonando
  • ✅ Muestra nombre del tenant y hora de inicio
  • ✅ Botón rápido para salir
  • ✅ Log completo de todas las impersonaciones
  • ✅ Acceso completo como si fueras el admin del tenant

Logs de Auditoría:

Todas las impersonaciones quedan registradas en:

SELECT
il.id,
il.started_at,
il.ended_at,
p.full_name as superadmin_name,
t.name as tenant_name,
il.reason
FROM impersonation_logs il
JOIN profiles p ON p.id = il.superadmin_id
JOIN tenants t ON t.id = il.target_tenant_id
ORDER BY il.started_at DESC;

🔐 Seguridad​

Políticas RLS​

El sistema usa RLS (Row Level Security) simple:

  • Todos los usuarios autenticados pueden leer/escribir (como especificaste)
  • La verificación de superadmin se hace a nivel de aplicación y funciones RPC

Verificaciones​

En el Backend (RPCs):

-- Todas las funciones RPC verifican:
IF NOT public.is_superadmin() THEN
RAISE EXCEPTION 'Access denied. Superadmin privileges required.';
END IF;

En el Frontend:

// Guard de rutas
<SuperadminGuard>
{children}
</SuperadminGuard>

// En componentes
const { is_super_admin } = useAuthContext();
if (!is_super_admin) return null;

Auditoría​

Todas las acciones de superadmin quedan registradas en:

  1. impersonation_logs: Log de impersonaciones
  2. audit_logs: Log general de acciones
-- Ver acciones de un superadmin
SELECT
action,
object_type,
meta,
created_at
FROM audit_logs
WHERE user_id = '[SUPERADMIN_ID]'
ORDER BY created_at DESC;

📝 Flujo Completo de Impersonación​

1. Superadmin → Click "Acceder" en tenant
↓
2. Frontend → startImpersonation(tenant_id)
↓
3. Backend RPC → Verificar is_superadmin
↓
4. Backend RPC → Crear registro en impersonation_logs
↓
5. Backend RPC → Devolver tenant_id, workspace_id, role_id
↓
6. Frontend → Actualizar contexto de usuario
↓
7. Frontend → Mostrar banner de impersonación
↓
8. Frontend → Redirigir a /dashboard
↓
9. Usuario navega como admin del tenant
↓
10. Usuario → Click "Salir" en banner
↓
11. Frontend → endImpersonation(impersonation_id)
↓
12. Backend RPC → Marcar ended_at en log
↓
13. Frontend → Recargar página → Volver a /admin/tenants

🎨 Rutas del Sistema​

/admin/tenants → Lista de tenants (solo superadmin)
/admin/tenants/new → Crear nuevo tenant
/admin/tenants/[id]/edit → Editar tenant

🧪 Testing​

1. Crear Usuario Superadmin de Prueba​

-- Crear usuario superadmin de prueba
UPDATE auth.users
SET raw_user_meta_data = raw_user_meta_data || '{"is_super_admin": true}'::jsonb
WHERE email = '[email protected]';

2. Verificar Funciones RPC​

-- Verificar que funciona is_superadmin
SELECT is_superadmin();

-- Listar todos los tenants (como superadmin)
SELECT * FROM tenant_list_accessible();

3. Probar Impersonación​

  1. Login como superadmin
  2. Ir a /admin/tenants
  3. Click en "Acceder" en un tenant
  4. Verificar que aparece el banner
  5. Navegar por el dashboard
  6. Click "Salir" para terminar

⚠️ Notas Importantes​

  1. Admin del tenant nuevo: createTenantWithAdmin crea el usuario de Auth (con invited_flow: true) y luego el tenant. No hace falta registrarlo antes. Si el email ya existe, el alta falla a propósito para no duplicar memberships.

  2. Impersonación persiste en sesión: La impersonación se mantiene mientras no cierres sesión o hagas logout.

  3. RLS simple: Como pediste, las políticas RLS son simples (authenticated = true). La seguridad real está en las funciones RPC.

  4. Logs de auditoría: Todas las acciones quedan registradas. Revisa periódicamente los logs.


🐛 Troubleshooting​

Error: "Access denied. Superadmin privileges required"​

Causa: El usuario no tiene is_super_admin: true en su metadata.

Solución:

UPDATE auth.users
SET raw_user_meta_data = raw_user_meta_data || '{"is_super_admin": true}'::jsonb
WHERE email = '[email protected]';

Error: "El email ya está registrado"​

Causa: Al crear un tenant, el email del admin ya existe en auth.users.

Solución: Usa otro correo. Reutilizar un usuario existente le daría una segunda membership. Ver Alta de usuarios e invited_flow.

Causa: La impersonación no se detectó correctamente.

Solución:

  1. Verifica que hay un registro activo en impersonation_logs:
SELECT * FROM impersonation_logs WHERE ended_at IS NULL;
  1. Refresca la página (F5)

En “Entrar al tenant” no se pueden borrar usuarios​

Causa: remove_user_from_tenant no dejaba pasar a un superadmin sin membership.

Solución: aplicar supabase/migrations/20261008000001_remove_user_from_tenant_superadmin.sql. El botón ya estaba; fallaba la RPC.

Al crear un usuario aparece un tenant extra / al impersonar el menú está vacío​

Causa: createUser se llamó sin invited_flow: true. El trigger de signup creó un tenant personal y el usuario quedó con dos memberships.

Solución: las altas desde la UI ya pasan el flag. Para usuarios ya rotos, impersonar sigue funcionando (gana el tenant del log de impersonación). Limpieza de tenants huérfanos: scripts backend/scripts/repair-*-memberships.ts. Documentación: Alta de usuarios e invited_flow.


📞 Soporte​

Si tienes dudas o problemas:

  1. Revisa los logs del navegador (F12 → Console)
  2. Revisa los logs de Supabase (Logs & Analytics)
  3. Verifica las tablas de auditoría (impersonation_logs, audit_logs)

¡Sistema de Superadmin configurado exitosamente! 🎉