932 lines
27 KiB
TypeScript
932 lines
27 KiB
TypeScript
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;
|
|
}
|
|
}
|