Added notifications!!

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-29 12:28:11 -06:00
parent 9e3cc3a03f
commit fef8ab225d
24 changed files with 3596 additions and 20 deletions

View File

@@ -0,0 +1,617 @@
# Prompt de Implementación: Integración de Notificaciones en el Frontend
## Resumen Ejecutivo
Se ha implementado una nueva **API de Notificaciones** en el backend que se integra automáticamente cuando el estado de los reportes cambia. Tu tarea es implementar la interfaz de usuario para que los usuarios puedan ver, gestionar y ser notificados de los cambios en sus reportes.
---
## 1. Arquitectura Backend - Información de Referencia
### Endpoints de la API de Notificaciones (Puerto 8002)
#### 1.1 Crear Notificación (Interno)
```
POST /notifications/
Content-Type: application/json
{
"id_usuario": 1,
"id_reporte": "uuid-del-reporte",
"message": "¡Tu reporte #uuid ha sido resuelto!"
}
Response: 200 OK
{
"id_notificacion": "ObjectId",
"id_usuario": 1,
"id_reporte": "uuid-del-reporte",
"message": "¡Tu reporte #uuid ha sido resuelto!",
"fecha": "2024-04-29T15:30:00Z",
"read": false
}
```
#### 1.2 Obtener Notificaciones de un Usuario (IMPORTANTE)
```
GET /notifications/{user_id}?limit=50&offset=0
Authorization: Bearer {jwt_token}
Query Parameters:
- limit: int (1-100, default: 50) - Número máximo de notificaciones
- offset: int (default: 0) - Paginación
Response: 200 OK
{
"total": 150,
"unread_count": 5,
"notifications": [
{
"id_notificacion": "ObjectId_1",
"id_usuario": 1,
"id_reporte": "uuid-reporte-1",
"message": "¡Tu reporte ha sido resuelto!",
"fecha": "2024-04-29T15:30:00Z",
"read": false
},
{
"id_notificacion": "ObjectId_2",
"id_usuario": 1,
"id_reporte": "uuid-reporte-2",
"message": "Tu reporte fue marcado como no resuelto.",
"fecha": "2024-04-29T14:20:00Z",
"read": true
}
]
}
```
#### 1.3 Obtener Conteo de No Leídas
```
GET /notifications/{user_id}/unread-count
Authorization: Bearer {jwt_token}
Response: 200 OK
{
"unread_count": 5
}
```
#### 1.4 Marcar como Leída
```
PUT /notifications/{notification_id}/read
Authorization: Bearer {jwt_token}
Response: 200 OK
{
"id_notificacion": "ObjectId",
"id_usuario": 1,
"id_reporte": "uuid",
"message": "¡Tu reporte ha sido resuelto!",
"fecha": "2024-04-29T15:30:00Z",
"read": true
}
```
#### 1.5 Marcar Todas como Leídas
```
PUT /notifications/{user_id}/read-all
Authorization: Bearer {jwt_token}
Response: 200 OK
{
"message": "All notifications marked as read",
"updated_count": 5
}
```
#### 1.6 Eliminar Notificación
```
DELETE /notifications/{notification_id}
Authorization: Bearer {jwt_token}
Response: 200 OK
{
"message": "Notification deleted successfully"
}
```
#### 1.7 Health Check
```
GET /
Response: 200 OK
{
"status": "ok",
"service": "Notificaciones Microservice"
}
```
---
## 2. Requerimientos Funcionales del Frontend
### 2.1 Panel de Notificaciones
**Ubicación:** Icono de campana (🔔) en la barra de navegación/header
**Funcionalidades:**
- Mostrar un badge con el número de notificaciones no leídas
- Mostrar un dropdown/modal al hacer click en el icono
- Listar las últimas 10-15 notificaciones con scroll infinito
- Mostrar fecha relativa (ej: "hace 2 minutos", "hace 1 hora")
- Indicar visualmente cuáles están leídas y cuáles no
- Cada notificación debe tener:
- Mensaje de estado
- ID del reporte (clickeable para ir al detalle del reporte)
- Fecha/hora
- Botón para marcar como leída/no leída
- Botón para eliminar
### 2.2 Sincronización en Tiempo Real
**Opciones (elige una o combina):**
**Opción A: Polling (Más Simple)**
- Hacer request a `/notifications/{user_id}` cada 30 segundos
- Comparar con estado anterior
- Mostrar toast/notificación visual si hay nuevas
**Opción B: WebSocket (Recomendado)**
- Conectar WebSocket a backend (requiere implementar en backend)
- Recibir eventos en tiempo real
- Badge se actualiza instantáneamente
**Opción C: Híbrido (Recomendado)**
- WebSocket para actualizaciones en tiempo real
- Polling cada 5 minutos como fallback
- Sincronización al abrir la app
### 2.3 Notificaciones Visuales
**Toast Notifications:**
- Mostrar toast en la esquina superior derecha cuando llega una notificación
- Ejemplo:
```
✓ ¡Tu reporte ha sido resuelto!
[Ver Reporte] [Cerrar]
```
- Auto-cerrar después de 5 segundos
- Permitir cerrar manualmente
- Solo mostrar en tiempo real (no históricos)
**Sound Notification (Opcional):**
- Reproducir sonido discreto cuando llega notificación nueva
- Respetar configuración de silencio del dispositivo
### 2.4 Página Dedicada de Notificaciones
**Ruta:** `/notifications` o similar
**Características:**
- Vista completa de todas las notificaciones del usuario
- Filtros:
- Todas
- No leídas
- Resueltas (estado: resuelto)
- No resueltas (estado: no resuelto)
- En proceso
- Sorting:
- Por fecha (descendente por defecto)
- Más antiguas
- Bulk actions:
- Marcar todas como leídas
- Eliminar seleccionadas
- Marcar seleccionadas como leídas
- Paginación (implementar scroll infinito o paginación tradicional)
- Búsqueda por ID de reporte o mensaje
---
## 3. Integración con Componentes Existentes
### 3.1 Detalle del Reporte
Cuando el usuario ve un reporte, mostrar un histórico/timeline de cambios:
```
Timeline de Cambios:
├── 2024-04-29 15:30 - Reporte resuelto
├── 2024-04-29 14:20 - Reporte marcado como no resuelto
└── 2024-04-29 10:00 - Reporte creado (estado: en proceso)
```
**Nota:** Esto requeriría agregar un endpoint en backend para obtener el histórico de cambios por reporte. Alternativa: mostrar las notificaciones del usuario filtradas por `id_reporte`.
### 3.2 Perfil del Usuario
En la página de perfil/configuración, agregar sección de "Preferencias de Notificaciones":
- [ ] Recibir notificaciones de reportes
- [ ] Notificación de sonido
- [ ] Notificaciones al escribir
- [ ] Resumen diario/semanal (opcional)
---
## 4. Especificaciones Técnicas
### 4.1 Configuración Base
```javascript
// config.ts o similar
const API_BASE_URL = 'http://localhost:8002'; // Notificaciones API
const NOTIFICATIONS_POLL_INTERVAL = 30000; // 30 segundos
const NOTIFICATIONS_TOAST_DURATION = 5000; // 5 segundos
```
### 4.2 Service/Hook para Notificaciones
```typescript
// Ejemplo con React
interface Notification {
id_notificacion: string;
id_usuario: number;
id_reporte: string;
message: string;
fecha: Date;
read: boolean;
}
interface NotificationsState {
notifications: Notification[];
unreadCount: number;
loading: boolean;
error: string | null;
}
// Hook personalizado recomendado
useNotifications(userId: number)
// Métodos del hook:
- fetchNotifications(limit?: number, offset?: number)
- getUnreadCount()
- markAsRead(notificationId: string)
- markAllAsRead(userId: number)
- deleteNotification(notificationId: string)
- subscribeToUpdates() // Para WebSocket
```
### 4.3 Estados y Transiciones
```
Estados del Reporte → Mensaje de Notificación:
─────────────────────────────────────────
en proceso → resuelto → "¡Tu reporte #xxx ha sido resuelto!"
en proceso → no resuelto → "Tu reporte #xxx fue marcado como no resuelto."
no resuelto → resuelto → "¡Tu reporte #xxx ha sido resuelto!"
resuelto → en proceso → "Tu reporte #xxx ha sido reabierto."
resuelto → no resuelto → "Tu reporte #xxx fue reabierto como no resuelto."
```
---
## 5. UI/UX Recomendaciones
### 5.1 Estilos y Componentes
- **Badge de no leídas:** Rojo/Naranja, número en blanco, esquina superior derecha del icono
- **Notificación no leída:** Fondo ligeramente coloreado, indicador visual (punto azul, negrita)
- **Notificación leída:** Fondo normal, texto gris
- **Hover state:** Fondo gris claro, mostrar botones de acción
- **Empty state:** "No tienes notificaciones" con icono de campana vacía
### 5.2 Animaciones (Opcional pero Recomendado)
- Badge pulsante cuando hay nuevas notificaciones
- Slide-in del toast desde la esquina
- Fade cuando se marca como leída
- Transición suave del dropdown
### 5.3 Diseño Responsivo
- Mobile: Dropdown debe ser full-width o modal
- Tablet: Dropdown con ancho fijo
- Desktop: Dropdown normal
---
## 6. Flujo Completo de Usuario
1. **Usuario inicia sesión** → Cargar notificaciones existentes
2. **Usuario navega por la app** → Polling/WebSocket actualiza notificaciones
3. **Un reporte cambia de estado en backend** →
- Evento enviado a RabbitMQ
- Consumidor de notificaciones lo procesa
- Notificación almacenada en BD
- WebSocket notifica al cliente (o lo ve en siguiente polling)
4. **Cliente recibe actualización** →
- Badge se actualiza con nuevo conteo
- Toast se muestra (opcional)
- Usuario puede ver en el dropdown/página de notificaciones
5. **Usuario hace click en notificación** →
- Va al detalle del reporte
- Marca como leída automáticamente (opcional)
6. **Usuario gestiona notificaciones** →
- Puede marcar como leída
- Puede eliminar
- Puede marcar todas como leídas
---
## 7. Consideraciones de Seguridad
- **Autenticación:** Todas las requests deben incluir JWT token
- **Autorización:** Un usuario solo puede ver SUS propias notificaciones
- Backend valida que `user_id` en token === `user_id` en path
- **Rate Limiting:** Considerar rate limiting en polling (máx 1 request cada 10s)
- **Validación:** Validar que `notification_id` pertenece al usuario antes de actualizar
---
## 8. Testing Recomendado
### 8.1 Unit Tests
- [ ] Hook `useNotifications` retorna estado correcto
- [ ] Funciones de formateo de fecha relativa
- [ ] Lógica de filtrado y sorting
### 8.2 Integration Tests
- [ ] Polling se ejecuta correctamente cada 30s
- [ ] Marcar como leída actualiza UI
- [ ] Eliminar notificación la remueve de la lista
- [ ] Badge se actualiza cuando llega notificación
### 8.3 E2E Tests
- [ ] Usuario ve notificación cuando reporte cambia de estado
- [ ] Usuario puede marcar todas como leídas
- [ ] Página de notificaciones carga correctamente
---
## 9. Dependencias Frontend Recomendadas
```json
{
"dependencies": {
"axios": "^1.x.x", // Para requests HTTP
"framer-motion": "^10.x.x", // Para animaciones
"react-query": "^3.x.x", // Para caching y sincronización
"react-hot-toast": "^2.x.x", // Para toast notifications
"date-fns": "^2.x.x", // Para formateo de fechas
"react-infinite-scroll-component": "^6.x.x" // Para scroll infinito (opcional)
}
}
```
---
## 10. Roadmap Futuro (Phase 2)
- [ ] Notificaciones push en navegador (Service Workers)
- [ ] Notificaciones por email
- [ ] Histórico completo de cambios de estado por reporte
- [ ] Suscripción a notificaciones de otros reportes
- [ ] Sistema de preferencias granular de notificaciones
- [ ] Archivado de notificaciones (en lugar de eliminar)
- [ ] Notificaciones de actividad comunitaria
---
## 11. Endpoints de Referencia Backend Completos
### Base URL
```
Desarrollo: http://localhost:8002
Producción: https://api.voxpopuli.com/notifications
```
### Headers Requeridos
```
Authorization: Bearer {jwt_token}
Content-Type: application/json
```
### Códigos de Respuesta HTTP
- `200 OK` - Operación exitosa
- `202 Accepted` - Solicitud aceptada pero en proceso
- `400 Bad Request` - Parámetros inválidos
- `401 Unauthorized` - Token inválido/expirado
- `404 Not Found` - Recurso no encontrado
- `500 Internal Server Error` - Error del servidor
---
## 12. Ejemplo de Implementación React
```typescript
// hooks/useNotifications.ts
import { useEffect, useState, useCallback } from 'react';
import { useQuery, useMutation } from 'react-query';
import axios from 'axios';
interface Notification {
id_notificacion: string;
id_usuario: number;
id_reporte: string;
message: string;
fecha: Date;
read: boolean;
}
const API_BASE = 'http://localhost:8002';
export function useNotifications(userId: number) {
const [unreadCount, setUnreadCount] = useState(0);
// Fetch notificaciones
const { data: response, refetch } = useQuery(
['notifications', userId],
() => axios.get(
`${API_BASE}/notifications/${userId}?limit=50&offset=0`,
{ headers: { Authorization: `Bearer ${getToken()}` } }
),
{ refetchInterval: 30000 } // Poll cada 30s
);
// Marcar como leída
const markAsReadMutation = useMutation(
(notificationId: string) =>
axios.put(
`${API_BASE}/notifications/${notificationId}/read`,
{},
{ headers: { Authorization: `Bearer ${getToken()}` } }
),
{ onSuccess: () => refetch() }
);
// Marcar todas como leídas
const markAllAsReadMutation = useMutation(
() =>
axios.put(
`${API_BASE}/notifications/${userId}/read-all`,
{},
{ headers: { Authorization: `Bearer ${getToken()}` } }
),
{ onSuccess: () => refetch() }
);
// Eliminar notificación
const deleteNotificationMutation = useMutation(
(notificationId: string) =>
axios.delete(
`${API_BASE}/notifications/${notificationId}`,
{ headers: { Authorization: `Bearer ${getToken()}` } }
),
{ onSuccess: () => refetch() }
);
useEffect(() => {
if (response?.data?.unread_count !== undefined) {
setUnreadCount(response.data.unread_count);
}
}, [response]);
return {
notifications: response?.data?.notifications || [],
unreadCount,
loading: false,
markAsRead: (id: string) => markAsReadMutation.mutate(id),
markAllAsRead: () => markAllAsReadMutation.mutate(),
deleteNotification: (id: string) => deleteNotificationMutation.mutate(id),
};
}
// components/NotificationBell.tsx
import { useNotifications } from '@/hooks/useNotifications';
import { formatDistanceToNow } from 'date-fns';
import { es } from 'date-fns/locale';
export function NotificationBell({ userId }: { userId: number }) {
const { notifications, unreadCount, markAsRead, deleteNotification } =
useNotifications(userId);
const [isOpen, setIsOpen] = useState(false);
return (
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="relative p-2 rounded-full hover:bg-gray-100"
>
🔔
{unreadCount > 0 && (
<span className="absolute top-0 right-0 bg-red-500 text-white
text-xs rounded-full w-5 h-5 flex items-center
justify-center animate-pulse">
{unreadCount}
</span>
)}
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-80 bg-white rounded-lg
shadow-lg border border-gray-200">
<div className="p-4 border-b border-gray-200 font-semibold">
Notificaciones
</div>
<div className="max-h-96 overflow-y-auto">
{notifications.length === 0 ? (
<div className="p-4 text-center text-gray-500">
No hay notificaciones
</div>
) : (
notifications.map((n) => (
<div
key={n.id_notificacion}
className={`p-4 border-b hover:bg-gray-50 ${
!n.read ? 'bg-blue-50' : ''
}`}
>
<p className={!n.read ? 'font-semibold' : ''}>
{n.message}
</p>
<p className="text-xs text-gray-500 mt-1">
Reporte: {n.id_reporte}
</p>
<p className="text-xs text-gray-400">
{formatDistanceToNow(new Date(n.fecha), {
locale: es,
addSuffix: true
})}
</p>
<div className="flex gap-2 mt-2">
{!n.read && (
<button
onClick={() => markAsRead(n.id_notificacion)}
className="text-xs text-blue-600 hover:underline"
>
Marcar como leída
</button>
)}
<button
onClick={() => deleteNotification(n.id_notificacion)}
className="text-xs text-red-600 hover:underline ml-auto"
>
Eliminar
</button>
</div>
</div>
))
)}
</div>
<div className="p-3 border-t border-gray-200 text-center">
<a href="/notifications" className="text-blue-600 text-sm hover:underline">
Ver todas las notificaciones
</a>
</div>
</div>
)}
</div>
);
}
```
---
## 13. Preguntas Frecuentes
**P: ¿Debo implementar WebSocket o polling está bien?**
R: Polling cada 30s está bien para MVP. WebSocket es mejor para experiencia en tiempo real pero requiere implementación en backend.
**P: ¿Cómo manejo notificaciones si el usuario está offline?**
R: Todas las notificaciones se guardan en BD. Cuando reconecte, vera el histórico completo.
**P: ¿Debo eliminar notificaciones antiguas?**
R: Por ahora, guardarlas todas. Puedes agregar lógica de archivado después de 30 días.
**P: ¿Puedo mostrar solo las últimas 10 notificaciones?**
R: Sí, el endpoint soporta `limit=10`. Usa scroll infinito para cargar más.
---
## 14. Checklist de Implementación
- [ ] Servicio HTTP creado para llamadas a `/notifications/{user_id}`
- [ ] Hook `useNotifications` implementado
- [ ] Componente de icono de campana con badge
- [ ] Dropdown de últimas notificaciones
- [ ] Página dedicada `/notifications`
- [ ] Toast notifications en tiempo real
- [ ] Funcionalidad de marcar como leída
- [ ] Funcionalidad de eliminar notificaciones
- [ ] Polling o WebSocket implementado
- [ ] Tests unitarios para hooks
- [ ] Tests de integración end-to-end
- [ ] Documentación de componentes
- [ ] Respuesta mobile optimizada
---
**Última actualización:** 29 de Abril de 2024
**Versión API:** 1.0.0
**Autor:** VoxPopuli Development Team