Simulador de Amortização Extraordinária: SAC vs PRICE | O Estrategista Imobiliário

Calcule o impacto de amortizações mensais ou anuais no seu financiamento. Descubra quanto você economiza em juros e quanto tempo reduz no prazo total.
Autor

O Estrategista Imobiliário

Data de Publicação

9 de abril de 2026

Palavras-chave

simulador amortização extraordinária, quitação antecipada financiamento, reduzir prazo ou parcela, SAC vs PRICE amortização

Simulador de Amortização Estratégica

Esta ferramenta permite visualizar o impacto real de aportes extras no seu saldo devedor.

Dica de Uso: Compare a estratégia de Redução de Prazo (para máxima economia de juros) com a Redução de Valor (para maior fôlego no orçamento mensal).


#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 820

## file: app.py
#| standalone: true
#| viewerHeight: 820

from shiny import App, render, ui, reactive
import pandas as pd
import plotly.graph_objs as go
from shinywidgets import output_widget, render_widget

# --- MOTOR FINANCEIRO COM AMORTIZAÇÃO ---
def calcular_simulacao(valor_fin, cet_anual, prazo_anos, tipo_amtz, freq_amtz, valor_amtz):
    prazo_meses = prazo_anos * 12
    taxa_mensal = (1 + cet_anual)**(1/12) - 1
    
    def simular(sistema="SAC"):
        dados = []
        saldo = valor_fin
        pago_acum = 0
        m = 1
        
        # Parâmetros iniciais
        parcela_base_price = valor_fin * (taxa_mensal * (1 + taxa_mensal)**prazo_meses) / ((1 + taxa_mensal)**prazo_meses - 1)
        amort_sac_base = valor_fin / prazo_meses
        meses_restantes = prazo_meses

        while saldo > 0.01 and m <= 600: # Limite de 50 anos por segurança
            juros = saldo * taxa_mensal
            
            # Cálculo da parcela base do sistema
            if sistema == "SAC":
                amort_sistema = amort_sac_base if tipo_amtz == "Prazo" else saldo / meses_restantes # Amortização fixa do contrato original
                parcela = amort_sistema + juros
            else: # PRICE
                # No recálculo de prazo, a parcela PRICE original se mantém
                parcela = parcela_base_price if tipo_amtz == "Prazo" else saldo * (taxa_mensal * (1 + taxa_mensal)**meses_restantes) / ((1 + taxa_mensal)**meses_restantes - 1)
                amort_sistema = parcela - juros

            # Aplicação da Amortização Extraordinária
            extra = 0
            if freq_amtz == "Mensal":
                extra = valor_amtz
            elif freq_amtz == "Anual" and m % 12 == 0:
                extra = valor_amtz
            
            # Ajuste se o extra for maior que o saldo
            extra = min(extra, max(0, saldo - amort_sistema))
            
            amort_total = amort_sistema + extra
            saldo -= amort_total
            pago_acum += (parcela + extra)
            
            dados.append({
                "Mes": m, "Ano": m/12, "Parcela": parcela, 
                "Saldo": max(0, saldo), "Total_Pago": pago_acum
            })
            
            if tipo_amtz == "Valor":
                meses_restantes -= 1
            
            m += 1
        return pd.DataFrame(dados)

    # Calculamos Original (sem extra) e Amortizada
    return {
        "sac_orig": simular("SAC") if valor_amtz == 0 else calcular_simulacao(valor_fin, cet_anual, prazo_anos, "Prazo", "Mensal", 0)["sac_orig"],
        "sac_amtz": simular("SAC"),
        "price_orig": simular("PRICE") if valor_amtz == 0 else calcular_simulacao(valor_fin, cet_anual, prazo_anos, "Prazo", "Mensal", 0)["price_orig"],
        "price_amtz": simular("PRICE")
    }

# --- UI ---
app_ui = ui.page_fluid(
    ui.head_content(
        # Meta tag para garantir que o mobile entenda o redimensionamento
        ui.tags.meta(name="viewport", content="width=device-width, initial-scale=0.8, maximum-scale=1.0, user-scalable=yes"),
        ui.tags.style("""
            body { background-color: #f8f9fa; }
            .insight-card { 
                background: white; 
                border-left: 5px solid #27ae60; 
                padding: 12px; 
                border-radius: 8px; 
                margin-bottom: 15px; 
                border: 1px solid #eee;
                font-size: 0.9rem;
            }
            /* Ajuste para telas pequenas */
            @media (max-width: 768px) {
                h2 { font-size: 1.4rem !important; }
                .insight-card { font-size: 0.8rem; padding: 8px; }
                .control-panel .shiny-input-container { margin-bottom: 5px !important; }
            }
        """)
    ),
    ui.markdown("## Estrategista Imobiliário: Simulador de Amortização"),
    
    ui.layout_column_wrap(
        ui.input_numeric("valor", "Valor Financiado (R$)", 500000),
        ui.input_slider("taxa", "Taxa CET Anual (%)", 5.0, 20.0, 13.5, step=0.1),
        ui.input_slider("prazo", "Prazo Original (Anos)", 5, 35, 35),
        width=1/3
    ),
    
    ui.layout_column_wrap(
        ui.input_select("freq", "Frequência", {"Mensal": "Mensal", "Anual": "Anual (13º/Bônus)"}),
        ui.input_select("tipo", "Objetivo", {"Prazo": "Reduzir Prazo (Tempo)", "Valor": "Reduzir Parcela (Fôlego)"}),
        ui.input_numeric("extra", "Valor do Aporte Extra (R$)", 1000, step=100),
        width=1/3
    ),

    ui.div(ui.output_ui("texto_insights"), class_="insight-card"),
    
    ui.navset_tab(
        ui.nav_panel("Parcelas", output_widget("grafico_parcelas")),
        ui.nav_panel("Dívida", output_widget("grafico_saldo")),
        ui.nav_panel("Despesa", output_widget("grafico_acumulado"))
    )
)

def server(input, output, session):
    @reactive.calc
    def dados():
        return calcular_simulacao(
            input.valor(), input.taxa()/100, input.prazo(),
            input.tipo(), input.freq(), input.extra()
        )

    @output
    @render.ui
    def texto_insights():
        res = dados()
        df_sa = res["sac_amtz"]
        df_pa = res["price_amtz"]
        df_so = res["sac_orig"]
        
        prazo_sa = df_sa['Mes'].iloc[-1]
        prazo_pa = df_pa['Mes'].iloc[-1]
        economia_s = res["sac_orig"]["Total_Pago"].iloc[-1] - df_sa["Total_Pago"].iloc[-1]
        economia_p = res["price_orig"]["Total_Pago"].iloc[-1] - df_pa["Total_Pago"].iloc[-1]
        
        return ui.markdown(f"""
            * **Impacto no Prazo:** Você quita o imóvel em: **SAC - {prazo_sa/12:.1f} anos** e **PRICE - {prazo_pa/12:.1f} anos**
            * **Economia de Juros (SAC):** R$ {economia_s:,.2f} evitados.
            * **Economia de Juros (PRICE):** R$ {economia_p:,.2f} evitados.
            """)

    def criar_figura(titulo, y_label):
        fig = go.Figure()
        fig.update_layout(
            template="plotly_white",
            font=dict(family="Arial, sans-serif", size=11), # Reduzi um pouco o padrão para mobile
            margin=dict(l=50, r=20, t=40, b=50), # Margens mais enxutas
            hovermode="x unified",
            legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5), # Legenda embaixo ajuda no mobile
            xaxis=dict(title="Anos", showline=True, linewidth=1, dtick=5),
            yaxis=dict(showline=True, linewidth=1, tickformat=",.0f"),
            title=titulo
        )
        return fig

    @render_widget
    def grafico_parcelas():
        res = dados()
        fig = criar_figura("Evolução da Parcela (Mensalidade + Extra)", "R$")
        fig.add_trace(go.Scatter(x=res["sac_orig"]["Ano"], y=res["sac_orig"]["Parcela"], name="SAC Original", line=dict(color='#0984e3', dash='dash', width = 1)))
        fig.add_trace(go.Scatter(x=res["sac_amtz"]["Ano"], y=res["sac_amtz"]["Parcela"], name="SAC Amortizada", line=dict(color='#0984e3')))
        fig.add_trace(go.Scatter(x=res["price_orig"]["Ano"], y=res["price_orig"]["Parcela"], name="PRICE Original", line=dict(color='#d63031', dash='dash', width = 1)))
        fig.add_trace(go.Scatter(x=res["price_amtz"]["Ano"], y=res["price_amtz"]["Parcela"], name="PRICE Amortizada", line=dict(color='#d63031')))
        return fig

    @render_widget
    def grafico_saldo():
        res = dados()
        fig = criar_figura("Redução do Saldo Devedor", "R$")
        fig.add_trace(go.Scatter(x=res["sac_orig"]["Ano"], y=res["sac_orig"]["Saldo"], name="SAC Original", line=dict(color='#0984e3', dash='dash', width = 1)))
        fig.add_trace(go.Scatter(x=res["sac_amtz"]["Ano"], y=res["sac_amtz"]["Saldo"], name="SAC Amortizada", line=dict(color='#0984e3')))
        fig.add_trace(go.Scatter(x=res["price_orig"]["Ano"], y=res["price_orig"]["Saldo"], name="PRICE Original", line=dict(color='#d63031', dash='dash', width = 1)))
        fig.add_trace(go.Scatter(x=res["price_amtz"]["Ano"], y=res["price_amtz"]["Saldo"], name="PRICE Amortizada", line=dict(color='#d63031')))
        return fig

    @render_widget
    def grafico_acumulado():
        res = dados()
        fig = criar_figura("Despesa Total Acumulada (Juros + Amortização)", "R$")
        fig.add_trace(go.Scatter(x=res["sac_orig"]["Ano"], y=res["sac_orig"]["Total_Pago"], name="SAC Original", line=dict(color='#0984e3', dash='dash', width = 1)))
        fig.add_trace(go.Scatter(x=res["sac_amtz"]["Ano"], y=res["sac_amtz"]["Total_Pago"], name="SAC Amortizada", line=dict(color='#0984e3')))
        fig.add_trace(go.Scatter(x=res["price_orig"]["Ano"], y=res["price_orig"]["Total_Pago"], name="PRICE Original", line=dict(color='#d63031', dash='dash', width = 1)))
        fig.add_trace(go.Scatter(x=res["price_amtz"]["Ano"], y=res["price_amtz"]["Total_Pago"], name="PRICE Amortizada", line=dict(color='#d63031')))
        return fig

app = App(app_ui, server)


## file: requirements.txt
pandas
plotly
shinywidgets
shiny
exceptiongroup; python_version < "3.11"

Para entender os conceitos técnicos por trás desta simulação, acesse nosso guia completo: SAC vs PRICE: Qual o melhor sistema de financiamento?