From 9979c148a9e30c4f4f9f4fb42778c87aa38721ee Mon Sep 17 00:00:00 2001 From: josedario87 Date: Thu, 30 Oct 2025 18:15:15 -0600 Subject: [PATCH] =?UTF-8?q?Docs:=20Agregar=20documentaci=C3=B3n=20completa?= =?UTF-8?q?=20del=20sistema=20de=20notificaciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docs/sistema-notificaciones.md | 557 +++++++++++++++++++++++++++++++++ 1 file changed, 557 insertions(+) create mode 100644 docs/sistema-notificaciones.md diff --git a/docs/sistema-notificaciones.md b/docs/sistema-notificaciones.md new file mode 100644 index 0000000..c194fe5 --- /dev/null +++ b/docs/sistema-notificaciones.md @@ -0,0 +1,557 @@ +# 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