Files
VoxPopuli/FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md
2026-04-29 12:28:11 -06:00

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

  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

{
  "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

// 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