Docs: Agregar documentación completa del sistema de notificaciones
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 9s

- 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
This commit is contained in:
2025-10-30 18:15:15 -06:00
parent b6dc08e599
commit 9979c148a9

View File

@@ -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<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):**
```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<string, any> // 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