18 KiB
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
// 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
// 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
- Usuario inicia sesión → Cargar notificaciones existentes
- Usuario navega por la app → Polling/WebSocket actualiza notificaciones
- 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)
- Cliente recibe actualización →
- Badge se actualiza con nuevo conteo
- Toast se muestra (opcional)
- Usuario puede ver en el dropdown/página de notificaciones
- Usuario hace click en notificación →
- Va al detalle del reporte
- Marca como leída automáticamente (opcional)
- 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_iden token ===user_iden path
- Backend valida que
- Rate Limiting: Considerar rate limiting en polling (máx 1 request cada 10s)
- Validación: Validar que
notification_idpertenece al usuario antes de actualizar
8. Testing Recomendado
8.1 Unit Tests
- Hook
useNotificationsretorna 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
{
"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 exitosa202 Accepted- Solicitud aceptada pero en proceso400 Bad Request- Parámetros inválidos401 Unauthorized- Token inválido/expirado404 Not Found- Recurso no encontrado500 Internal Server Error- Error del servidor
12. Ejemplo de Implementación React
// 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
useNotificationsimplementado - 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