---
id: "ET-PFM-011"
title: "Sistema de Goals (Metas de Inversion)"
type: "Technical Specification"
status: "Done"
priority: "Alta"
epic: "OQI-008"
project: "trading-platform"
version: "1.0.0"
created_date: "2026-01-28"
updated_date: "2026-01-28"
---

# ET-PFM-011: Sistema de Goals (Metas de Inversion)

**Epica:** OQI-008 - Portfolio Manager
**Version:** 1.0
**Fecha:** 2026-01-28
**Estado:** Planificado

---

## 1. Vision General

El Sistema de Goals permite a los usuarios definir, seguir y proyectar metas financieras a largo plazo como retiro, compra de casa, educacion de hijos, etc. Integra simulaciones Monte Carlo para proyectar probabilidades de exito.

### 1.1 Tipos de Metas Soportadas

| Tipo | Icono | Descripcion | Horizonte Tipico |
|------|-------|-------------|------------------|
| `retirement` | 🏖️ | Retiro/Jubilacion | 10-30 anos |
| `home` | 🏠 | Compra de casa/enganche | 2-10 anos |
| `education` | 🎓 | Educacion de hijos | 5-18 anos |
| `emergency` | 🚨 | Fondo de emergencia | 1-2 anos |
| `travel` | ✈️ | Viaje mayor | 1-5 anos |
| `vehicle` | 🚗 | Compra de vehiculo | 1-5 anos |
| `wedding` | 💍 | Boda/evento | 1-3 anos |
| `custom` | 🎯 | Meta personalizada | Variable |

---

## 2. Modelo de Datos

### 2.1 Entity: InvestmentGoal

```typescript
@Entity('investment_goals')
export class InvestmentGoal {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ name: 'user_id' })
  @Index()
  userId: string;

  @Column({ length: 100 })
  name: string;

  @Column({ length: 500, nullable: true })
  description: string;

  @Column({
    type: 'enum',
    enum: GoalType,
    default: GoalType.CUSTOM,
  })
  type: GoalType;

  @Column('decimal', { precision: 18, scale: 2, name: 'target_amount' })
  targetAmount: number;

  @Column('decimal', { precision: 18, scale: 2, name: 'current_amount', default: 0 })
  currentAmount: number;

  @Column('decimal', { precision: 18, scale: 2, name: 'initial_amount', default: 0 })
  initialAmount: number;

  @Column('decimal', { precision: 18, scale: 2, name: 'monthly_contribution', default: 0 })
  monthlyContribution: number;

  @Column({ name: 'target_date', type: 'date' })
  targetDate: Date;

  @Column('decimal', { precision: 5, scale: 2, name: 'expected_return', default: 7.0 })
  expectedReturn: number; // Annual return %

  @Column('decimal', { precision: 5, scale: 2, name: 'expected_volatility', default: 15.0 })
  expectedVolatility: number; // Annual volatility %

  @Column({
    type: 'enum',
    enum: GoalStatus,
    default: GoalStatus.ON_TRACK,
  })
  status: GoalStatus;

  @Column({
    type: 'enum',
    enum: GoalPriority,
    default: GoalPriority.MEDIUM,
  })
  priority: GoalPriority;

  @Column({ name: 'linked_account_id', type: 'uuid', nullable: true })
  linkedAccountId: string | null;

  @Column({ name: 'is_active', default: true })
  isActive: boolean;

  @Column('jsonb', { name: 'risk_profile', nullable: true })
  riskProfile: GoalRiskProfile | null;

  @Column('jsonb', { name: 'last_projection', nullable: true })
  lastProjection: GoalProjection | null;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;

  @OneToMany(() => GoalContribution, (c) => c.goal)
  contributions: GoalContribution[];

  @OneToMany(() => GoalMilestone, (m) => m.goal)
  milestones: GoalMilestone[];
}
```

### 2.2 Enums

```typescript
enum GoalType {
  RETIREMENT = 'retirement',
  HOME = 'home',
  EDUCATION = 'education',
  EMERGENCY = 'emergency',
  TRAVEL = 'travel',
  VEHICLE = 'vehicle',
  WEDDING = 'wedding',
  CUSTOM = 'custom',
}

enum GoalStatus {
  ON_TRACK = 'on_track',
  AHEAD = 'ahead',
  BEHIND = 'behind',
  AT_RISK = 'at_risk',
  ACHIEVED = 'achieved',
  PAUSED = 'paused',
}

enum GoalPriority {
  HIGH = 'high',
  MEDIUM = 'medium',
  LOW = 'low',
}
```

### 2.3 Entity: GoalContribution

```typescript
@Entity('goal_contributions')
export class GoalContribution {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ name: 'goal_id' })
  goalId: string;

  @ManyToOne(() => InvestmentGoal, (g) => g.contributions)
  @JoinColumn({ name: 'goal_id' })
  goal: InvestmentGoal;

  @Column('decimal', { precision: 18, scale: 2 })
  amount: number;

  @Column({
    type: 'enum',
    enum: ContributionType,
    default: ContributionType.DEPOSIT,
  })
  type: ContributionType;

  @Column({ nullable: true })
  source: string; // 'manual', 'automatic', 'dividend', 'interest'

  @Column({ type: 'text', nullable: true })
  notes: string;

  @Column({ name: 'transaction_id', nullable: true })
  transactionId: string;

  @CreateDateColumn({ name: 'contributed_at' })
  contributedAt: Date;
}

enum ContributionType {
  DEPOSIT = 'deposit',
  WITHDRAWAL = 'withdrawal',
  INTEREST = 'interest',
  DIVIDEND = 'dividend',
  ADJUSTMENT = 'adjustment',
}
```

### 2.4 Entity: GoalMilestone

```typescript
@Entity('goal_milestones')
export class GoalMilestone {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ name: 'goal_id' })
  goalId: string;

  @ManyToOne(() => InvestmentGoal, (g) => g.milestones)
  @JoinColumn({ name: 'goal_id' })
  goal: InvestmentGoal;

  @Column({ length: 100 })
  name: string;

  @Column('decimal', { precision: 18, scale: 2, name: 'target_amount' })
  targetAmount: number;

  @Column('decimal', { precision: 5, scale: 2, name: 'target_percentage' })
  targetPercentage: number; // % of goal

  @Column({ name: 'target_date', type: 'date', nullable: true })
  targetDate: Date | null;

  @Column({ name: 'achieved_at', type: 'timestamp', nullable: true })
  achievedAt: Date | null;

  @Column({ default: false })
  isAchieved: boolean;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;
}
```

---

## 3. Interfaces de Proyeccion

### 3.1 GoalProjection

```typescript
interface GoalProjection {
  // Metadata
  goalId: string;
  calculatedAt: Date;

  // Current State
  currentAmount: number;
  targetAmount: number;
  progressPercent: number;
  monthsRemaining: number;

  // Projections
  projectedValue: number;
  projectedProgress: number;
  shortfall: number;

  // Probability Analysis
  probabilityOfSuccess: number;
  confidenceInterval: {
    low: number;  // P10
    mid: number;  // P50 (median)
    high: number; // P90
  };

  // Scenarios
  scenarios: GoalScenarios;

  // Recommendations
  requiredMonthlyContribution: number;
  suggestedActions: string[];
}

interface GoalScenarios {
  optimistic: ScenarioResult;
  base: ScenarioResult;
  pessimistic: ScenarioResult;
}

interface ScenarioResult {
  projectedValue: number;
  endDate: Date;
  returnRate: number;
  probabilityOfSuccess: number;
}
```

### 3.2 MonteCarloParams

```typescript
interface MonteCarloParams {
  initialAmount: number;
  monthlyContribution: number;
  expectedReturn: number;      // Annual, e.g., 0.07 for 7%
  volatility: number;          // Annual, e.g., 0.15 for 15%
  months: number;
  numSimulations: number;      // Default: 10000
  targetAmount: number;
}

interface MonteCarloResult {
  simulations: number[][];     // [sim][month]
  finalValues: number[];       // Final value per simulation
  percentiles: {
    p10: number;
    p25: number;
    p50: number;
    p75: number;
    p90: number;
    p95: number;
  };
  probabilityOfSuccess: number;
  averageFinalValue: number;
  medianFinalValue: number;
}
```

---

## 4. Servicio de Goals

### 4.1 GoalService

```typescript
@Injectable()
export class GoalService {
  constructor(
    @InjectRepository(InvestmentGoal)
    private goalRepo: Repository<InvestmentGoal>,
    @InjectRepository(GoalContribution)
    private contributionRepo: Repository<GoalContribution>,
    private projectionService: GoalProjectionService,
    private eventEmitter: EventEmitter2,
  ) {}

  async createGoal(dto: CreateGoalDto): Promise<InvestmentGoal> {
    const goal = this.goalRepo.create({
      ...dto,
      status: GoalStatus.ON_TRACK,
      currentAmount: dto.initialAmount || 0,
    });

    // Create default milestones
    goal.milestones = this.createDefaultMilestones(goal);

    const saved = await this.goalRepo.save(goal);

    // Calculate initial projection
    const projection = await this.projectionService.calculateProjection(saved);
    saved.lastProjection = projection;
    saved.status = this.determineStatus(projection);

    await this.goalRepo.save(saved);
    this.eventEmitter.emit('goal.created', saved);

    return saved;
  }

  async addContribution(
    goalId: string,
    dto: AddContributionDto
  ): Promise<GoalContribution> {
    const goal = await this.getGoalById(goalId);

    const contribution = this.contributionRepo.create({
      goalId,
      ...dto,
    });

    await this.contributionRepo.save(contribution);

    // Update goal current amount
    const delta = dto.type === ContributionType.WITHDRAWAL
      ? -dto.amount
      : dto.amount;

    goal.currentAmount = Number(goal.currentAmount) + delta;

    // Recalculate projection
    const projection = await this.projectionService.calculateProjection(goal);
    goal.lastProjection = projection;
    goal.status = this.determineStatus(projection);

    // Check milestones
    await this.checkMilestones(goal);

    await this.goalRepo.save(goal);
    this.eventEmitter.emit('goal.contribution.added', { goal, contribution });

    return contribution;
  }

  async getGoalsByUser(userId: string): Promise<InvestmentGoal[]> {
    return this.goalRepo.find({
      where: { userId, isActive: true },
      relations: ['contributions', 'milestones'],
      order: { priority: 'ASC', targetDate: 'ASC' },
    });
  }

  async getGoalProgress(goalId: string): Promise<GoalProgressDto> {
    const goal = await this.getGoalById(goalId);
    const projection = await this.projectionService.calculateProjection(goal);

    const contributions = await this.contributionRepo.find({
      where: { goalId },
      order: { contributedAt: 'DESC' },
      take: 12, // Last 12 contributions
    });

    return {
      goal,
      projection,
      contributions,
      milestones: goal.milestones.map(m => ({
        ...m,
        progress: (goal.currentAmount / m.targetAmount) * 100,
      })),
    };
  }

  private createDefaultMilestones(goal: InvestmentGoal): GoalMilestone[] {
    return [
      { name: '25% alcanzado', targetPercentage: 25, targetAmount: goal.targetAmount * 0.25 },
      { name: '50% alcanzado', targetPercentage: 50, targetAmount: goal.targetAmount * 0.50 },
      { name: '75% alcanzado', targetPercentage: 75, targetAmount: goal.targetAmount * 0.75 },
      { name: 'Meta completada', targetPercentage: 100, targetAmount: goal.targetAmount },
    ].map(m => this.milestoneRepo.create({ ...m, goalId: goal.id }));
  }

  private determineStatus(projection: GoalProjection): GoalStatus {
    if (projection.probabilityOfSuccess >= 95) return GoalStatus.AHEAD;
    if (projection.probabilityOfSuccess >= 75) return GoalStatus.ON_TRACK;
    if (projection.probabilityOfSuccess >= 50) return GoalStatus.BEHIND;
    return GoalStatus.AT_RISK;
  }
}
```

---

## 5. Servicio de Proyeccion

### 5.1 GoalProjectionService

```typescript
@Injectable()
export class GoalProjectionService {

  async calculateProjection(goal: InvestmentGoal): Promise<GoalProjection> {
    const monthsRemaining = this.getMonthsRemaining(goal.targetDate);

    // Deterministic projection (Future Value formula)
    const projectedValue = this.calculateFutureValue(
      goal.currentAmount,
      goal.monthlyContribution,
      goal.expectedReturn / 100 / 12, // Monthly rate
      monthsRemaining
    );

    // Monte Carlo simulation
    const mcResult = await this.runMonteCarloSimulation({
      initialAmount: Number(goal.currentAmount),
      monthlyContribution: Number(goal.monthlyContribution),
      expectedReturn: goal.expectedReturn / 100,
      volatility: goal.expectedVolatility / 100,
      months: monthsRemaining,
      numSimulations: 10000,
      targetAmount: Number(goal.targetAmount),
    });

    const progressPercent = (goal.currentAmount / goal.targetAmount) * 100;
    const projectedProgress = (projectedValue / goal.targetAmount) * 100;

    return {
      goalId: goal.id,
      calculatedAt: new Date(),

      currentAmount: Number(goal.currentAmount),
      targetAmount: Number(goal.targetAmount),
      progressPercent,
      monthsRemaining,

      projectedValue,
      projectedProgress,
      shortfall: Math.max(0, Number(goal.targetAmount) - projectedValue),

      probabilityOfSuccess: mcResult.probabilityOfSuccess,
      confidenceInterval: {
        low: mcResult.percentiles.p10,
        mid: mcResult.percentiles.p50,
        high: mcResult.percentiles.p90,
      },

      scenarios: this.calculateScenarios(goal, monthsRemaining),

      requiredMonthlyContribution: this.calculateRequiredContribution(goal),
      suggestedActions: this.generateSuggestions(goal, mcResult.probabilityOfSuccess),
    };
  }

  /**
   * Future Value with regular deposits
   * FV = PV(1+r)^n + PMT × ((1+r)^n - 1) / r
   */
  private calculateFutureValue(
    presentValue: number,
    monthlyPayment: number,
    monthlyRate: number,
    months: number
  ): number {
    if (monthlyRate === 0) {
      return presentValue + monthlyPayment * months;
    }

    const growthFactor = Math.pow(1 + monthlyRate, months);
    const fvOfPresentValue = presentValue * growthFactor;
    const fvOfPayments = monthlyPayment * ((growthFactor - 1) / monthlyRate);

    return fvOfPresentValue + fvOfPayments;
  }

  /**
   * Monte Carlo Simulation
   * Geometric Brownian Motion: S(t+1) = S(t) * exp((mu - sigma^2/2)*dt + sigma*sqrt(dt)*Z)
   */
  async runMonteCarloSimulation(params: MonteCarloParams): Promise<MonteCarloResult> {
    const { initialAmount, monthlyContribution, expectedReturn, volatility, months, numSimulations, targetAmount } = params;

    const monthlyReturn = expectedReturn / 12;
    const monthlyVolatility = volatility / Math.sqrt(12);
    const dt = 1; // 1 month

    const simulations: number[][] = [];
    const finalValues: number[] = [];

    for (let sim = 0; sim < numSimulations; sim++) {
      const path: number[] = [initialAmount];
      let value = initialAmount;

      for (let month = 1; month <= months; month++) {
        // Random normal (Box-Muller transform)
        const z = this.randomNormal();

        // GBM step
        const drift = (monthlyReturn - 0.5 * monthlyVolatility ** 2) * dt;
        const diffusion = monthlyVolatility * Math.sqrt(dt) * z;
        value = value * Math.exp(drift + diffusion) + monthlyContribution;

        path.push(Math.max(0, value)); // No negative values
      }

      simulations.push(path);
      finalValues.push(path[path.length - 1]);
    }

    // Sort for percentile calculation
    const sorted = [...finalValues].sort((a, b) => a - b);

    const percentile = (p: number) => sorted[Math.floor(p * numSimulations)];

    const successCount = finalValues.filter(v => v >= targetAmount).length;

    return {
      simulations,
      finalValues,
      percentiles: {
        p10: percentile(0.10),
        p25: percentile(0.25),
        p50: percentile(0.50),
        p75: percentile(0.75),
        p90: percentile(0.90),
        p95: percentile(0.95),
      },
      probabilityOfSuccess: (successCount / numSimulations) * 100,
      averageFinalValue: finalValues.reduce((a, b) => a + b, 0) / numSimulations,
      medianFinalValue: percentile(0.50),
    };
  }

  /**
   * Box-Muller transform for normal distribution
   */
  private randomNormal(): number {
    const u1 = Math.random();
    const u2 = Math.random();
    return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
  }

  /**
   * Calculate required monthly contribution to reach target
   * PMT = (FV - PV(1+r)^n) × r / ((1+r)^n - 1)
   */
  calculateRequiredContribution(goal: InvestmentGoal): number {
    const months = this.getMonthsRemaining(goal.targetDate);
    const monthlyRate = goal.expectedReturn / 100 / 12;

    if (months <= 0) return 0;
    if (monthlyRate === 0) {
      return (Number(goal.targetAmount) - Number(goal.currentAmount)) / months;
    }

    const growthFactor = Math.pow(1 + monthlyRate, months);
    const fvOfCurrent = Number(goal.currentAmount) * growthFactor;
    const remaining = Number(goal.targetAmount) - fvOfCurrent;

    if (remaining <= 0) return 0;

    const factor = (growthFactor - 1) / monthlyRate;
    return remaining / factor;
  }

  private calculateScenarios(goal: InvestmentGoal, months: number): GoalScenarios {
    return {
      optimistic: this.projectWithReturn(goal, months, goal.expectedReturn + 3),
      base: this.projectWithReturn(goal, months, goal.expectedReturn),
      pessimistic: this.projectWithReturn(goal, months, goal.expectedReturn - 3),
    };
  }

  private projectWithReturn(goal: InvestmentGoal, months: number, annualReturn: number): ScenarioResult {
    const monthlyRate = annualReturn / 100 / 12;
    const projectedValue = this.calculateFutureValue(
      Number(goal.currentAmount),
      Number(goal.monthlyContribution),
      monthlyRate,
      months
    );

    return {
      projectedValue,
      endDate: goal.targetDate,
      returnRate: annualReturn,
      probabilityOfSuccess: projectedValue >= Number(goal.targetAmount) ? 100 : (projectedValue / Number(goal.targetAmount)) * 100,
    };
  }

  private generateSuggestions(goal: InvestmentGoal, probability: number): string[] {
    const suggestions: string[] = [];

    if (probability < 50) {
      const required = this.calculateRequiredContribution(goal);
      const increase = required - Number(goal.monthlyContribution);
      if (increase > 0) {
        suggestions.push(`Incrementar aportacion mensual en $${increase.toFixed(0)} para alcanzar meta`);
      }
      suggestions.push('Considerar extender la fecha objetivo');
      suggestions.push('Evaluar perfil de riesgo para mayor rendimiento esperado');
    } else if (probability < 75) {
      suggestions.push('Meta en riesgo. Considerar aumentar aportaciones');
    } else if (probability < 95) {
      suggestions.push('Meta en buen camino. Mantener aportaciones actuales');
    } else {
      suggestions.push('Excelente progreso! Meta muy probable de alcanzar');
    }

    return suggestions;
  }

  private getMonthsRemaining(targetDate: Date): number {
    const now = new Date();
    const target = new Date(targetDate);
    return Math.max(0, (target.getFullYear() - now.getFullYear()) * 12 +
      (target.getMonth() - now.getMonth()));
  }
}
```

---

## 6. API Endpoints

### 6.1 Goals CRUD

| Method | Endpoint | Descripcion |
|--------|----------|-------------|
| GET | `/api/goals` | Lista de metas del usuario |
| POST | `/api/goals` | Crear nueva meta |
| GET | `/api/goals/:id` | Detalle de meta |
| PUT | `/api/goals/:id` | Actualizar meta |
| DELETE | `/api/goals/:id` | Eliminar meta |
| PATCH | `/api/goals/:id/pause` | Pausar meta |
| PATCH | `/api/goals/:id/resume` | Reanudar meta |

### 6.2 Contributions

| Method | Endpoint | Descripcion |
|--------|----------|-------------|
| GET | `/api/goals/:id/contributions` | Historial de contribuciones |
| POST | `/api/goals/:id/contributions` | Agregar contribucion |

### 6.3 Projections & Analytics

| Method | Endpoint | Descripcion |
|--------|----------|-------------|
| GET | `/api/goals/:id/progress` | Progreso con proyeccion |
| GET | `/api/goals/:id/projection` | Proyeccion Monte Carlo |
| POST | `/api/goals/:id/simulate` | Simular escenarios custom |
| GET | `/api/goals/:id/milestones` | Estado de milestones |

---

## 7. Componentes Frontend

### 7.1 GoalDashboard

```typescript
interface GoalDashboardProps {
  userId: string;
}

// Componentes hijos:
// - GoalSummaryCard: Resumen de todas las metas
// - GoalList: Lista de metas con progreso
// - AddGoalButton: Crear nueva meta
```

### 7.2 GoalCard

```typescript
interface GoalCardProps {
  goal: InvestmentGoal;
  onEdit: () => void;
  onContribute: () => void;
}

// Muestra:
// - Nombre e icono del tipo
// - Barra de progreso visual
// - Monto actual / objetivo
// - Fecha objetivo
// - Estado (on_track, behind, etc.)
// - Probabilidad de exito
```

### 7.3 GoalProjectionChart

```typescript
interface GoalProjectionChartProps {
  projection: GoalProjection;
  height?: number;
}

// Grafico que muestra:
// - Linea de progreso actual
// - Bandas de confianza (P10, P50, P90)
// - Linea objetivo
// - Fecha objetivo
```

### 7.4 GoalWizard

```typescript
interface GoalWizardProps {
  onComplete: (goal: InvestmentGoal) => void;
}

// Pasos:
// 1. Seleccionar tipo de meta
// 2. Definir nombre y monto objetivo
// 3. Fecha objetivo
// 4. Aportacion mensual inicial
// 5. Perfil de riesgo (conservador/moderado/agresivo)
// 6. Confirmar y ver proyeccion inicial
```

---

## 8. Configuraciones por Tipo de Meta

### 8.1 Defaults por Tipo

```typescript
const GOAL_DEFAULTS: Record<GoalType, GoalDefaults> = {
  retirement: {
    expectedReturn: 7.0,
    expectedVolatility: 12.0,
    suggestedHorizon: '20+ years',
    riskProfile: 'moderate',
  },
  home: {
    expectedReturn: 5.0,
    expectedVolatility: 8.0,
    suggestedHorizon: '3-7 years',
    riskProfile: 'conservative',
  },
  education: {
    expectedReturn: 6.0,
    expectedVolatility: 10.0,
    suggestedHorizon: '5-18 years',
    riskProfile: 'moderate',
  },
  emergency: {
    expectedReturn: 2.0,
    expectedVolatility: 2.0,
    suggestedHorizon: '6-12 months',
    riskProfile: 'conservative',
  },
  travel: {
    expectedReturn: 4.0,
    expectedVolatility: 5.0,
    suggestedHorizon: '1-3 years',
    riskProfile: 'conservative',
  },
  vehicle: {
    expectedReturn: 4.0,
    expectedVolatility: 6.0,
    suggestedHorizon: '1-5 years',
    riskProfile: 'conservative',
  },
  wedding: {
    expectedReturn: 3.0,
    expectedVolatility: 4.0,
    suggestedHorizon: '1-3 years',
    riskProfile: 'conservative',
  },
  custom: {
    expectedReturn: 5.0,
    expectedVolatility: 10.0,
    suggestedHorizon: 'Variable',
    riskProfile: 'moderate',
  },
};
```

---

## 9. Notificaciones y Alertas

### 9.1 Eventos de Notificacion

| Evento | Trigger | Canal |
|--------|---------|-------|
| `goal.milestone.reached` | Progreso >= milestone % | Push, Email |
| `goal.status.changed` | Status cambia a behind/at_risk | Push, Email |
| `goal.achieved` | Monto actual >= objetivo | Push, Email, Celebracion UI |
| `goal.contribution.reminder` | Dia de aportacion programada | Push |
| `goal.review.monthly` | Primer dia del mes | Email digest |

### 9.2 Configuracion de Alertas

```typescript
interface GoalAlertSettings {
  goalId: string;
  milestoneAlerts: boolean;
  statusChangeAlerts: boolean;
  contributionReminders: boolean;
  reminderDayOfMonth: number; // 1-28
  channels: ('push' | 'email' | 'sms')[];
}
```

---

## 10. Referencias

- [ET-PFM-007: Motor de Metas](./ET-PFM-007-motor-metas.md)
- [RF-PFM-007: Metas de Inversion](../requerimientos/RF-PFM-007-metas-inversion.md)
- [US-PFM-012: Reporte Fiscal](../historias-usuario/US-PFM-012-reporte-fiscal.md)
- [TRACEABILITY.yml](../implementacion/TRACEABILITY.yml)

---

*Especificacion tecnica - Sistema NEXUS*
