revert fef8ab225d
revert Added notifications!! Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -270,168 +270,8 @@
|
|||||||
"body": null
|
"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": {
|
"tipos_reporte": {
|
||||||
"1": "Infraestructura/Vía pública",
|
"1": "Infraestructura/Vía pública",
|
||||||
"2": "Inseguridad",
|
"2": "Inseguridad",
|
||||||
@@ -439,31 +279,11 @@
|
|||||||
"4": "Servicios públicos",
|
"4": "Servicios públicos",
|
||||||
"5": "Otro"
|
"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": {
|
"codigos_estado_http": {
|
||||||
"200": "OK - La solicitud fue exitosa",
|
"200": "OK - La solicitud fue exitosa",
|
||||||
"201": "Created - Recurso creado exitosamente",
|
"201": "Created - Recurso creado exitosamente",
|
||||||
"202": "Accepted - Solicitud aceptada en cola",
|
|
||||||
"204": "No Content - Eliminación exitosa",
|
"204": "No Content - Eliminación exitosa",
|
||||||
"400": "Bad Request - Error en la solicitud",
|
"400": "Bad Request - Error en la solicitud",
|
||||||
"401": "Unauthorized - Token inválido/expirado",
|
|
||||||
"404": "Not Found - Recurso no encontrado",
|
"404": "Not Found - Recurso no encontrado",
|
||||||
"500": "Internal Server Error - Error en el servidor"
|
"500": "Internal Server Error - Error en el servidor"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<div className="relative">
|
|
||||||
<button
|
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
|
||||||
className="relative p-2 rounded-full hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
🔔
|
|
||||||
{unreadCount > 0 && (
|
|
||||||
<span className="absolute top-0 right-0 bg-red-500 text-white
|
|
||||||
text-xs rounded-full w-5 h-5 flex items-center
|
|
||||||
justify-center animate-pulse">
|
|
||||||
{unreadCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<div className="absolute right-0 mt-2 w-80 bg-white rounded-lg
|
|
||||||
shadow-lg border border-gray-200">
|
|
||||||
<div className="p-4 border-b border-gray-200 font-semibold">
|
|
||||||
Notificaciones
|
|
||||||
</div>
|
|
||||||
<div className="max-h-96 overflow-y-auto">
|
|
||||||
{notifications.length === 0 ? (
|
|
||||||
<div className="p-4 text-center text-gray-500">
|
|
||||||
No hay notificaciones
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
notifications.map((n) => (
|
|
||||||
<div
|
|
||||||
key={n.id_notificacion}
|
|
||||||
className={`p-4 border-b hover:bg-gray-50 ${
|
|
||||||
!n.read ? 'bg-blue-50' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<p className={!n.read ? 'font-semibold' : ''}>
|
|
||||||
{n.message}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
|
||||||
Reporte: {n.id_reporte}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-400">
|
|
||||||
{formatDistanceToNow(new Date(n.fecha), {
|
|
||||||
locale: es,
|
|
||||||
addSuffix: true
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<div className="flex gap-2 mt-2">
|
|
||||||
{!n.read && (
|
|
||||||
<button
|
|
||||||
onClick={() => markAsRead(n.id_notificacion)}
|
|
||||||
className="text-xs text-blue-600 hover:underline"
|
|
||||||
>
|
|
||||||
Marcar como leída
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => deleteNotification(n.id_notificacion)}
|
|
||||||
className="text-xs text-red-600 hover:underline ml-auto"
|
|
||||||
>
|
|
||||||
Eliminar
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="p-3 border-t border-gray-200 text-center">
|
|
||||||
<a href="/notifications" className="text-blue-600 text-sm hover:underline">
|
|
||||||
Ver todas las notificaciones
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. Preguntas Frecuentes
|
|
||||||
|
|
||||||
**P: ¿Debo implementar WebSocket o polling está bien?**
|
|
||||||
R: Polling cada 30s está bien para MVP. WebSocket es mejor para experiencia en tiempo real pero requiere implementación en backend.
|
|
||||||
|
|
||||||
**P: ¿Cómo manejo notificaciones si el usuario está offline?**
|
|
||||||
R: Todas las notificaciones se guardan en BD. Cuando reconecte, vera el histórico completo.
|
|
||||||
|
|
||||||
**P: ¿Debo eliminar notificaciones antiguas?**
|
|
||||||
R: Por ahora, guardarlas todas. Puedes agregar lógica de archivado después de 30 días.
|
|
||||||
|
|
||||||
**P: ¿Puedo mostrar solo las últimas 10 notificaciones?**
|
|
||||||
R: Sí, el endpoint soporta `limit=10`. Usa scroll infinito para cargar más.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 14. Checklist de Implementación
|
|
||||||
|
|
||||||
- [ ] Servicio HTTP creado para llamadas a `/notifications/{user_id}`
|
|
||||||
- [ ] Hook `useNotifications` implementado
|
|
||||||
- [ ] Componente de icono de campana con badge
|
|
||||||
- [ ] Dropdown de últimas notificaciones
|
|
||||||
- [ ] Página dedicada `/notifications`
|
|
||||||
- [ ] Toast notifications en tiempo real
|
|
||||||
- [ ] Funcionalidad de marcar como leída
|
|
||||||
- [ ] Funcionalidad de eliminar notificaciones
|
|
||||||
- [ ] Polling o WebSocket implementado
|
|
||||||
- [ ] Tests unitarios para hooks
|
|
||||||
- [ ] Tests de integración end-to-end
|
|
||||||
- [ ] Documentación de componentes
|
|
||||||
- [ ] Respuesta mobile optimizada
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Última actualización:** 29 de Abril de 2024
|
|
||||||
**Versión API:** 1.0.0
|
|
||||||
**Autor:** VoxPopuli Development Team
|
|
||||||
@@ -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
|
|
||||||
@@ -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 ✅**
|
|
||||||
@@ -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.
|
|
||||||
561
RESUMEN_FINAL.md
561
RESUMEN_FINAL.md
@@ -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
|
|
||||||
438
VERIFICACION.md
438
VERIFICACION.md
@@ -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
|
|
||||||
@@ -19,61 +19,21 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
mongodb-reports:
|
mongodb:
|
||||||
image: mongo:7.0
|
image: mongo:7.0
|
||||||
container_name: voxpopuli_mongo_reports
|
container_name: voxpopuli_mongo
|
||||||
environment:
|
environment:
|
||||||
MONGO_INITDB_ROOT_USERNAME: admin
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: admin_password
|
|
||||||
MONGO_INITDB_DATABASE: voxpopuli_reports
|
MONGO_INITDB_DATABASE: voxpopuli_reports
|
||||||
ports:
|
ports:
|
||||||
- "27017:27017"
|
- "27017:27017"
|
||||||
volumes:
|
volumes:
|
||||||
- mongo_reports_data:/data/db
|
- mongo_data:/data/db
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
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:
|
volumes:
|
||||||
mysql_data:
|
mysql_data:
|
||||||
mongo_reports_data:
|
mongo_data:
|
||||||
mongo_notifications_data:
|
|
||||||
rabbitmq_data:
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
@@ -256,7 +256,7 @@ class DeleteReport:
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UpdateReportStatus:
|
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):
|
def __init__(self, repo: ReportRepository):
|
||||||
if not isinstance(repo, ReportRepository):
|
if not isinstance(repo, ReportRepository):
|
||||||
raise TypeError("repo must implement 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]:
|
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:
|
Valida previamente:
|
||||||
- Reporte existe
|
- Reporte existe
|
||||||
- Estado es válido
|
- Estado es válido
|
||||||
@@ -294,32 +294,9 @@ class UpdateReportStatus:
|
|||||||
"message": f"Error al buscar reporte: {str(e)}"
|
"message": f"Error al buscar reporte: {str(e)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Guardar estado anterior para notificación
|
|
||||||
old_estado = report.estado
|
|
||||||
|
|
||||||
# Actualizar estado
|
# Actualizar estado
|
||||||
try:
|
try:
|
||||||
self.repo.update_estado(report_id, new_estado)
|
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 {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"message": f"Estado del reporte actualizado a '{new_estado}'",
|
"message": f"Estado del reporte actualizado a '{new_estado}'",
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -11,29 +11,19 @@ class Settings(BaseSettings):
|
|||||||
description="URL de conexión a MySQL para API de Usuarios"
|
description="URL de conexión a MySQL para API de Usuarios"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Base de datos MongoDB - Reportes (Instancia Separada)
|
# Base de datos MongoDB
|
||||||
mongodb_reports_url: str = Field(
|
mongodb_url: str = Field(
|
||||||
default=os.getenv("MONGODB_REPORTS_URL", "mongodb://admin:admin_password@localhost:27017"),
|
default=os.getenv("MONGODB_URL", "mongodb://localhost:27017"),
|
||||||
description="URL de conexión a MongoDB para API de Reportes (Instancia 1)"
|
description="URL de conexión a MongoDB para API de Reportes"
|
||||||
)
|
)
|
||||||
mongodb_reports_db: str = Field(
|
mongodb_db: str = Field(
|
||||||
default="voxpopuli_reports",
|
default="voxpopuli_reports",
|
||||||
description="Base de datos MongoDB para Reportes"
|
description="Base de datos MongoDB"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Base de datos MongoDB - Notificaciones (Instancia Separada)
|
rabbitmq: str = Field (
|
||||||
mongodb_notifications_url: str = Field(
|
default=os.getenv("RABBITMQ_URI", "localhost")
|
||||||
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"),
|
|
||||||
description="URL de conexión a RabbitMQ"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# JWT Configuration
|
# JWT Configuration
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -2,18 +2,10 @@ from pymongo import MongoClient
|
|||||||
from pymongo.collection import Collection
|
from pymongo.collection import Collection
|
||||||
from core.config import ConfSettings
|
from core.config import ConfSettings
|
||||||
|
|
||||||
# Conexión a MongoDB para Reportes (Instancia Separada - Puerto 27017)
|
# Conexión a MongoDB para Reportes
|
||||||
mongo_client_reports = MongoClient(ConfSettings.mongodb_reports_url)
|
mongo_client = MongoClient(ConfSettings.mongodb_url)
|
||||||
mongodb_reports = mongo_client_reports[ConfSettings.mongodb_reports_db]
|
mongodb = mongo_client[ConfSettings.mongodb_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]
|
|
||||||
|
|
||||||
def get_reports_collection() -> Collection:
|
def get_reports_collection() -> Collection:
|
||||||
"""Obtiene la colección de reportes desde MongoDB (Instancia 1)"""
|
"""Obtiene la colección de reportes desde MongoDB"""
|
||||||
return mongodb_reports["reportes"]
|
return mongodb["reportes"]
|
||||||
|
|
||||||
def get_notifications_collection() -> Collection:
|
|
||||||
"""Obtiene la colección de notificaciones desde MongoDB (Instancia 2)"""
|
|
||||||
return mongodb_notifications["notificaciones"]
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
)
|
|
||||||
@@ -17,7 +17,6 @@ class ReportEventType(str, Enum):
|
|||||||
"""Types of report events"""
|
"""Types of report events"""
|
||||||
CREATE = "report.create"
|
CREATE = "report.create"
|
||||||
UPDATE_VISIBILITY = "report.update_visibility"
|
UPDATE_VISIBILITY = "report.update_visibility"
|
||||||
UPDATE_STATUS = "report.update_status"
|
|
||||||
DELETE = "report.delete"
|
DELETE = "report.delete"
|
||||||
|
|
||||||
|
|
||||||
@@ -70,10 +69,6 @@ class ReportMessage:
|
|||||||
estado: Optional[str] = None # Estado del reporte: "en proceso", "no resuelto", "resuelto"
|
estado: Optional[str] = None # Estado del reporte: "en proceso", "no resuelto", "resuelto"
|
||||||
fecha_creacion: Optional[str] = None # ISO format datetime string
|
fecha_creacion: Optional[str] = None # ISO format datetime string
|
||||||
penalize_author: Optional[bool] = None # For update_visibility event
|
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):
|
def to_dict(self):
|
||||||
"""Convert to dictionary"""
|
"""Convert to dictionary"""
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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))
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"]
|
|
||||||
)
|
|
||||||
@@ -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
|
|
||||||
26
src/main.py
26
src/main.py
@@ -1,13 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
Punto de entrada principal para VoxPopuli Microservices
|
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.users.app import create_app as create_users_app
|
||||||
from infrastructure.api.reports.app import create_app as create_reports_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.report_consumer import ReportConsumer
|
||||||
from consumers.user_consumer import UserConsumer
|
from consumers.user_consumer import UserConsumer
|
||||||
from consumers.notification_consumer import NotificationConsumer
|
|
||||||
from core.config import ConfSettings
|
from core.config import ConfSettings
|
||||||
import threading
|
import threading
|
||||||
import uvicorn
|
import uvicorn
|
||||||
@@ -34,17 +32,6 @@ def run_reports_api():
|
|||||||
log_level=ConfSettings.log_level,
|
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():
|
def run_user_consumer():
|
||||||
consumer = UserConsumer()
|
consumer = UserConsumer()
|
||||||
consumer.start()
|
consumer.start()
|
||||||
@@ -53,10 +40,6 @@ def run_reports_consumer():
|
|||||||
consumer = ReportConsumer()
|
consumer = ReportConsumer()
|
||||||
consumer.start()
|
consumer.start()
|
||||||
|
|
||||||
def run_notifications_consumer():
|
|
||||||
consumer = NotificationConsumer()
|
|
||||||
consumer.start()
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
"""Inicia ambas APIs en threads separados"""
|
"""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")
|
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")
|
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")
|
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")
|
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()
|
users_thread.start()
|
||||||
reports_thread.start()
|
reports_thread.start()
|
||||||
notifications_thread.start()
|
|
||||||
user_consumer_thread.start()
|
user_consumer_thread.start()
|
||||||
report_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("\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 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("\nDocumentación disponible en:")
|
||||||
print(" - Usuarios: http://localhost:8000/docs")
|
print(" - Usuarios: http://localhost:8000/docs")
|
||||||
print(" - Reportes: http://localhost:8001/docs")
|
print(" - Reportes: http://localhost:8001/docs")
|
||||||
print(" - Notificaciones: http://localhost:8002/docs")
|
|
||||||
print("\n" + "=" * 60 + "\n")
|
print("\n" + "=" * 60 + "\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
users_thread.join()
|
users_thread.join()
|
||||||
reports_thread.join()
|
reports_thread.join()
|
||||||
notifications_thread.join()
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n\nRecibiendo señal de salida...")
|
print("\n\nRecibiendo señal de salida...")
|
||||||
print("Cerrando APIs...")
|
print("Cerrando APIs...")
|
||||||
|
|||||||
Reference in New Issue
Block a user