# @infosel-sdk/funds

[![npm version](https://badge.fury.io/js/@infosel-sdk%2Ffunds.svg)](https://badge.fury.io/js/@infosel-sdk%2Ffunds)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)

SDK de Fondos para la plataforma de servicios financieros Infosel. Proporciona acceso completo a información de fondos de inversión, incluyendo prospectos, rendimientos históricos, precios y datos fundamentales.

## 🚀 Características

- **📊 Información de Fondos**: Acceso a datos completos de fondos de inversión
- **📈 Rendimientos**: Cálculo de rendimientos en múltiples períodos (diario, semanal, mensual, anual)
- **📋 Prospectos**: Información detallada de prospectos de fondos
- **📊 Precios Históricos**: Datos históricos de precios con diferentes intervalos
- **🔍 Filtros Avanzados**: Búsqueda por tipo, administrador y otros criterios
- **⚡ Integración Core**: Completamente integrado con @infosel-sdk/core
- **🌍 Multi-Ambiente**: Soporte para QA y producción

## 📦 Instalación

```bash
npm install @infosel-sdk/funds
```

### Dependencias

- **@infosel-sdk/core**: ^0.0.2 (requerido)
- **dayjs**: ^1.11.10 (para manejo de fechas)
- **tslib**: ^2.8.1 (para TypeScript)

## 🔧 Configuración Inicial

### Configurar el SDK Core

Primero, necesitas configurar el SDK Core con autenticación:

```typescript
import { InfoselSdkManager, AuthConfigurationBuilder } from '@infosel-sdk/core';
import InfoselFunds from '@infosel-sdk/funds';

// Configurar autenticación KeyCloak
const authConfig = AuthConfigurationBuilder.keyCloak()
  .withRealm('funds-realm')
  .withEnvironment('qa') // o 'prod'
  .withCredentials({
    grant_type: 'client_credentials',
    client_id: 'your-funds-client',
    client_secret: 'your-funds-secret',
  })
  .build();

// Inicializar el SDK Core
const sdkManager = InfoselSdkManager.initWithConfiguration({
  authConfiguration: authConfig,
});

// Inicializar el SDK de Fondos
const fundsSdk = InfoselFunds.init({
  sdkManager,
});
```

## 📚 Métodos Disponibles

### 1. Obtener Lista de Fondos

```typescript
// Obtener todos los fondos
const allFunds = await fundsSdk.getFunds();

// Filtrar por tipo de fondo
const equityFunds = await fundsSdk.getFunds({
  typeId: 1, // Fondos de renta variable
});

// Filtrar por administrador
const specificAdminFunds = await fundsSdk.getFunds({
  administratorId: 123,
});

// Combinar filtros
const filteredFunds = await fundsSdk.getFunds({
  typeId: 2, // Fondos de renta fija
  administratorId: 456,
});

console.log('Fondos encontrados:', allFunds.length);
console.log('Primer fondo:', allFunds[0]);
```

**Respuesta esperada:**

```typescript
type Funds = {
  readonly id: number;
  readonly marketTypeId: number;
  readonly valueTypeIds: number;
  readonly exchangeId: number;
  readonly symbol: string;
  readonly issuer: string;
  readonly series: string;
  readonly isTrading: number;
  readonly name: string;
  readonly instrumentKey: string;
  readonly exchangeValueType: string;
  readonly isin: string;
  readonly active: number;
};
```

### 2. Obtener Prospecto de Fondo

```typescript
// Obtener prospecto por emisora
const prospectus = await fundsSdk.getFundProspectus({
  issuer: 'BANORTE',
});

console.log('Nombre del negocio:', prospectus.bussinesName);
console.log('Tipo de fondo:', prospectus.fundType);
console.log('Clase de activo:', prospectus.fundAssetClass);
console.log('Administrador:', prospectus.administrator);
console.log('Activos bajo gestión:', prospectus.assetsUnderManagement);
```

**Respuesta esperada:**

```typescript
type FundProspectus = {
  readonly fundId: number;
  readonly series: string[];
  readonly issuer: string;
  readonly bussinesName: string;
  readonly fundDateAuth: string;
  readonly fundType: string;
  readonly fundAssetClass: string;
  readonly administrator: string;
  readonly fundTerm: string;
  readonly investmentRegime: string;
  readonly liquidity: string;
  readonly lockInPeriod: string;
  readonly netAsset: number;
  readonly assetsUnderManagement: number;
  readonly fundHoldings: number;
  readonly marketRating: string;
  readonly creditRating: string;
  readonly fundManager: string;
  readonly active: number;
  readonly closeTime: string;
};
```

### 3. Obtener Rendimientos de Fondo

```typescript
// Obtener rendimientos para una fecha específica
const yields = await fundsSdk.getFundYield({
  issuer: 'BANORTE',
  series: 'A',
  date: new Date('2024-01-15'),
});

console.log('Rendimiento anual:', yields.annual);
console.log('Rendimiento diario:', yields.daily);
console.log('Rendimiento mensual:', yields.monthly);
console.log('Rendimiento semanal:', yields.weekly);
console.log('Rendimiento año a la fecha:', yields.yearToDate);
```

**Respuesta esperada:**

```typescript
type FundYield = {
  readonly annual: number;
  readonly daily: number;
  readonly dailyAnnualized: number;
  readonly monthly: number;
  readonly monthlyAnnualized: number;
  readonly weekly: number;
  readonly weeklyAnnualized: number;
  readonly yearToDate: number;
  readonly yearToDateAnnualized: number;
};
```

### 4. Obtener Precios Históricos

```typescript
// Obtener precios históricos diarios
const dailyPrices = await fundsSdk.getFundHistoricalPrices({
  issuer: 'BANORTE',
  series: 'A',
  period: 30, // 30 días
  interval: 'D', // Diario
});

// Obtener precios semanales
const weeklyPrices = await fundsSdk.getFundHistoricalPrices({
  issuer: 'BANORTE',
  series: 'A',
  period: 12, // 12 semanas
  interval: 'W', // Semanal
});

// Obtener precios mensuales
const monthlyPrices = await fundsSdk.getFundHistoricalPrices({
  issuer: 'BANORTE',
  series: 'A',
  period: 24, // 24 meses
  interval: 'M', // Mensual
});

// Obtener precios trimestrales
const quarterlyPrices = await fundsSdk.getFundHistoricalPrices({
  issuer: 'BANORTE',
  series: 'A',
  period: 8, // 8 trimestres
  interval: 'Q', // Trimestral
});

console.log('Precios diarios:', dailyPrices.length);
console.log('Último precio:', dailyPrices[dailyPrices.length - 1]);
```

**Respuesta esperada:**

```typescript
type FundsHistoricalPrices = {
  readonly date: string;
  readonly high: number;
  readonly low: number;
  readonly close: number;
};
```

## 🔍 Casos de Uso Comunes

### Dashboard de Fondos

```typescript
async function createFundsDashboard() {
  try {
    // Obtener lista de fondos activos
    const activeFunds = await fundsSdk.getFunds();

    const dashboardData = await Promise.all(
      activeFunds.slice(0, 10).map(async fund => {
        // Obtener prospecto
        const prospectus = await fundsSdk.getFundProspectus({
          issuer: fund.issuer,
        });

        // Obtener rendimiento actual
        const yield = await fundsSdk.getFundYield({
          issuer: fund.issuer,
          series: fund.series,
          date: new Date(),
        });

        // Obtener precios históricos (últimos 30 días)
        const historicalPrices = await fundsSdk.getFundHistoricalPrices({
          issuer: fund.issuer,
          series: fund.series,
          period: 30,
          interval: 'D',
        });

        return {
          fund,
          prospectus,
          currentYield: yield.annual,
          priceHistory: historicalPrices,
        };
      }),
    );

    return dashboardData;
  } catch (error) {
    console.error('Error al crear dashboard:', error);
    throw error;
  }
}
```

### Análisis de Rendimiento

```typescript
async function analyzeFundPerformance(issuer: string, series: string) {
  try {
    // Obtener rendimientos en diferentes períodos
    const yields = await fundsSdk.getFundYield({
      issuer,
      series,
      date: new Date(),
    });

    // Obtener precios históricos para análisis técnico
    const dailyPrices = await fundsSdk.getFundHistoricalPrices({
      issuer,
      series,
      period: 90, // 90 días
      interval: 'D',
    });

    const monthlyPrices = await fundsSdk.getFundHistoricalPrices({
      issuer,
      series,
      period: 36, // 36 meses
      interval: 'M',
    });

    // Calcular métricas de rendimiento
    const performanceMetrics = {
      shortTerm: yields.monthly,
      mediumTerm: yields.annual,
      longTerm: yields.yearToDate,
      volatility: calculateVolatility(dailyPrices),
      trend: analyzeTrend(monthlyPrices),
    };

    return performanceMetrics;
  } catch (error) {
    console.error('Error en análisis de rendimiento:', error);
    throw error;
  }
}

function calculateVolatility(prices: FundsHistoricalPrices[]): number {
  // Implementar cálculo de volatilidad
  const returns = prices
    .slice(1)
    .map((price, i) => (price.close - prices[i].close) / prices[i].close);

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance =
    returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) /
    returns.length;

  return Math.sqrt(variance);
}

function analyzeTrend(
  prices: FundsHistoricalPrices[],
): 'up' | 'down' | 'sideways' {
  if (prices.length < 2) return 'sideways';

  const firstPrice = prices[0].close;
  const lastPrice = prices[prices.length - 1].close;
  const change = (lastPrice - firstPrice) / firstPrice;

  if (change > 0.05) return 'up';
  if (change < -0.05) return 'down';
  return 'sideways';
}
```

### Comparación de Fondos

```typescript
async function compareFunds(
  fund1: { issuer: string; series: string },
  fund2: { issuer: string; series: string },
) {
  try {
    const [prospectus1, prospectus2, yield1, yield2] = await Promise.all([
      fundsSdk.getFundProspectus({ issuer: fund1.issuer }),
      fundsSdk.getFundProspectus({ issuer: fund2.issuer }),
      fundsSdk.getFundYield({
        issuer: fund1.issuer,
        series: fund1.series,
        date: new Date(),
      }),
      fundsSdk.getFundYield({
        issuer: fund2.issuer,
        series: fund2.series,
        date: new Date(),
      }),
    ]);

    const comparison = {
      fund1: {
        name: prospectus1.bussinesName,
        type: prospectus1.fundType,
        assetClass: prospectus1.fundAssetClass,
        annualYield: yield1.annual,
        assetsUnderManagement: prospectus1.assetsUnderManagement,
      },
      fund2: {
        name: prospectus2.bussinesName,
        type: prospectus2.fundType,
        assetClass: prospectus2.fundAssetClass,
        annualYield: yield2.annual,
        assetsUnderManagement: prospectus2.assetsUnderManagement,
      },
      analysis: {
        higherYield: yield1.annual > yield2.annual ? 'fund1' : 'fund2',
        yieldDifference: Math.abs(yield1.annual - yield2.annual),
        largerFund:
          prospectus1.assetsUnderManagement > prospectus2.assetsUnderManagement
            ? 'fund1'
            : 'fund2',
      },
    };

    return comparison;
  } catch (error) {
    console.error('Error al comparar fondos:', error);
    throw error;
  }
}
```

## 🚨 Manejo de Errores

```typescript
import { SdkError, SdkErrorType } from '@infosel-sdk/core';

try {
  const funds = await fundsSdk.getFunds();
  console.log('Fondos obtenidos:', funds);
} catch (error) {
  if (error instanceof SdkError) {
    switch (error.type) {
      case SdkErrorType.REQUEST_OBJECT_IS_REQUIRED:
        console.error('Request object is required for this operation');
        break;
      case SdkErrorType.AXIOS_RESPONSE_ERROR:
        console.error('HTTP response error:', error.message);
        break;
      case SdkErrorType.GRAPH_QL_ERROR:
        console.error('GraphQL error:', error.message);
        break;
      default:
        console.error('SDK error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}
```

## 🔧 Configuración Avanzada

### Personalización de URLs

```typescript
import { InfoselSdkManager, AuthConfigurationBuilder } from '@infosel-sdk/core';
import InfoselFunds from '@infosel-sdk/funds';

// Configuración personalizada para diferentes ambientes
const customConfig = {
  qa: {
    baseUrl: 'https://custom-qa-api.infosel.com/funds/api/v1',
    realm: 'qa-funds',
    clientId: 'qa-funds-client',
    clientSecret: 'qa-funds-secret',
  },
  prod: {
    baseUrl: 'https://custom-prod-api.infosel.com/funds/api/v1',
    realm: 'prod-funds',
    clientId: 'prod-funds-client',
    clientSecret: 'prod-funds-secret',
  },
};

function createCustomFundsSdk(environment: 'qa' | 'prod') {
  const config = customConfig[environment];

  const authConfig = AuthConfigurationBuilder.keyCloak()
    .withEnvironment(environment)
    .withRealm(config.realm)
    .withCredentials({
      grant_type: 'client_credentials',
      client_id: config.clientId,
      client_secret: config.clientSecret,
    })
    .build();

  const sdkManager = InfoselSdkManager.initWithConfiguration({
    authConfiguration: authConfig,
  });

  return InfoselFunds.init({ sdkManager });
}
```

## 🧪 Testing

```typescript
import { InfoselSdkManager, AuthConfigurationBuilder } from '@infosel-sdk/core';
import InfoselFunds from '@infosel-sdk/funds';

// Mock del SDK Manager para testing
const mockSdkManager = {
  mode: 'qa',
  getRealm: () => 'test-realm',
} as any;

// Test de inicialización
describe('InfoselFunds', () => {
  it('should initialize with SDK manager', () => {
    const fundsSdk = InfoselFunds.init({ sdkManager: mockSdkManager });
    expect(fundsSdk).toBeDefined();
  });
});
```

## 📊 Estructura de Datos

### Tipos de Request

```typescript
// Filtros para obtener fondos
type FundsRequest = {
  readonly typeId?: number; // ID del tipo de fondo
  readonly administratorId?: number; // ID del administrador
};

// Request para prospecto
type FundProspectusRequest = {
  readonly issuer: string; // Emisora del fondo
};

// Request para rendimientos
type FundYieldRequest = {
  readonly issuer: string; // Emisora del fondo
  readonly series: string; // Serie del fondo
  readonly date: Date; // Fecha para el cálculo
};

// Request para precios históricos
type FundHistoricalPricesRequest = {
  readonly issuer: string; // Emisora del fondo
  readonly series: string; // Serie del fondo
  readonly period: number; // Período en unidades del intervalo
  readonly interval: 'D' | 'W' | 'M' | 'Q'; // D=Diario, W=Semanal, M=Mensual, Q=Trimestral
};
```

## 🔗 Integración con Otros SDKs

```typescript
import { InfoselSdkManager, AuthConfigurationBuilder } from '@infosel-sdk/core';
import InfoselFunds from '@infosel-sdk/funds';
import InfoselMarkets from '@infosel-sdk/markets';

// Configuración compartida
const authConfig = AuthConfigurationBuilder.keyCloak()
  .withRealm('shared-realm')
  .withEnvironment('prod')
  .withCredentials({
    grant_type: 'client_credentials',
    client_id: 'shared-client',
    client_secret: 'shared-secret',
  })
  .build();

const sdkManager = InfoselSdkManager.initWithConfiguration({
  authConfiguration: authConfig,
});

// Inicializar múltiples SDKs
const fundsSdk = InfoselFunds.init({ sdkManager });
const marketsSdk = InfoselMarkets.init({ sdkManager });

// Uso combinado
async function getFundWithMarketData(issuer: string, series: string) {
  const [fundProspectus, marketInstruments] = await Promise.all([
    fundsSdk.getFundProspectus({ issuer }),
    marketsSdk.searchInstruments({ query: issuer, limit: 10 }),
  ]);

  return {
    fund: fundProspectus,
    relatedInstruments: marketInstruments,
  };
}
```

## 📈 Performance y Optimización

### Lazy Loading de Use Cases

El SDK implementa lazy loading para optimizar el uso de memoria:

```typescript
// Los use cases se crean solo cuando se necesitan
const funds = await fundsSdk.getFunds(); // Crea GetFundsUseCase internamente

// Reutiliza la instancia existente
const moreFunds = await fundsSdk.getFunds(); // Usa la instancia existente
```

### Manejo de Promesas

```typescript
// Ejecutar múltiples operaciones en paralelo
const [funds, prospectus, yields] = await Promise.all([
  fundsSdk.getFunds(),
  fundsSdk.getFundProspectus({ issuer: 'BANORTE' }),
  fundsSdk.getFundYield({ issuer: 'BANORTE', series: 'A', date: new Date() }),
]);

// Ejecutar operaciones secuenciales cuando hay dependencias
const funds = await fundsSdk.getFunds();
const firstFund = funds[0];
const prospectus = await fundsSdk.getFundProspectus({
  issuer: firstFund.issuer,
});
```

## 🔍 Troubleshooting

### Problemas Comunes

1. **Error de autenticación**

   ```typescript
   // Verificar configuración del realm
   console.log('Realm configurado:', sdkManager.getRealm());

   // Verificar ambiente
   console.log('Ambiente:', sdkManager.mode);
   ```

2. **Fondos no encontrados**

   ```typescript
   // Verificar filtros aplicados
   const allFunds = await fundsSdk.getFunds();
   console.log('Total de fondos:', allFunds.length);

   // Verificar fondos activos
   const activeFunds = allFunds.filter(fund => fund.active === 1);
   console.log('Fondos activos:', activeFunds.length);
   ```

3. **Errores de fecha**

   ```typescript
   // Asegurar formato correcto de fecha
   const today = new Date();
   console.log('Fecha utilizada:', today.toISOString());

   // Para rendimientos, usar fecha válida
   const yields = await fundsSdk.getFundYield({
     issuer: 'BANORTE',
     series: 'A',
     date: new Date('2024-01-15'), // Formato ISO
   });
   ```

## 📚 API Reference

### Clases Principales

- `InfoselFunds`: Clase principal del SDK de fondos
- `GetFundsUseCase`: Use case para obtener fondos
- `GetProspectusUseCase`: Use case para obtener prospectos
- `GetFundYieldUseCase`: Use case para obtener rendimientos
- `GetFundHistoricalPricesUseCase`: Use case para obtener precios históricos

### Métodos Públicos

- `getFunds(request?: FundsRequest): Promise<Funds[]>`
- `getFundProspectus(request: FundProspectusRequest): Promise<FundProspectus>`
- `getFundYield(request: FundYieldRequest): Promise<FundYield>`
- `getFundHistoricalPrices(request: FundHistoricalPricesRequest): Promise<FundsHistoricalPrices[]>`

## 🤝 Contribución

1. Fork el proyecto
2. Crea una rama para tu feature (`git checkout -b feature/AmazingFeature`)
3. Commit tus cambios (`git commit -m 'Add some AmazingFeature'`)
4. Push a la rama (`git push origin feature/AmazingFeature`)
5. Abre un Pull Request

## 📄 Licencia

Este proyecto está bajo la Licencia MIT. Ver el archivo `LICENSE` para más detalles.

## 📞 Soporte

- **Documentación**: [SDK Core Documentation](../core/README.md)
- **Issues**: [GitHub Issues](https://github.com/infosel/am-sdks/issues)
- **Equipo**: Infosel Team

---

**Hecho con ❤️ por el equipo de Infosel**
