first commit
This commit is contained in:
commit
c9fb67e751
423 files changed
+53287
No files matched your search
@@ -0,0 +1,931 @@
|
||||
import { PrismaClient, Decimal } from '@prisma/client';
|
||||
import axios from 'axios';
|
||||
import { v4 as uuidv4 } from 'uuid'; // Importar uuid para gerar IDs
|
||||
import { prisma } from '../prismaClient';
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export class IndiceMonetarioService {
|
||||
/**
|
||||
* Adiciona um novo índice monetário
|
||||
*/
|
||||
async adicionarIndice(data: {
|
||||
nome: string;
|
||||
mesReferencia: number;
|
||||
anoReferencia: number;
|
||||
valor: number;
|
||||
dataPublicacao?: Date;
|
||||
fonte?: string;
|
||||
}) {
|
||||
try {
|
||||
// Verificar se já existe
|
||||
const existente = await prisma.indiceMonetario.findFirst({
|
||||
where: {
|
||||
nome: data.nome,
|
||||
mesReferencia: data.mesReferencia,
|
||||
anoReferencia: data.anoReferencia,
|
||||
}
|
||||
});
|
||||
|
||||
if (existente) {
|
||||
// Atualizar o existente
|
||||
return await prisma.indiceMonetario.update({
|
||||
where: { id: existente.id },
|
||||
data: {
|
||||
valor: new Decimal(data.valor),
|
||||
dataPublicacao: data.dataPublicacao,
|
||||
fonte: data.fonte,
|
||||
atualizadoEm: new Date()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Criar novo
|
||||
return await prisma.indiceMonetario.create({
|
||||
data: {
|
||||
id: uuidv4(), // Gerar ID UUID explicitamente
|
||||
nome: data.nome,
|
||||
mesReferencia: data.mesReferencia,
|
||||
anoReferencia: data.anoReferencia,
|
||||
valor: new Decimal(data.valor),
|
||||
dataPublicacao: data.dataPublicacao,
|
||||
fonte: data.fonte
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao adicionar índice monetário:', error);
|
||||
throw new Error(`Falha ao adicionar índice: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista índices monetários com filtros
|
||||
*/
|
||||
async listarIndices(filtros?: {
|
||||
nome?: string;
|
||||
anoReferencia?: number;
|
||||
fonte?: string;
|
||||
}) {
|
||||
try {
|
||||
const where: any = {};
|
||||
|
||||
if (filtros) {
|
||||
if (filtros.nome) {
|
||||
where.nome = filtros.nome;
|
||||
}
|
||||
|
||||
if (filtros.anoReferencia) {
|
||||
where.anoReferencia = filtros.anoReferencia;
|
||||
}
|
||||
|
||||
if (filtros.fonte) {
|
||||
where.fonte = { contains: filtros.fonte };
|
||||
}
|
||||
}
|
||||
|
||||
return await prisma.indiceMonetario.findMany({
|
||||
where,
|
||||
orderBy: [
|
||||
{ anoReferencia: 'desc' },
|
||||
{ mesReferencia: 'desc' },
|
||||
{ nome: 'asc' }
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao listar índices monetários:', error);
|
||||
throw new Error(`Falha ao listar índices: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca um índice específico
|
||||
*/
|
||||
async buscarIndice(data: {
|
||||
nome: string;
|
||||
mesReferencia: number;
|
||||
anoReferencia: number;
|
||||
}) {
|
||||
try {
|
||||
return await prisma.indiceMonetario.findFirst({
|
||||
where: {
|
||||
nome: data.nome,
|
||||
mesReferencia: data.mesReferencia,
|
||||
anoReferencia: data.anoReferencia
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar índice monetário:', error);
|
||||
throw new Error(`Falha ao buscar índice: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove um índice monetário
|
||||
*/
|
||||
async removerIndice(id: string) {
|
||||
try {
|
||||
return await prisma.indiceMonetario.delete({
|
||||
where: { id }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao remover índice monetário:', error);
|
||||
throw new Error(`Falha ao remover índice: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Importa índices INPC do IBGE
|
||||
*/
|
||||
async importarIndicesINPC() {
|
||||
try {
|
||||
console.log('Iniciando importação de índices INPC...');
|
||||
// Buscar últimos 120 meses
|
||||
const url = 'https://servicodados.ibge.gov.br/api/v3/agregados/1736/periodos/-120/variaveis/44?localidades=N1[all]';
|
||||
|
||||
const response = await axios.get(url);
|
||||
const data = response.data[0];
|
||||
|
||||
if (!data?.resultados?.[0]?.series?.[0]?.serie) {
|
||||
throw new Error('Formato de resposta inválido do IBGE');
|
||||
}
|
||||
|
||||
const indices = data.resultados[0].series[0].serie;
|
||||
|
||||
// Processar e salvar os índices
|
||||
for (const [periodo, valor] of Object.entries(indices)) {
|
||||
const ano = parseInt(periodo.substring(0, 4));
|
||||
const mes = parseInt(periodo.substring(4));
|
||||
const valorNumerico = parseFloat(String(valor).replace(',', '.'));
|
||||
|
||||
if (!isNaN(ano) && !isNaN(mes) && !isNaN(valorNumerico)) {
|
||||
await prisma.indiceMonetario.upsert({
|
||||
where: {
|
||||
nome_mesReferencia_anoReferencia: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano
|
||||
}
|
||||
},
|
||||
update: {
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
},
|
||||
create: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Índice importado: ${mes}/${ano} = ${valorNumerico}%`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Importação concluída com sucesso');
|
||||
} catch (error) {
|
||||
console.error('Erro ao importar índices:', error);
|
||||
throw new Error('Falha ao importar índices do IBGE');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Importa índices SELIC do Banco Central
|
||||
*/
|
||||
async importarIndicesSELIC() {
|
||||
try {
|
||||
// Usar a API do Banco Central em vez de CSV
|
||||
const hoje = new Date();
|
||||
const dataFim = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}-${String(hoje.getDate()).padStart(2, '0')}`;
|
||||
|
||||
// Pegar dados do último ano
|
||||
const dataInicio = `${hoje.getFullYear() - 1}-${String(hoje.getMonth() + 1).padStart(2, '0')}-${String(hoje.getDate()).padStart(2, '0')}`;
|
||||
|
||||
const url = `https://api.bcb.gov.br/dados/serie/bcdata.sgs.11/dados?formato=json&dataInicial=${dataInicio}&dataFinal=${dataFim}`;
|
||||
const response = await axios.get(url);
|
||||
|
||||
if (!Array.isArray(response.data)) {
|
||||
throw new Error('Formato de resposta da API do Banco Central inválido');
|
||||
}
|
||||
|
||||
const indices = [];
|
||||
|
||||
for (const item of response.data) {
|
||||
// Tratar a data (formato DD/MM/YYYY)
|
||||
const dataParts = item.data.split('/');
|
||||
const ano = parseInt(dataParts[2]);
|
||||
const mes = parseInt(dataParts[1]);
|
||||
const valorNumerico = parseFloat(item.valor);
|
||||
|
||||
if (!isNaN(ano) && !isNaN(mes) && !isNaN(valorNumerico)) {
|
||||
await this.adicionarIndice({
|
||||
nome: 'SELIC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico / 100, // Converter para decimal (ex: 13.25% vira 0.1325)
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'BACEN'
|
||||
});
|
||||
|
||||
indices.push({
|
||||
nome: 'SELIC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico / 100
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Importados ${indices.length} índices SELIC`,
|
||||
indices
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Erro ao importar índices SELIC:', error);
|
||||
throw new Error(`Falha ao importar índices SELIC: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula o fator de correção entre duas datas para um índice
|
||||
*/
|
||||
async calcularFatorCorrecao(data: {
|
||||
indice: string;
|
||||
dataInicial: Date;
|
||||
dataFinal: Date;
|
||||
}) {
|
||||
try {
|
||||
const { indice, dataInicial, dataFinal } = data;
|
||||
|
||||
// Obter o mês/ano das datas
|
||||
const mesInicial = dataInicial.getMonth() + 1; // getMonth retorna 0-11
|
||||
const anoInicial = dataInicial.getFullYear();
|
||||
const mesFinal = dataFinal.getMonth() + 1;
|
||||
const anoFinal = dataFinal.getFullYear();
|
||||
|
||||
// Buscar todos os índices necessários para o período
|
||||
const indices = await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: indice,
|
||||
OR: [
|
||||
// Índices do ano inicial
|
||||
{
|
||||
anoReferencia: anoInicial,
|
||||
mesReferencia: { gte: mesInicial }
|
||||
},
|
||||
// Anos intermediários (se houver)
|
||||
{
|
||||
anoReferencia: { gt: anoInicial, lt: anoFinal }
|
||||
},
|
||||
// Índices do ano final
|
||||
{
|
||||
anoReferencia: anoFinal,
|
||||
mesReferencia: { lte: mesFinal }
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' }
|
||||
]
|
||||
});
|
||||
|
||||
// Se não houver índices suficientes, usar uma estimativa
|
||||
if (indices.length === 0) {
|
||||
const mesesDiferenca =
|
||||
(anoFinal - anoInicial) * 12 + (mesFinal - mesInicial);
|
||||
|
||||
// Taxa média mensal estimada (usar 0.5% ao mês como estimativa conservadora)
|
||||
const taxaMediaMensal = 0.005;
|
||||
return new Decimal(Math.pow(1 + taxaMediaMensal, mesesDiferenca));
|
||||
}
|
||||
|
||||
// Calcular o fator de correção acumulado
|
||||
let fatorAcumulado = new Decimal(1);
|
||||
|
||||
for (const indice of indices) {
|
||||
// Somar 1 ao valor do índice (ex: índice de 0.0082 vira fator de 1.0082)
|
||||
const fator = new Decimal(1).add(indice.valor);
|
||||
fatorAcumulado = fatorAcumulado.mul(fator);
|
||||
}
|
||||
|
||||
return fatorAcumulado;
|
||||
} catch (error) {
|
||||
console.error('Erro ao calcular fator de correção:', error);
|
||||
throw new Error(`Falha ao calcular fator de correção: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Função para baixar índices do IBGE
|
||||
export async function atualizarIndicesINPC() {
|
||||
try {
|
||||
console.log('Iniciando atualização dos índices INPC...');
|
||||
|
||||
// Definir período de busca (5 anos para trás)
|
||||
const hoje = new Date();
|
||||
const anoInicial = hoje.getFullYear() - 5;
|
||||
|
||||
// Buscar último índice no banco
|
||||
const ultimoIndice = await prisma.indiceMonetario.findFirst({
|
||||
orderBy: [
|
||||
{ anoReferencia: 'desc' },
|
||||
{ mesReferencia: 'desc' }
|
||||
]
|
||||
});
|
||||
|
||||
// URL da API do IBGE para INPC (últimos 120 meses = 10 anos)
|
||||
const url = 'https://servicodados.ibge.gov.br/api/v3/agregados/1736/periodos/-120/variaveis/44?localidades=N1[all]';
|
||||
|
||||
console.log('Buscando índices desde:', anoInicial);
|
||||
const response = await axios.get(url);
|
||||
|
||||
if (!response.data?.[0]?.resultados?.[0]?.series?.[0]?.serie) {
|
||||
throw new Error('Formato de resposta inválido do IBGE');
|
||||
}
|
||||
|
||||
const indices = response.data[0].resultados[0].series[0].serie;
|
||||
let totalIndices = 0;
|
||||
|
||||
// Processar cada índice
|
||||
for (const [periodo, valor] of Object.entries(indices)) {
|
||||
const ano = parseInt(periodo.substring(0, 4));
|
||||
const mes = parseInt(periodo.substring(4));
|
||||
const valorNumerico = parseFloat(String(valor).replace(',', '.'));
|
||||
|
||||
// Verificar se é um índice válido
|
||||
if (!isNaN(ano) && !isNaN(mes) && !isNaN(valorNumerico)) {
|
||||
await prisma.indiceMonetario.upsert({
|
||||
where: {
|
||||
nome_mesReferencia_anoReferencia: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano
|
||||
}
|
||||
},
|
||||
update: {
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
},
|
||||
create: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
}
|
||||
});
|
||||
|
||||
totalIndices++;
|
||||
console.log(`Índice atualizado: ${mes}/${ano} = ${valorNumerico}%`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Atualização concluída. Total de índices: ${totalIndices}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar índices INPC:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Função para verificar se os índices existem para um período
|
||||
export async function verificarIndicesDisponiveis(dataInicial: Date, dataFinal: Date): Promise<boolean> {
|
||||
try {
|
||||
const indices = await prisma.indiceMonetario.findFirst({
|
||||
where: {
|
||||
nome: 'INPC',
|
||||
AND: [
|
||||
{
|
||||
anoReferencia: dataInicial.getFullYear(),
|
||||
mesReferencia: dataInicial.getMonth() + 1
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Se não encontrar índices, tenta atualizar
|
||||
if (!indices) {
|
||||
console.log('Índices não encontrados. Iniciando atualização...');
|
||||
await atualizarIndicesINPC();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Erro ao verificar índices:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getIndicesParaPeriodo({
|
||||
indice,
|
||||
dataInicial,
|
||||
dataFinal
|
||||
}: {
|
||||
indice: string;
|
||||
dataInicial: Date;
|
||||
dataFinal: Date;
|
||||
}) {
|
||||
try {
|
||||
// Buscar índices no período
|
||||
let indices = await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: indice,
|
||||
AND: [
|
||||
{
|
||||
anoReferencia: {
|
||||
gte: dataInicial.getFullYear(),
|
||||
lte: dataFinal.getFullYear()
|
||||
}
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
// Índices do primeiro ano
|
||||
{
|
||||
anoReferencia: dataInicial.getFullYear(),
|
||||
mesReferencia: {
|
||||
gte: dataInicial.getMonth() + 1
|
||||
}
|
||||
},
|
||||
// Índices do último ano
|
||||
{
|
||||
anoReferencia: dataFinal.getFullYear(),
|
||||
mesReferencia: {
|
||||
lte: dataFinal.getMonth() + 1
|
||||
}
|
||||
},
|
||||
// Índices dos anos intermediários
|
||||
{
|
||||
anoReferencia: {
|
||||
gt: dataInicial.getFullYear(),
|
||||
lt: dataFinal.getFullYear()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' }
|
||||
]
|
||||
});
|
||||
|
||||
// Se não encontrar índices, tentar importar
|
||||
if (!indices || indices.length === 0) {
|
||||
console.log('Índices não encontrados. Iniciando importação...');
|
||||
await importarIndicesINPC();
|
||||
|
||||
// Buscar novamente após importação
|
||||
indices = await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: indice,
|
||||
AND: [
|
||||
{
|
||||
anoReferencia: {
|
||||
gte: dataInicial.getFullYear(),
|
||||
lte: dataFinal.getFullYear()
|
||||
}
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
// Índices do primeiro ano
|
||||
{
|
||||
anoReferencia: dataInicial.getFullYear(),
|
||||
mesReferencia: {
|
||||
gte: dataInicial.getMonth() + 1
|
||||
}
|
||||
},
|
||||
// Índices do último ano
|
||||
{
|
||||
anoReferencia: dataFinal.getFullYear(),
|
||||
mesReferencia: {
|
||||
lte: dataFinal.getMonth() + 1
|
||||
}
|
||||
},
|
||||
// Índices dos anos intermediários
|
||||
{
|
||||
anoReferencia: {
|
||||
gt: dataInicial.getFullYear(),
|
||||
lt: dataFinal.getFullYear()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!indices.length) {
|
||||
throw new Error('Não foram encontrados índices para o período');
|
||||
}
|
||||
|
||||
return indices;
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar/importar índices:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function importarIndicesINPC() {
|
||||
try {
|
||||
// API do IBGE para INPC
|
||||
const url = 'https://servicodados.ibge.gov.br/api/v3/agregados/1736/periodos/-120/variaveis/44?localidades=N1[all]';
|
||||
|
||||
console.log('Buscando índices do IBGE...');
|
||||
const response = await axios.get(url);
|
||||
|
||||
if (!response.data || !response.data[0] || !response.data[0].resultados) {
|
||||
throw new Error('Formato de resposta inválido da API do IBGE');
|
||||
}
|
||||
|
||||
const dados = response.data[0].resultados[0].series[0].serie;
|
||||
|
||||
// Processar e salvar os índices
|
||||
for (const [periodo, valor] of Object.entries(dados)) {
|
||||
const ano = parseInt(periodo.substring(0, 4));
|
||||
const mes = parseInt(periodo.substring(4));
|
||||
const valorNumerico = parseFloat(String(valor).replace(',', '.'));
|
||||
|
||||
if (!isNaN(ano) && !isNaN(mes) && !isNaN(valorNumerico)) {
|
||||
await prisma.indiceMonetario.upsert({
|
||||
where: {
|
||||
nome_mesReferencia_anoReferencia: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano
|
||||
}
|
||||
},
|
||||
update: {
|
||||
valor: valorNumerico / 100 + 1, // Converter para fator (ex: 1.0053 para 0.53%)
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
},
|
||||
create: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico / 100 + 1,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Índices INPC importados com sucesso');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Erro ao importar índices INPC:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function obterIndicesNecessarios(dataInicial: Date, dataFinal: Date) {
|
||||
try {
|
||||
// 1. Verificar índices existentes no banco
|
||||
const indicesExistentes = await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: 'INPC',
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
anoReferencia: {
|
||||
gte: dataInicial.getFullYear(),
|
||||
lte: dataFinal.getFullYear(),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' },
|
||||
],
|
||||
});
|
||||
|
||||
// 2. Criar mapa dos índices existentes para fácil verificação
|
||||
const mapaIndices = new Map();
|
||||
indicesExistentes.forEach(indice => {
|
||||
const chave = `${indice.anoReferencia}-${indice.mesReferencia.toString().padStart(2, '0')}`;
|
||||
mapaIndices.set(chave, indice);
|
||||
});
|
||||
|
||||
// 3. Verificar quais meses estão faltando
|
||||
const mesesFaltantes: { ano: number; mes: number }[] = [];
|
||||
let dataAtual = new Date(dataInicial);
|
||||
|
||||
while (dataAtual <= dataFinal) {
|
||||
const chave = `${dataAtual.getFullYear()}-${(dataAtual.getMonth() + 1).toString().padStart(2, '0')}`;
|
||||
|
||||
if (!mapaIndices.has(chave)) {
|
||||
mesesFaltantes.push({
|
||||
ano: dataAtual.getFullYear(),
|
||||
mes: dataAtual.getMonth() + 1
|
||||
});
|
||||
}
|
||||
|
||||
// Avançar para o próximo mês
|
||||
dataAtual.setMonth(dataAtual.getMonth() + 1);
|
||||
}
|
||||
|
||||
// 4. Se houver meses faltantes, buscar da API
|
||||
if (mesesFaltantes.length > 0) {
|
||||
console.log(`Buscando ${mesesFaltantes.length} índices faltantes do IBGE...`);
|
||||
|
||||
// Calcular o número de meses entre a data mais antiga e mais recente necessária
|
||||
const mesesTotal = Math.ceil(
|
||||
(dataFinal.getTime() - dataInicial.getTime()) / (1000 * 60 * 60 * 24 * 30.44)
|
||||
);
|
||||
|
||||
// Buscar um período maior que o necessário para garantir
|
||||
const periodoAPI = Math.max(mesesTotal + 12, 120); // Mínimo de 120 meses (10 anos)
|
||||
|
||||
const url = `https://servicodados.ibge.gov.br/api/v3/agregados/1736/periodos/-${periodoAPI}/variaveis/44?localidades=N1[all]`;
|
||||
|
||||
const response = await axios.get(url);
|
||||
const data = response.data[0];
|
||||
|
||||
if (!data?.resultados?.[0]?.series?.[0]?.serie) {
|
||||
throw new Error('Formato de resposta inválido do IBGE');
|
||||
}
|
||||
|
||||
const indices = data.resultados[0].series[0].serie;
|
||||
|
||||
// Criar os índices faltantes
|
||||
for (const [periodo, valor] of Object.entries(indices)) {
|
||||
const ano = parseInt(periodo.substring(0, 4));
|
||||
const mes = parseInt(periodo.substring(4));
|
||||
const valorNumerico = parseFloat(String(valor).replace(',', '.'));
|
||||
|
||||
if (!isNaN(ano) && !isNaN(mes) && !isNaN(valorNumerico)) {
|
||||
// Verificar se este índice é necessário
|
||||
const chave = `${ano}-${mes.toString().padStart(2, '0')}`;
|
||||
if (!mapaIndices.has(chave) &&
|
||||
ano >= dataInicial.getFullYear() &&
|
||||
ano <= dataFinal.getFullYear()) {
|
||||
|
||||
await prisma.indiceMonetario.upsert({
|
||||
where: {
|
||||
nome_mesReferencia_anoReferencia: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano
|
||||
}
|
||||
},
|
||||
update: {
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
},
|
||||
create: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Índice criado: ${mes}/${ano} = ${valorNumerico}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Retornar todos os índices necessários
|
||||
return await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: 'INPC',
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
anoReferencia: {
|
||||
gte: dataInicial.getFullYear(),
|
||||
lte: dataFinal.getFullYear(),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' },
|
||||
],
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro ao obter índices:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Atualizar a função principal para usar a nova função de obtenção de índices
|
||||
export async function calcularFatorCorrecaoTotal({
|
||||
dataInicial,
|
||||
dataFinal,
|
||||
indice
|
||||
}: {
|
||||
dataInicial: Date;
|
||||
dataFinal: Date;
|
||||
indice: string;
|
||||
}): Promise<Decimal> {
|
||||
try {
|
||||
// Buscar ou criar todos os índices necessários
|
||||
const indices = await obterIndicesNecessarios(dataInicial, dataFinal);
|
||||
|
||||
if (!indices.length) {
|
||||
throw new Error('Não foram encontrados índices para o período mesmo após tentativa de atualização');
|
||||
}
|
||||
|
||||
// Calcular fator acumulado
|
||||
let fatorAcumulado = new Decimal(1);
|
||||
for (const indice of indices) {
|
||||
fatorAcumulado = fatorAcumulado.mul(new Decimal(1 + indice.valor / 100));
|
||||
}
|
||||
|
||||
console.log('Fator de correção calculado:', {
|
||||
periodo: `${dataInicial.toISOString()} até ${dataFinal.toISOString()}`,
|
||||
indices: indices.length,
|
||||
primeiro: indices[0],
|
||||
ultimo: indices[indices.length - 1],
|
||||
fatorAcumulado: fatorAcumulado.toFixed(6)
|
||||
});
|
||||
|
||||
return fatorAcumulado;
|
||||
} catch (error) {
|
||||
console.error('Erro ao calcular fator de correção:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function buscarIndicesParaPeriodo(dataInicial: Date, dataFinal: Date) {
|
||||
console.log(`Buscando índices para o período de ${dataInicial.toISOString()} até ${dataFinal.toISOString()}`);
|
||||
|
||||
try {
|
||||
// Primeiro verificar quais meses já temos
|
||||
const indicesExistentes = await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: 'INPC',
|
||||
OR: [
|
||||
{
|
||||
anoReferencia: {
|
||||
gte: dataInicial.getFullYear(),
|
||||
lte: dataFinal.getFullYear()
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' }
|
||||
]
|
||||
});
|
||||
|
||||
console.log(`Encontrados ${indicesExistentes.length} índices no banco`);
|
||||
|
||||
// Se não houver índices suficientes, buscar da API do IBGE
|
||||
if (indicesExistentes.length === 0) {
|
||||
console.log('Buscando índices do IBGE...');
|
||||
|
||||
// Configurar timeout maior para a requisição
|
||||
const axiosInstance = axios.create({
|
||||
timeout: 30000 // 30 segundos
|
||||
});
|
||||
|
||||
// Fazer múltiplas requisições em paralelo para diferentes períodos
|
||||
const promises = [];
|
||||
let anoAtual = dataInicial.getFullYear();
|
||||
const anoFinal = dataFinal.getFullYear();
|
||||
|
||||
while (anoAtual <= anoFinal) {
|
||||
const url = `https://servicodados.ibge.gov.br/api/v3/agregados/1736/periodos/${anoAtual}/variaveis/44?localidades=N1[all]`;
|
||||
promises.push(axiosInstance.get(url));
|
||||
anoAtual++;
|
||||
}
|
||||
|
||||
console.log(`Fazendo ${promises.length} requisições ao IBGE...`);
|
||||
|
||||
const responses = await Promise.allSettled(promises);
|
||||
|
||||
// Processar cada resposta
|
||||
for (const response of responses) {
|
||||
if (response.status === 'fulfilled' && response.value.data?.[0]?.resultados?.[0]?.series?.[0]?.serie) {
|
||||
const indices = response.value.data[0].resultados[0].series[0].serie;
|
||||
|
||||
for (const [periodo, valor] of Object.entries(indices)) {
|
||||
const ano = parseInt(periodo.substring(0, 4));
|
||||
const mes = parseInt(periodo.substring(4));
|
||||
const valorNumerico = parseFloat(String(valor).replace(',', '.'));
|
||||
|
||||
if (!isNaN(ano) && !isNaN(mes) && !isNaN(valorNumerico)) {
|
||||
await prisma.indiceMonetario.upsert({
|
||||
where: {
|
||||
nome_mesReferencia_anoReferencia: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano
|
||||
}
|
||||
},
|
||||
update: {
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
},
|
||||
create: {
|
||||
nome: 'INPC',
|
||||
mesReferencia: mes,
|
||||
anoReferencia: ano,
|
||||
valor: valorNumerico,
|
||||
dataPublicacao: new Date(),
|
||||
fonte: 'IBGE'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Índice salvo: ${mes}/${ano} = ${valorNumerico}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar novamente após importação
|
||||
return await prisma.indiceMonetario.findMany({
|
||||
where: {
|
||||
nome: 'INPC',
|
||||
OR: [
|
||||
{
|
||||
anoReferencia: {
|
||||
gte: dataInicial.getFullYear(),
|
||||
lte: dataFinal.getFullYear()
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: [
|
||||
{ anoReferencia: 'asc' },
|
||||
{ mesReferencia: 'asc' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
return indicesExistentes;
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar índices:', error);
|
||||
throw new Error(`Falha ao buscar índices monetários: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Atualizar a função principal para usar o novo método
|
||||
export async function calcularFatorCorrecaoTotal({
|
||||
dataInicial,
|
||||
dataFinal,
|
||||
indice
|
||||
}: {
|
||||
dataInicial: Date;
|
||||
dataFinal: Date;
|
||||
indice: string;
|
||||
}): Promise<Decimal> {
|
||||
try {
|
||||
const indices = await buscarIndicesParaPeriodo(dataInicial, dataFinal);
|
||||
|
||||
if (!indices.length) {
|
||||
throw new Error('Não foram encontrados índices para o período informado');
|
||||
}
|
||||
|
||||
// Calcular fator acumulado
|
||||
let fatorAcumulado = new Decimal(1);
|
||||
for (const indice of indices) {
|
||||
fatorAcumulado = fatorAcumulado.mul(new Decimal(1 + indice.valor / 100));
|
||||
}
|
||||
|
||||
console.log('Fator de correção calculado:', {
|
||||
indices: indices.length,
|
||||
primeiro: indices[0],
|
||||
ultimo: indices[indices.length - 1],
|
||||
fatorAcumulado: fatorAcumulado.toString()
|
||||
});
|
||||
|
||||
return fatorAcumulado;
|
||||
} catch (error) {
|
||||
console.error('Erro ao calcular fator de correção:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user