Los anuncios por grupos de chat se pierden, llegan a quien no corresponde y no dejan rastro de quién los leyó. TeamNotify los reemplaza por un canal dirigido: un administrador envía un mensaje a un puesto (por ejemplo, "Depósito") y cada trabajador de ese puesto lo recibe al instante en su propia bandeja, con confirmación de lectura.
Los trabajadores nunca ven mensajes de otros puestos y nadie puede saltarse las reglas, ni siquiera llamando a la API directamente.
Por qué importa
- Dirigido: los mensajes van a un puesto, no a todos.
- Con responsabilidad: cada trabajador tiene su propia confirmación de lectura por mensaje.
- Instantáneo: los mensajes aparecen en vivo, con una notificación.
- Controlado: las cuentas las crea un administrador, nunca se registran solas, y los roles se aplican en la base de datos, no solo en la interfaz.
- Reutilizable: puestos, textos y marca viven en un único archivo de configuración, así que la misma base sirve para cualquier equipo.
Qué hace
Panel de administración
- Crea trabajadores con contraseña temporal (con generador incluido) y puesto.
- Cambia el puesto de un trabajador o lo elimina, con confirmación.
- Envía mensajes a un puesto, y puede abrirlos, reasignarlos a otro puesto o eliminarlos.
- Busca por nombre, filtra por puesto y alterna entre las vistas Trabajadores y Mensajes.
Bandeja del trabajador
- Ve solo sus mensajes, los más nuevos primero, con insignia "Nuevo" y contador de no leídos.
- Al abrir un mensaje, queda marcado como leído.
- Actualizaciones en vivo: los mensajes nuevos aparecen con una notificación, y los eliminados o reasignados desaparecen.
Cuentas y acceso
- Inicio de sesión con email y contraseña, recuperación de contraseña y cambio obligatorio de contraseña en el primer ingreso.
- Solo dos roles:
adminyworker.
Qué pasa cuando se envía un mensaje
- El administrador envía el mensaje desde el panel. La Server Action verifica que sea admin y valida los datos.
- Se inserta en la base de datos, donde la política de seguridad solo permite escribir a administradores.
- Un trigger de Postgres crea una confirmación de lectura por cada trabajador del puesto.
- Supabase Realtime avisa a cada bandeja, que pide el mensaje (la base solo lo entrega si el usuario es destinatario) y lo muestra con una notificación.
- Al abrirlo, una función de la base lo marca como leído, solo para el propio usuario.
Si el administrador reasigna el mensaje a otro puesto, otro trigger retira las confirmaciones anteriores y entrega el mensaje al nuevo puesto como no leído.
Decisiones de diseño
- Tres capas de defensa (proxy, guardas de servidor y base de datos), para que el fallo de una no exponga datos.
- La clave de servicio nunca llega a la aplicación ni al navegador. Vive solo dentro de dos Edge Functions.
- Las reglas de negocio viven en la base de datos (triggers y funciones), así que se cumplen sin importar qué cliente llame a la API.
- La URL es la fuente de verdad de los filtros del panel: las vistas se pueden compartir y los datos llegan ya filtrados desde el servidor.
Seguridad
Defensa en profundidad: cada operación sensible se comprueba en varios niveles independientes.
- Proxy: refresca la sesión y redirige a
/logina quien no la tiene. Solo responde "¿hay sesión?"; a propósito no es la única protección. - Guardas de servidor:
requireAdmin()yrequireWorker()validan la sesión contra Supabase, cargan el perfil y comprueban rol y estado de la contraseña en cada página y cada acción. - Server Actions: vuelven a verificar el rol, validan y sanean toda entrada (tipos, longitudes, UUID, puestos permitidos) y nunca confían en el cliente.
- Row Level Security: un trabajador solo lee su propio perfil, sus confirmaciones y los mensajes que le fueron entregados.
- Privilegios por columna: aunque RLS permita un UPDATE, solo se pueden modificar columnas concretas.
role,nameypassword_changedno se pueden cambiar desde un cliente, lo que bloquea la escalada de privilegios. - Funciones
SECURITY DEFINER: puntos de entrada acotados que solo tocan la fila de quien llama. - Edge Functions: validan el token contra el servidor de Auth, confirman el rol
adminen la base y son el único lugar donde existe la clave de servicio. No pueden eliminar administradores. - Realtime: las suscripciones respetan las mismas políticas RLS. Una prueba verifica que un trabajador no recibe los eventos de otro.
Otras protecciones:
- El registro público está deshabilitado: las cuentas existen solo si un administrador las crea.
- El cambio de contraseña del primer ingreso se exige en el servidor, no solo en la interfaz.
- El callback de autenticación valida su parámetro
next(solo rutas internas), lo que evita redirecciones abiertas. - El texto de los usuarios se muestra siempre como texto plano, y la búsqueda se escapa para que
%y_coincidan literalmente. - Los errores de base de datos se registran solo en el servidor; el usuario ve un mensaje genérico.
- Una cuenta sin perfil va a una página dedicada, en lugar de fallar abierta.
Verificación
El script security-tests.mjs ataca la API real como lo haría un atacante, solo con la clave pública y tokens de usuario: más de 55 comprobaciones automatizadas que cubren acceso anónimo, escalada de privilegios, lectura entre puestos, escrituras directas a tablas protegidas, abuso de funciones RPC, autorización de las Edge Functions, aislamiento de Realtime y acceso sin sesión a las rutas. Crea mensajes de prueba etiquetados y los elimina al terminar.
El esquema SQL también se validó en una instancia limpia de PostgreSQL: se aplica sin errores y la entrega de mensajes, la reasignación, las cascadas y el modelo de permisos se comportan como está documentado.
Stack
- Next.js 16 (App Router, Server Components, Server Actions,
proxy.ts) y React 19. - TypeScript, Tailwind CSS v4 y shadcn/ui.
- Supabase: PostgreSQL, Auth, Row Level Security, Realtime y Edge Functions (Deno).
- Autenticación base generada con
@dariomvg/create(módulobasic-authde Supabase). Todo lo demás es propio: guardas de rol, carga de perfil, cambio obligatorio de contraseña, enrutado por rol y la interfaz.
Alcance y límites conocidos
- Las tablas muestran hasta 200 filas (todavía sin paginación).
- Los trabajadores creados después de enviar un mensaje no reciben los mensajes anteriores.
- Las Edge Functions no tienen límite de frecuencia; solo las pueden usar administradores.
- El servicio de email integrado de Supabase está limitado a pocos correos por hora, por lo que en producción hace falta un proveedor SMTP propio.
Lo que demuestra este proyecto
- Diseño de autorización en varias capas, con las reglas importantes dentro de la base de datos.
- Uso de Realtime respetando las políticas de acceso.
- Pruebas de seguridad que atacan el sistema real, no solo comprueban el camino feliz.
- Una base reutilizable: cambiar los puestos en un archivo adapta la app a otro equipo.
