Simulador SAC vs PRICE: Comparativo com Custo de Oportunidade | O Estrategista Imobiliário

Simule seu financiamento imobiliário e descubra qual sistema é mais vantajoso. Compare parcelas, saldo devedor e o impacto de investir a diferença entre SAC e PRICE.
Autor

O Estrategista Imobiliário

Data de Publicação

9 de abril de 2026

Palavras-chave

simulador sac vs price, comparar financiamento imobiliário, tabela sac ou price, custo de oportunidade financiamento, simulador equity imobiliário

Simulador de Financiamento: SAC vs PRICE

Esta ferramenta foi desenvolvida para ajudar você a decidir entre os dois principais sistemas de amortização do Brasil. Diferente dos simuladores bancários, aqui você visualiza o impacto patrimonial e o custo de oportunidade de cada escolha.

O que comparar? Observe a aba de Equity para ver como seu patrimônio cresce em cada sistema e utilize a aba Invest. para entender se vale a pena investir a diferença das parcelas.

#| '!! 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 ---
def calcular_tabelas(valor_fin, cet_anual, prazo_anos):
    prazo_meses = prazo_anos * 12
    taxa_mensal = (1 + cet_anual)**(1/12) - 1
    
    dados_sac = []
    saldo_sac = valor_fin
    amort_sac = valor_fin / prazo_meses
    pago_acum_sac = 0
    for m in range(1, prazo_meses + 1):
        juros = saldo_sac * taxa_mensal
        parcela = amort_sac + juros
        saldo_sac -= amort_sac
        pago_acum_sac += parcela
        dados_sac.append({"Mes": m, "Ano": m/12, "Parcela": parcela, "Amort": amort_sac,  "Juros": juros, "Saldo": max(0, saldo_sac), "Total_Pago": pago_acum_sac})
        
    dados_price = []
    saldo_price = valor_fin
    parcela_price = valor_fin * (taxa_mensal * (1 + taxa_mensal)**prazo_meses) / ((1 + taxa_mensal)**prazo_meses - 1)
    pago_acum_price = 0
    for m in range(1, prazo_meses + 1):
        juros = saldo_price * taxa_mensal
        amort = parcela_price - juros
        saldo_price -= amort
        pago_acum_price += parcela_price
        dados_price.append({"Mes": m, "Ano": m/12, "Parcela": parcela_price, "Amort": amort, "Juros": juros, "Saldo": max(0, saldo_price), "Total_Pago": pago_acum_price})
        
    return pd.DataFrame(dados_sac), pd.DataFrame(dados_price)

# --- CONFIGURACAO VISUAL ---
ESTILO_LAYOUT_BASE = dict(
    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")
)

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: SAC vs PRICE"),
    
    # Layout responsivo: 1 coluna no mobile, 4 no desktop
    ui.layout_column_wrap(
        ui.input_numeric("valor", "Valor (R$)", 500000, step=10000),
        ui.input_slider("taxa", "CET (%)", 5.0, 20.0, 13.5, step=0.1),
        ui.input_slider("selic", "Selic (%)", 5.0, 20.0, 14.65, step=0.05),
        ui.input_slider("prazo", "Prazo (Anos)", 5, 35, 35),
        width=1/2, # No mobile o Shiny tenta ajustar, mas o 'width' aqui define a base
        widths_sm=1, 
        class_="control-panel"
    ),
    
    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")),
        ui.nav_panel("Invest.", output_widget("grafico_investimento")),
        ui.nav_panel("Equity", output_widget("grafico_equity"))
    )
)

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

    @output
    @render.ui
    def texto_insights():
        df_sac, df_price = dados()
        try:
            idx_p = df_sac[df_sac['Parcela'] <= df_price['Parcela'].iloc[0]].index[0]
            anos_p, meses_p = int(df_sac.iloc[idx_p]['Mes'] // 12), int(df_sac.iloc[idx_p]['Mes'] % 12)
            ponto_c = df_price[df_price['Total_Pago'] > df_sac['Total_Pago']].iloc[0]
            anos_c, meses_c = int(ponto_c['Mes'] // 12), int(ponto_c['Mes'] % 12)
            economia = df_price['Total_Pago'].iloc[-1] - df_sac['Total_Pago'].iloc[-1]
            return ui.markdown(f"""
                * **Inversão de parcelas:** As parcelas SAC tornam-se mais econômicas que a PRICE após **{anos_p} anos e {meses_p} meses**.
                * **Custo Total:** O sistema SAC torna-se mais vantajoso no valor acumulado após **{anos_c} anos e {meses_c} meses**.
                * **Resultado Final:** A economia real do SAC ao quitar o contrato será de **R$ {economia:,.2f}**.
                """)
        except: return ui.markdown("Ajuste os filtros.")

    @render_widget
    def grafico_parcelas():
        df_sac, df_price = dados()
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Parcela'], name="SAC", line=dict(color='#0984e3', width=2)))
        fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Juros'], name="Juros", line=dict(color='#0984e3', width=2, dash='dash')))
        fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Parcela'], name="PRICE", line=dict(color='#d63031', width=2)))
        fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Juros'], name="Juros", line=dict(color='#d63031', width=2, dash='dash')))
        fig.update_layout(**ESTILO_LAYOUT_BASE, title="Parcela Mensal")
        return fig

    @render_widget
    def grafico_saldo():
        df_sac, df_price = dados()
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Saldo'], name="SAC", line=dict(color='#0984e3', width=2)))
        fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Saldo'], name="PRICE", line=dict(color='#d63031', width=2)))
        fig.update_layout(**ESTILO_LAYOUT_BASE, title="Saldo Devedor")
        return fig

    @render_widget
    def grafico_acumulado():
        df_sac, df_price = dados()
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=df_sac['Ano'], y=df_sac['Total_Pago'], name="Total SAC", line=dict(color='#0984e3')))
        fig.add_trace(go.Scatter(x=df_price['Ano'], y=df_price['Total_Pago'], name="Total PRICE", line=dict(color='#d63031')))
        fig.update_layout(**ESTILO_LAYOUT_BASE, title="Custo Total Pago")
        return fig

    @render_widget
    def grafico_investimento():
        df_sac, df_price = dados()
        cdi_base, ir = input.selic() / 100, 0.15
        percentuais = [1.0, 1.05, 1.10] # Reduzi para 3 curvas no mobile para não poluir
        cores = ['#e74c3c', '#f1c40f', '#27ae60']
        
        fig = go.Figure()
        for pct, cor in zip(percentuais, cores):
            taxa_anual = cdi_base * pct
            taxa_m = (1 + taxa_anual)**(1/12) - 1
            invest = [0]
            for i in range(len(df_sac)):
                diff = df_sac['Parcela'].iloc[i] - df_price['Parcela'].iloc[i]
                saldo = invest[-1] * (1 + taxa_m * (1 - ir)) + diff
                invest.append(max(0, saldo))
            invest.pop(0)
            fig.add_trace(go.Scatter(x=df_sac['Ano'], y=invest, name=f"{int(pct*100)}% CDI", line=dict(color=cor, width=2)))

        fig.update_layout(**ESTILO_LAYOUT_BASE, title="Saldo de Oportunidade")
        return fig

    @render_widget
    def grafico_equity():
        df_sac, df_price = dados()
        v_fin = input.valor()
        cdi_base, ir = input.selic() / 100, 0.15
        
        equity_sac = v_fin - df_sac['Saldo']
        equity_price_puro = v_fin - df_price['Saldo']
        
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=df_sac['Ano'], y=equity_sac, name="Equity SAC", line=dict(color='#0984e3', width=2)))
        fig.add_trace(go.Scatter(x=df_price['Ano'], y=equity_price_puro, name="Equity PRICE", line=dict(color='#d63031', width=2)))
        
        # Apenas 100% CDI para limpar o gráfico mobile
        taxa_m = (1 + cdi_base)**(1/12) - 1
        invest = [0]
        divida = [0]
        for i in range(len(df_sac)):
            diff = df_sac['Parcela'].iloc[i] - df_price['Parcela'].iloc[i]
            saldo = invest[-1] * (1 + taxa_m * (1 - ir)) + diff
            invest.append(max(0, saldo))
            divida.append(min(0, saldo))
        invest.pop(0)
        divida.pop(0)

        # Eficiência de gerar patrimônio caso invista o dinheiro
        eff = ((equity_price_puro+invest)/(df_sac['Total_Pago']-divida))

        fig.add_trace(go.Scatter(x=df_sac['Ano'], y=equity_price_puro + invest, name="PRICE + 100% CDI", line=dict(color='#f1c40f', dash='dash')))

        fig.update_layout(**ESTILO_LAYOUT_BASE, title="Evolução do Patrimônio")
        return fig

app = App(app_ui, server)


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

Guia de Decisão Rápida

  • Escolha SAC se: Você busca pagar menos juros no total e quer que sua dívida caia rápido desde o primeiro mês.

  • Escolha PRICE se: Você precisa de uma parcela inicial menor e tem disciplina para investir a diferença.

Para uma análise detalhada sobre os riscos e vantagens de cada tabela, leia nosso Artigo Completo sobre SAC vs PRICE.