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

5.0 KiB

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

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

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

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

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)

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)

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)

// Nuevos endpoints
export async function registerPushToken(req: AuthenticatedRequest, res: Response)
export async function removePushToken(req: AuthenticatedRequest, res: Response)

notification.routes.ts (modificado)

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

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)

// Antes: Bell icon estatico
// Despues: <NotificationBell />

Dependencias Instaladas

npm install firebase-admin web-push
npm install -D @types/web-push

Estado Final

Todos los archivos creados, tests estructurados, commits pusheados.