trading-platform/orchestration/tareas/TASK-2026-01-25-NOTIFICACIONES-COMPLETAS/05-EJECUCION.md
Adrian Flores Cortes 9c2fce0083 docs: Complete documentation for notifications system implementation
- Create TASK-2026-01-25-NOTIFICACIONES-COMPLETAS with full CAPVED docs
- Update DATABASE_INVENTORY with auth.notifications, auth.user_push_tokens,
  investment.distribution_history, investment.distribution_runs tables
- Update BACKEND_INVENTORY with push-token endpoints, firebase.client,
  and unit tests
- Update FRONTEND_INVENTORY with notification components, store, service
- Update MASTER_INVENTORY with updated totals
- Update _INDEX.yml with new task entry

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 04:10:09 -06:00

192 lines
5.0 KiB
Markdown

# 05-EJECUCION - Sistema de Notificaciones Completo
## Resumen de Ejecucion
**Fecha**: 2026-01-25
**Agente**: claude-opus-4.5
**Duracion**: ~45 minutos
## 1. DDL Creados
### auth/tables/11-notifications.sql
```sql
CREATE TABLE auth.notifications (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
priority VARCHAR(20) DEFAULT 'normal',
data JSONB,
action_url VARCHAR(500),
icon_type VARCHAR(20) DEFAULT 'info',
channels TEXT[] DEFAULT '{}',
is_read BOOLEAN DEFAULT FALSE,
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### auth/tables/12-user_push_tokens.sql
```sql
CREATE TABLE auth.user_push_tokens (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
token TEXT NOT NULL UNIQUE,
platform VARCHAR(20) NOT NULL, -- web, ios, android
device_info JSONB,
is_active BOOLEAN DEFAULT TRUE,
last_used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
);
```
### investment/tables/08-distribution_history.sql
```sql
CREATE TABLE investment.distribution_history (
id UUID PRIMARY KEY,
account_id UUID NOT NULL REFERENCES investment.accounts(id),
product_id UUID NOT NULL REFERENCES investment.products(id),
distribution_date DATE NOT NULL,
gross_amount DECIMAL(18,2) NOT NULL,
fee_amount DECIMAL(18,2) DEFAULT 0,
net_amount DECIMAL(18,2) NOT NULL,
balance_before DECIMAL(18,2) NOT NULL,
balance_after DECIMAL(18,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_daily_distribution UNIQUE (account_id, distribution_date)
);
```
### investment/tables/09-distribution_runs.sql
```sql
CREATE TABLE investment.distribution_runs (
id UUID PRIMARY KEY,
run_date DATE NOT NULL UNIQUE,
total_accounts INTEGER DEFAULT 0,
successful_count INTEGER DEFAULT 0,
failed_count INTEGER DEFAULT 0,
total_gross_amount DECIMAL(18,2) DEFAULT 0,
total_fee_amount DECIMAL(18,2) DEFAULT 0,
total_net_amount DECIMAL(18,2) DEFAULT 0,
started_at TIMESTAMPTZ NOT NULL,
completed_at TIMESTAMPTZ,
duration_ms INTEGER,
error_details JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
## 2. Backend - Firebase Client
### firebase.client.ts (~300 lineas)
```typescript
class FirebaseClient {
private app: admin.app.App | null = null;
initialize(): void // Init con service account
async sendToDevice(token: string, payload: NotificationPayload): Promise<boolean>
async sendToMultiple(tokens: string[], payload: NotificationPayload): Promise<SendResult>
async deactivateInvalidTokens(tokens: string[]): Promise<void>
}
export const firebaseClient = new FirebaseClient();
```
### config/index.ts (modificado)
```typescript
firebase: {
serviceAccountKey: process.env.FIREBASE_SERVICE_ACCOUNT_KEY || '',
projectId: process.env.FIREBASE_PROJECT_ID || '',
},
webPush: {
publicKey: process.env.VAPID_PUBLIC_KEY || '',
privateKey: process.env.VAPID_PRIVATE_KEY || '',
subject: process.env.VAPID_SUBJECT || 'mailto:admin@orbiquant.io',
},
```
### notification.controller.ts (modificado)
```typescript
// Nuevos endpoints
export async function registerPushToken(req: AuthenticatedRequest, res: Response)
export async function removePushToken(req: AuthenticatedRequest, res: Response)
```
### notification.routes.ts (modificado)
```typescript
router.post('/push-token', authHandler(registerPushToken));
router.delete('/push-token', authHandler(removePushToken));
```
## 3. Tests Unitarios
### notification.service.spec.ts (~450 lineas)
- Mocks: db, wsManager, nodemailer, firebaseClient
- Tests: sendNotification, getUserNotifications, markAsRead, etc.
### distribution.job.spec.ts (~380 lineas)
- Mocks: db, notificationService
- Tests: run, getActiveAccounts, distributeReturns, etc.
## 4. Frontend
### notificationStore.ts
```typescript
interface NotificationState {
notifications: Notification[];
unreadCount: number;
preferences: NotificationPreferences | null;
loading: boolean;
error: string | null;
fetchNotifications: (unreadOnly?: boolean) => Promise<void>;
fetchUnreadCount: () => Promise<void>;
markAsRead: (id: string) => Promise<void>;
markAllAsRead: () => Promise<void>;
addNotification: (notification: Notification) => void;
initializeWebSocket: () => () => void;
}
```
### NotificationBell.tsx
- Badge con unreadCount
- Click toggle dropdown
- useEffect para fetch inicial y WebSocket
### NotificationDropdown.tsx
- Lista de ultimas 10 notificaciones
- Mark all as read button
- View all link
### NotificationItem.tsx
- Iconos por tipo (success, warning, error, info)
- Tiempo relativo (hace X minutos)
- Click marca como leida y navega
### NotificationsPage.tsx
- Lista completa con paginacion
- Filtros por tipo y estado
- Seccion de preferencias
### MainLayout.tsx (modificado)
```typescript
// Antes: Bell icon estatico
// Despues: <NotificationBell />
```
## Dependencias Instaladas
```bash
npm install firebase-admin web-push
npm install -D @types/web-push
```
## Estado Final
Todos los archivos creados, tests estructurados, commits pusheados.