# 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](#características-principales) - [Arquitectura del Sistema](#arquitectura-del-sistema) - [Componentes Principales](#componentes-principales) - [Uso Básico](#uso-básico) - [API del Backend](#api-del-backend) - [Estructura de Datos](#estructura-de-datos) - [Separación por Usuario](#separación-por-usuario) - [Limpieza Automática](#limpieza-automática) - [Sincronización entre Pestañas](#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:** ```typescript const { notifications, // Ref - Array reactivo de todas las notificaciones unreadCount, // ComputedRef - 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):** ```typescript 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 ```typescript 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 ```typescript const { markAsRead } = useNotifications() markAsRead('notification-id-123') ``` ### Obtener notificaciones filtradas ```typescript 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` ```typescript // 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 destino - `type` (string, requerido): Tipo de notificación (`'info'`, `'warning'`, `'success'`, `'error'`) - `title` (string, requerido): Título de la notificación - `message` (string, opcional): Mensaje descriptivo - `metadata` (object, opcional): Datos adicionales ### Obtener notificaciones pendientes **Endpoint:** `GET /api/notifications/list` ```typescript // 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` ```typescript 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 // Datos adicionales opcionales } ``` ### Ejemplo de notificación guardada ```json { "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 1. **Autenticación con Authentik:** - El usuario se autentica mediante Authentik Proxy Outpost - El header `x-authentik-uid` contiene el UID único del usuario 2. **Clave de localStorage:** ```typescript const storageKey = `notifications-${user.uid}` // Ejemplo: "notifications-a3f8d9e2b4c1a5d6" ``` 3. **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 ### 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 ```typescript // En useNotifications.ts const RETENTION_DAYS = 30 // Días de retención ``` ### Cuándo se ejecuta 1. **Al iniciar la aplicación:** - Se ejecuta `cleanupOldNotifications()` en `app.vue` - Elimina notificaciones mayores a 30 días 2. **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: ```typescript // 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 ```typescript // 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 1. **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 2. **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` ```typescript // 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 1. **App se monta** → `onMounted()` se ejecuta 2. **Usuario autenticado** → Se obtiene el UID de Authentik 3. **Carga desde localStorage** → Se cargan notificaciones del usuario 4. **Limpieza automática** → Se eliminan notificaciones > 30 días 5. **Configurar sincronización** → Se activa el listener de `storage` 6. **Sistema listo** → El usuario puede ver su historial --- ## Casos de Uso ### 1. Notificación de operación exitosa ```typescript // 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 ```typescript // 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 ```typescript 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: 1. **Persistencia en base de datos:** - Migrar de localStorage a Supabase - Acceso desde cualquier dispositivo - Historial ilimitado 2. **Notificaciones push:** - Integración con Web Push API - Notificaciones incluso cuando la app está cerrada 3. **Categorías personalizadas:** - Permitir al usuario crear categorías - Filtros más granulares 4. **Acciones en notificaciones:** - Botones de acción (Descargar, Ver más, Abrir) - Ejecutar código al hacer clic 5. **Notificaciones programadas:** - Enviar notificaciones en fecha/hora específica - Recordatorios recurrentes 6. **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: 1. Revisar los logs en la consola del navegador 2. Verificar que el UID de Authentik esté presente 3. Comprobar el localStorage en DevTools: `localStorage.getItem('notifications-{uid}')` 4. Crear un issue en el repositorio con los detalles --- **Última actualización:** 2025-10-30 **Versión:** 1.0.0