Grupos jerárquicos de 6 niveles: la característica más compleja
Puntos clave: Construir un sistema de grupos jerárquicos de 6 niveles de profundidad con 4 roles, consultas recursivas PostgreSQL (CTE) y 31 políticas RLS es la característica más compleja de TAMSIV. Este artículo cubre el modelo de datos, la herencia de permisos, el componente frontend HierarchicalGroupPicker y las lecciones aprendidas.
Hay características que parecen simples en el papel. "Añadir grupos" — suena inocente. Como añadir un carrito en una aplicación de comercio electrónico. Luego empiezas a investigar y te das cuenta de que acabas de abrir la caja de Pandora.
TAMSIV soporta grupos jerárquicos de hasta 6 niveles de profundidad. Mi club de buceo fue el caso de uso perfecto: Club → Comisión Técnica → Nivel 1 → Grupo del martes. Una familia usa: Familia → Casa → Cocina / Jardín / Garaje. Una PYME: Empresa → Departamento → Equipo → Proyecto.
Esta es la característica que más tiempo me ha llevado. También es la que hace que TAMSIV sea utilizable para organizaciones reales, no solo para uso individual.
¿Cómo modelar grupos jerárquicos en PostgreSQL?
El modelo de datos se basa en un concepto simple: cada grupo tiene un parent_id que apunta a su grupo padre. Un grupo raíz tiene parent_id = NULL.
-- Tabla simplificada
CREATE TABLE collaborative.groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
parent_id UUID REFERENCES collaborative.groups(id),
created_by UUID REFERENCES auth.users(id),
depth INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now()
);
Para mostrar el árbol completo de un grupo, utilizo consultas recursivas (Common Table Expressions, o CTE) en PostgreSQL:
WITH RECURSIVE group_tree AS (
-- Base: el grupo raíz
SELECT id, name, parent_id, 0 AS depth
FROM collaborative.groups
WHERE id = p_group_id
UNION ALL
-- Recursión: los subgrupos
SELECT g.id, g.name, g.parent_id, gt.depth + 1
FROM collaborative.groups g
JOIN group_tree gt ON g.parent_id = gt.id
WHERE gt.depth < 6 -- Salvaguarda
)
SELECT * FROM group_tree;
La cláusula WHERE depth < 6 es una salvaguarda crítica. Sin ella, un error de datos (un ciclo de referencias, por ejemplo) haría que la consulta se ejecutara en un bucle infinito. Seis niveles cubren todos los casos de uso reales que he encontrado.
Este modelo de datos reside en el esquema collaborative, separado del resto — explico por qué en el artículo sobre la estructuración de la base de datos.
¿Cuáles son los cuatro roles y cómo funcionan?
Cada miembro de un grupo tiene un rol que determina sus permisos:
- Admin — Hacer todo: crear, modificar, eliminar, gestionar miembros, invitar, modificar la configuración del grupo. Es el creador del grupo por defecto.
- Manager — Gestionar el contenido: crear/modificar/eliminar tareas y notas, asignar miembros, validar listas de verificación. Pero no gestionar miembros ni la configuración.
- Member — Contribuir: crear contenido, ver todo el contenido del grupo, validar sus propios elementos de lista de verificación.
- Viewer — Solo lectura: ver el contenido, nada más. Útil para observadores o partes interesadas que quieren seguir el progreso sin intervenir.
La trampa de la herencia de permisos
Si tú eres Admin del grupo "Club de buceo", ¿eres automáticamente Admin de "Comisión Técnica" (un subgrupo)? En TAMSIV, la respuesta es sí — pero con matices.
La herencia funciona hacia abajo: un Admin de un grupo padre tiene los mismos derechos en todos los subgrupos. Pero un Admin de un subgrupo no tiene ningún derecho en el grupo padre. Esta asimetría es natural (el director lo ve todo, el jefe de equipo ve a su equipo), pero añade complejidad a las consultas SQL.
Para verificar los permisos, cada consulta debe subir por el árbol jerárquico hasta encontrar un rol o alcanzar la raíz:
-- ¿Tiene el usuario un rol en este grupo o en un padre?
WITH RECURSIVE parent_chain AS (
SELECT id, parent_id FROM collaborative.groups WHERE id = p_group_id
UNION ALL
SELECT g.id, g.parent_id
FROM collaborative.groups g
JOIN parent_chain pc ON g.id = pc.parent_id
)
SELECT role FROM collaborative.group_members
WHERE group_id IN (SELECT id FROM parent_chain)
AND user_id = auth.uid()
ORDER BY role ASC -- Admin > Manager > Member > Viewer
LIMIT 1;
¿Cómo escribir y probar 31 políticas RLS?
Este es el número que duele: 31 políticas de Row Level Security para el esquema collaborative. Supabase utiliza RLS para asegurar el acceso a los datos — cada tabla tiene reglas que determinan quién puede leer, escribir, modificar, eliminar.
Aquí tienes ejemplos concretos:
Política de lectura simple
-- Un miembro puede ver las tareas de sus grupos
CREATE POLICY "members_read_tasks" ON collaborative.group_tasks
FOR SELECT USING (
EXISTS (
SELECT 1 FROM collaborative.group_members gm
WHERE gm.group_id = group_tasks.group_id
AND gm.user_id = auth.uid()
)
);
Política con herencia jerárquica
-- Un admin puede eliminar las tareas de sus grupos Y subgrupos
CREATE POLICY "admin_delete_tasks" ON collaborative.group_tasks
FOR DELETE USING (
EXISTS (
WITH RECURSIVE parent_chain AS (
SELECT id, parent_id FROM collaborative.groups
WHERE id = group_tasks.group_id
UNION ALL
SELECT g.id, g.parent_id
FROM collaborative.groups g
JOIN parent_chain pc ON g.id = pc.parent_id
)
SELECT 1 FROM collaborative.group_members gm
WHERE gm.group_id IN (SELECT id FROM parent_chain)
AND gm.user_id = auth.uid()
AND gm.role = 'admin'
)
);
Probar 31 políticas significa un mínimo de 31 escenarios de prueba. En realidad, es mucho más, porque cada política debe ser probada positivamente (el acceso se concede correctamente) Y negativamente (el acceso se deniega correctamente). Metódico, aburrido, indispensable. Los detalles sobre el enfoque de prueba están en el artículo sobre la auditoría de seguridad.
¿Cómo construir el componente HierarchicalGroupPicker?
En el frontend, mostrar un árbol de grupos con sangría, iconos de rol e interacciones es un desafío de UI por sí mismo.
El HierarchicalGroupPicker es un componente React Native que:
- Muestra el árbol con sangría proporcional a la profundidad (utilizando
spacing()del sistema de escalado). - Indica el rol del usuario en cada grupo a través de un icono de color.
- Permite la selección con un interruptor "incluir subgrupos" — cuando seleccionas un grupo padre, los subgrupos pueden incluirse automáticamente.
- Soporta plegar/desplegar: los subgrupos son plegables para no saturar la pantalla en jerarquías profundas.
Este componente se reutiliza en todas partes: en el filtro de la agenda, en la creación de tareas, en la pantalla de gestión de grupos. Es una inversión de tiempo enorme al principio, pero un ahorro de tiempo colosal después.
El patrón FilterBar
El HierarchicalGroupPicker funciona con la FilterBar, un componente de filtrado con 3 contextos (Privado / Compartido / Todo) y modos (todas las tareas / creadas por mí / asignadas a mí). La combinación de ambos permite consultas muy precisas: "muéstrame las tareas asignadas a mí en el grupo Familia y sus subgrupos".
¿Cuáles son los casos de uso reales de los grupos jerárquicos?
Aquí están los casos de uso que mis probadores utilizan activamente:
- Familia: Familia → Casa (tareas domésticas) / Vacaciones (planificación) / Escuela (deberes de los niños). Hablo de ello en el artículo sobre la organización familiar.
- Club deportivo: Club → Comisiones → Niveles → Grupos por franja horaria.
- Pequeño equipo: Empresa → Departamento → Proyecto → Sprint.
- Asociación: Asociación → Área → Evento.
El límite de 6 niveles rara vez se alcanza. En la práctica, 3-4 niveles cubren el 95% de las necesidades. Pero esta profundidad máxima es una red de seguridad — es mejor tenerla y no necesitarla.
¿Qué lecciones se pueden extraer de esta implementación?
- Las consultas recursivas de PostgreSQL son potentes pero costosas: Cada consulta que sube por la jerarquía realiza N uniones (donde N = la profundidad). Un índice en
parent_ides indispensable. - Las políticas RLS se acumulan rápidamente: Cada tabla × cada operación (SELECT, INSERT, UPDATE, DELETE) × cada rol = muchas políticas. Documéntalas en un archivo dedicado.
- La herencia de permisos es el punto más delicado: Decide pronto si los permisos se propagan hacia abajo, hacia arriba o ambos. Cambiar esta decisión más tarde es extremadamente costoso.
- El componente frontend es una inversión: Un buen HierarchicalGroupPicker tarda una semana en construirse correctamente, pero se reutiliza en todas partes.
- Prueba con datos reales: Un árbol de 3 grupos en desarrollo no revela los mismos errores que un árbol de 15 grupos en 4 niveles en producción.
Si construyes un sistema colaborativo, la jerarquía es lo que transforma una simple "lista de grupos" en una herramienta que las organizaciones adoptan realmente. Es difícil, es largo, pero es lo que marca la diferencia con los competidores que se limitan a grupos planos.
Preguntas Frecuentes
¿Por qué limitar a 6 niveles de profundidad?
Seis niveles cubren todos los casos de uso reales que he encontrado. Más allá, el árbol se vuelve inmanejable para el usuario. El límite también protege contra consultas recursivas demasiado profundas que podrían afectar el rendimiento. En la práctica, 3-4 niveles son suficientes para el 95% de las organizaciones.
¿Las consultas recursivas plantean problemas de rendimiento?
Con un índice en parent_id y un límite de profundidad, el rendimiento es excelente incluso con cientos de grupos. Una consulta recursiva de 6 niveles tarda menos de 10ms en Supabase PostgreSQL. El verdadero riesgo es un ciclo de referencias (grupo A padre de B, B padre de A) — la cláusula WHERE depth < 6 protege contra eso.
¿Cómo gestionar la invitación de nuevos miembros?
El administrador de un grupo puede invitar por correo electrónico o mediante un enlace para compartir. El invitado se une al grupo con el rol elegido por el administrador. Si el grupo tiene subgrupos, el invitado solo tiene acceso al grupo al que fue invitado — a menos que un administrador del grupo padre le dé acceso explícitamente.
¿Las listas de verificación de grupo funcionan de manera diferente a las listas de verificación personales?
Sí. Las listas de verificación de grupo tienen dos modos de validación: "single" (una sola persona valida para todos) y "everyone" (cada miembro debe validar individualmente). El modo "everyone" es perfecto para las listas de compras familiares donde nadie compra lo mismo.
¿Se puede mover un grupo a otro grupo padre?
Sí, un administrador puede reorganizar la jerarquía cambiando el parent_id de un grupo. Pero es una operación delicada: los permisos heredados cambian inmediatamente, y los miembros del nuevo padre ven el contenido del grupo movido. Un mensaje de confirmación explícito advierte al administrador de las consecuencias.