trading-platform/docs/02-definicion-modulos/OQI-008-portfolio-manager/especificaciones/ET-PFM-006-reportes-fiscales.md
rckrdmrd a7cca885f0 feat: Major platform documentation and architecture updates
Changes include:
- Updated architecture documentation
- Enhanced module definitions (OQI-001 to OQI-008)
- ML integration documentation updates
- Trading strategies documentation
- Orchestration and inventory updates
- Docker configuration updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:33:35 -06:00

3.7 KiB

id title type status priority epic project version created_date updated_date
ET-PFM-006 Motor de Reportes Fiscales Technical Specification Done Alta OQI-008 trading-platform 1.0.0 2025-12-05 2026-01-04

ET-PFM-006: Motor de Reportes Fiscales

Épica: OQI-008 - Portfolio Manager Versión: 1.0 Fecha: 2025-12-05 Estado: Planificado


Servicios de Cálculo Fiscal

@Injectable()
export class TaxReportService {

  async generateCapitalGainsReport(
    userId: string,
    year: number
  ): Promise<CapitalGainsReport> {
    const sales = await this.getSalesForYear(userId, year);

    const gains: CapitalGain[] = [];
    let shortTermTotal = 0;
    let longTermTotal = 0;

    for (const sale of sales) {
      const costBasis = await this.getCostBasis(sale, 'FIFO');
      const gain = sale.total - costBasis.totalCost;
      const holdingDays = this.calculateHoldingPeriod(
        costBasis.purchaseDate,
        sale.executedAt
      );
      const isLongTerm = holdingDays > 365;

      gains.push({
        symbol: sale.symbol,
        purchaseDate: costBasis.purchaseDate,
        saleDate: sale.executedAt,
        quantity: sale.quantity,
        costBasis: costBasis.totalCost,
        proceeds: sale.total,
        gain,
        holdingPeriod: isLongTerm ? 'long' : 'short',
      });

      if (isLongTerm) {
        longTermTotal += gain;
      } else {
        shortTermTotal += gain;
      }
    }

    return {
      year,
      gains,
      summary: {
        shortTerm: {
          gains: gains.filter(g => g.holdingPeriod === 'short' && g.gain > 0)
            .reduce((sum, g) => sum + g.gain, 0),
          losses: gains.filter(g => g.holdingPeriod === 'short' && g.gain < 0)
            .reduce((sum, g) => sum + g.gain, 0),
          net: shortTermTotal,
        },
        longTerm: {
          gains: gains.filter(g => g.holdingPeriod === 'long' && g.gain > 0)
            .reduce((sum, g) => sum + g.gain, 0),
          losses: gains.filter(g => g.holdingPeriod === 'long' && g.gain < 0)
            .reduce((sum, g) => sum + g.gain, 0),
          net: longTermTotal,
        },
        total: shortTermTotal + longTermTotal,
      },
    };
  }

  async generateDividendReport(
    userId: string,
    year: number
  ): Promise<DividendReport> {
    const dividends = await this.getDividendsForYear(userId, year);

    return {
      year,
      dividends: dividends.map(d => ({
        date: d.executedAt,
        symbol: d.symbol,
        amount: d.total,
        type: d.metadata?.qualified ? 'qualified' : 'ordinary',
      })),
      summary: {
        qualified: dividends
          .filter(d => d.metadata?.qualified)
          .reduce((sum, d) => sum + d.total, 0),
        ordinary: dividends
          .filter(d => !d.metadata?.qualified)
          .reduce((sum, d) => sum + d.total, 0),
        total: dividends.reduce((sum, d) => sum + d.total, 0),
      },
    };
  }
}

Tax-Loss Harvesting (Premium)

async getTaxLossOpportunities(
  userId: string
): Promise<TaxLossOpportunity[]> {
  const positions = await this.portfolioService.getPositions(userId);
  const ytdGains = await this.getYTDRealizedGains(userId);

  const opportunities = positions
    .filter(p => p.pnl < 0)
    .map(p => ({
      symbol: p.symbol,
      unrealizedLoss: p.pnl,
      estimatedTaxSavings: Math.abs(p.pnl) * 0.25, // ~25% tax rate
      washSaleStatus: this.checkWashSale(p.symbol, userId),
      canHarvest: this.checkWashSale(p.symbol, userId) === 'clear',
    }))
    .filter(o => o.canHarvest);

  return opportunities;
}

Referencias


Especificación técnica - Sistema NEXUS