--- id: "ET-PFM-006" title: "Motor de Reportes Fiscales" type: "Technical Specification" status: "Done" priority: "Alta" epic: "OQI-008" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "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 ```typescript @Injectable() export class TaxReportService { async generateCapitalGainsReport( userId: string, year: number ): Promise { 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 { 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) ```typescript async getTaxLossOpportunities( userId: string ): Promise { 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 - [RF-PFM-006: Reportes Fiscales](../requerimientos/RF-PFM-006-reportes-fiscales.md) --- *Especificación técnica - Sistema NEXUS*