revert fef8ab225d
revert Added notifications!! Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -270,168 +270,8 @@
|
||||
"body": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"notificaciones": {
|
||||
"health_check": {
|
||||
"metodo": "GET",
|
||||
"url": "http://localhost:8002/",
|
||||
"respuesta": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"status": "ok",
|
||||
"service": "Notificaciones Microservice"
|
||||
}
|
||||
}
|
||||
},
|
||||
"crear_notificacion": {
|
||||
"metodo": "POST",
|
||||
"url": "http://localhost:8002/notifications/",
|
||||
"descripcion": "Crear notificación (uso interno, generalmente disparado por cambios de reportes)",
|
||||
"solicitud": {
|
||||
"Content-Type": "application/json",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {jwt_token}"
|
||||
},
|
||||
"body": {
|
||||
"id_usuario": 1,
|
||||
"id_reporte": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"message": "¡Tu reporte ha sido resuelto!"
|
||||
}
|
||||
},
|
||||
"respuesta_exitosa": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"id_notificacion": "660f8401-f39c-41e5-b727-557666551111",
|
||||
"id_usuario": 1,
|
||||
"id_reporte": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"message": "¡Tu reporte ha sido resuelto!",
|
||||
"fecha": "2024-04-29T15:30:00Z",
|
||||
"read": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"obtener_notificaciones_usuario": {
|
||||
"metodo": "GET",
|
||||
"url": "http://localhost:8002/notifications/1?limit=50&offset=0",
|
||||
"descripcion": "Obtener todas las notificaciones de un usuario con paginación",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {jwt_token}"
|
||||
},
|
||||
"query_params": {
|
||||
"limit": "50 (máximo 100)",
|
||||
"offset": "0 (para paginación)"
|
||||
},
|
||||
"respuesta": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"total": 150,
|
||||
"unread_count": 5,
|
||||
"notifications": [
|
||||
{
|
||||
"id_notificacion": "660f8401-f39c-41e5-b727-557666551111",
|
||||
"id_usuario": 1,
|
||||
"id_reporte": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"message": "¡Tu reporte ha sido resuelto!",
|
||||
"fecha": "2024-04-29T15:30:00Z",
|
||||
"read": false
|
||||
},
|
||||
{
|
||||
"id_notificacion": "660f8401-f39c-41e5-b727-557666551112",
|
||||
"id_usuario": 1,
|
||||
"id_reporte": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"message": "Tu reporte fue marcado como no resuelto.",
|
||||
"fecha": "2024-04-29T14:20:00Z",
|
||||
"read": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"obtener_conteo_no_leidas": {
|
||||
"metodo": "GET",
|
||||
"url": "http://localhost:8002/notifications/1/unread-count",
|
||||
"descripcion": "Obtener el número de notificaciones no leídas para un usuario",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {jwt_token}"
|
||||
},
|
||||
"respuesta": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"unread_count": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"marcar_como_leida": {
|
||||
"metodo": "PUT",
|
||||
"url": "http://localhost:8002/notifications/660f8401-f39c-41e5-b727-557666551111/read",
|
||||
"descripcion": "Marcar una notificación específica como leída",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {jwt_token}"
|
||||
},
|
||||
"respuesta_exitosa": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"id_notificacion": "660f8401-f39c-41e5-b727-557666551111",
|
||||
"id_usuario": 1,
|
||||
"id_reporte": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"message": "¡Tu reporte ha sido resuelto!",
|
||||
"fecha": "2024-04-29T15:30:00Z",
|
||||
"read": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"marcar_todas_como_leidas": {
|
||||
"metodo": "PUT",
|
||||
"url": "http://localhost:8002/notifications/1/read-all",
|
||||
"descripcion": "Marcar todas las notificaciones de un usuario como leídas",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {jwt_token}"
|
||||
},
|
||||
"respuesta_exitosa": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"message": "All notifications marked as read",
|
||||
"updated_count": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"eliminar_notificacion": {
|
||||
"metodo": "DELETE",
|
||||
"url": "http://localhost:8002/notifications/660f8401-f39c-41e5-b727-557666551111",
|
||||
"descripcion": "Eliminar una notificación específica",
|
||||
"headers": {
|
||||
"Authorization": "Bearer {jwt_token}"
|
||||
},
|
||||
"respuesta_exitosa": {
|
||||
"codigo": 200,
|
||||
"body": {
|
||||
"message": "Notification deleted successfully"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cambios_estado_notificaciones": {
|
||||
"transiciones": {
|
||||
"en_proceso_a_resuelto": {
|
||||
"mensaje": "¡Tu reporte #[id_reporte] ha sido resuelto!",
|
||||
"tipo": "positivo"
|
||||
},
|
||||
"en_proceso_a_no_resuelto": {
|
||||
"mensaje": "Tu reporte #[id_reporte] fue marcado como no resuelto.",
|
||||
"tipo": "info"
|
||||
},
|
||||
"no_resuelto_a_resuelto": {
|
||||
"mensaje": "¡Tu reporte #[id_reporte] ha sido resuelto!",
|
||||
"tipo": "positivo"
|
||||
},
|
||||
"resuelto_a_en_proceso": {
|
||||
"mensaje": "Tu reporte #[id_reporte] ha sido reabierto.",
|
||||
"tipo": "info"
|
||||
}
|
||||
},
|
||||
"trigger": "Cuando cambias el estado en PUT /reports/{report_id}/status"
|
||||
},
|
||||
"tipos_reporte": {
|
||||
"1": "Infraestructura/Vía pública",
|
||||
"2": "Inseguridad",
|
||||
@@ -439,31 +279,11 @@
|
||||
"4": "Servicios públicos",
|
||||
"5": "Otro"
|
||||
},
|
||||
"apis_disponibles": {
|
||||
"usuarios": {
|
||||
"url": "http://localhost:8000",
|
||||
"puerto": 8000,
|
||||
"docs": "http://localhost:8000/docs"
|
||||
},
|
||||
"reportes": {
|
||||
"url": "http://localhost:8001",
|
||||
"puerto": 8001,
|
||||
"docs": "http://localhost:8001/docs"
|
||||
},
|
||||
"notificaciones": {
|
||||
"url": "http://localhost:8002",
|
||||
"puerto": 8002,
|
||||
"docs": "http://localhost:8002/docs",
|
||||
"nuevo": true
|
||||
}
|
||||
},
|
||||
"codigos_estado_http": {
|
||||
"200": "OK - La solicitud fue exitosa",
|
||||
"201": "Created - Recurso creado exitosamente",
|
||||
"202": "Accepted - Solicitud aceptada en cola",
|
||||
"204": "No Content - Eliminación exitosa",
|
||||
"400": "Bad Request - Error en la solicitud",
|
||||
"401": "Unauthorized - Token inválido/expirado",
|
||||
"404": "Not Found - Recurso no encontrado",
|
||||
"500": "Internal Server Error - Error en el servidor"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
retries: 5
|
||||
|
||||
mongodb-reports:
|
||||
mongodb:
|
||||
image: mongo:7.0
|
||||
container_name: voxpopuli_mongo_reports
|
||||
container_name: voxpopuli_mongo
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: admin
|
||||
MONGO_INITDB_ROOT_PASSWORD: admin_password
|
||||
MONGO_INITDB_DATABASE: voxpopuli_reports
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongo_reports_data:/data/db
|
||||
- mongo_data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command: mongod --auth
|
||||
|
||||
mongodb-notifications:
|
||||
image: mongo:7.0
|
||||
container_name: voxpopuli_mongo_notifications
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: admin
|
||||
MONGO_INITDB_ROOT_PASSWORD: admin_password
|
||||
MONGO_INITDB_DATABASE: voxpopuli_notifications
|
||||
ports:
|
||||
- "27018:27017"
|
||||
volumes:
|
||||
- mongo_notifications_data:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command: mongod --auth
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:3.13-management
|
||||
container_name: voxpopuli_rabbitmq
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: voxpopuli
|
||||
RABBITMQ_DEFAULT_PASS: voxpopuli_pass
|
||||
ports:
|
||||
- "5672:5672"
|
||||
- "15672:15672" # Management UI
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
mongo_reports_data:
|
||||
mongo_notifications_data:
|
||||
rabbitmq_data:
|
||||
mongo_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:
|
||||
"""Use case para actualizar el estado de un reporte - envía evento a RabbitMQ"""
|
||||
"""Use case para actualizar el estado de un reporte"""
|
||||
def __init__(self, repo: ReportRepository):
|
||||
if not isinstance(repo, ReportRepository):
|
||||
raise TypeError("repo must implement ReportRepository")
|
||||
@@ -264,7 +264,7 @@ class UpdateReportStatus:
|
||||
|
||||
def execute(self, report_id: str, new_estado: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Actualiza el estado de un reporte y envía notificación vía RabbitMQ.
|
||||
Actualiza el estado de un reporte.
|
||||
Valida previamente:
|
||||
- Reporte existe
|
||||
- Estado es válido
|
||||
@@ -294,32 +294,9 @@ class UpdateReportStatus:
|
||||
"message": f"Error al buscar reporte: {str(e)}"
|
||||
}
|
||||
|
||||
# Guardar estado anterior para notificación
|
||||
old_estado = report.estado
|
||||
|
||||
# Actualizar estado
|
||||
try:
|
||||
self.repo.update_estado(report_id, new_estado)
|
||||
|
||||
# Enviar evento a RabbitMQ solo si el estado cambió
|
||||
if old_estado != new_estado:
|
||||
message = ReportMessage(
|
||||
event_type=ReportEventType.UPDATE_STATUS,
|
||||
id_reporte=report_id,
|
||||
id_usuario=report.id_usuario,
|
||||
old_estado=old_estado,
|
||||
new_estado=new_estado,
|
||||
estado=new_estado,
|
||||
tipo_reporte=report.tipo_reporte,
|
||||
descripcion=report.descripcion,
|
||||
ubicacion=report.ubicacion,
|
||||
lat=report.lat,
|
||||
lng=report.lng,
|
||||
visibilidad=report.visibilidad,
|
||||
fecha_creacion=report.fecha_creacion.isoformat() if report.fecha_creacion else None
|
||||
)
|
||||
send_to_queue("notifications_queue", message.to_dict())
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Estado del reporte actualizado a '{new_estado}'",
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
# Base de datos MongoDB - Reportes (Instancia Separada)
|
||||
mongodb_reports_url: str = Field(
|
||||
default=os.getenv("MONGODB_REPORTS_URL", "mongodb://admin:admin_password@localhost:27017"),
|
||||
description="URL de conexión a MongoDB para API de Reportes (Instancia 1)"
|
||||
# Base de datos MongoDB
|
||||
mongodb_url: str = Field(
|
||||
default=os.getenv("MONGODB_URL", "mongodb://localhost:27017"),
|
||||
description="URL de conexión a MongoDB para API de Reportes"
|
||||
)
|
||||
mongodb_reports_db: str = Field(
|
||||
mongodb_db: str = Field(
|
||||
default="voxpopuli_reports",
|
||||
description="Base de datos MongoDB para Reportes"
|
||||
description="Base de datos MongoDB"
|
||||
)
|
||||
|
||||
# Base de datos MongoDB - Notificaciones (Instancia Separada)
|
||||
mongodb_notifications_url: str = Field(
|
||||
default=os.getenv("MONGODB_NOTIFICATIONS_URL", "mongodb://admin:admin_password@localhost:27018"),
|
||||
description="URL de conexión a MongoDB para Notificaciones (Instancia 2)"
|
||||
)
|
||||
mongodb_notifications_db: str = Field(
|
||||
default="voxpopuli_notifications",
|
||||
description="Base de datos MongoDB para Notificaciones"
|
||||
)
|
||||
rabbitmq: str = Field (
|
||||
default=os.getenv("RABBITMQ_URI", "localhost")
|
||||
|
||||
rabbitmq: str = Field(
|
||||
default=os.getenv("RABBITMQ_URI", "localhost"),
|
||||
description="URL de conexión a RabbitMQ"
|
||||
)
|
||||
|
||||
# JWT Configuration
|
||||
|
||||
@@ -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 core.config import ConfSettings
|
||||
|
||||
# Conexión a MongoDB para Reportes (Instancia Separada - Puerto 27017)
|
||||
mongo_client_reports = MongoClient(ConfSettings.mongodb_reports_url)
|
||||
mongodb_reports = mongo_client_reports[ConfSettings.mongodb_reports_db]
|
||||
|
||||
# Conexión a MongoDB para Notificaciones (Instancia Separada - Puerto 27018)
|
||||
mongo_client_notifications = MongoClient(ConfSettings.mongodb_notifications_url)
|
||||
mongodb_notifications = mongo_client_notifications[ConfSettings.mongodb_notifications_db]
|
||||
# Conexión a MongoDB para Reportes
|
||||
mongo_client = MongoClient(ConfSettings.mongodb_url)
|
||||
mongodb = mongo_client[ConfSettings.mongodb_db]
|
||||
|
||||
def get_reports_collection() -> Collection:
|
||||
"""Obtiene la colección de reportes desde MongoDB (Instancia 1)"""
|
||||
return mongodb_reports["reportes"]
|
||||
|
||||
def get_notifications_collection() -> Collection:
|
||||
"""Obtiene la colección de notificaciones desde MongoDB (Instancia 2)"""
|
||||
return mongodb_notifications["notificaciones"]
|
||||
"""Obtiene la colección de reportes desde MongoDB"""
|
||||
return mongodb["reportes"]
|
||||
|
||||
@@ -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"""
|
||||
CREATE = "report.create"
|
||||
UPDATE_VISIBILITY = "report.update_visibility"
|
||||
UPDATE_STATUS = "report.update_status"
|
||||
DELETE = "report.delete"
|
||||
|
||||
|
||||
@@ -70,10 +69,6 @@ class ReportMessage:
|
||||
estado: Optional[str] = None # Estado del reporte: "en proceso", "no resuelto", "resuelto"
|
||||
fecha_creacion: Optional[str] = None # ISO format datetime string
|
||||
penalize_author: Optional[bool] = None # For update_visibility event
|
||||
old_estado: Optional[str] = None # Estado anterior (para UPDATE_STATUS)
|
||||
new_estado: Optional[str] = None # Nuevo estado (para UPDATE_STATUS)
|
||||
old_visibility: Optional[float] = None # Visibilidad anterior (para UPDATE_VISIBILITY)
|
||||
new_visibility: Optional[float] = None # Nueva visibilidad (para UPDATE_VISIBILITY)
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary"""
|
||||
|
||||
@@ -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
|
||||
Ejecuta tres APIs en paralelo: Usuarios (puerto 8000), Reportes (puerto 8001) y Notificaciones (puerto 8002)
|
||||
Ejecuta dos APIs en paralelo: Usuarios (puerto 8000) y Reportes (puerto 8001)
|
||||
"""
|
||||
from infrastructure.api.users.app import create_app as create_users_app
|
||||
from infrastructure.api.reports.app import create_app as create_reports_app
|
||||
from infrastructure.api.notifications.app import create_app as create_notifications_app
|
||||
from consumers.report_consumer import ReportConsumer
|
||||
from consumers.user_consumer import UserConsumer
|
||||
from consumers.notification_consumer import NotificationConsumer
|
||||
from core.config import ConfSettings
|
||||
import threading
|
||||
import uvicorn
|
||||
@@ -34,17 +32,6 @@ def run_reports_api():
|
||||
log_level=ConfSettings.log_level,
|
||||
)
|
||||
|
||||
def run_notifications_api():
|
||||
"""Ejecuta la API de Notificaciones en puerto 8002"""
|
||||
app_notifications = create_notifications_app()
|
||||
uvicorn.run(
|
||||
app_notifications,
|
||||
host=ConfSettings.host,
|
||||
port=8002,
|
||||
reload=False,
|
||||
log_level=ConfSettings.log_level,
|
||||
)
|
||||
|
||||
def run_user_consumer():
|
||||
consumer = UserConsumer()
|
||||
consumer.start()
|
||||
@@ -53,10 +40,6 @@ def run_reports_consumer():
|
||||
consumer = ReportConsumer()
|
||||
consumer.start()
|
||||
|
||||
def run_notifications_consumer():
|
||||
consumer = NotificationConsumer()
|
||||
consumer.start()
|
||||
|
||||
|
||||
def run():
|
||||
"""Inicia ambas APIs en threads separados"""
|
||||
@@ -66,32 +49,25 @@ def run():
|
||||
|
||||
users_thread = threading.Thread(target=run_users_api, daemon=True, name="Users-API")
|
||||
reports_thread = threading.Thread(target=run_reports_api, daemon=True, name="Reports-API")
|
||||
notifications_thread = threading.Thread(target=run_notifications_api, daemon=True, name="Notifications-API")
|
||||
user_consumer_thread = threading.Thread(target=run_user_consumer, daemon=True, name="Users-Consumer")
|
||||
report_consumer_thread = threading.Thread(target=run_reports_consumer, daemon=True, name="Reports-Consumer")
|
||||
notifications_consumer_thread = threading.Thread(target=run_notifications_consumer, daemon=True, name="Notifications-Consumer")
|
||||
|
||||
|
||||
users_thread.start()
|
||||
reports_thread.start()
|
||||
notifications_thread.start()
|
||||
user_consumer_thread.start()
|
||||
report_consumer_thread.start()
|
||||
notifications_consumer_thread.start()
|
||||
|
||||
print("\n✓ API de Usuarios ejecutándose en http://0.0.0.0:8000")
|
||||
print("✓ API de Reportes ejecutándose en http://0.0.0.0:8001")
|
||||
print("✓ API de Notificaciones ejecutándose en http://0.0.0.0:8002")
|
||||
print("\nDocumentación disponible en:")
|
||||
print(" - Usuarios: http://localhost:8000/docs")
|
||||
print(" - Reportes: http://localhost:8001/docs")
|
||||
print(" - Notificaciones: http://localhost:8002/docs")
|
||||
print("\n" + "=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
users_thread.join()
|
||||
reports_thread.join()
|
||||
notifications_thread.join()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nRecibiendo señal de salida...")
|
||||
print("Cerrando APIs...")
|
||||
|
||||
Reference in New Issue
Block a user