618 lines
18 KiB
Markdown
618 lines
18 KiB
Markdown
# 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
|