Ir al contenido
Portal de documentación del EGD
Menú de la sección

Gestión de sesión

Segunda familia de la página interna /design-system/componentes. Referencia: captura aportada de dos modales centrados sobre una superficie oscura. Se observaron título destacado, descripción corta, anillo con mm:ss, cierre en el aviso, icono de advertencia en expirado y acciones de contorno. El SGD aplica closePolicy="blocked" a ambos estados según la regla posterior del producto: el aviso tampoco muestra X ni se cierra con Escape o clic exterior. La captura no define duración de inactividad, fecha de expiración, comportamiento de renovación, teclado, error ni modo oscuro; esos comportamientos son reglas del SGD descritas aquí.

Estado real de la autenticación

El proyecto usa NextAuth/Auth.js v5 con Zitadel en src/lib/auth/auth.ts. La sesión JWT tiene maxAge de 12 horas. El callback jwt intenta renovar el token de Zitadel cuando faltan cinco minutos para su vencimiento. Son dos expiraciones distintas: la cookie de sesión Auth.js y el ID token de Zitadel. No existe en el proyecto un plazo de inactividad del backend, un evento de cierre por inactividad ni un umbral de aviso definido para las pantallas protegidas. Por eso esta entrega no monta todavía un temporizador global en /admin o /dashboard ni presenta 49 segundos como tiempo de seguridad real.

El GET de sesión ya registrado en /api/auth/session renueva la expiración de la cookie JWT si la sesión aún es válida. renewExistingSession() en src/lib/auth/renew-session-client.ts lo invoca solo al pulsar Continuar sesión y exige expires válido, ID token presente y ausencia de error de refresh. El diálogo también exige que la nueva fecha sea posterior a la anterior y al momento actual antes de cerrarse. No se añadió endpoint ni se cambiaron los tiempos de auth.ts.

API

import { SessionManagementDialog } from '@/components/ui/session-management-dialog'

<SessionManagementDialog
  expiresAt={expiryFromAuthoritativeSession}
  warningStartedAt={warningStartFromSessionPolicy}
  open={warningOpen}
  onOpenChange={setWarningOpen}
/>
  • expiresAt es una fecha absoluta en milisegundos Unix. No pasar una duración ni reiniciarla al volver a renderizar.
  • warningStartedAt es la fecha absoluta de inicio del periodo de aviso; solo calcula el porcentaje del anillo. El texto mm:ss siempre se calcula como expiresAt - Date.now().
  • onRenew puede inyectarse en pruebas; si se omite usa el endpoint de sesión de Auth.js existente.
  • onSignIn puede inyectarse en pruebas; si se omite navega a /auth/signin preservando la ruta actual como callbackUrl.
  • Aviso y expiración usan closePolicy="blocked": no hay X y Escape ni clic en el fondo cierran el diálogo. «Continuar sesión» cierra solo tras confirmar una nueva fecha de expiración válida; si falla, muestra la alerta flotante «No se pudo renovar la sesión. Inténtalo de nuevo.» y mantiene el aviso para reintentar. Al llegar a cero pasa al estado expirado.
  • En expirado, solo «Iniciar sesión» inicia el flujo de autenticación. El modal de Radix bloquea la interacción con el contenido subyacente. El acceso protegido en servidor sigue bajo src/proxy.ts.

La demo del catálogo inyecta fechas y respuestas simuladas; nunca llama a Auth.js ni cierra la sesión real. Ofrece aviso de 49 segundos, transición acelerada de tres segundos, expiración inicial y error en la siguiente renovación. La sesión real no debe usar esos valores de prueba.

Accesibilidad y tokens

El componente reutiliza DialogContent, DialogHeader, DialogBody, DialogFooter, DialogTitle y DialogDescription de Radix/shadcn. Aviso y expiración usan --sgd-session-dialog-height; el contador permanece en el cuerpo desplazable y ambas acciones ocupan la misma posición del pie, incluso cuando cambia la longitud de la descripción. Los errores generales de renovación usan la alerta flotante compartida, fuera del cuerpo, por lo que no alteran la altura ni añaden scroll. El foco queda atrapado en el modal; al confirmar la renovación, el flujo que abrió el aviso recupera el control de la pantalla. El contador tiene role="timer" y aria-live="off" para evitar anuncios cada segundo; la descripción se anuncia al abrir y la alerta expone su mensaje sin mover el foco. El botón de carga usa aria-busy. El icono de advertencia acompaña el 00:00, y la alerta de error tiene texto e icono.

El anillo usa --sgd-session-ring (vinculado a primary) y --sgd-session-track; la placa de advertencia usa --sgd-session-warning-badge y --sgd-session-warning-foreground. La vista comparativa usa --sgd-session-preview-backdrop, una superficie neutra translúcida con desenfoque sutil y borde, en lugar del fondo carbón de la captura inicial. Estos tokens están en src/app/globals.css; tamaño, radio y tipografía siguen las reglas compartidas. Theme LATAM, Theme Adobe Spectrum y Theme Institucional solo cambian la paleta, en claro y oscuro.

El tramo restante del anillo se consume en sentido horario desde la parte superior. Su progreso, igual que mm:ss, se calcula con fechas absolutas; no se mantiene un contador paralelo.

En expirado, el centro de la placa de advertencia se sitúa sobre la circunferencia a 45° en el cuadrante superior derecho. La posición se deriva del radio y del viewBox como porcentaje del contador, de modo que acompaña sus cambios de tamaño. El trazo queda detrás de la placa y su borde fino usa el color de la superficie para separar ambos elementos.

Pendiente para producción

  1. Definir con el servicio de autenticación el plazo real de inactividad y el momento de aviso; no derivarlos del ejemplo visual ni confundirlos con el refresh del ID token.
  2. Exponer o confirmar desde la autoridad de sesión una fecha absoluta de expiración estable para cada vista protegida. Una consulta a /api/auth/session extiende la cookie; no usar sondeos frecuentes para dibujar el contador.
  3. Montar el diálogo/guardia en ambos layouts protegidos, sincronizar la fecha si otra pestaña o navegación renueva la sesión y bloquear el contenido al caducar. Verificar que el retorno a /auth/signin obligue a reautenticar si el plazo de inactividad ya venció.
  4. Probar el flujo con sesiones reales de Zitadel, pérdida de red, error de refresh, varias pestañas y retorno de una pestaña suspendida. Mantener las comprobaciones del servidor (src/proxy.ts y el backend) como fuente de autorización.