- Explicación de arquitectura y componentes - Guía de uso para desarrolladores - Ejemplos de código y casos de uso - Detalles de separación por usuario - Configuración de limpieza automática - Sincronización entre pestañas - FAQ y mejoras futuras
17 KiB
Sistema de Notificaciones
Sistema de notificaciones con historial persistente por usuario, implementado con localStorage y separación automática basada en el UID de Authentik.
Tabla de Contenidos
- Características Principales
- Arquitectura del Sistema
- Componentes Principales
- Uso Básico
- API del Backend
- Estructura de Datos
- Separación por Usuario
- Limpieza Automática
- Sincronización entre Pestañas
Características Principales
✅ Historial separado por usuario: Cada usuario tiene su propio historial usando su UID de Authentik como clave
✅ Persistencia local: Las notificaciones se guardan en localStorage del navegador
✅ Guardado automático de toasts: Todos los toasts se guardan automáticamente en el historial
✅ Auto-limpieza: Notificaciones mayores a 30 días se eliminan automáticamente
✅ Sincronización entre pestañas: Los cambios se reflejan en tiempo real en todas las pestañas
✅ Filtrado y búsqueda: Filtrar por tipo (info, warning, success, error) y buscar por texto
✅ Múltiples orígenes: Soporta notificaciones desde toasts, backend y manuales
Arquitectura del Sistema
┌─────────────────────────────────────────────────────────────┐
│ Aplicación Nuxt │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ useNotifications() │ │
│ │ - Gestión de notificaciones │ │
│ │ - Filtrado y búsqueda │ │
│ │ - Limpieza automática │ │
│ └──────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ useToast() │ │
│ │ - Wrapper que intercepta toasts │ │
│ │ - Guarda automáticamente en historial │ │
│ └──────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ API Endpoints (Backend) │ │
│ │ - /api/notifications/send │ │
│ │ - /api/notifications/list │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ localStorage │
│ notifications-{uid} │
└────────────────────────┘
Componentes Principales
1. Composable useNotifications()
Ubicación: app/composables/useNotifications.ts
El composable principal que gestiona todo el sistema de notificaciones.
Funciones disponibles:
const {
notifications, // Ref<Notification[]> - Array reactivo de todas las notificaciones
unreadCount, // ComputedRef<number> - Contador de notificaciones no leídas
addNotification, // Agregar una notificación al historial
markAsRead, // Marcar una notificación como leída
markAllAsRead, // Marcar todas como leídas
removeNotification, // Eliminar una notificación específica
clearAll, // Eliminar todas las notificaciones
cleanupOldNotifications, // Eliminar notificaciones > 30 días
getFilteredNotifications, // Obtener notificaciones filtradas
initialize // Inicializar el sistema (se llama automáticamente)
} = useNotifications()
2. Wrapper useToast()
Ubicación: app/composables/useToast.ts
Wrapper transparente que intercepta todos los toasts de Nuxt UI y los guarda automáticamente en el historial.
Uso (sin cambios en código existente):
const toast = useToast()
// Funciona exactamente igual que antes, pero ahora se guarda en el historial
toast.add({
title: 'Operación exitosa',
description: 'Los datos se guardaron correctamente',
color: 'success'
})
3. API del Backend
Ubicación: server/api/notifications/
Endpoints para crear y obtener notificaciones desde el servidor.
Uso Básico
Agregar una notificación manualmente
const { addNotification } = useNotifications()
addNotification({
type: 'success',
title: 'Proceso completado',
message: 'El reporte se generó correctamente',
origin: 'manual',
metadata: {
reportId: 123,
timestamp: new Date().toISOString()
}
})
Marcar notificación como leída
const { markAsRead } = useNotifications()
markAsRead('notification-id-123')
Obtener notificaciones filtradas
const { getFilteredNotifications } = useNotifications()
// Solo notificaciones de error no leídas
const errorNotifications = getFilteredNotifications({
type: ['error'],
read: false
})
// Buscar por texto
const searchResults = getFilteredNotifications({
search: 'reporte'
})
// Limitar resultados
const latest = getFilteredNotifications({
limit: 10
})
API del Backend
Enviar notificación desde el servidor
Endpoint: POST /api/notifications/send
// Desde cualquier endpoint del servidor
const response = await $fetch('/api/notifications/send', {
method: 'POST',
body: {
targetUserId: 'uid-del-usuario-destino',
type: 'info',
title: 'Nueva actualización',
message: 'Hay una nueva versión disponible',
metadata: {
version: '2.0.0',
changelog: 'https://...'
}
}
})
Parámetros:
targetUserId(string, requerido): UID del usuario destinotype(string, requerido): Tipo de notificación ('info','warning','success','error')title(string, requerido): Título de la notificaciónmessage(string, opcional): Mensaje descriptivometadata(object, opcional): Datos adicionales
Obtener notificaciones pendientes
Endpoint: GET /api/notifications/list
// Desde el cliente (automático)
const { data } = await $fetch('/api/notifications/list')
console.log(data.notifications) // Array de notificaciones pendientes
console.log(data.count) // Número de notificaciones
Nota: Este endpoint usa una cola en memoria. En producción, considera usar Redis o una base de datos para persistencia.
Estructura de Datos
Interfaz Notification
interface Notification {
id: string // ID único generado automáticamente
type: 'info' | 'warning' | 'success' | 'error'
title: string // Título de la notificación
message: string // Mensaje descriptivo
timestamp: number // Timestamp en milisegundos
read: boolean // Estado de lectura
origin: 'toast' | 'backend' | 'manual' // Origen de la notificación
metadata?: Record<string, any> // Datos adicionales opcionales
}
Ejemplo de notificación guardada
{
"id": "1730328465123-a3f8d9e2b",
"type": "success",
"title": "Tema guardado",
"message": "Los cambios se aplicaron correctamente",
"timestamp": 1730328465123,
"read": false,
"origin": "toast",
"metadata": {
"icon": "i-lucide-check",
"color": "green",
"hasActions": false
}
}
Separación por Usuario
El sistema utiliza el UID de Authentik como clave única para separar las notificaciones de cada usuario.
Cómo funciona
-
Autenticación con Authentik:
- El usuario se autentica mediante Authentik Proxy Outpost
- El header
x-authentik-uidcontiene el UID único del usuario
-
Clave de localStorage:
const storageKey = `notifications-${user.uid}` // Ejemplo: "notifications-a3f8d9e2b4c1a5d6" -
Cambio de usuario:
- Al detectar un cambio en el UID, el sistema automáticamente:
- Guarda las notificaciones del usuario anterior
- Carga las notificaciones del nuevo usuario
- Limpia el estado en memoria
- Al detectar un cambio en el UID, el sistema automáticamente:
Ejemplo práctico
Usuario A (UID: abc123):
localStorage["notifications-abc123"] = [notif1, notif2, notif3]
Usuario B (UID: xyz789):
localStorage["notifications-xyz789"] = [notif4, notif5]
// Cada usuario solo ve sus propias notificaciones
// Incluso si usan el mismo navegador en la misma máquina
Limpieza Automática
El sistema elimina automáticamente las notificaciones antiguas para evitar acumulación excesiva.
Configuración
// En useNotifications.ts
const RETENTION_DAYS = 30 // Días de retención
Cuándo se ejecuta
-
Al iniciar la aplicación:
- Se ejecuta
cleanupOldNotifications()enapp.vue - Elimina notificaciones mayores a 30 días
- Se ejecuta
-
Al cargar notificaciones:
- Las notificaciones antiguas se filtran automáticamente
- Solo se cargan las recientes
Modificar el período de retención
Si necesitas cambiar el período de retención, modifica la constante:
// app/composables/useNotifications.ts
const RETENTION_DAYS = 60 // Cambiar a 60 días
Sincronización entre Pestañas
El sistema mantiene sincronizadas todas las pestañas del navegador usando el evento storage.
Cómo funciona
// Cuando cambia el localStorage en una pestaña...
window.addEventListener('storage', (event) => {
if (event.key === storageKey && event.newValue) {
// ...todas las demás pestañas se actualizan automáticamente
notifications.value = JSON.parse(event.newValue)
}
})
Casos de uso
-
Usuario agrega notificación en Pestaña A:
- Se guarda en localStorage
- Pestaña B y C reciben el evento
storage - Todas las pestañas muestran la nueva notificación
-
Usuario elimina notificación en Pestaña B:
- Se actualiza localStorage
- Pestaña A y C se sincronizan automáticamente
Página de Notificaciones
Ubicación: app/pages/notifications.vue
Características
- ✅ Lista completa de notificaciones del usuario actual
- ✅ Búsqueda por texto (título y mensaje)
- ✅ Filtros por tipo (info, warning, success, error)
- ✅ Marcar como leídas (individual o todas)
- ✅ Eliminar (individual o todas)
- ✅ Indicadores de tiempo relativo ("hace 2 horas", "hace 3 días")
- ✅ Badges de origen (toast, backend, manual)
- ✅ Estados vacíos informativos
Badge en Sidebar
Ubicación: app/components/app/AppSidebar.vue
- Muestra el número de notificaciones no leídas
- Se oculta automáticamente cuando no hay notificaciones
- Indicador visual (punto rojo) cuando hay notificaciones pendientes
Inicialización del Sistema
Ubicación: app/app.vue
// Se inicializa automáticamente al montar la aplicación
const { initialize, cleanupOldNotifications } = useNotifications()
onMounted(() => {
initialize() // Inicializar el sistema
cleanupOldNotifications() // Limpiar notificaciones antiguas
})
Flujo de inicialización
- App se monta →
onMounted()se ejecuta - Usuario autenticado → Se obtiene el UID de Authentik
- Carga desde localStorage → Se cargan notificaciones del usuario
- Limpieza automática → Se eliminan notificaciones > 30 días
- Configurar sincronización → Se activa el listener de
storage - Sistema listo → El usuario puede ver su historial
Casos de Uso
1. Notificación de operación exitosa
// Desde cualquier componente
const toast = useToast()
async function saveData() {
try {
await $fetch('/api/save', { method: 'POST', body: data })
// Se guarda automáticamente en el historial
toast.add({
title: 'Datos guardados',
description: 'Los cambios se guardaron correctamente',
color: 'success'
})
} catch (error) {
toast.add({
title: 'Error al guardar',
description: error.message,
color: 'error'
})
}
}
2. Notificación desde el backend
// server/api/generate-report.post.ts
export default defineEventHandler(async (event) => {
const headers = getHeaders(event)
const userId = headers['x-authentik-uid']
// Generar el reporte...
const report = await generateReport()
// Notificar al usuario
await $fetch('/api/notifications/send', {
method: 'POST',
body: {
targetUserId: userId,
type: 'success',
title: 'Reporte generado',
message: `El reporte ${report.name} está listo para descargar`,
metadata: {
reportId: report.id,
downloadUrl: report.url
}
}
})
return { success: true, report }
})
3. Consultar historial programáticamente
const { getFilteredNotifications } = useNotifications()
// Obtener últimas 5 notificaciones de error
const recentErrors = getFilteredNotifications({
type: ['error'],
limit: 5
})
// Mostrar en consola
recentErrors.forEach(notif => {
console.error(`[${notif.timestamp}] ${notif.title}: ${notif.message}`)
})
Mejoras Futuras
Posibles extensiones del sistema:
-
Persistencia en base de datos:
- Migrar de localStorage a Supabase
- Acceso desde cualquier dispositivo
- Historial ilimitado
-
Notificaciones push:
- Integración con Web Push API
- Notificaciones incluso cuando la app está cerrada
-
Categorías personalizadas:
- Permitir al usuario crear categorías
- Filtros más granulares
-
Acciones en notificaciones:
- Botones de acción (Descargar, Ver más, Abrir)
- Ejecutar código al hacer clic
-
Notificaciones programadas:
- Enviar notificaciones en fecha/hora específica
- Recordatorios recurrentes
-
Sistema de preferencias:
- Configurar qué notificaciones recibir
- Silenciar notificaciones por tipo
- Modo "No molestar"
Preguntas Frecuentes
¿Las notificaciones se pierden al cerrar el navegador?
No, las notificaciones se guardan en localStorage y persisten entre sesiones.
¿Qué pasa si dos usuarios usan el mismo navegador?
Cada usuario tiene su propio historial separado por su UID de Authentik. Al cambiar de usuario (logout/login), el sistema carga automáticamente las notificaciones del nuevo usuario.
¿Cuántas notificaciones se pueden guardar?
El límite lo impone localStorage (aproximadamente 5-10 MB dependiendo del navegador). Con la limpieza automática de 30 días, es poco probable alcanzar este límite.
¿Se pueden recuperar notificaciones eliminadas?
No, una vez eliminadas del localStorage, no se pueden recuperar. Considera implementar persistencia en base de datos si necesitas esta funcionalidad.
¿Las notificaciones son privadas?
Sí, están almacenadas localmente en el navegador del usuario. Solo son accesibles por el usuario que las recibió y en el dispositivo donde se guardaron.
Soporte
Para reportar problemas o sugerencias sobre el sistema de notificaciones:
- Revisar los logs en la consola del navegador
- Verificar que el UID de Authentik esté presente
- Comprobar el localStorage en DevTools:
localStorage.getItem('notifications-{uid}') - Crear un issue en el repositorio con los detalles
Última actualización: 2025-10-30 Versión: 1.0.0