Para instalar, crie uma planilha no Google Sheets, clique em Extensões → Apps Script
Em arquivos, crie 4:
01_Interface.gs
02_Motor.gs
03_ImportarSGS.gs
04_ImportarFERIADOS.gs
Clique em “Serviços +” e adicione Drive API
Depois de colocar os códigos abaixo em cada arquivo, ao lado direito de “Depuração”, selecione instalarCalculadora e depóis “Executar”. Vai pedir as permissões, autorize e deixe instalar a calculadora. Toda a planilha é gerada automaticamente.
01_Interface.gs
/**
* CALCULADORA DE INFLAÇÃO | 01_Interface.gs | v0.3
* Execute instalarCalculadora() UMA VEZ após colar os 4 arquivos no Apps Script
* de uma planilha Google. Reexecutar recria o leiaute da aba Cálculo.
* Menu INFLAÇÃO: apenas 'Atualizar dados' e 'Limpar cálculo'.
* Cada edição de campo relevante recalcula automaticamente, sem API externa.
*/
const UI_IPCA = Object.freeze({
METODOS: {
'IPCA divulgado (sem defasagem)': 'OFICIAL',
'IPCA divulgado com defasagem': 'DEFASAGEM',
'Repetir último IPCA conhecido': 'REPETIR',
'Usar projeção de inflação': 'PROJECAO'
},
FONTES: {
'ANBIMA — taxa digitada': 'ANBIMA',
'Focus — taxa digitada': 'FOCUS',
'Estimativa própria': 'PROPRIA'
},
CALENDARIOS: {
'Por mês do calendário': 'MES_CALENDARIO',
'Entre aniversários mensais': 'ANIVERSARIO'
},
DIAS: {
'Dias corridos': 'CORRIDOS',
'Dias úteis (feriados ANBIMA)': 'UTEIS'
},
FORMULAS: {
'Exponencial (recomendado)': 'EXPONENCIAL',
'Linear': 'LINEAR'
},
JUROS: {
'Sem juros': 'NAO',
'Adicionar juros': 'SIM'
},
BASE_JUROS: {
'252 dias úteis/ano (feriados ANBIMA)': 'DU252',
'365 dias corridos/ano': 'DC365',
'360 dias corridos/ano': 'DC360'
},
EDICAO: ['D7', 'D8', 'D9', 'D13', 'D14', 'D15', 'D16', 'D19', 'D20', 'D21', 'D22', 'D25', 'D26', 'D27'],
PALETA: {
navy: '#172B45', teal: '#146B72', paper: '#F7F9FC', panel: '#EEF4F6', ink: '#183047',
blue: '#1557B0', muted: '#596C7B', white: '#FFFFFF', border: '#D8E3E9', amber: '#FFF2D5', red: '#9C2D2D', green: '#1C6554'
}
});
function onOpen(e) {
SpreadsheetApp.getUi().createMenu('INFLAÇÃO')
.addItem('Atualizar dados', 'atualizarDados')
.addItem('Limpar cálculo', 'limparCalculadora')
.addToUi();
}
function regraLista_(valores, dica) {
return SpreadsheetApp.newDataValidation().requireValueInList(valores, true)
.setAllowInvalid(false).setHelpText(dica).build();
}
function regraNumero_(min, max, dica) {
return SpreadsheetApp.newDataValidation().requireNumberBetween(min, max)
.setAllowInvalid(false).setHelpText(dica).build();
}
function montarVisualCalculo_(sh) {
const C = UI_IPCA.PALETA;
if (sh.getMaxColumns() < 10) sh.insertColumnsAfter(sh.getMaxColumns(), 10 - sh.getMaxColumns());
if (sh.getMaxRows() < 160) sh.insertRowsAfter(sh.getMaxRows(), 160 - sh.getMaxRows());
sh.getRange('A1:J160').breakApart();
sh.clear();
sh.showRows(1, Math.min(45, sh.getMaxRows()));
sh.setHiddenGridlines(true);
sh.setFrozenRows(4);
sh.setTabColor(C.teal);
sh.getRange('A1:J160').setBackground(C.paper).setFontFamily('Arial').setFontSize(10).setFontColor(C.ink);
const widths = { 1: 22, 2: 242, 3: 125, 4: 250, 5: 20, 6: 200, 7: 148, 8: 148, 9: 100, 10: 290 };
Object.keys(widths).forEach(n => sh.setColumnWidth(Number(n), widths[n]));
sh.setRowHeights(1, 160, 26);
sh.setRowHeight(2, 29);
sh.setRowHeight(3, 29);
sh.setRowHeight(4, 42);
sh.setRowHeight(31, 40);
sh.getRange('B2:I3').merge().setValue('CALCULADORA DE INFLAÇÃO')
.setBackground(C.navy).setFontColor(C.white).setFontSize(17).setFontWeight('bold')
.setVerticalAlignment('middle');
sh.getRange('B4:I4').merge().setValue('IPCA + juros reais | preencha as células azuis; os resultados são automáticos.')
.setBackground(C.panel).setFontColor(C.muted).setWrap(true).setVerticalAlignment('middle');
function secao(linha, texto) {
sh.getRange('B' + linha + ':I' + linha).merge().setValue(texto).setFontWeight('bold')
.setFontColor(C.white).setBackground(C.teal).setVerticalAlignment('middle');
sh.setRowHeight(linha, 31);
}
secao(6, '01 PERÍODO E VALOR');
secao(11, '02 COMO CALCULAR A INFLAÇÃO');
secao(18, '03 COMO DISTRIBUIR O ÍNDICE NOS DIAS');
secao(24, '04 JUROS REAIS (OPCIONAL)');
secao(30, '05 RESULTADO');
secao(43, '06 MEMÓRIA DE CÁLCULO POR CICLO');
const linhas = {
7: 'Valor inicial (R$)', 8: 'Data inicial', 9: 'Data final', 12: 'Índice',
13: 'Como tratar o IPCA?', 14: 'Meses de defasagem', 15: 'Origem da projeção', 16: 'Taxa prevista (% a.m.)',
19: 'Período de rateio', 20: 'Dia do aniversário', 21: 'Contagem para o IPCA', 22: 'Cálculo de frações de mês',
25: 'Juros adicionais?', 26: 'Juros reais (% a.a.)', 27: 'Base dos juros',
33: 'Correção monetária', 34: 'Juros remuneratórios', 35: 'Valor final', 36: 'IPCA acumulado',
37: 'Rentabilidade acumulada', 38: 'Fator IPCA', 39: 'Fator dos juros', 40: 'Dias transcorridos'
};
Object.keys(linhas).forEach(r => {
sh.getRange('B' + r + ':C' + r).merge().setValue(linhas[r]).setFontWeight('bold').setVerticalAlignment('middle');
});
const ajudas = {
7: 'Digite o principal a atualizar, em reais.',
8: 'O dia inicial não é contado. Ex.: 05 a 21 = 16 dias corridos.',
9: 'O dia final é contado. Permite datas no mesmo mês.',
12: 'Nesta versão, usamos apenas o IPCA mensal (SGS 433).',
13: 'Escolha UMA alternativa. Não existe combinação de projeção com defasagem.',
14: 'Ex.: outubro com 2 meses de defasagem usa o IPCA de agosto.',
15: 'A instituição identifica a ORIGEM INFORMADA; esta versão não baixa projeções.',
16: 'Digite 0,50 para 0,50% ao mês. A taxa se repete nos meses sem índice oficial.',
19: 'Mês do calendário: do dia 1 ao dia 1 seguinte. Aniversário: de uma data mensal até a seguinte.',
20: 'Ex.: aniversário 21 = ciclos 21/set a 21/out, 21/out a 21/nov etc.',
21: 'Dias úteis excluem sábados, domingos e feriados bancários nacionais do calendário ANBIMA, quando atualizado.',
22: 'Exponencial preserva a composição entre trechos. Linear é alternativa opcional.',
25: 'IPCA sem juros ou IPCA acrescido de juros reais compostos.',
26: 'Digite 6 para IPCA + 6% ao ano. Não digite 0,06.',
27: '252 é uma convenção anual fixa. Dias úteis descontam sábados, domingos e feriados ANBIMA; 365/360 são dias corridos.',
33: 'Variação do principal causada apenas pela inflação.',
34: 'Juros calculados sobre o principal corrigido, em regime composto.',
35: 'Principal + inflação + juros.',
36: 'IPCA acumulado considerando o pró-rata selecionado.',
37: 'Variação total sobre o principal, incluindo inflação e juros.',
38: 'Produto dos fatores de atualização monetária.',
39: 'Fator de juros reais.',
40: 'Dias corridos e dias úteis, já descontados os feriados ANBIMA quando a base está carregada.'
};
Object.keys(ajudas).forEach(r => {
sh.getRange('F' + r + ':I' + r).merge().setValue(ajudas[r])
.setFontSize(9).setFontColor(C.muted).setWrap(true).setVerticalAlignment('middle');
sh.setRowHeight(Number(r), 37);
});
const editaveis = UI_IPCA.EDICAO;
editaveis.forEach(a1 => sh.getRange(a1).setBackground('#E5F0FE')
.setFontColor(C.blue).setFontWeight('bold').setVerticalAlignment('middle')
.setBorder(true, true, true, true, false, false, C.border, SpreadsheetApp.BorderStyle.SOLID));
sh.getRange('D12').setValue('IPCA').setBackground(C.panel).setFontColor(C.ink).setFontWeight('bold');
sh.getRange('D7').setNumberFormat('"R$" #,##0.00;[Red]("R$" #,##0.00);"-"');
sh.getRangeList(['D8', 'D9']).setNumberFormat('dd/MM/yyyy');
sh.getRangeList(['D14', 'D20']).setNumberFormat('0');
sh.getRangeList(['D16', 'D26']).setNumberFormat('0.0000');
sh.getRange('D13').setDataValidation(regraLista_(Object.keys(UI_IPCA.METODOS), 'Selecione apenas um método para obter a inflação.'));
sh.getRange('D14').setDataValidation(regraNumero_(1, 24, 'Número inteiro de meses: 1 a 24.'));
sh.getRange('D15').setDataValidation(regraLista_(Object.keys(UI_IPCA.FONTES), 'Origem declarada da taxa digitada.'));
sh.getRange('D16').setDataValidation(regraNumero_(-99.999, 1000, 'Em pontos percentuais: 0,50 representa 0,50% a.m.'));
sh.getRange('D19').setDataValidation(regraLista_(Object.keys(UI_IPCA.CALENDARIOS), 'Como dividir os ciclos mensais.'));
sh.getRange('D20').setDataValidation(regraNumero_(1, 31, 'Dia do mês (1 a 31), ajustado ao último dia em meses curtos.'));
sh.getRange('D21').setDataValidation(regraLista_(Object.keys(UI_IPCA.DIAS), 'Exclui sábados, domingos e feriados bancários nacionais ANBIMA. Requer aba Feriados atualizada.'));
sh.getRange('D22').setDataValidation(regraLista_(Object.keys(UI_IPCA.FORMULAS), 'Como obter o fator pró-rata.'));
sh.getRange('D25').setDataValidation(regraLista_(Object.keys(UI_IPCA.JUROS), 'Ativar/desativar juros reais.'));
sh.getRange('D26').setDataValidation(regraNumero_(-99.99, 1000, '6 = seis por cento ao ano.'));
sh.getRange('D27').setDataValidation(regraLista_(Object.keys(UI_IPCA.BASE_JUROS), 'Base anual para capitalização dos juros.'));
sh.getRange('D8').setDataValidation(SpreadsheetApp.newDataValidation().requireDate().setAllowInvalid(false).build());
sh.getRange('D9').setDataValidation(SpreadsheetApp.newDataValidation().requireDate().setAllowInvalid(false).build());
sh.getRange('D7').setDataValidation(SpreadsheetApp.newDataValidation().requireNumberGreaterThanOrEqualTo(0).setAllowInvalid(false).build());
sh.getRange('B31:I31').merge().setBackground(C.panel).setValue('Informe o valor e as duas datas.')
.setFontColor(C.muted).setWrap(true).setVerticalAlignment('middle').setFontWeight('bold');
sh.getRange('D33:D35').setNumberFormat('"R$" #,##0.00;[Red]("R$" #,##0.00);"-"');
sh.getRange('D35').setBackground('#E3F1EA').setFontSize(14).setFontWeight('bold');
sh.getRange('D36:D37').setNumberFormat('0.000000"%";[Red](0.000000"%");–');
sh.getRange('D38:D39').setNumberFormat('0.00000000');
sh.getRange('D33:D40').setFontWeight('bold');
sh.getRange('F43:I43').setFontColor(C.white);
sh.getRange('B44:J44').merge().setValue('O detalhamento mostra o IPCA aplicado a cada trecho e sua origem.')
.setFontColor(C.muted).setFontSize(9).setBackground(C.paper);
sh.getRange('B45:D45').setValues([['Trecho considerado', 'Competência', 'Origem da taxa']]);
sh.getRange('F45:J45').setValues([['Taxa mês (%)', 'Dias / ciclo', 'Fator trecho', 'Fator acumulado', 'Mês de origem']]);
sh.getRange('B45:J45').setBackground(C.navy).setFontColor(C.white)
.setFontWeight('bold').setWrap(true).setVerticalAlignment('middle');
sh.setRowHeight(45, 38);
sh.getRange('F46:F160').setNumberFormat('0.0000"%"');
sh.getRange('H46:I160').setNumberFormat('0.00000000');
sh.getRange('J2').setValue('IPCA_V02').setFontColor(C.paper).setFontSize(7);
sh.setActiveRange(sh.getRange('D7'));
}
function montarVisualSeries_(sh) {
const C = UI_IPCA.PALETA;
if (sh.getMaxColumns() < 8) sh.insertColumnsAfter(sh.getMaxColumns(), 8 - sh.getMaxColumns());
if (sh.getMaxRows() < 1000) sh.insertRowsAfter(sh.getMaxRows(), 1000 - sh.getMaxRows());
sh.setHiddenGridlines(true);
sh.setFrozenRows(5);
sh.setTabColor('#64748B');
const largura = [142, 170, 180, 185, 260, 350, 26, 260];
largura.forEach((w, i) => sh.setColumnWidth(i + 1, w));
sh.getRange('A1:H5').breakApart();
sh.getRange('A1:H2').setBackground(C.navy).setFontColor(C.white);
sh.getRange('A1:F2').merge().setValue('SÉRIES | IPCA MENSAL — BANCO CENTRAL')
.setFontSize(14).setFontWeight('bold').setVerticalAlignment('middle');
sh.getRange('A3:F3').merge().setValue('Valores em % ao mês: 0,48 representa 0,48% — não 48%.')
.setBackground(C.panel).setFontColor(C.muted).setFontSize(10);
sh.getRange('A4:F4').merge().setValue('A data de importação não comprova quando o IPCA foi publicado.')
.setFontColor(C.muted).setFontSize(9);
sh.getRange('A5:F5').setValues([['Competência', 'IPCA (% ao mês)', 'Fonte', 'Importado em', 'Endpoint SGS', 'Observação']])
.setBackground(C.teal).setFontColor(C.white).setFontWeight('bold');
sh.getRange('A6:A1000').setNumberFormat('@');
sh.getRange('B6:B1000').setNumberFormat('0.00"%"');
sh.getRange('D6:D1000').setNumberFormat('dd/MM/yyyy HH:mm');
if (sh.getLastRow() < 6) {
sh.getRange('H3').setValue('Ainda não atualizado').setFontColor(C.muted);
sh.getRange('H4').setValue('Use INFLAÇÃO → Atualizar dados').setFontColor(C.muted).setWrap(true);
}
sh.setRowHeight(5, 30);
}
/** Layout independente: nunca apaga a tabela importada A6:D. */
function montarVisualFeriados_(sh) {
const C = UI_IPCA.PALETA;
if (sh.getMaxColumns() < 8) sh.insertColumnsAfter(sh.getMaxColumns(), 8 - sh.getMaxColumns());
if (sh.getMaxRows() < 1100) sh.insertRowsAfter(sh.getMaxRows(), 1100 - sh.getMaxRows());
sh.setHiddenGridlines(true);
sh.setFrozenRows(5);
sh.setTabColor(C.teal);
[142, 164, 290, 155, 190, 330, 22, 310].forEach((w, i) => sh.setColumnWidth(i + 1, w));
sh.getRange('A1:H5').breakApart();
sh.getRange('A1:H2').setBackground(C.navy).setFontColor(C.white);
sh.getRange('A1:F2').merge().setValue('FERIADOS | CALENDÁRIO BANCÁRIO — ANBIMA')
.setFontSize(14).setFontWeight('bold').setVerticalAlignment('middle');
sh.getRange('A3:F3').merge().setValue('Dias úteis: excluem sábados, domingos e feriados bancários nacionais da ANBIMA.')
.setBackground(C.panel).setFontColor(C.muted).setFontSize(10);
sh.getRange('A4:F4').merge().setValue('Fonte: anbima.com.br/feriados/ | Não inclui feriados municipais nem todas as exceções operacionais.')
.setFontColor(C.muted).setFontSize(9);
sh.getRange('A5:F5').setValues([['Data (AAAA-MM-DD)', 'Dia da semana', 'Feriado', 'Fonte', 'Importado em', 'Observação']])
.setBackground(C.teal).setFontColor(C.white).setFontWeight('bold');
sh.getRange('A6:A' + sh.getMaxRows()).setNumberFormat('@');
sh.getRange('E6:E' + sh.getMaxRows()).setNumberFormat('dd/MM/yyyy HH:mm');
sh.setRowHeight(5, 31);
if (sh.getRange('H3').isBlank()) sh.getRange('H3').setValue('Ainda não atualizado').setFontColor(C.muted);
if (sh.getRange('H4').isBlank()) sh.getRange('H4').setValue('Use INFLAÇÃO → Atualizar dados').setFontColor(C.muted).setWrap(true);
}
/** Único comando do menu para atualizar SGS e ANBIMA, independentemente. */
function atualizarDados() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const lock = LockService.getDocumentLock();
if (!lock || !lock.tryLock(20000)) {
ss.toast('Já existe uma atualização em andamento.', 'Atualizar dados', 6);
return;
}
const ok = [], falhas = [];
try {
try {
ss.toast('Consultando SGS 433...', 'Atualizar dados', 6);
const r = atualizarSerieIPCA_();
ok.push('IPCA: ' + r.total + ' competências');
} catch (e) { falhas.push('IPCA: ' + e.message); }
try {
ss.toast('Importando calendário ANBIMA...', 'Atualizar dados', 6);
const r = atualizarFeriadosANBIMA_();
ok.push('Feriados: ' + r.total + ' datas');
} catch (e) { falhas.push('Feriados: ' + e.message); }
recalcularSemAviso();
ss.toast(ok.concat(falhas).join(' | '), falhas.length ? 'Atualização parcial' : 'Dados atualizados', 10);
if (falhas.length) SpreadsheetApp.getUi().alert(
'Atualização ' + (ok.length ? 'parcial' : 'não concluída'),
'Atualizados: ' + (ok.join('; ') || 'nenhum') + '\n\nFalhas:\n' + falhas.join('\n') +
'\n\nOs dados anteriores de cada origem com falha foram preservados.',
SpreadsheetApp.getUi().ButtonSet.OK);
} finally { lock.releaseLock(); }
}
function instalarCalculadora() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
if (!ss.getSheetByName('Cálculo') && ss.getSheets().length === 1 && ss.getSheets()[0].getLastRow() === 0) {
ss.getSheets()[0].setName('Cálculo');
}
const c = ss.getSheetByName('Cálculo') || ss.insertSheet('Cálculo');
const s = ss.getSheetByName('Séries') || ss.insertSheet('Séries');
const h = ss.getSheetByName('Feriados') || ss.insertSheet('Feriados');
const jaInstalado = String(c.getRange('J2').getValue()) === 'IPCA_V02';
const valores = jaInstalado ? Object.fromEntries(UI_IPCA.EDICAO.map(a1 => [a1, c.getRange(a1).getValue()])) : {};
montarVisualCalculo_(c);
const padrao = {
D13: 'IPCA divulgado (sem defasagem)', D19: 'Por mês do calendário',
D21: 'Dias corridos', D22: 'Exponencial (recomendado)', D25: 'Sem juros',
D27: '365 dias corridos/ano'
};
Object.keys(padrao).forEach(a1 => c.getRange(a1).setValue(padrao[a1]));
if (jaInstalado) {
// Ajusta rótulos legados, antes da reposição dos campos editados.
if (valores.D21 === 'Dias úteis (sem feriados)') valores.D21 = 'Dias úteis (feriados ANBIMA)';
if (valores.D27 === '252 dias úteis/ano (sem feriados)') valores.D27 = '252 dias úteis/ano (feriados ANBIMA)';
Object.keys(valores).forEach(a1 => {
if (valores[a1] !== '' && valores[a1] !== null && a1 !== 'D12') {
c.getRange(a1).setValue(valores[a1]);
}
});
}
montarVisualSeries_(s);
montarVisualFeriados_(h);
ajustarCamposCalculo_(c, false);
onOpen();
ss.setActiveSheet(c);
recalcularSemAviso();
ss.toast('Interface instalada. Use INFLAÇÃO → Atualizar dados.', 'Calculadora', 8);
}
function ajustarCamposCalculo_(sh, aposEdicao) {
const modo = sh.getRange('D13').getValue();
const cal = sh.getRange('D19').getValue();
const juros = sh.getRange('D25').getValue();
const eDef = modo === 'IPCA divulgado com defasagem';
const eProj = modo === 'Usar projeção de inflação';
[[14, eDef], [15, eProj], [16, eProj], [20, cal === 'Entre aniversários mensais'], [26, juros === 'Adicionar juros'], [27, juros === 'Adicionar juros']]
.forEach(([r, visivel]) => {
if (visivel) sh.showRows(r); else sh.hideRows(r);
});
if (eDef && (sh.getRange('D14').getValue() === '' || sh.getRange('D14').getValue() === null)) sh.getRange('D14').setValue(2);
if (eProj && !sh.getRange('D15').getValue()) sh.getRange('D15').setValue('Estimativa própria');
if (cal === 'Entre aniversários mensais' && !sh.getRange('D20').getValue()) {
const inicial = sh.getRange('D8').getValue();
const tz = SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone();
sh.getRange('D20').setValue(inicial instanceof Date ? Number(Utilities.formatDate(inicial, tz, 'd')) : 15);
}
if (juros === 'Adicionar juros' && sh.getRange('D26').getValue() === '') sh.getRange('D26').setValue(6);
}
function onEdit(e) {
if (!e || !e.range || e.range.getSheet().getName() !== 'Cálculo') return;
const r = e.range;
if (r.getColumn() > 4 || r.getLastColumn() < 4 || r.getRow() > 27 || r.getLastRow() < 7) return;
const sh = r.getSheet();
const mudouModo = r.getRow() <= 13 && r.getLastRow() >= 13;
if (mudouModo) sh.getRangeList(['D14', 'D15', 'D16']).clearContent();
if (r.getRow() <= 15 && r.getLastRow() >= 15 && !mudouModo) sh.getRange('D16').clearContent();
if (r.getRow() <= 19 && r.getLastRow() >= 19) sh.getRange('D20').clearContent();
if (r.getRow() <= 25 && r.getLastRow() >= 25) sh.getRange('D26').clearContent();
ajustarCamposCalculo_(sh, true);
recalcularSemAviso();
}
function limparResultados_(sh) {
sh.getRange('D33:D40').clearContent();
const ultima = Math.min(Math.max(sh.getLastRow(), 45), sh.getMaxRows());
if (ultima >= 46) sh.getRange(46, 2, ultima - 45, 9).clearContent();
}
function escreverStatus_(sh, texto, erro) {
const cel = sh.getRange('B31');
cel.setValue(texto).setBackground(erro ? '#FFF0EA' : UI_IPCA.PALETA.panel)
.setFontColor(erro ? UI_IPCA.PALETA.red : UI_IPCA.PALETA.muted);
}
function dataEntradaISO_(valor, tz) {
if (!(valor instanceof Date) || Number.isNaN(valor.getTime())) throw Error('As datas precisam ser datas válidas no Google Sheets.');
return Utilities.formatDate(valor, tz, 'yyyy-MM-dd');
}
function parametrosDaInterface_(sh, ss) {
const get = a1 => sh.getRange(a1).getValue();
const modo = UI_IPCA.METODOS[get('D13')];
if (!modo) throw Error('Escolha um dos métodos de inflação no campo azul.');
const proj = modo === 'PROJECAO';
if (proj && get('D16') === '') throw Error('Digite a projeção mensal em D16 (0,50 significa 0,50%).');
const fonte = proj ? UI_IPCA.FONTES[get('D15')] : null;
if (proj && !fonte) throw Error('Selecione a origem da taxa projetada.');
const comJuros = get('D25') === 'Adicionar juros';
const base = UI_IPCA.BASE_JUROS[comJuros ? get('D27') : '365 dias corridos/ano'];
// O calendário é opcional em cálculos só com dias corridos.
// Em DU/252 ou rateio por dias úteis, o motor exigirá calendário completo.
const calendarioFeriados = lerFeriadosANBIMA_();
const p = {
valorInicial: get('D7'),
inicio: dataEntradaISO_(get('D8'), ss.getSpreadsheetTimeZone()),
fim: dataEntradaISO_(get('D9'), ss.getSpreadsheetTimeZone()),
metodo: modo,
mesesDefasagem: modo === 'DEFASAGEM' ? get('D14') : 0,
fonteProjecao: fonte,
taxaProjetadaPct: proj ? get('D16') : undefined,
calendario: UI_IPCA.CALENDARIOS[get('D19')],
diaAniversario: get('D19') === 'Entre aniversários mensais' ? get('D20') : 1,
baseInflacao: UI_IPCA.DIAS[get('D21')],
formulaInflacao: UI_IPCA.FORMULAS[get('D22')],
jurosPctAA: comJuros ? get('D26') : 0,
baseJuros: base,
feriados: calendarioFeriados.datas,
coberturaFeriados: calendarioFeriados.cobertura
};
if (typeof p.valorInicial !== 'number') throw Error('Informe o valor inicial em reais.');
if (!p.calendario || !p.baseInflacao || !p.formulaInflacao || !p.baseJuros) throw Error('Complete as opções de rateio e juros.');
return p;
}
/** Chamado pelo onEdit e após atualização da série; não mostra pop-up durante edição. */
function recalcularSemAviso() {
const ss = SpreadsheetApp.getActiveSpreadsheet(), sh = ss.getSheetByName('Cálculo');
if (!sh) return;
limparResultados_(sh);
if (sh.getRange('D7').getValue() === '' || !sh.getRange('D8').getValue() || !sh.getRange('D9').getValue()) {
escreverStatus_(sh, 'Preencha valor inicial, data inicial e data final. O cálculo é automático.', false);
return;
}
try {
const params = parametrosDaInterface_(sh, ss);
const registros = lerSerieIPCA_();
if (registros.length === 0) throw Error('A aba Séries está vazia. Use INFLAÇÃO → Atualizar dados.');
const resultado = MotorIPCA.calcular(params, registros);
sh.getRange('D33:D40').setValues([
[resultado.correcaoMonetaria], [resultado.jurosRemuneratorios], [resultado.valorFinal],
[resultado.ipcaPct], [resultado.retornoTotalPct], [resultado.fatorIPCA],
[resultado.fatorJuros], [resultado.diasCorridos + ' corridos / ' + (resultado.diasUteis === null ? '— (atualize Feriados)' : resultado.diasUteis) + ' úteis']
]);
if (resultado.memoria.length > 0) {
if (sh.getMaxRows() < 45 + resultado.memoria.length) sh.insertRowsAfter(sh.getMaxRows(), 45 + resultado.memoria.length - sh.getMaxRows());
const memoriaEsquerda = resultado.memoria.map(x => [x.inicio + ' a ' + x.fim, x.competencia, x.tipo]);
const memoriaDireita = resultado.memoria.map(x => [x.taxaPct, x.dias + '/' + x.diasCiclo, x.fator, x.fatorAcumulado, x.mesOrigem]);
sh.getRange(46, 2, memoriaEsquerda.length, 3).setValues(memoriaEsquerda).setFontSize(9);
sh.getRange(46, 6, memoriaDireita.length, 5).setValues(memoriaDireita).setFontSize(9);
sh.getRange(46, 2, resultado.memoria.length, 9).setBackground('#FFFFFF');
sh.getRange(46, 6, resultado.memoria.length, 1).setNumberFormat('0.0000"%"');
sh.getRange(46, 8, resultado.memoria.length, 2).setNumberFormat('0.00000000');
}
const hipotese = resultado.diasUteis === null ? ' Calendário bancário ainda não disponível para informar dias úteis.' : '';
const proj = resultado.memoria.some(x => x.tipo.indexOf('PROJEÇÃO') >= 0);
const repet = resultado.memoria.some(x => x.tipo.indexOf('REPETIDO') >= 0);
const fonte = proj ? ' | inclui taxa projetada DIGITADA' : (repet ? ' | inclui repetição de IPCA' : ' | somente IPCA oficial');
escreverStatus_(sh, 'Calculado: ' + resultado.memoria.length + ' trecho(s)' + fonte + '.' + hipotese, false);
} catch (erro) {
escreverStatus_(sh, 'Não foi possível calcular: ' + erro.message, true);
}
}
/** Limpa apenas inputs e resultado; a aba Séries permanece intacta. */
function limparCalculadora() {
const ui = SpreadsheetApp.getUi();
if (ui.alert('Limpar cálculo', 'Limpar campos e resultados? Séries e Feriados serão mantidos.', ui.ButtonSet.YES_NO) !== ui.Button.YES) return;
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Cálculo');
if (!sh) return;
sh.getRangeList(UI_IPCA.EDICAO).clearContent();
const padrao = {
D13: 'IPCA divulgado (sem defasagem)', D19: 'Por mês do calendário',
D21: 'Dias corridos', D22: 'Exponencial (recomendado)', D25: 'Sem juros', D27: '365 dias corridos/ano'
};
Object.keys(padrao).forEach(a1 => sh.getRange(a1).setValue(padrao[a1]));
ajustarCamposCalculo_(sh, false);
recalcularSemAviso();
sh.activate();
sh.getRange('D7').activate();
}