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
558 lines
17 KiB
Markdown
558 lines
17 KiB
Markdown
# 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
|