diff --git a/API_EXAMPLES.json b/API_EXAMPLES.json index a3c4e98..82cee85 100644 --- a/API_EXAMPLES.json +++ b/API_EXAMPLES.json @@ -270,168 +270,8 @@ "body": null } } - }, - "notificaciones": { - "health_check": { - "metodo": "GET", - "url": "http://localhost:8002/", - "respuesta": { - "codigo": 200, - "body": { - "status": "ok", - "service": "Notificaciones Microservice" - } - } - }, - "crear_notificacion": { - "metodo": "POST", - "url": "http://localhost:8002/notifications/", - "descripcion": "Crear notificación (uso interno, generalmente disparado por cambios de reportes)", - "solicitud": { - "Content-Type": "application/json", - "headers": { - "Authorization": "Bearer {jwt_token}" - }, - "body": { - "id_usuario": 1, - "id_reporte": "550e8400-e29b-41d4-a716-446655440000", - "message": "¡Tu reporte ha sido resuelto!" - } - }, - "respuesta_exitosa": { - "codigo": 200, - "body": { - "id_notificacion": "660f8401-f39c-41e5-b727-557666551111", - "id_usuario": 1, - "id_reporte": "550e8400-e29b-41d4-a716-446655440000", - "message": "¡Tu reporte ha sido resuelto!", - "fecha": "2024-04-29T15:30:00Z", - "read": false - } - } - }, - "obtener_notificaciones_usuario": { - "metodo": "GET", - "url": "http://localhost:8002/notifications/1?limit=50&offset=0", - "descripcion": "Obtener todas las notificaciones de un usuario con paginación", - "headers": { - "Authorization": "Bearer {jwt_token}" - }, - "query_params": { - "limit": "50 (máximo 100)", - "offset": "0 (para paginación)" - }, - "respuesta": { - "codigo": 200, - "body": { - "total": 150, - "unread_count": 5, - "notifications": [ - { - "id_notificacion": "660f8401-f39c-41e5-b727-557666551111", - "id_usuario": 1, - "id_reporte": "550e8400-e29b-41d4-a716-446655440000", - "message": "¡Tu reporte ha sido resuelto!", - "fecha": "2024-04-29T15:30:00Z", - "read": false - }, - { - "id_notificacion": "660f8401-f39c-41e5-b727-557666551112", - "id_usuario": 1, - "id_reporte": "550e8400-e29b-41d4-a716-446655440001", - "message": "Tu reporte fue marcado como no resuelto.", - "fecha": "2024-04-29T14:20:00Z", - "read": true - } - ] - } - } - }, - "obtener_conteo_no_leidas": { - "metodo": "GET", - "url": "http://localhost:8002/notifications/1/unread-count", - "descripcion": "Obtener el número de notificaciones no leídas para un usuario", - "headers": { - "Authorization": "Bearer {jwt_token}" - }, - "respuesta": { - "codigo": 200, - "body": { - "unread_count": 5 - } - } - }, - "marcar_como_leida": { - "metodo": "PUT", - "url": "http://localhost:8002/notifications/660f8401-f39c-41e5-b727-557666551111/read", - "descripcion": "Marcar una notificación específica como leída", - "headers": { - "Authorization": "Bearer {jwt_token}" - }, - "respuesta_exitosa": { - "codigo": 200, - "body": { - "id_notificacion": "660f8401-f39c-41e5-b727-557666551111", - "id_usuario": 1, - "id_reporte": "550e8400-e29b-41d4-a716-446655440000", - "message": "¡Tu reporte ha sido resuelto!", - "fecha": "2024-04-29T15:30:00Z", - "read": true - } - } - }, - "marcar_todas_como_leidas": { - "metodo": "PUT", - "url": "http://localhost:8002/notifications/1/read-all", - "descripcion": "Marcar todas las notificaciones de un usuario como leídas", - "headers": { - "Authorization": "Bearer {jwt_token}" - }, - "respuesta_exitosa": { - "codigo": 200, - "body": { - "message": "All notifications marked as read", - "updated_count": 5 - } - } - }, - "eliminar_notificacion": { - "metodo": "DELETE", - "url": "http://localhost:8002/notifications/660f8401-f39c-41e5-b727-557666551111", - "descripcion": "Eliminar una notificación específica", - "headers": { - "Authorization": "Bearer {jwt_token}" - }, - "respuesta_exitosa": { - "codigo": 200, - "body": { - "message": "Notification deleted successfully" - } - } - } } }, - "cambios_estado_notificaciones": { - "transiciones": { - "en_proceso_a_resuelto": { - "mensaje": "¡Tu reporte #[id_reporte] ha sido resuelto!", - "tipo": "positivo" - }, - "en_proceso_a_no_resuelto": { - "mensaje": "Tu reporte #[id_reporte] fue marcado como no resuelto.", - "tipo": "info" - }, - "no_resuelto_a_resuelto": { - "mensaje": "¡Tu reporte #[id_reporte] ha sido resuelto!", - "tipo": "positivo" - }, - "resuelto_a_en_proceso": { - "mensaje": "Tu reporte #[id_reporte] ha sido reabierto.", - "tipo": "info" - } - }, - "trigger": "Cuando cambias el estado en PUT /reports/{report_id}/status" - }, "tipos_reporte": { "1": "Infraestructura/Vía pública", "2": "Inseguridad", @@ -439,31 +279,11 @@ "4": "Servicios públicos", "5": "Otro" }, - "apis_disponibles": { - "usuarios": { - "url": "http://localhost:8000", - "puerto": 8000, - "docs": "http://localhost:8000/docs" - }, - "reportes": { - "url": "http://localhost:8001", - "puerto": 8001, - "docs": "http://localhost:8001/docs" - }, - "notificaciones": { - "url": "http://localhost:8002", - "puerto": 8002, - "docs": "http://localhost:8002/docs", - "nuevo": true - } - }, "codigos_estado_http": { "200": "OK - La solicitud fue exitosa", "201": "Created - Recurso creado exitosamente", - "202": "Accepted - Solicitud aceptada en cola", "204": "No Content - Eliminación exitosa", "400": "Bad Request - Error en la solicitud", - "401": "Unauthorized - Token inválido/expirado", "404": "Not Found - Recurso no encontrado", "500": "Internal Server Error - Error en el servidor" } diff --git a/FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md b/FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md deleted file mode 100644 index fc9e340..0000000 --- a/FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md +++ /dev/null @@ -1,617 +0,0 @@ -# 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 ( -
- - - {isOpen && ( -
-
- Notificaciones -
-
- {notifications.length === 0 ? ( -
- No hay notificaciones -
- ) : ( - notifications.map((n) => ( -
-

- {n.message} -

-

- Reporte: {n.id_reporte} -

-

- {formatDistanceToNow(new Date(n.fecha), { - locale: es, - addSuffix: true - })} -

-
- {!n.read && ( - - )} - -
-
- )) - )} -
-
- - Ver todas las notificaciones - -
-
- )} -
- ); -} -``` - ---- - -## 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 diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index b297f58..0000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,515 +0,0 @@ -# Resumen de Implementación: API de Notificaciones - VoxPopuli - -**Fecha:** 29 de Abril de 2024 -**Versión:** 1.0.0 -**Autor:** VoxPopuli Development Team - ---- - -## 📋 Resumen Ejecutivo - -Se ha implementado **exitosamente** una nueva **API de Notificaciones** que se integra con la arquitectura existente del proyecto. Las notificaciones se disparan automáticamente cuando los reportes cambian de estado, proporcionando a los usuarios actualizaciones en tiempo real sobre el progreso de sus reportes. - -### Puntos Clave: -✅ Nueva API de Notificaciones en **puerto 8002** -✅ Base de datos MongoDB dedicada (`voxpopuli_notifications`) -✅ Integración automática con eventos de cambio de estado de reportes -✅ Sistema de consumidores RabbitMQ para procesamiento asíncrono -✅ Arquitectura hexagonal consistente con el resto del proyecto -✅ Docker-compose actualizado con nuevos servicios - ---- - -## 📁 Estructura de Archivos Creados - -``` -src/ -├── domain/ -│ └── notifications.py ✨ Nuevo -├── application/ -│ ├── ports/ -│ │ └── notification_repository.py ✨ Nuevo -│ └── services/ -│ └── notification_services.py ✨ Nuevo -├── infrastructure/ -│ ├── adapters/ -│ │ └── persistence/ -│ │ └── notification_repository_mongo.py ✨ Nuevo -│ └── api/ -│ └── notifications/ ✨ Nuevo directorio -│ ├── __init__.py -│ ├── app.py -│ ├── router.py -│ ├── root.py -│ ├── schemas.py -│ └── notifications.py -└── consumers/ - └── notification_consumer.py ✨ Nuevo - -📄 FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md ✨ Nuevo -📄 IMPLEMENTATION_SUMMARY.md ✨ Nuevo (este archivo) -``` - ---- - -## 🏗️ Cambios en Archivos Existentes - -### 1. `src/core/config.py` -- ✏️ Agregado: `mongodb_notifications_db` para configuración de BD de notificaciones -- ✏️ Actualizado comentario de `mongodb_url` para incluir ambas APIs - -```python -mongodb_notifications_db: str = Field( - default="voxpopuli_notifications", - description="Base de datos MongoDB para Notificaciones" -) -``` - -### 2. `src/main.py` -- ✏️ Importado: `create_notifications_app` desde API de notificaciones -- ✏️ Importado: `NotificationConsumer` -- ✏️ Agregado: Thread para ejecutar API de notificaciones (puerto 8002) -- ✏️ Agregado: Thread para ejecutar consumidor de notificaciones -- ✏️ Actualizado: Mensajes de inicio para mostrar la nueva API - -```python -# Cambios principales -from infrastructure.api.notifications.app import create_app as create_notifications_app -from consumers.notification_consumer import NotificationConsumer - -# Agregado en run(): -notifications_thread = threading.Thread(target=run_notifications_api, daemon=True) -notifications_consumer_thread = threading.Thread(target=run_notifications_consumer, daemon=True) -``` - -### 3. `src/infrastructure/adapters/rabbitmq/messages.py` -- ✏️ Agregado: Tipo de evento `UPDATE_STATUS` a `ReportEventType` -- ✏️ Agregado: Campos `old_estado`, `new_estado`, `old_visibility`, `new_visibility` a `ReportMessage` - -```python -class ReportEventType(str, Enum): - CREATE = "report.create" - UPDATE_VISIBILITY = "report.update_visibility" - UPDATE_STATUS = "report.update_status" # ✨ Nuevo - DELETE = "report.delete" - -@dataclass -class ReportMessage: - # ... campos existentes ... - old_estado: Optional[str] = None # ✨ Nuevo - new_estado: Optional[str] = None # ✨ Nuevo -``` - -### 4. `src/application/services/report_services.py` -- ✏️ **Clase `UpdateReportStatus`:** Completamente refactorizada para enviar eventos a RabbitMQ -- ✏️ Agregado: Importación de `ReportMessage` y `send_to_queue` -- ✏️ Agregado: Lógica para crear mensaje UPDATE_STATUS y enviarlo a `notifications_queue` -- ✏️ Agregado: Captura del estado anterior para comparación - -```python -# Cambios en UpdateReportStatus.execute(): -old_estado = report.estado # Guardar estado anterior - -# Después de actualizar: -if old_estado != new_estado: - message = ReportMessage( - event_type=ReportEventType.UPDATE_STATUS, - id_reporte=report_id, - id_usuario=report.id_usuario, - old_estado=old_estado, - new_estado=new_estado, - # ... otros campos ... - ) - send_to_queue("notifications_queue", message.to_dict()) -``` - -### 5. `docker-compose.yaml` -- ✏️ **MongoDB:** Agregados credenciales de autenticación - - Usuario: `admin` / Contraseña: `admin_password` - - Agregado flag `--auth` en comando -- ✏️ **Agregado:** Servicio RabbitMQ completo con: - - Usuario: `voxpopuli` / Contraseña: `voxpopuli_pass` - - Management UI en puerto 15672 - - Healthcheck configurado -- ✏️ **Volúmenes:** Agregado `rabbitmq_data` para persistencia - ---- - -## 🚀 Nuevos Servicios en Docker Compose - -### RabbitMQ (Nuevo) -```yaml -rabbitmq: - image: rabbitmq:3.13-management - container_name: voxpopuli_rabbitmq - ports: - - "5672:5672" # AMQP - - "15672:15672" # Management UI (http://localhost:15672) - credentials: - user: voxpopuli - password: voxpopuli_pass -``` - -**Acceso Management UI:** -- URL: http://localhost:15672 -- Usuario: `voxpopuli` -- Contraseña: `voxpopuli_pass` - ---- - -## 📊 Base de Datos MongoDB - -### Nuevas Colecciones - -**Base de datos:** `voxpopuli_notifications` - -#### Colección: `notificaciones` -```json -{ - "_id": ObjectId, - "id_usuario": 1, - "id_reporte": "uuid-del-reporte", - "message": "¡Tu reporte #uuid ha sido resuelto!", - "fecha": ISODate("2024-04-29T15:30:00Z"), - "read": false -} -``` - -**Índices Recomendados:** -```javascript -db.notificaciones.createIndex({ "id_usuario": 1, "fecha": -1 }) -db.notificaciones.createIndex({ "id_usuario": 1, "read": 1 }) -``` - ---- - -## 🔄 Flujo de Eventos - -### Cambio de Estado de Reporte → Notificación - -``` -1. Frontend llama: - PUT /reports/{report_id}/status - { "estado": "resuelto" } - -2. API de Reportes: - └─ UpdateReportStatus.execute() - ├─ Valida estado - ├─ Obtiene reporte actual (estado anterior) - ├─ Actualiza en MongoDB - └─ NUEVO: Envía mensaje a RabbitMQ - { - "event_type": "report.update_status", - "id_reporte": "uuid", - "id_usuario": 1, - "old_estado": "en proceso", - "new_estado": "resuelto", - ... - } - -3. RabbitMQ: - └─ Almacena en queue `notifications_queue` - -4. Consumidor de Notificaciones: - ├─ Escucha la cola - ├─ Recibe el evento UPDATE_STATUS - └─ Procesa mensaje: - └─ NotificationService.send_report_status_notification() - └─ Crea notificación en MongoDB - { - "id_usuario": 1, - "id_reporte": "uuid", - "message": "¡Tu reporte ha sido resuelto!", - "fecha": now(), - "read": false - } - -5. Frontend (Polling o WebSocket): - └─ GET /notifications/1 - └─ Obtiene notificación creada -``` - ---- - -## 📡 Colas RabbitMQ - -### `reports_queue` -- **Propósito:** Eventos de creación, eliminación y cambios de reportes -- **Consumidor:** `ReportConsumer` (consumer de reportes) -- **Eventos:** CREATE, UPDATE_VISIBILITY, DELETE - -### `notifications_queue` (NUEVA) -- **Propósito:** Eventos de cambios de estado para crear notificaciones -- **Consumidor:** `NotificationConsumer` (consumer de notificaciones) -- **Eventos:** UPDATE_STATUS, UPDATE_VISIBILITY - -### `users_queue` -- **Propósito:** Eventos de usuarios -- **Consumidor:** `UserConsumer` - ---- - -## 🔌 API Endpoints de Notificaciones - -### Base: `http://localhost:8002` - -| Método | Endpoint | Descripción | Auth | -|--------|----------|-------------|------| -| GET | `/` | Health check | No | -| POST | `/notifications/` | Crear notificación (interno) | Sí | -| GET | `/notifications/{user_id}` | Obtener notificaciones del usuario | Sí | -| GET | `/notifications/{user_id}/unread-count` | Contar no leídas | Sí | -| PUT | `/notifications/{notification_id}/read` | Marcar como leída | Sí | -| PUT | `/notifications/{user_id}/read-all` | Marcar todas como leídas | Sí | -| DELETE | `/notifications/{notification_id}` | Eliminar notificación | Sí | - -**Documentación interactiva:** http://localhost:8002/docs - ---- - -## 🔐 Seguridad y Validaciones - -### Por Implementar en Frontend - -1. **Autenticación:** - - Incluir JWT token en header `Authorization: Bearer {token}` - - Validar que el token no esté expirado - -2. **Autorización:** - - Backend valida que `user_id` en token === `user_id` en path - - Usuario solo puede ver sus propias notificaciones - -3. **Rate Limiting:** - - Considerar máximo 1 request de polling cada 10 segundos - - WebSocket es alternativa para mayor escalabilidad - ---- - -## 🧪 Pruebas Recomendadas - -### 1. Test Manual: Crear Notificación -```bash -# Terminal 1: Iniciar el proyecto -python src/main.py - -# Terminal 2: Test crear notificación -curl -X POST http://localhost:8002/notifications/ \ - -H "Content-Type: application/json" \ - -d '{ - "id_usuario": 1, - "id_reporte": "test-report-123", - "message": "Tu reporte ha sido actualizado" - }' -``` - -### 2. Test Manual: Obtener Notificaciones -```bash -curl http://localhost:8002/notifications/1 \ - -H "Authorization: Bearer {tu_jwt_token}" -``` - -### 3. Test Manual: Cambio de Estado -```bash -# Cambiar estado de reporte → dispara notificación automáticamente -curl -X PUT http://localhost:8002/reports/{report_id}/status \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer {tu_jwt_token}" \ - -d '{"estado": "resuelto"}' - -# Luego verificar que notificación se creó: -curl http://localhost:8002/notifications/1 \ - -H "Authorization: Bearer {tu_jwt_token}" -``` - ---- - -## 🐳 Comandos Docker - -```bash -# Levantar todos los servicios -docker-compose up -d - -# Ver logs en tiempo real -docker-compose logs -f - -# Ver logs específicos de un servicio -docker-compose logs -f mongodb -docker-compose logs -f rabbitmq - -# Detener servicios -docker-compose down - -# Verificar estado -docker-compose ps -``` - ---- - -## 📋 Arquitectura de Capas - -``` -┌─────────────────────────────────────────────────────┐ -│ Presentación (Frontend) │ -│ (Implementar según FRONTEND_NOTIFICATIONS_...) │ -└────────────────────┬────────────────────────────────┘ - │ HTTP/REST + WebSocket (opcional) -┌────────────────────v────────────────────────────────┐ -│ API FastAPI │ -│ ├─ /notifications/{user_id} │ -│ ├─ /notifications/{notification_id}/read │ -│ └─ ... (Ver endpoints arriba) │ -└────────────────────┬────────────────────────────────┘ - │ -┌────────────────────v────────────────────────────────┐ -│ Capa de Aplicación (Services) │ -│ └─ NotificationService │ -│ ├─ create_notification() │ -│ ├─ get_user_notifications() │ -│ ├─ mark_as_read() │ -│ └─ send_report_status_notification() │ -└────────────────────┬────────────────────────────────┘ - │ -┌────────────────────v────────────────────────────────┐ -│ Capa de Dominio │ -│ └─ Notification (dataclass) │ -│ ├─ id_usuario │ -│ ├─ id_reporte │ -│ ├─ message │ -│ ├─ fecha │ -│ └─ read │ -└────────────────────┬────────────────────────────────┘ - │ -┌────────────────────v────────────────────────────────┐ -│ Puertos (Interfaces Abstractas) │ -│ └─ NotificationRepository │ -│ ├─ create() │ -│ ├─ get_by_user() │ -│ ├─ mark_as_read() │ -│ └─ ... │ -└────────────────────┬────────────────────────────────┘ - │ -┌────────────────────v────────────────────────────────┐ -│ Infraestructura (Adaptadores) │ -│ └─ NotificationRepositoryMongo │ -│ ├─ Implementa NotificationRepository │ -│ └─ Usa MongoDB │ -└─────────────────────────────────────────────────────┘ -``` - ---- - -## 📈 Escalabilidad Futura - -### Fase 2 - Recomendaciones - -1. **WebSocket Server:** - - Implementar socket.io en FastAPI - - Notificaciones push en tiempo real - - Reducir latencia para usuarios conectados - -2. **Cache Redis:** - - Cachear conteo de no leídas - - Sincronizar estado entre instancias - -3. **Histórico de Cambios:** - - Almacenar todos los cambios de estado - - Mostrar timeline en detalle de reporte - -4. **Notificaciones Push:** - - Integrar con Firebase Cloud Messaging - - Enviar notificaciones a dispositivos móviles - -5. **Búsqueda Full-Text:** - - Elasticsearch para buscar en notificaciones - - Filtrado avanzado - ---- - -## 🆘 Troubleshooting - -### Problema: "Cannot connect to MongoDB" -```bash -# Solución: Asegurar que MongoDB esté levantado -docker-compose up -d mongodb - -# Verificar logs -docker-compose logs mongodb -``` - -### Problema: "RabbitMQ connection refused" -```bash -# Solución: Asegurar que RabbitMQ esté levantado -docker-compose up -d rabbitmq - -# Verificar logs -docker-compose logs rabbitmq - -# Acceder a management UI -http://localhost:15672 (usuario: voxpopuli, pass: voxpopuli_pass) -``` - -### Problema: "Notification not found" -- Verificar que `id_usuario` es correcto -- Verificar que JWT token pertenece al usuario -- Revisar que el consumidor está ejecutando - -### Problema: No se crean notificaciones al cambiar estado -```bash -# 1. Verificar que consumidor está corriendo -# (Debe haber thread "Notifications-Consumer" en logs) - -# 2. Verificar mensaje en RabbitMQ -# Ir a http://localhost:15672 > Queues > notifications_queue - -# 3. Revisar logs del consumidor -docker-compose logs notifications-consumer # Si estuviera en Docker -``` - ---- - -## 📚 Documentación Relacionada - -- **FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md** - Guía completa para implementar frontend -- **ARCHITECTURE.md** - Arquitectura general del proyecto -- **DATABASE.md** - Información sobre bases de datos -- **README.md** - Setup y ejecución del proyecto - ---- - -## ✅ Checklist de Implementación Backend (Completado) - -- [x] Crear modelo de dominio `Notification` -- [x] Crear interfaz `NotificationRepository` -- [x] Implementar `NotificationRepositoryMongo` -- [x] Crear `NotificationService` con lógica de negocio -- [x] Crear esquemas Pydantic -- [x] Implementar endpoints de API -- [x] Crear router de notificaciones -- [x] Crear app FastAPI para notificaciones -- [x] Crear `NotificationConsumer` -- [x] Actualizar `config.py` con nueva BD -- [x] Actualizar `docker-compose.yaml` -- [x] Modificar `UpdateReportStatus` para enviar eventos -- [x] Actualizar `messages.py` con `UPDATE_STATUS` -- [x] Actualizar `main.py` para ejecutar nueva API y consumidor -- [x] Crear documentación detallada para frontend -- [x] Crear este documento de resumen - ---- - -## 🎯 Próximos Pasos para Frontend - -1. **Leer:** `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` -2. **Crear:** Hook `useNotifications` con React Query -3. **Implementar:** Componente de icono de campana -4. **Agregar:** Dropdown de notificaciones -5. **Crear:** Página dedicada de notificaciones -6. **Integrar:** Toast notifications -7. **Implementar:** Polling cada 30 segundos -8. **Testear:** Flujo completo de usuario - ---- - -**Estado:** ✅ Completado -**Próxima Revisión:** Después de implementación de frontend -**Contacto:** Development Team diff --git a/MONGODB_SEPARADO.md b/MONGODB_SEPARADO.md deleted file mode 100644 index 799701f..0000000 --- a/MONGODB_SEPARADO.md +++ /dev/null @@ -1,275 +0,0 @@ -# Corrección: MongoDB Separado por Microservicio - -**Fecha:** 29 de Abril de 2026 -**Cambio:** Arquitectura actualizada a instancias independientes de MongoDB - ---- - -## 📋 Cambio Realizado - -Se corrigió la arquitectura para que **cada microservicio tenga su propia instancia de MongoDB** completamente separada. - -### Antes ❌ -``` -┌──────────────────────────────┐ -│ MongoDB (Instancia Única) │ -├──────────────────────────────┤ -│ Base de datos: reports │ -│ Base de datos: notifications │ -└──────────────────────────────┘ - ↑ - Compartida entre servicios -``` - -### Ahora ✅ -``` -┌──────────────────────────────┐ -│ MongoDB Reports │ ← Puerto 27017 -│ (Instancia 1) │ -│ BD: voxpopuli_reports │ -└──────────────────────────────┘ - API Reportes - -┌──────────────────────────────┐ -│ MongoDB Notifications │ ← Puerto 27018 -│ (Instancia 2) │ -│ BD: voxpopuli_notifications │ -└──────────────────────────────┘ - API Notificaciones -``` - ---- - -## 🔧 Cambios Realizados - -### 1. `docker-compose.yaml` - -**Antes:** Una única instancia `mongodb` - -**Ahora:** Dos instancias separadas -```yaml -mongodb-reports: - image: mongo:7.0 - container_name: voxpopuli_mongo_reports - ports: - - "27017:27017" # Puerto específico para reportes - volumes: - - mongo_reports_data:/data/db - -mongodb-notifications: - image: mongo:7.0 - container_name: voxpopuli_mongo_notifications - ports: - - "27018:27017" # Puerto específico para notificaciones (mapea al 27017 interno) - volumes: - - mongo_notifications_data:/data/db -``` - -**Volúmenes:** -```yaml -volumes: - mysql_data: - mongo_reports_data: # Nuevo - mongo_notifications_data: # Nuevo - rabbitmq_data: -``` - -### 2. `src/core/config.py` - -**Antes:** -```python -mongodb_url: str = "mongodb://localhost:27017" -mongodb_db: str = "voxpopuli_reports" -mongodb_notifications_db: str = "voxpopuli_notifications" -``` - -**Ahora:** -```python -# Instancia 1: Reportes -mongodb_reports_url: str = "mongodb://admin:admin_password@localhost:27017" -mongodb_reports_db: str = "voxpopuli_reports" - -# Instancia 2: Notificaciones -mongodb_notifications_url: str = "mongodb://admin:admin_password@localhost:27018" -mongodb_notifications_db: str = "voxpopuli_notifications" -``` - -### 3. `src/infrastructure/adapters/persistence/mongodb.py` - -**Antes:** -```python -mongo_client = MongoClient(ConfSettings.mongodb_url) -mongodb = mongo_client[ConfSettings.mongodb_db] - -def get_reports_collection() -> Collection: - return mongodb["reportes"] -``` - -**Ahora:** -```python -# Cliente separado para Reportes (Puerto 27017) -mongo_client_reports = MongoClient(ConfSettings.mongodb_reports_url) -mongodb_reports = mongo_client_reports[ConfSettings.mongodb_reports_db] - -# Cliente separado para Notificaciones (Puerto 27018) -mongo_client_notifications = MongoClient(ConfSettings.mongodb_notifications_url) -mongodb_notifications = mongo_client_notifications[ConfSettings.mongodb_notifications_db] - -def get_reports_collection() -> Collection: - return mongodb_reports["reportes"] - -def get_notifications_collection() -> Collection: - return mongodb_notifications["notificaciones"] -``` - -### 4. `src/infrastructure/adapters/persistence/notification_repository_mongo.py` - -**Cambio:** Usar la función `get_notifications_collection()` centralizada en lugar de crear el cliente localmente - -```python -from infrastructure.adapters.persistence.mongodb import get_notifications_collection - -class NotificationRepositoryMongo(NotificationRepository): - def __init__(self): - self.collection = get_notifications_collection() # Usa instancia separada -``` - ---- - -## 📊 Arquitectura Final - -``` -┌─────────────────────────────────────────────────────┐ -│ VoxPopuli Services │ -├─────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ -│ │ Usuarios │ │ Reportes │ │Notificacio│ │ -│ │ │ │ │ │nes │ │ -│ │ Puerto 8000 │ │ Puerto 8001 │ │ Puerto... │ │ -│ └──────┬───────┘ └──────┬───────┘ └────┬──────┘ │ -│ │ │ │ │ -│ ↓ ↓ ↓ │ -│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ -│ │ MySQL │ │ MongoDB │ │ MongoDB │ │ -│ │ (Usuarios) │ │ Reports │ │ Notif. │ │ -│ │ │ │ │ │ │ │ -│ │ Puerto 3306 │ │ Puerto 27017 │ │ Puerto... │ │ -│ └──────────────┘ └──────────────┘ └────────────┘ │ -│ │ -│ Instancias │ -│ Independientes │ -│ │ -└─────────────────────────────────────────────────────┘ - ↓ - ┌──────────────────┐ - │ RabbitMQ │ - │ (Compartida) │ - │ Puerto 5672 │ - └──────────────────┘ -``` - ---- - -## 🚀 Cómo Levantar - -```bash -# Levantar todos los servicios con instancias separadas -docker-compose up -d - -# Verificar que ambas instancias de MongoDB están corriendo -docker-compose ps - -# Resultado esperado: -# voxpopuli_mysql Up (healthy) -# voxpopuli_mongo_reports Up (healthy) Puerto 27017 -# voxpopuli_mongo_notifications Up (healthy) Puerto 27018 -# voxpopuli_rabbitmq Up (healthy) -``` - ---- - -## 🔌 Puertos Finales - -| Servicio | Contenedor | Puerto Externo | Puerto Interno | -|----------|-----------|-----------------|-----------------| -| MySQL | voxpopuli_mysql | 3306 | 3306 | -| MongoDB (Reports) | voxpopuli_mongo_reports | 27017 | 27017 | -| MongoDB (Notifications) | voxpopuli_mongo_notifications | 27018 | 27017 | -| RabbitMQ AMQP | voxpopuli_rabbitmq | 5672 | 5672 | -| RabbitMQ Management | voxpopuli_rabbitmq | 15672 | 15672 | - ---- - -## 🧪 Verificar Separación - -### Conectar a MongoDB Reports -```bash -docker exec -it voxpopuli_mongo_reports mongosh \ - -u admin -p admin_password \ - --authenticationDatabase admin - -# En la terminal de MongoDB: -use voxpopuli_reports -db.reportes.find() # Solo verá reportes -``` - -### Conectar a MongoDB Notifications -```bash -docker exec -it voxpopuli_mongo_notifications mongosh \ - -u admin -p admin_password \ - --authenticationDatabase admin - -# En la terminal de MongoDB: -use voxpopuli_notifications -db.notificaciones.find() # Solo verá notificaciones -``` - ---- - -## ✅ Beneficios de esta Arquitectura - -1. **Aislamiento Completo** - Cada servicio es totalmente independiente -2. **Escalabilidad** - Escalar reportes sin afectar notificaciones (y viceversa) -3. **Backup Independiente** - Backups separados por tipo de dato -4. **Seguridad** - Credenciales y acceso separados -5. **Performance** - Sin contención de recursos entre servicios -6. **Mantenibilidad** - Más fácil de mantener y depurar -7. **High Availability** - Una BD puede fallar sin afectar la otra - ---- - -## 📝 Variables de Entorno (Opcional) - -Si necesitas cambiar los puertos en producción: - -```bash -# .env -MONGODB_REPORTS_URL=mongodb://admin:password@mongo-reports:27017 -MONGODB_NOTIFICATIONS_URL=mongodb://admin:password@mongo-notifications:27017 -``` - -Luego actualizar en `config.py`: -```python -mongodb_reports_url: str = Field( - default=os.getenv("MONGODB_REPORTS_URL", "...") -) -mongodb_notifications_url: str = Field( - default=os.getenv("MONGODB_NOTIFICATIONS_URL", "...") -) -``` - ---- - -## 🎯 Resumen - -✅ Cada microservicio tiene su **propia instancia de MongoDB** -✅ Datos completamente aislados -✅ Puertos independientes (27017 y 27018) -✅ Conexiones separadas en código -✅ Configuración centralizada -✅ Totalmente escalable - ---- - -**Cambio Completado: Arquitectura microservicios con MongoDB independiente por servicio ✅** diff --git a/QUICKSTART_NOTIFICATIONS.md b/QUICKSTART_NOTIFICATIONS.md deleted file mode 100644 index faccac3..0000000 --- a/QUICKSTART_NOTIFICATIONS.md +++ /dev/null @@ -1,241 +0,0 @@ -# Quick Start: API de Notificaciones - -## 🚀 Inicio Rápido - -### Requisitos Previos -- Docker y Docker Compose instalados -- Python 3.10+ (si ejecutar sin Docker) -- Proyecto VoxPopuli clonado - ---- - -## 📦 Opción 1: Con Docker Compose (Recomendado) - -### 1. Levantar todos los servicios -```bash -cd c:\Users\Rodo Machenike\Documents\API\VoxPopuli -docker-compose up -d -``` - -### 2. Verificar que todo está corriendo -```bash -docker-compose ps -``` - -Deberías ver: -- voxpopuli_mysql (3306) -- voxpopuli_mongo (27017) -- voxpopuli_rabbitmq (5672, 15672) - -### 3. Levantar la aplicación Python -```bash -# Crear entorno virtual si no existe -python -m venv venv - -# Activar entorno virtual -# En Windows: -venv\Scripts\activate -# En Linux/Mac: -source venv/bin/activate - -# Instalar dependencias -pip install -r requirements.txt - -# Ejecutar la aplicación -python src/main.py -``` - -### 4. Verificar que APIs están funcionando -```bash -# Terminal nueva -curl http://localhost:8000/ # Usuarios -curl http://localhost:8001/ # Reportes -curl http://localhost:8002/ # Notificaciones (NUEVA) -``` - ---- - -## ✅ Verificar Conexión a RabbitMQ - -### Acceder al Dashboard de RabbitMQ -1. Ir a: http://localhost:15672 -2. Usuario: `voxpopuli` -3. Contraseña: `voxpopuli_pass` - -### Ver colas -- Queues > `reports_queue` - Para eventos de reportes -- Queues > `notifications_queue` - Para eventos de notificaciones (NUEVA) -- Queues > `users_queue` - Para eventos de usuarios - ---- - -## 🧪 Prueba Rápida del Flujo Completo - -### 1. Crear un usuario -```bash -curl -X POST http://localhost:8000/users/ \ - -H "Content-Type: application/json" \ - -d '{ - "nombre": "Juan", - "apellido": "Pérez", - "email": "juan@example.com", - "contraseña": "password123", - "fecha_nacimiento": "1990-01-15" - }' -``` - -Guardar el `user_id` que retorna (ej: 1) - -### 2. Login para obtener token JWT -```bash -curl -X POST http://localhost:8000/login \ - -H "Content-Type: application/json" \ - -d '{ - "email": "juan@example.com", - "contraseña": "password123" - }' -``` - -Guardar el `access_token` que retorna - -### 3. Crear un reporte -```bash -curl -X POST http://localhost:8001/reports/ \ - -H "Authorization: Bearer {access_token}" \ - -F "id_usuario=1" \ - -F "tipo_reporte=1" \ - -F "descripcion=Calle rota en la esquina" \ - -F "ubicacion=Calle 5 y Carrera 10" -``` - -Guardar el `id_reporte` que retorna - -### 4. Cambiar estado del reporte (Dispara Notificación) -```bash -curl -X PUT http://localhost:8001/reports/{id_reporte}/status \ - -H "Authorization: Bearer {access_token}" \ - -H "Content-Type: application/json" \ - -d '{"estado": "resuelto"}' -``` - -### 5. Verificar que notificación se creó -```bash -curl http://localhost:8002/notifications/1 \ - -H "Authorization: Bearer {access_token}" -``` - -**Resultado esperado:** -```json -{ - "total": 1, - "unread_count": 1, - "notifications": [ - { - "id_notificacion": "...", - "id_usuario": 1, - "id_reporte": "...", - "message": "¡Tu reporte ha sido resuelto!", - "fecha": "2024-04-29T...", - "read": false - } - ] -} -``` - -### 6. Marcar como leída -```bash -curl -X PUT http://localhost:8002/notifications/{notification_id}/read \ - -H "Authorization: Bearer {access_token}" -``` - ---- - -## 📊 Verificar en Bases de Datos - -### MongoDB - Ver notificación creada -```bash -# Conectar a MongoDB -docker exec -it voxpopuli_mongo mongosh - -# En la terminal de MongoDB: -use voxpopuli_notifications -db.notificaciones.find() -``` - -### RabbitMQ - Ver evento enviado -1. Ir a http://localhost:15672 -2. Queues > `notifications_queue` -3. Debería mostrar mensajes procesados - ---- - -## 📝 Logs en Tiempo Real - -### Ver logs del contenedor de la app -```bash -# Si ejecutas en Docker -docker-compose logs -f - -# Si ejecutas Python directamente -# Los logs aparecen en la terminal donde ejecutaste python src/main.py -``` - -### Buscar errores de notificaciones -```bash -docker-compose logs -f notifications-consumer # Si estuviera en Docker -# O en los logs de Python cuando ejecutas src/main.py -``` - ---- - -## 🔧 Solución de Problemas Rápida - -| Problema | Solución | -|----------|----------| -| Error de conexión a MongoDB | `docker-compose up -d mongodb && docker-compose logs mongodb` | -| Error de conexión a RabbitMQ | `docker-compose up -d rabbitmq && docker-compose logs rabbitmq` | -| Notificaciones no se crean | Verificar que `notification_consumer.py` está ejecutando (ver logs) | -| Token expirado | Crear nuevo token con login | -| No aparecen notificaciones | Verificar `unread_count` primero con `/notifications/{user_id}/unread-count` | - ---- - -## 📚 Documentos Disponibles - -1. **IMPLEMENTATION_SUMMARY.md** - Descripción completa de cambios -2. **FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md** - Guía para implementar frontend -3. **API_EXAMPLES.json** - Ejemplos de requests (actualizado con notificaciones) -4. **docker-compose.yaml** - Servicios disponibles - ---- - -## 🎯 Próximos Pasos - -1. ✅ Backend: Servicios levantados -2. ⬜ Frontend: Implementar según `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` -3. ⬜ Testing: Pruebas unitarias e integración -4. ⬜ Producción: Deploy en servidor - ---- - -## 💡 Tips Útiles - -- **Debugging RabbitMQ:** Acceder a http://localhost:15672 para ver estado de colas -- **Debugging MongoDB:** Usar MongoDB Compass (herramienta GUI) -- **Debugging API:** Usar Swagger UI en http://localhost:8002/docs -- **Health Check:** GET http://localhost:8002/ para verificar que la API está viva - ---- - -## 📞 Contacto - -Si hay dudas o errores: -1. Revisar logs: `docker-compose logs -f` -2. Revisar documentación en `IMPLEMENTATION_SUMMARY.md` -3. Verificar endpoints en http://localhost:8002/docs - ---- - -**¡Todo listo! 🚀** - -La API de Notificaciones está lista para usar. diff --git a/RESUMEN_FINAL.md b/RESUMEN_FINAL.md deleted file mode 100644 index a8537f2..0000000 --- a/RESUMEN_FINAL.md +++ /dev/null @@ -1,561 +0,0 @@ -# 🎉 API de Notificaciones - Implementación Completada - -## 📌 Estado: ✅ 100% COMPLETADO - ---- - -## 🎯 Objetivo Alcanzado - -Se ha implementado **exitosamente** una **API de Notificaciones** completamente integrada con la arquitectura existente de VoxPopuli. Las notificaciones se disparan automáticamente cuando los reportes cambian de estado. - -### Lo que ahora es posible: -``` -Usuario Crea Reporte → Estado Cambia → Notificación Creada → Usuario Recibe Alerta -``` - ---- - -## 📊 Resumen de Implementación - -### 🏢 Infraestructura -``` -┌─────────────────────────────────────────────────────┐ -│ VoxPopuli Services │ -├─────────────────────────────────────────────────────┤ -│ Usuarios API │ Reportes API │ Notificaciones │ -│ Puerto 8000 │ Puerto 8001 │ Puerto 8002 │ -│ (MySQL) │ (MongoDB) │ (MongoDB) │ -└─────────────────────────────────────────────────────┘ - ↓ ↓ ↓ - ┌─────────────────────────────────────────────────┐ - │ RabbitMQ Message Queue │ - │ (reports_queue | notifications_queue) │ - └─────────────────────────────────────────────────┘ -``` - -### 📁 Archivos Creados: 13 -``` -✨ 6 Archivos en src/infrastructure/api/notifications/ -✨ 1 Archivo en src/domain/ (notifications.py) -✨ 1 Archivo en src/application/ports/ (notification_repository.py) -✨ 1 Archivo en src/application/services/ (notification_services.py) -✨ 1 Archivo en src/infrastructure/adapters/persistence/ -✨ 1 Archivo en src/consumers/ (notification_consumer.py) -✨ 2 Archivos de documentación técnica -``` - -### 📄 Documentación Creada: 4 Documentos -``` -1. FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md (Guía Completa) -2. IMPLEMENTATION_SUMMARY.md (Resumen Técnico) -3. QUICKSTART_NOTIFICATIONS.md (Inicio Rápido) -4. VERIFICACION.md (Checklist) -``` - -### 🔧 Cambios en Archivos Existentes: 5 -``` -✏️ src/core/config.py -✏️ src/main.py -✏️ src/infrastructure/adapters/rabbitmq/messages.py -✏️ src/application/services/report_services.py -✏️ docker-compose.yaml -``` - ---- - -## 🚀 Nuevas Funcionalidades - -### 1. API de Notificaciones (Puerto 8002) -``` -GET / - Health check -POST /notifications/ - Crear notificación -GET /notifications/{user_id} - Obtener notificaciones del usuario -GET /notifications/{user_id}/unread-count - Conteo de no leídas -PUT /notifications/{id}/read - Marcar como leída -PUT /notifications/{user_id}/read-all - Marcar todas como leídas -DELETE /notifications/{id} - Eliminar notificación -``` - -### 2. Evento UPDATE_STATUS en RabbitMQ -```json -{ - "event_type": "report.update_status", - "id_reporte": "uuid", - "id_usuario": 1, - "old_estado": "en proceso", - "new_estado": "resuelto", - "mensaje": "¡Tu reporte ha sido resuelto!" -} -``` - -### 3. Base de Datos MongoDB Dedicada -```javascript -// Base de datos: voxpopuli_notifications -// Colección: notificaciones -{ - "_id": ObjectId, - "id_usuario": 1, - "id_reporte": "uuid", - "message": "¡Tu reporte ha sido resuelto!", - "fecha": ISODate, - "read": false -} -``` - -### 4. Consumidor Automático -```python -# Escucha la cola 'notifications_queue' -# Procesa eventos UPDATE_STATUS -# Crea notificaciones en MongoDB -# Se ejecuta en thread separado -``` - ---- - -## 📈 Arquitectura Implementada - -### Patrón Hexagonal (Limpia) -``` -┌─────────────────────────────────────────────┐ -│ Capa Presentación (FastAPI) │ -│ - Endpoints REST │ -│ - Validación con Pydantic │ -└──────────────┬──────────────────────────────┘ - ↓ -┌──────────────────────────────────────────────┐ -│ Capa de Aplicación (Services) │ -│ - Lógica de negocio │ -│ - Orquestación de casos de uso │ -└──────────────┬──────────────────────────────┘ - ↓ -┌──────────────────────────────────────────────┐ -│ Capa de Dominio (Domain Models) │ -│ - Entidades puras │ -│ - Lógica de negocio esencial │ -└──────────────┬──────────────────────────────┘ - ↓ -┌──────────────────────────────────────────────┐ -│ Puertos (Interfaces Abstractas) │ -│ - NotificationRepository │ -│ - Define contratos sin implementación │ -└──────────────┬──────────────────────────────┘ - ↓ -┌──────────────────────────────────────────────┐ -│ Adaptadores (Infraestructura) │ -│ - NotificationRepositoryMongo │ -│ - Detalles de implementación │ -└──────────────┬──────────────────────────────┘ - ↓ -┌──────────────────────────────────────────────┐ -│ Base de Datos (MongoDB) │ -│ - Persistencia física │ -└──────────────────────────────────────────────┘ -``` - ---- - -## 🔄 Flujo de Eventos Completo - -### Step-by-Step del Proceso - -``` -1️⃣ FRONTEND - └─ Usuario cambia estado de reporte - PUT /reports/{id}/status { "estado": "resuelto" } - -2️⃣ API DE REPORTES - ├─ Valida datos - ├─ Actualiza reporte en MongoDB - └─ Captura estado anterior/nuevo - -3️⃣ CREA EVENTO - └─ Construye ReportMessage con: - - event_type: "report.update_status" - - old_estado: "en proceso" - - new_estado: "resuelto" - - id_usuario: 1 - -4️⃣ ENVÍA A RABBITMQ - └─ send_to_queue("notifications_queue", message) - -5️⃣ RABBITMQ ALMACENA - └─ Persiste mensaje en cola - -6️⃣ NOTIFICATION CONSUMER ESCUCHA - └─ Thread escuchando notifications_queue - -7️⃣ PROCESA EVENTO - ├─ Recibe mensaje del queue - ├─ Valida mensaje - └─ Extrae datos - -8️⃣ CREA NOTIFICACIÓN - └─ NotificationService.send_report_status_notification() - -9️⃣ ALMACENA EN MONGODB - └─ Inserta documento en voxpopuli_notifications.notificaciones - -🔟 FRONTEND OBTIENE - ├─ GET /notifications/{user_id} - └─ Recibe lista de notificaciones - -✅ USUARIO VE NOTIFICACIÓN -``` - ---- - -## 🔌 Integración de Servicios - -### Colas RabbitMQ Configuradas - -``` -┌─────────────────────────────────────────┐ -│ reports_queue │ -│ (Existente) │ -│ ├─ ReportConsumer escucha │ -│ ├─ Eventos: CREATE, DELETE, ... │ -│ └─ Guarda en MongoDB │ -└─────────────────────────────────────────┘ - -┌─────────────────────────────────────────┐ ✨ NUEVA -│ notifications_queue │ -│ ├─ NotificationConsumer escucha │ -│ ├─ Eventos: UPDATE_STATUS │ -│ └─ Crea notificaciones en MongoDB │ -└─────────────────────────────────────────┘ - -┌─────────────────────────────────────────┐ -│ users_queue │ -│ (Existente) │ -│ ├─ UserConsumer escucha │ -│ └─ Eventos: CREATE, UPDATE, DELETE │ -└─────────────────────────────────────────┘ -``` - -### Bases de Datos - -``` -MySQL -├─ voxpopuli_users (Existente) -│ └─ Tabla: usuarios - -MongoDB -├─ voxpopuli_reports (Existente) -│ └─ Colección: reportes -│ -└─ voxpopuli_notifications (✨ NUEVA) - └─ Colección: notificaciones -``` - ---- - -## 💻 Servicios Docker - -### docker-compose.yaml Actualizado - -```yaml -services: - mysql: - image: mysql:8.0 - port: 3306 - - mongodb: - image: mongo:7.0 - port: 27017 - - rabbitmq: # ✨ NUEVO - image: rabbitmq:3.13-management - port: 5672 (AMQP) - port: 15672 (Management UI) -``` - ---- - -## 🎓 Documentación Entregada - -### 1. FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md -``` -Secciones: -├─ 1. Arquitectura Backend -├─ 2. Requerimientos Funcionales -│ ├─ Panel de Notificaciones -│ ├─ Sincronización Tiempo Real -│ ├─ Notificaciones Visuales -│ └─ Página Dedicada -├─ 3. Integración con Componentes -├─ 4. Especificaciones Técnicas -├─ 5. UI/UX Recomendaciones -├─ 6. Flujo de Usuario -├─ 7. Seguridad -├─ 8. Testing -├─ 9. Dependencias -├─ 10. Roadmap Futuro -├─ 11. Endpoints Referencia -├─ 12. Ejemplo React Completo -├─ 13. FAQ -└─ 14. Checklist Implementación -``` - -### 2. IMPLEMENTATION_SUMMARY.md -``` -Secciones: -├─ Resumen Ejecutivo -├─ Estructura de Archivos -├─ Cambios en Archivos Existentes -├─ Flujo de Eventos -├─ BD MongoDB -├─ Colas RabbitMQ -├─ API Endpoints -├─ Seguridad -├─ Pruebas -├─ Troubleshooting -├─ Documentación Relacionada -└─ Checklist -``` - -### 3. QUICKSTART_NOTIFICATIONS.md -``` -Secciones: -├─ Inicio Rápido con Docker -├─ Verificación de Conexiones -├─ Prueba Completa del Flujo -├─ Verificar en BD -├─ Logs en Tiempo Real -├─ Troubleshooting -└─ Tips Útiles -``` - -### 4. VERIFICACION.md -``` -Verificación de: -├─ Archivos Creados (13 archivos) -├─ Cambios en Existentes (5 archivos) -├─ Tests de Verificación -├─ Arquitectura -├─ BD y Colas -├─ Endpoints -├─ Seguridad -└─ Checklist de Pruebas -``` - ---- - -## 🎯 Lo que Ahora Pueden Hacer - -### Backend (Ya Implementado) -✅ Crear y almacenar notificaciones -✅ Obtener notificaciones de un usuario -✅ Marcar como leídas -✅ Eliminar notificaciones -✅ Contar no leídas -✅ Sincronizar eventos con cambios de reportes - -### Frontend (A Implementar) -⏳ Mostrar icono de campana en navegación -⏳ Dropdown con últimas notificaciones -⏳ Página dedicada de notificaciones -⏳ Toast notifications en tiempo real -⏳ Polling o WebSocket -⏳ Filtros y búsqueda -⏳ Paginación - ---- - -## 🚀 Pasos Siguientes - -### Fase 1: Testing Backend ✅ -- [x] Crear archivos de dominio -- [x] Crear repositorio MongoDB -- [x] Crear servicios -- [x] Crear API endpoints -- [x] Crear consumidor -- [x] Integrar eventos -- [x] Documentar - -### Fase 2: Implementación Frontend ⏳ -- [ ] Leer `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` -- [ ] Crear hook `useNotifications` -- [ ] Implementar componente de campana -- [ ] Implementar dropdown -- [ ] Agregar polling/WebSocket -- [ ] Crear página de notificaciones -- [ ] Implementar toasts -- [ ] Testing - -### Fase 3: Optimización Futura -- [ ] WebSocket en lugar de polling -- [ ] Redis para caching -- [ ] Histórico de cambios -- [ ] Notificaciones push - ---- - -## 📊 Estadísticas - -| Métrica | Cantidad | -|---------|----------| -| Archivos creados | 13 | -| Archivos modificados | 5 | -| Líneas de código (backend) | ~2,500+ | -| Endpoints API | 7 | -| Colas RabbitMQ | 3 | -| BD MongoDB | 2 | -| Documentación escrita | 4 docs | -| Ejemplos de código | 200+ líneas | -| Tiempo estimado de implementación | 8-10 horas | - ---- - -## 🎁 Archivos Entregados - -### Código Backend -``` -src/domain/notifications.py -src/application/ports/notification_repository.py -src/application/services/notification_services.py -src/infrastructure/adapters/persistence/notification_repository_mongo.py -src/infrastructure/api/notifications/__init__.py -src/infrastructure/api/notifications/app.py -src/infrastructure/api/notifications/router.py -src/infrastructure/api/notifications/schemas.py -src/infrastructure/api/notifications/notifications.py -src/infrastructure/api/notifications/root.py -src/consumers/notification_consumer.py -``` - -### Actualizaciones -``` -src/core/config.py (actualizado) -src/main.py (actualizado) -src/infrastructure/adapters/rabbitmq/messages.py (actualizado) -src/application/services/report_services.py (actualizado) -docker-compose.yaml (actualizado) -API_EXAMPLES.json (actualizado) -``` - -### Documentación -``` -FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md -IMPLEMENTATION_SUMMARY.md -QUICKSTART_NOTIFICATIONS.md -VERIFICACION.md -RESUMEN_FINAL.md (este archivo) -``` - ---- - -## 🔐 Aspectos de Seguridad Implementados - -✅ Autenticación JWT requerida -✅ Autorización por usuario -✅ Validación de datos con Pydantic -✅ Manejo de excepciones -✅ Logs detallados -✅ Rate limiting (a considerar en frontend) - ---- - -## 🧪 Cómo Probar Rápidamente - -```bash -# 1. Levantar servicios -docker-compose up -d - -# 2. Ejecutar la app -python src/main.py - -# 3. En otra terminal, crear un usuario y un reporte -curl -X POST http://localhost:8000/users/ ... - -# 4. Cambiar estado del reporte (dispara notificación) -curl -X PUT http://localhost:8001/reports/{id}/status ... - -# 5. Obtener notificaciones -curl http://localhost:8002/notifications/1 ... -``` - ---- - -## 📞 Recursos de Ayuda - -### Documentación -- 📖 `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` - Guía completa para frontend -- 📋 `IMPLEMENTATION_SUMMARY.md` - Arquitectura técnica -- 🚀 `QUICKSTART_NOTIFICATIONS.md` - Inicio rápido -- ✅ `VERIFICACION.md` - Checklist - -### URLs Importantes -- 🔗 API Docs: http://localhost:8002/docs -- 🔗 RabbitMQ: http://localhost:15672 (user: voxpopuli, pass: voxpopuli_pass) -- 🔗 MongoDB: localhost:27017 - -### Comandos Útiles -```bash -# Ver logs en tiempo real -docker-compose logs -f - -# Acceder a MongoDB -docker exec -it voxpopuli_mongo mongosh - -# Ver colas RabbitMQ -http://localhost:15672 > Queues -``` - ---- - -## ✨ Resumen Ejecutivo - -### Antes -``` -Reporte Estado Cambia - ↓ -¿Cómo sabe el usuario? - ↓ -No tiene forma de saberlo -``` - -### Ahora -``` -Reporte Estado Cambia - ↓ -Evento a RabbitMQ - ↓ -Consumidor procesa - ↓ -Notificación creada - ↓ -Usuario recibe alerta - ↓ -✅ PERFECTO -``` - ---- - -## 🎉 Conclusión - -La **API de Notificaciones** está completamente implementada y lista para que el equipo de frontend la integre. - -### Estado: ✅ LISTO PARA PRODUCCIÓN (Backend) - -Todos los archivos han sido creados siguiendo: -- ✅ Arquitectura hexagonal -- ✅ Mejores prácticas de código -- ✅ Documentación exhaustiva -- ✅ Ejemplos funcionales - -### Próximo paso: Implementación en Frontend - -Consultar `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` para comenzar. - ---- - -**🎊 ¡Implementación Completada Exitosamente! 🎊** - ---- - -**Información del Proyecto:** -- Versión: 1.0.0 -- Fecha: 29 de Abril de 2024 -- API Nueva: Notificaciones (Puerto 8002) -- BD Nueva: voxpopuli_notifications (MongoDB) -- Estado: ✅ Completado diff --git a/VERIFICACION.md b/VERIFICACION.md deleted file mode 100644 index fee1ff5..0000000 --- a/VERIFICACION.md +++ /dev/null @@ -1,438 +0,0 @@ -# Verificación de Implementación: API de Notificaciones - -**Fecha de Completación:** 29 de Abril de 2024 -**Estado:** ✅ COMPLETADO - ---- - -## ✅ Verificación de Archivos Creados - -### Archivos de Dominio -- [x] `src/domain/notifications.py` - Modelo de dominio Notification - -### Archivos de Puertos/Interfaces -- [x] `src/application/ports/notification_repository.py` - Interfaz NotificationRepository - -### Archivos de Servicios -- [x] `src/application/services/notification_services.py` - Lógica de negocio - -### Archivos de Infraestructura - Persistencia -- [x] `src/infrastructure/adapters/persistence/notification_repository_mongo.py` - Implementación MongoDB - -### Archivos de API -- [x] `src/infrastructure/api/notifications/__init__.py` - Package init -- [x] `src/infrastructure/api/notifications/app.py` - App FastAPI factory -- [x] `src/infrastructure/api/notifications/router.py` - Enrutador principal -- [x] `src/infrastructure/api/notifications/schemas.py` - Esquemas Pydantic -- [x] `src/infrastructure/api/notifications/notifications.py` - Endpoints principales -- [x] `src/infrastructure/api/notifications/root.py` - Endpoints raíz - -### Archivos de Consumidores -- [x] `src/consumers/notification_consumer.py` - Consumidor RabbitMQ - -### Documentación -- [x] `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` - Guía completa para frontend -- [x] `IMPLEMENTATION_SUMMARY.md` - Resumen de cambios y arquitectura -- [x] `QUICKSTART_NOTIFICATIONS.md` - Guía de inicio rápido -- [x] `VERIFICACION.md` - Este documento - -### Ejemplos de API -- [x] `API_EXAMPLES.json` - Actualizado con endpoints de notificaciones - ---- - -## ✅ Verificación de Cambios en Archivos Existentes - -### `src/core/config.py` -- [x] Agregado: `mongodb_notifications_db` configuration -- [x] Actualizado: Comentario de `mongodb_url` - -### `src/main.py` -- [x] Importado: `create_notifications_app` -- [x] Importado: `NotificationConsumer` -- [x] Agregado: `run_notifications_api()` function -- [x] Agregado: `run_notifications_consumer()` function -- [x] Agregado: Thread para API de notificaciones (puerto 8002) -- [x] Agregado: Thread para consumidor de notificaciones -- [x] Actualizado: Mensajes de inicio - -### `src/infrastructure/adapters/rabbitmq/messages.py` -- [x] Agregado: `UPDATE_STATUS` a `ReportEventType` enum -- [x] Agregado: Campos `old_estado`, `new_estado` a `ReportMessage` -- [x] Agregado: Campos `old_visibility`, `new_visibility` a `ReportMessage` - -### `src/application/services/report_services.py` -- [x] Refactorizado: Clase `UpdateReportStatus` -- [x] Agregado: Importación de `ReportMessage` y `send_to_queue` -- [x] Agregado: Lógica para capturar estado anterior -- [x] Agregado: Envío de evento UPDATE_STATUS a `notifications_queue` - -### `docker-compose.yaml` -- [x] Actualizado: MongoDB con autenticación -- [x] Agregado: Servicio RabbitMQ completo -- [x] Agregado: Volumen para RabbitMQ -- [x] Actualizado: Comentarios - ---- - -## 🧪 Tests de Verificación - -### Test 1: Verificar que MongoDB está configurado -```bash -# Verificar en config.py -grep -n "mongodb_notifications_db" src/core/config.py -# Resultado esperado: Debe haber una línea con la configuración -``` - -### Test 2: Verificar que RabbitMQ está en docker-compose -```bash -# Verificar en docker-compose.yaml -grep -n "rabbitmq" docker-compose.yaml -# Resultado esperado: Debe haber múltiples líneas de configuración -``` - -### Test 3: Verificar que UPDATE_STATUS existe -```bash -# Verificar en messages.py -grep -n "UPDATE_STATUS" src/infrastructure/adapters/rabbitmq/messages.py -# Resultado esperado: Debe haber línea en la enumeración -``` - -### Test 4: Verificar que API está configurada para ejecutarse -```bash -# Verificar en main.py -grep -n "run_notifications_api" src/main.py -# Resultado esperado: Debe haber función y thread -``` - -### Test 5: Verificar que los archivos de notificaciones existen -```bash -# Listar archivos creados -ls -la src/infrastructure/api/notifications/ -# Resultado esperado: Debe haber 6 archivos .py - -# Listar consumidor -ls -la src/consumers/notification_consumer.py -# Resultado esperado: Archivo debe existir -``` - ---- - -## 🏗️ Verificación de Arquitectura - -### Arquitectura Hexagonal ✅ - -La implementación sigue el patrón hexagonal existente: - -``` -┌─────────────────────────────────────────┐ -│ EXTERNA: FastAPI (Puerto 8002) │ -├─────────────────────────────────────────┤ -│ ADAPTADOR: API Router & Schemas │ -├─────────────────────────────────────────┤ -│ APLICACIÓN: NotificationService │ -├─────────────────────────────────────────┤ -│ PUERTOS: NotificationRepository │ -├─────────────────────────────────────────┤ -│ DOMINIO: Notification (dataclass) │ -├─────────────────────────────────────────┤ -│ ADAPTADOR: MongoDB Persistencia │ -├─────────────────────────────────────────┤ -│ EXTERNA: MongoDB (Base de datos) │ -└─────────────────────────────────────────┘ -``` - -### Integración de Eventos ✅ - -Flujo de eventos completamente integrado: - -``` -Reporte Estado Cambio - ↓ -UpdateReportStatus.execute() - ↓ -Envía evento UPDATE_STATUS a RabbitMQ - ↓ -NotificationConsumer escucha - ↓ -Procesa evento y crea notificación - ↓ -Almacena en MongoDB - ↓ -Frontend obtiene con polling/WebSocket -``` - ---- - -## 📊 Especificación de Base de Datos - -### MongoDB - `voxpopuli_notifications` - -```javascript -db.notificaciones.find().pretty() -{ - "_id": ObjectId("..."), - "id_usuario": 1, - "id_reporte": "uuid", - "message": "string", - "fecha": ISODate("..."), - "read": boolean -} -``` - -### Índices Automáticos ✅ -- `id_usuario` - para búsquedas por usuario -- `fecha` - para ordenamiento -- `read` - para filtrado - ---- - -## 🔌 Colas RabbitMQ Configuradas - -| Cola | Propósito | Consumidor | Estado | -|------|-----------|-----------|--------| -| `reports_queue` | Eventos de reportes | ReportConsumer | ✅ Existente | -| `notifications_queue` | Eventos de notificaciones | NotificationConsumer | ✅ Nuevo | -| `users_queue` | Eventos de usuarios | UserConsumer | ✅ Existente | - ---- - -## 🚀 Endpoints Implementados - -### Health Check -- [x] `GET /` - Health check - -### Notificaciones (CRUD) -- [x] `POST /notifications/` - Crear notificación -- [x] `GET /notifications/{user_id}` - Obtener notificaciones del usuario -- [x] `GET /notifications/{user_id}/unread-count` - Obtener conteo de no leídas -- [x] `PUT /notifications/{notification_id}/read` - Marcar como leída -- [x] `PUT /notifications/{user_id}/read-all` - Marcar todas como leídas -- [x] `DELETE /notifications/{notification_id}` - Eliminar notificación - ---- - -## 📝 Documentación Completa - -### Documentos Creados -1. **FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md** (14 secciones) - - Arquitectura backend - - Requerimientos funcionales - - Integración con componentes - - Especificaciones técnicas - - Ejemplos de código React - - Testing - - Troubleshooting - -2. **IMPLEMENTATION_SUMMARY.md** (14 secciones) - - Resumen ejecutivo - - Estructura de archivos - - Cambios en existentes - - Flujo de eventos - - Seguridad - - Tests - - Troubleshooting - -3. **QUICKSTART_NOTIFICATIONS.md** - - Setup con Docker - - Prueba rápida - - Debugging - - Tips útiles - -4. **API_EXAMPLES.json** - - Todos los endpoints documentados - - Ejemplos de requests/responses - - Mensajes de notificación - ---- - -## 🔐 Seguridad Implementada - -- [x] Autenticación JWT (requiere token) -- [x] Autorización por usuario (valida en backend) -- [x] Base de datos MongoDB con índices -- [x] Validación de datos en Pydantic -- [x] Manejo de excepciones -- [x] Logs de errores - ---- - -## 🧪 Checklist de Pruebas - -### Pruebas Manuales Recomendadas - -- [ ] Docker compose levanta sin errores -- [ ] MongoDB se conecta correctamente -- [ ] RabbitMQ se conecta correctamente -- [ ] API de notificaciones responde a health check -- [ ] Cambiar estado de reporte dispara notificación -- [ ] Notificación se guarda en MongoDB -- [ ] Se obtiene notificación via GET -- [ ] Marcar como leída funciona -- [ ] Conteo de no leídas es correcto -- [ ] Marcar todas como leídas funciona -- [ ] Eliminar notificación funciona -- [ ] Badge se actualiza correctamente - -### Pruebas de Integración (Frontend) - -- [ ] Hook `useNotifications` se conecta correctamente -- [ ] Polling obtiene notificaciones -- [ ] Toast se muestra en tiempo real -- [ ] Dropdown de notificaciones funciona -- [ ] Página dedicada carga correctamente -- [ ] Filtros funcionan -- [ ] Paginación funciona -- [ ] Búsqueda funciona - ---- - -## 🐳 Servicios en Docker - -### Verificar que todos están corriendo - -```bash -# Comando -docker-compose ps - -# Resultado esperado: -# NAME STATUS -# voxpopuli_mysql Up (healthy) -# voxpopuli_mongo Up (healthy) -# voxpopuli_rabbitmq Up (healthy) -``` - ---- - -## 📊 Métricas de Implementación - -| Métrica | Valor | -|---------|-------| -| Archivos creados | 13 | -| Archivos modificados | 5 | -| Líneas de código (backend) | ~2,000+ | -| Endpoints API | 7 | -| Colas RabbitMQ | 3 | -| Base de datos MongoDB | 1 nueva | -| Documentación (caracteres) | ~50,000+ | -| Ejemplo de código (líneas) | 200+ | - ---- - -## 🚀 Próximos Pasos - -### Inmediatos -1. Leer `FRONTEND_NOTIFICATIONS_IMPLEMENTATION.md` -2. Levantar servicios con `docker-compose up -d` -3. Ejecutar `python src/main.py` -4. Probar endpoints con ejemplos en `API_EXAMPLES.json` - -### Corto Plazo (Frontend) -1. Crear hook `useNotifications` -2. Implementar componente de campana -3. Implementar dropdown -4. Agregar polling o WebSocket -5. Implementar página de notificaciones - -### Mediano Plazo (Mejoras) -1. Implementar WebSocket para tiempo real -2. Agregar Redis para caching -3. Implementar histórico completo -4. Agregar notificaciones push - ---- - -## 🎯 Estado Final - -``` -Backend: ✅ COMPLETADO -Documentación: ✅ COMPLETA -Código: ✅ PROBADO -Arquitectura: ✅ VÁLIDA -Tests: ⏳ PENDIENTE (Frontend) -Producción: ⏳ PENDIENTE -``` - ---- - -## 📞 Notas Importantes - -### ⚠️ Importante: Autenticación JWT -Todos los endpoints excepto health check requieren JWT token en header: -``` -Authorization: Bearer {tu_token_jwt} -``` - -### ⚠️ Importante: RabbitMQ -Debe estar levantado para que los eventos de notificaciones funcionen: -```bash -docker-compose up -d rabbitmq -``` - -### ⚠️ Importante: Consumidor -El consumidor de notificaciones debe estar ejecutándose: -```bash -# En threads (automático con python src/main.py) -# O manualmente: -python src/consumers/notification_consumer.py -``` - -### ℹ️ Información: Escalabilidad -Para producción, considerar: -- WebSocket en lugar de polling -- Redis para cache -- Multiple instancias de consumidor -- Load balancer - ---- - -## 📄 Referencia Rápida - -### URLs Importantes -- API Notificaciones: http://localhost:8002 -- Swagger UI: http://localhost:8002/docs -- RabbitMQ Management: http://localhost:15672 (user: voxpopuli, pass: voxpopuli_pass) - -### Archivos Clave -- Configuración: `src/core/config.py` -- Main: `src/main.py` -- Servicios: `src/application/services/notification_services.py` -- API: `src/infrastructure/api/notifications/notifications.py` -- Consumidor: `src/consumers/notification_consumer.py` - -### Comandos Útiles -```bash -# Levantar servicios -docker-compose up -d - -# Ver logs -docker-compose logs -f - -# Ejecutar app -python src/main.py - -# Acceder a MongoDB -docker exec -it voxpopuli_mongo mongosh - -# Verificar colas RabbitMQ -# Ir a http://localhost:15672 > Queues -``` - ---- - -## ✨ Conclusión - -La **API de Notificaciones** ha sido implementada **completamente** siguiendo: -- ✅ Arquitectura hexagonal -- ✅ Patrones del proyecto existente -- ✅ Mejores prácticas de code -- ✅ Documentación exhaustiva -- ✅ Ejemplos de implementación - -**Estado de Implementación: LISTO PARA FRONTEND** - ---- - -**Última actualización:** 29 de Abril de 2024 -**Versión:** 1.0.0 -**Verificador:** Development Team diff --git a/docker-compose.yaml b/docker-compose.yaml index e3c5f0a..539acd6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,61 +19,21 @@ services: timeout: 5s retries: 5 - mongodb-reports: + mongodb: image: mongo:7.0 - container_name: voxpopuli_mongo_reports + container_name: voxpopuli_mongo environment: - MONGO_INITDB_ROOT_USERNAME: admin - MONGO_INITDB_ROOT_PASSWORD: admin_password MONGO_INITDB_DATABASE: voxpopuli_reports ports: - "27017:27017" volumes: - - mongo_reports_data:/data/db + - mongo_data:/data/db healthcheck: test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] interval: 10s timeout: 5s retries: 5 - command: mongod --auth - - mongodb-notifications: - image: mongo:7.0 - container_name: voxpopuli_mongo_notifications - environment: - MONGO_INITDB_ROOT_USERNAME: admin - MONGO_INITDB_ROOT_PASSWORD: admin_password - MONGO_INITDB_DATABASE: voxpopuli_notifications - ports: - - "27018:27017" - volumes: - - mongo_notifications_data:/data/db - healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] - interval: 10s - timeout: 5s - retries: 5 - command: mongod --auth - - rabbitmq: - image: rabbitmq:3.13-management - container_name: voxpopuli_rabbitmq - environment: - RABBITMQ_DEFAULT_USER: voxpopuli - RABBITMQ_DEFAULT_PASS: voxpopuli_pass - ports: - - "5672:5672" - - "15672:15672" # Management UI - volumes: - - rabbitmq_data:/var/lib/rabbitmq - healthcheck: - test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] - interval: 10s - timeout: 5s - retries: 5 volumes: mysql_data: - mongo_reports_data: - mongo_notifications_data: - rabbitmq_data: \ No newline at end of file + mongo_data: \ No newline at end of file diff --git a/src/application/ports/notification_repository.py b/src/application/ports/notification_repository.py deleted file mode 100644 index 368380a..0000000 --- a/src/application/ports/notification_repository.py +++ /dev/null @@ -1,100 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List, Optional -from domain.notifications import Notification - - -class NotificationRepository(ABC): - """Puerto/Interfaz para el repositorio de notificaciones""" - - @abstractmethod - def create(self, notification: Notification) -> str: - """ - Crea una nueva notificación - - Args: - notification: Objeto Notification a crear - - Returns: - ID de la notificación creada - """ - pass - - @abstractmethod - def get_by_id(self, notification_id: str) -> Optional[Notification]: - """ - Obtiene una notificación por ID - - Args: - notification_id: ID de la notificación - - Returns: - Objeto Notification o None si no existe - """ - pass - - @abstractmethod - def get_by_user(self, user_id: int, limit: int = 50, offset: int = 0) -> List[Notification]: - """ - Obtiene notificaciones de un usuario - - Args: - user_id: ID del usuario - limit: Número máximo de notificaciones - offset: Desplazamiento de registros - - Returns: - Lista de notificaciones del usuario - """ - pass - - @abstractmethod - def mark_as_read(self, notification_id: str) -> bool: - """ - Marca una notificación como leída - - Args: - notification_id: ID de la notificación - - Returns: - True si se actualizó exitosamente - """ - pass - - @abstractmethod - def mark_all_as_read(self, user_id: int) -> int: - """ - Marca todas las notificaciones de un usuario como leídas - - Args: - user_id: ID del usuario - - Returns: - Número de notificaciones actualizadas - """ - pass - - @abstractmethod - def delete(self, notification_id: str) -> bool: - """ - Elimina una notificación - - Args: - notification_id: ID de la notificación - - Returns: - True si se eliminó exitosamente - """ - pass - - @abstractmethod - def get_unread_count(self, user_id: int) -> int: - """ - Obtiene el número de notificaciones no leídas para un usuario - - Args: - user_id: ID del usuario - - Returns: - Número de notificaciones no leídas - """ - pass diff --git a/src/application/services/notification_services.py b/src/application/services/notification_services.py deleted file mode 100644 index 575ffa1..0000000 --- a/src/application/services/notification_services.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import List, Optional -from domain.notifications import Notification -from infrastructure.adapters.persistence.notification_repository_mongo import NotificationRepositoryMongo -from datetime import datetime - - -class NotificationService: - """Servicio de negocio para notificaciones""" - - def __init__(self): - self.repository = NotificationRepositoryMongo() - - def create_notification( - self, - id_usuario: int, - id_reporte: str, - message: str - ) -> str: - """ - Crea una nueva notificación - - Args: - id_usuario: ID del usuario - id_reporte: ID del reporte relacionado - message: Mensaje de la notificación - - Returns: - ID de la notificación creada - """ - notification = Notification( - id_usuario=id_usuario, - id_reporte=id_reporte, - message=message, - fecha=datetime.utcnow(), - read=False - ) - return self.repository.create(notification) - - def get_notification(self, notification_id: str) -> Optional[Notification]: - """Obtiene una notificación por ID""" - return self.repository.get_by_id(notification_id) - - def get_user_notifications( - self, - user_id: int, - limit: int = 50, - offset: int = 0 - ) -> List[Notification]: - """Obtiene notificaciones de un usuario""" - return self.repository.get_by_user(user_id, limit, offset) - - def mark_as_read(self, notification_id: str) -> bool: - """Marca una notificación como leída""" - return self.repository.mark_as_read(notification_id) - - def mark_all_as_read(self, user_id: int) -> int: - """Marca todas las notificaciones de un usuario como leídas""" - return self.repository.mark_all_as_read(user_id) - - def delete_notification(self, notification_id: str) -> bool: - """Elimina una notificación""" - return self.repository.delete(notification_id) - - def get_unread_count(self, user_id: int) -> int: - """Obtiene el número de notificaciones no leídas""" - return self.repository.get_unread_count(user_id) - - def send_report_status_notification( - self, - id_usuario: int, - id_reporte: str, - old_status: str, - new_status: str - ) -> str: - """ - Envía una notificación cuando cambia el estado de un reporte - - Args: - id_usuario: ID del usuario propietario del reporte - id_reporte: ID del reporte - old_status: Estado anterior - new_status: Nuevo estado - - Returns: - ID de la notificación creada - """ - status_messages = { - ("en proceso", "resuelto"): f"¡Tu reporte #{id_reporte} ha sido resuelto!", - ("en proceso", "no resuelto"): f"Tu reporte #{id_reporte} fue marcado como no resuelto.", - ("no resuelto", "resuelto"): f"¡Tu reporte #{id_reporte} ha sido resuelto!", - ("resuelto", "en proceso"): f"Tu reporte #{id_reporte} ha sido reabierto.", - } - - message = status_messages.get( - (old_status, new_status), - f"El estado de tu reporte #{id_reporte} ha cambiado a {new_status}" - ) - - return self.create_notification(id_usuario, id_reporte, message) diff --git a/src/application/services/report_services.py b/src/application/services/report_services.py index 0268118..2c6cb47 100644 --- a/src/application/services/report_services.py +++ b/src/application/services/report_services.py @@ -256,7 +256,7 @@ class DeleteReport: } class UpdateReportStatus: - """Use case para actualizar el estado de un reporte - envía evento a RabbitMQ""" + """Use case para actualizar el estado de un reporte""" def __init__(self, repo: ReportRepository): if not isinstance(repo, ReportRepository): raise TypeError("repo must implement ReportRepository") @@ -264,7 +264,7 @@ class UpdateReportStatus: def execute(self, report_id: str, new_estado: str) -> Dict[str, Any]: """ - Actualiza el estado de un reporte y envía notificación vía RabbitMQ. + Actualiza el estado de un reporte. Valida previamente: - Reporte existe - Estado es válido @@ -294,32 +294,9 @@ class UpdateReportStatus: "message": f"Error al buscar reporte: {str(e)}" } - # Guardar estado anterior para notificación - old_estado = report.estado - # Actualizar estado try: self.repo.update_estado(report_id, new_estado) - - # Enviar evento a RabbitMQ solo si el estado cambió - if old_estado != new_estado: - message = ReportMessage( - event_type=ReportEventType.UPDATE_STATUS, - id_reporte=report_id, - id_usuario=report.id_usuario, - old_estado=old_estado, - new_estado=new_estado, - estado=new_estado, - tipo_reporte=report.tipo_reporte, - descripcion=report.descripcion, - ubicacion=report.ubicacion, - lat=report.lat, - lng=report.lng, - visibilidad=report.visibilidad, - fecha_creacion=report.fecha_creacion.isoformat() if report.fecha_creacion else None - ) - send_to_queue("notifications_queue", message.to_dict()) - return { "status": "success", "message": f"Estado del reporte actualizado a '{new_estado}'", diff --git a/src/consumers/notification_consumer.py b/src/consumers/notification_consumer.py deleted file mode 100644 index 97aa234..0000000 --- a/src/consumers/notification_consumer.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Notifications RabbitMQ Consumer - Processes report status change events""" -import sys -import os -import logging -from datetime import datetime - -# Add src to path to import modules -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from infrastructure.adapters.rabbitmq.consumer import RabbitMQConsumer -from infrastructure.adapters.rabbitmq.messages import ReportMessage, ReportEventType -from application.services.notification_services import NotificationService - -# Set up logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler('logs/notifications_consumer.log'), - logging.StreamHandler() - ] -) -logger = logging.getLogger(__name__) - - -class NotificationConsumer: - """Consumer para eventos de cambio de estado de reportes desde RabbitMQ""" - - def __init__(self): - self.notification_service = NotificationService() - self.consumer = RabbitMQConsumer(queue_name='notifications_queue') - self.consumer.set_callback(self.process_message) - - def start(self): - """Inicia el consumidor""" - logger.info("Notifications Consumer started") - self.consumer.start_consuming() - - def process_message(self, message_dict: dict): - """ - Procesa un evento de cambio de estado de reporte desde RabbitMQ - - Args: - message_dict: Diccionario con los datos del mensaje - """ - try: - # Reconstruir el objeto ReportMessage - message = ReportMessage.from_dict(message_dict) - - # Solo procesar eventos de actualización de estado - if message.event_type == ReportEventType.UPDATE_STATUS: - self._handle_status_update(message) - elif message.event_type == ReportEventType.UPDATE_VISIBILITY: - self._handle_visibility_update(message) - else: - logger.debug(f"Ignoring event type: {message.event_type}") - - except Exception as e: - logger.error(f"Error processing notification message: {e}", exc_info=True) - raise - - def _handle_status_update(self, message: ReportMessage): - """Maneja la actualización de estado de un reporte""" - try: - logger.info( - f"Creating notification for report {message.id_reporte} " - f"status change from {message.old_estado} to {message.new_estado}" - ) - - # Crear notificación para el usuario propietario del reporte - self.notification_service.send_report_status_notification( - id_usuario=message.id_usuario, - id_reporte=message.id_reporte, - old_status=message.old_estado, - new_status=message.new_estado - ) - - logger.info(f"Notification created for user {message.id_usuario}") - - except Exception as e: - logger.error(f"Error creating status notification: {e}", exc_info=True) - raise - - def _handle_visibility_update(self, message: ReportMessage): - """Maneja la actualización de visibilidad de un reporte""" - try: - # Notificar si la visibilidad cambió significativamente - if hasattr(message, 'old_visibility') and hasattr(message, 'new_visibility'): - logger.info( - f"Report {message.id_reporte} visibility changed " - f"from {message.old_visibility} to {message.new_visibility}" - ) - - # Puedes agregar lógica para notificar sobre cambios de visibilidad - # Por ahora solo registramos el evento - - except Exception as e: - logger.error(f"Error handling visibility update: {e}", exc_info=True) diff --git a/src/core/config.py b/src/core/config.py index 66ce650..2a89b8f 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -11,29 +11,19 @@ class Settings(BaseSettings): description="URL de conexión a MySQL para API de Usuarios" ) - # Base de datos MongoDB - Reportes (Instancia Separada) - mongodb_reports_url: str = Field( - default=os.getenv("MONGODB_REPORTS_URL", "mongodb://admin:admin_password@localhost:27017"), - description="URL de conexión a MongoDB para API de Reportes (Instancia 1)" + # Base de datos MongoDB + mongodb_url: str = Field( + default=os.getenv("MONGODB_URL", "mongodb://localhost:27017"), + description="URL de conexión a MongoDB para API de Reportes" ) - mongodb_reports_db: str = Field( + mongodb_db: str = Field( default="voxpopuli_reports", - description="Base de datos MongoDB para Reportes" + description="Base de datos MongoDB" ) - # Base de datos MongoDB - Notificaciones (Instancia Separada) - mongodb_notifications_url: str = Field( - default=os.getenv("MONGODB_NOTIFICATIONS_URL", "mongodb://admin:admin_password@localhost:27018"), - description="URL de conexión a MongoDB para Notificaciones (Instancia 2)" - ) - mongodb_notifications_db: str = Field( - default="voxpopuli_notifications", - description="Base de datos MongoDB para Notificaciones" - ) + rabbitmq: str = Field ( + default=os.getenv("RABBITMQ_URI", "localhost") - rabbitmq: str = Field( - default=os.getenv("RABBITMQ_URI", "localhost"), - description="URL de conexión a RabbitMQ" ) # JWT Configuration diff --git a/src/domain/notifications.py b/src/domain/notifications.py deleted file mode 100644 index 8639cc8..0000000 --- a/src/domain/notifications.py +++ /dev/null @@ -1,14 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime -from typing import Optional - - -@dataclass -class Notification: - """Modelo de dominio para Notificación""" - id_notificacion: Optional[str] = None - id_usuario: int = None - id_reporte: str = None - message: str = None - fecha: Optional[datetime] = None - read: bool = False diff --git a/src/infrastructure/adapters/persistence/mongodb.py b/src/infrastructure/adapters/persistence/mongodb.py index 1b05a4b..974f734 100644 --- a/src/infrastructure/adapters/persistence/mongodb.py +++ b/src/infrastructure/adapters/persistence/mongodb.py @@ -2,18 +2,10 @@ from pymongo import MongoClient from pymongo.collection import Collection from core.config import ConfSettings -# Conexión a MongoDB para Reportes (Instancia Separada - Puerto 27017) -mongo_client_reports = MongoClient(ConfSettings.mongodb_reports_url) -mongodb_reports = mongo_client_reports[ConfSettings.mongodb_reports_db] - -# Conexión a MongoDB para Notificaciones (Instancia Separada - Puerto 27018) -mongo_client_notifications = MongoClient(ConfSettings.mongodb_notifications_url) -mongodb_notifications = mongo_client_notifications[ConfSettings.mongodb_notifications_db] +# Conexión a MongoDB para Reportes +mongo_client = MongoClient(ConfSettings.mongodb_url) +mongodb = mongo_client[ConfSettings.mongodb_db] def get_reports_collection() -> Collection: - """Obtiene la colección de reportes desde MongoDB (Instancia 1)""" - return mongodb_reports["reportes"] - -def get_notifications_collection() -> Collection: - """Obtiene la colección de notificaciones desde MongoDB (Instancia 2)""" - return mongodb_notifications["notificaciones"] + """Obtiene la colección de reportes desde MongoDB""" + return mongodb["reportes"] diff --git a/src/infrastructure/adapters/persistence/notification_repository_mongo.py b/src/infrastructure/adapters/persistence/notification_repository_mongo.py deleted file mode 100644 index 2967c13..0000000 --- a/src/infrastructure/adapters/persistence/notification_repository_mongo.py +++ /dev/null @@ -1,87 +0,0 @@ -from application.ports.notification_repository import NotificationRepository -from domain.notifications import Notification -from infrastructure.adapters.persistence.mongodb import get_notifications_collection -from typing import List, Optional -from bson import ObjectId -from datetime import datetime - - -class NotificationRepositoryMongo(NotificationRepository): - """Implementación del repositorio de Notificaciones usando MongoDB (Instancia Separada)""" - - def __init__(self): - self.collection = get_notifications_collection() - - def create(self, notification: Notification) -> str: - """Crea una nueva notificación""" - notification_dict = { - "id_usuario": notification.id_usuario, - "id_reporte": notification.id_reporte, - "message": notification.message, - "fecha": notification.fecha or datetime.utcnow(), - "read": notification.read - } - result = self.collection.insert_one(notification_dict) - return str(result.inserted_id) - - def get_by_id(self, notification_id: str) -> Optional[Notification]: - """Obtiene una notificación por ID""" - try: - doc = self.collection.find_one({"_id": ObjectId(notification_id)}) - if doc: - return self._to_domain(doc) - except Exception: - pass - return None - - def get_by_user(self, user_id: int, limit: int = 50, offset: int = 0) -> List[Notification]: - """Obtiene notificaciones de un usuario ordenadas por fecha descendente""" - docs = self.collection.find( - {"id_usuario": user_id} - ).sort("fecha", -1).skip(offset).limit(limit) - return [self._to_domain(doc) for doc in docs] - - def mark_as_read(self, notification_id: str) -> bool: - """Marca una notificación como leída""" - try: - result = self.collection.update_one( - {"_id": ObjectId(notification_id)}, - {"$set": {"read": True}} - ) - return result.modified_count > 0 - except Exception: - return False - - def mark_all_as_read(self, user_id: int) -> int: - """Marca todas las notificaciones de un usuario como leídas""" - result = self.collection.update_many( - {"id_usuario": user_id, "read": False}, - {"$set": {"read": True}} - ) - return result.modified_count - - def delete(self, notification_id: str) -> bool: - """Elimina una notificación""" - try: - result = self.collection.delete_one({"_id": ObjectId(notification_id)}) - return result.deleted_count > 0 - except Exception: - return False - - def get_unread_count(self, user_id: int) -> int: - """Obtiene el número de notificaciones no leídas para un usuario""" - return self.collection.count_documents({ - "id_usuario": user_id, - "read": False - }) - - def _to_domain(self, doc: dict) -> Notification: - """Convierte un documento de MongoDB a un objeto de dominio""" - return Notification( - id_notificacion=str(doc.get("_id")), - id_usuario=doc.get("id_usuario"), - id_reporte=doc.get("id_reporte"), - message=doc.get("message"), - fecha=doc.get("fecha"), - read=doc.get("read", False) - ) diff --git a/src/infrastructure/adapters/rabbitmq/messages.py b/src/infrastructure/adapters/rabbitmq/messages.py index 631589c..18724f4 100644 --- a/src/infrastructure/adapters/rabbitmq/messages.py +++ b/src/infrastructure/adapters/rabbitmq/messages.py @@ -17,7 +17,6 @@ class ReportEventType(str, Enum): """Types of report events""" CREATE = "report.create" UPDATE_VISIBILITY = "report.update_visibility" - UPDATE_STATUS = "report.update_status" DELETE = "report.delete" @@ -70,10 +69,6 @@ class ReportMessage: estado: Optional[str] = None # Estado del reporte: "en proceso", "no resuelto", "resuelto" fecha_creacion: Optional[str] = None # ISO format datetime string penalize_author: Optional[bool] = None # For update_visibility event - old_estado: Optional[str] = None # Estado anterior (para UPDATE_STATUS) - new_estado: Optional[str] = None # Nuevo estado (para UPDATE_STATUS) - old_visibility: Optional[float] = None # Visibilidad anterior (para UPDATE_VISIBILITY) - new_visibility: Optional[float] = None # Nueva visibilidad (para UPDATE_VISIBILITY) def to_dict(self): """Convert to dictionary""" diff --git a/src/infrastructure/api/notifications/__init__.py b/src/infrastructure/api/notifications/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/infrastructure/api/notifications/app.py b/src/infrastructure/api/notifications/app.py deleted file mode 100644 index 39dab89..0000000 --- a/src/infrastructure/api/notifications/app.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI -from core.config import ConfSettings -from infrastructure.api.notifications.router import router - - -def create_app() -> FastAPI: - """Factory para crear la aplicación de Notificaciones""" - app = FastAPI( - title="Notificaciones Microservice", - version="1.0.0", - description="Microservicio de gestión de notificaciones de reportes" - ) - app.include_router(router) - return app diff --git a/src/infrastructure/api/notifications/notifications.py b/src/infrastructure/api/notifications/notifications.py deleted file mode 100644 index 9e2faea..0000000 --- a/src/infrastructure/api/notifications/notifications.py +++ /dev/null @@ -1,148 +0,0 @@ -from fastapi import APIRouter, HTTPException, Query, Depends, Header -from infrastructure.api.notifications.schemas import ( - NotificationCreateRequest, - NotificationResponse, - NotificationListResponse, - UnreadCountResponse, - NotificationMarkAsReadRequest -) -from application.services.notification_services import NotificationService -from typing import Optional - -router = APIRouter() -notification_service = NotificationService() - - -@router.post("/", response_model=NotificationResponse, tags=["notifications"]) -async def create_notification(request: NotificationCreateRequest): - """Crea una nueva notificación (uso interno)""" - try: - notification_id = notification_service.create_notification( - id_usuario=request.id_usuario, - id_reporte=request.id_reporte, - message=request.message - ) - - notification = notification_service.get_notification(notification_id) - if not notification: - raise HTTPException(status_code=500, detail="Error creating notification") - - return NotificationResponse( - id_notificacion=notification.id_notificacion, - id_usuario=notification.id_usuario, - id_reporte=notification.id_reporte, - message=notification.message, - fecha=notification.fecha, - read=notification.read - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{user_id}", response_model=NotificationListResponse, tags=["notifications"]) -async def get_user_notifications( - user_id: int, - limit: int = Query(50, ge=1, le=100), - offset: int = Query(0, ge=0) -): - """ - Obtiene notificaciones de un usuario - - Args: - user_id: ID del usuario - limit: Número máximo de notificaciones (default: 50, max: 100) - offset: Desplazamiento de registros - """ - try: - notifications = notification_service.get_user_notifications( - user_id=user_id, - limit=limit, - offset=offset - ) - - unread_count = notification_service.get_unread_count(user_id) - total = len(notifications) + offset # Aproximado - - notification_responses = [ - NotificationResponse( - id_notificacion=n.id_notificacion, - id_usuario=n.id_usuario, - id_reporte=n.id_reporte, - message=n.message, - fecha=n.fecha, - read=n.read - ) - for n in notifications - ] - - return NotificationListResponse( - total=total, - unread_count=unread_count, - notifications=notification_responses - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{user_id}/unread-count", response_model=UnreadCountResponse, tags=["notifications"]) -async def get_unread_count(user_id: int): - """Obtiene el número de notificaciones no leídas para un usuario""" - try: - unread_count = notification_service.get_unread_count(user_id) - return UnreadCountResponse(unread_count=unread_count) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.put("/{notification_id}/read", response_model=NotificationResponse, tags=["notifications"]) -async def mark_notification_as_read(notification_id: str): - """Marca una notificación como leída""" - try: - success = notification_service.mark_as_read(notification_id) - if not success: - raise HTTPException(status_code=404, detail="Notification not found") - - notification = notification_service.get_notification(notification_id) - if not notification: - raise HTTPException(status_code=404, detail="Notification not found") - - return NotificationResponse( - id_notificacion=notification.id_notificacion, - id_usuario=notification.id_usuario, - id_reporte=notification.id_reporte, - message=notification.message, - fecha=notification.fecha, - read=notification.read - ) - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.put("/{user_id}/read-all", response_model=dict, tags=["notifications"]) -async def mark_all_as_read(user_id: int): - """Marca todas las notificaciones de un usuario como leídas""" - try: - updated_count = notification_service.mark_all_as_read(user_id) - return { - "message": "All notifications marked as read", - "updated_count": updated_count - } - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.delete("/{notification_id}", response_model=dict, tags=["notifications"]) -async def delete_notification(notification_id: str): - """Elimina una notificación""" - try: - success = notification_service.delete_notification(notification_id) - if not success: - raise HTTPException(status_code=404, detail="Notification not found") - - return {"message": "Notification deleted successfully"} - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) diff --git a/src/infrastructure/api/notifications/root.py b/src/infrastructure/api/notifications/root.py deleted file mode 100644 index 3b08833..0000000 --- a/src/infrastructure/api/notifications/root.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi import APIRouter -from pydantic import BaseModel - -router = APIRouter() - - -class HealthCheck(BaseModel): - """Health check response""" - status: str - service: str - - -@router.get("/", response_model=HealthCheck, tags=["health"]) -async def health_check(): - """Health check endpoint""" - return { - "status": "ok", - "service": "Notificaciones Microservice" - } diff --git a/src/infrastructure/api/notifications/router.py b/src/infrastructure/api/notifications/router.py deleted file mode 100644 index 4fb6834..0000000 --- a/src/infrastructure/api/notifications/router.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi import APIRouter -from infrastructure.api.notifications.notifications import router as notifications_router -from infrastructure.api.notifications.root import router as root_router - -router = APIRouter() - -router.include_router( - notifications_router, - prefix="/notifications", - tags=["notifications"] -) - -router.include_router( - root_router, - prefix='', - tags=["root"] -) diff --git a/src/infrastructure/api/notifications/schemas.py b/src/infrastructure/api/notifications/schemas.py deleted file mode 100644 index a69351f..0000000 --- a/src/infrastructure/api/notifications/schemas.py +++ /dev/null @@ -1,43 +0,0 @@ -from pydantic import BaseModel -from datetime import datetime -from typing import Optional - - -class NotificationCreateRequest(BaseModel): - """Solicitud para crear una notificación""" - id_usuario: int - id_reporte: str - message: str - - -class NotificationResponse(BaseModel): - """Respuesta con datos de notificación""" - id_notificacion: str - id_usuario: int - id_reporte: str - message: str - fecha: datetime - read: bool - - class Config: - from_attributes = True - - -class NotificationListResponse(BaseModel): - """Respuesta con lista de notificaciones""" - total: int - unread_count: int - notifications: list[NotificationResponse] - - class Config: - from_attributes = True - - -class NotificationMarkAsReadRequest(BaseModel): - """Solicitud para marcar como leída""" - pass - - -class UnreadCountResponse(BaseModel): - """Respuesta con conteo de no leídas""" - unread_count: int diff --git a/src/main.py b/src/main.py index 02666af..7f608f9 100644 --- a/src/main.py +++ b/src/main.py @@ -1,13 +1,11 @@ """ Punto de entrada principal para VoxPopuli Microservices -Ejecuta tres APIs en paralelo: Usuarios (puerto 8000), Reportes (puerto 8001) y Notificaciones (puerto 8002) +Ejecuta dos APIs en paralelo: Usuarios (puerto 8000) y Reportes (puerto 8001) """ from infrastructure.api.users.app import create_app as create_users_app from infrastructure.api.reports.app import create_app as create_reports_app -from infrastructure.api.notifications.app import create_app as create_notifications_app from consumers.report_consumer import ReportConsumer from consumers.user_consumer import UserConsumer -from consumers.notification_consumer import NotificationConsumer from core.config import ConfSettings import threading import uvicorn @@ -34,17 +32,6 @@ def run_reports_api(): log_level=ConfSettings.log_level, ) -def run_notifications_api(): - """Ejecuta la API de Notificaciones en puerto 8002""" - app_notifications = create_notifications_app() - uvicorn.run( - app_notifications, - host=ConfSettings.host, - port=8002, - reload=False, - log_level=ConfSettings.log_level, - ) - def run_user_consumer(): consumer = UserConsumer() consumer.start() @@ -53,10 +40,6 @@ def run_reports_consumer(): consumer = ReportConsumer() consumer.start() -def run_notifications_consumer(): - consumer = NotificationConsumer() - consumer.start() - def run(): """Inicia ambas APIs en threads separados""" @@ -66,32 +49,25 @@ def run(): users_thread = threading.Thread(target=run_users_api, daemon=True, name="Users-API") reports_thread = threading.Thread(target=run_reports_api, daemon=True, name="Reports-API") - notifications_thread = threading.Thread(target=run_notifications_api, daemon=True, name="Notifications-API") user_consumer_thread = threading.Thread(target=run_user_consumer, daemon=True, name="Users-Consumer") report_consumer_thread = threading.Thread(target=run_reports_consumer, daemon=True, name="Reports-Consumer") - notifications_consumer_thread = threading.Thread(target=run_notifications_consumer, daemon=True, name="Notifications-Consumer") users_thread.start() reports_thread.start() - notifications_thread.start() user_consumer_thread.start() report_consumer_thread.start() - notifications_consumer_thread.start() print("\n✓ API de Usuarios ejecutándose en http://0.0.0.0:8000") print("✓ API de Reportes ejecutándose en http://0.0.0.0:8001") - print("✓ API de Notificaciones ejecutándose en http://0.0.0.0:8002") print("\nDocumentación disponible en:") print(" - Usuarios: http://localhost:8000/docs") print(" - Reportes: http://localhost:8001/docs") - print(" - Notificaciones: http://localhost:8002/docs") print("\n" + "=" * 60 + "\n") try: users_thread.join() reports_thread.join() - notifications_thread.join() except KeyboardInterrupt: print("\n\nRecibiendo señal de salida...") print("Cerrando APIs...")