Como Criar um Dashboard Financeiro com Python, Dash e SQL Server

Saiba como construir um painel interativo completo para controle financeiro utilizando Python, Dash, Plotly e integração com banco de dados SQL Server. Ideal para empresas que utilizam o Protheus ou desejam centralizar dados financeiros em tempo real.

Visão Geral do Projeto

Este artigo técnico explica passo a passo como criar um dashboard financeiro com gráficos interativos e indicadores chave usando:

  • Dash: framework web baseado em Python
  • Plotly: biblioteca para gráficos interativos
  • SQL Server: banco de dados corporativo
  • PyODBC: driver de conexão com o banco

O painel permite acompanhar receitas, despesas, saldo real, contas a pagar/receber e atrasos, com gráficos de fluxo de caixa e medidores percentuais.

Como Criar um Dashboard Financeiro com Python, Dash e SQL Server buscando dados do Protheus

1. Conectando ao Banco de Dados SQL Server

Utilizamos o pyodbc para acessar as tabelas financeiras do sistema Protheus, como SE1, SE2 e SE5:


import pyodbc

conexao = pyodbc.connect(
    'DRIVER={ODBC Driver 17 for SQL Server};'
    'SERVER=SEU_SERVIDOR;'
    'DATABASE=SEU_BANCO;'
    'UID=USUARIO;'
    'PWD=SENHA'
)
    

Em seguida, extraímos os seguintes dados:

  • Recebimentos: SE1
  • Pagamentos: SE2
  • Contas a pagar e receber: SE5

2. Processando e Calculando Indicadores Financeiros

Após obter os dados, calculamos os principais indicadores:

  • Valor recebido e pago
  • Saldo real (receita - despesa)
  • Totais a receber e a pagar
  • Valores em atraso
  • Percentuais de recebimento e pagamento

3. Gráficos Interativos com Plotly

Gráfico de Fluxo de Caixa (Waterfall)

O gráfico do tipo Waterfall simula a entrada e saída mensal de caixa:


fig = go.Figure(go.Waterfall(
    x=["Jul", "Ago", "Set", "Out", "Nov", "Dez", "Total"],
    y=[5000, -2000, 3000, -4000, -1000, 2000, 3000]
))
    

Gráficos de Medidor (Gauge)

Mostram o desempenho percentual de recebimentos e pagamentos:


fig = go.Figure(go.Indicator(
    mode="gauge+number",
    value=percentual_recebido,
    title={"text": "% Recebido"},
    gauge={"axis": {"range": [0, 100]}}
))
    

Código completo:

    import dash
    from dash import html, dcc
    import plotly.graph_objs as go
    from datetime import datetime
    import pandas as pd
    import pyodbc

    # Conexão com SQL Server
    def obter_dados():
        conexao = pyodbc.connect(
            'DRIVER={ODBC Driver 17 for SQL Server};'
            'SERVER=DESKTOP-M3OPBE4;'
            'DATABASE=P12;'
            'UID=sa;'
            'PWD=abc123'
        )

        # Exemplo: Buscando valores da SE1 (Recebimentos)
        df_se1 = pd.read_sql("""
            SELECT 
                SUM(E1_VALOR) AS valor_recebido
            FROM SE1990
        """, conexao)

        # Exemplo: Buscando valores da SE2 (Pagamentos)
        df_se2 = pd.read_sql("""
            SELECT 
                SUM(E2_VALOR) AS valor_pago
            FROM SE2990
        """, conexao)

        # Exemplo: Buscando totais a pagar/receber da SE5 (Financeiro aberto)
        df_se5 = pd.read_sql("""
            SELECT 
                SUM(CASE WHEN E5_TIPO = 'R' THEN E5_VALOR ELSE 0 END) AS total_receber,
                SUM(CASE WHEN E5_TIPO = 'P' THEN E5_VALOR ELSE 0 END) AS total_pagar,
                SUM(CASE WHEN E5_TIPO = 'R' AND E5_VENCTO < GETDATE() THEN E5_VALOR ELSE 0 END) AS atraso_receber,
                SUM(CASE WHEN E5_TIPO = 'P' AND E5_VENCTO < GETDATE() THEN E5_VALOR ELSE 0 END) AS atraso_pagar
            FROM SE5990
        """, conexao)

        conexao.close()

        # Valores tratados
        valor_recebido = df_se1['valor_recebido'].iloc[0] or 0
        valor_pago = df_se2['valor_pago'].iloc[0] or 0
        saldo_real = valor_recebido - valor_pago
        valor_total_receber = df_se5['total_receber'].iloc[0] or 0
        valor_total_pagar = df_se5['total_pagar'].iloc[0] or 0
        valor_atrasado_receber = df_se5['atraso_receber'].iloc[0] or 0
        valor_atrasado_pagar = df_se5['atraso_pagar'].iloc[0] or 0

        percentual_recebido = (valor_recebido / valor_total_receber) * 100 if valor_total_receber else 0
        percentual_pago = (valor_pago / valor_total_pagar) * 100 if valor_total_pagar else 0

        return {
            "valor_recebido": valor_recebido,
            "valor_pago": valor_pago,
            "saldo_real": saldo_real,
            "valor_total_receber": valor_total_receber,
            "valor_total_pagar": valor_total_pagar,
            "valor_atrasado_receber": valor_atrasado_receber,
            "valor_atrasado_pagar": valor_atrasado_pagar,
            "percentual_recebido": percentual_recebido,
            "percentual_pago": percentual_pago
        }

    # Obter dados reais do banco
    dados = obter_dados()

    # Gráfico de fluxo de caixa
    fig_fluxo = go.Figure(go.Waterfall(
        name="Fluxo de Caixa",
        orientation="v",
        measure=["relative"] * 6 + ["total"],
        x=["Jul", "Ago", "Set", "Out", "Nov", "Dez", "Total"],
        y=[5000, -2000, 3000, -4000, -1000, 2000, 3000],
        increasing={"marker": {"color": "#4CAF50"}},
        decreasing={"marker": {"color": "#F44336"}},
        totals={"marker": {"color": "#2196F3"}}
    ))
    fig_fluxo.update_layout(
        paper_bgcolor='white',
        plot_bgcolor='white',
        height=400,
        margin=dict(l=20, r=20, t=30, b=20)
    )

    # Gráficos de medidor radial
    gauge_recebido = go.Figure(go.Indicator(
        mode="gauge+number",
        value=dados['percentual_recebido'],
        title={'text': "% Recebido"},
        gauge={'axis': {'range': [0, 100]}, 'bar': {'color': "#4CAF50"}}
    ))






    gauge_pago = go.Figure(go.Indicator(
        mode="gauge+number",
        value=dados['percentual_pago'],
        title={'text': "% Pago"},
        gauge={'axis': {'range': [0, 100]}, 'bar': {'color': "#F44336"}}
    ))

    # App
    app = dash.Dash(__name__)
    server = app.server

    # Layout
    app.layout = html.Div(style={'backgroundColor': '#f5f7fa', 'fontFamily': 'Arial, sans-serif', 'padding': '20px'}, children=[

        html.H1("💰 Dashboard Financeiro", style={'textAlign': 'center', 'color': '#333'}),

        html.Div([
            dcc.DatePickerRange(
                id='data-range',
                start_date=datetime(2021, 1, 1),
                end_date=datetime(2021, 12, 31),
                display_format='DD/MM/YYYY',
                style={'marginBottom': '20px'}
            )
        ], style={'textAlign': 'center'}),

        html.Div(style={'display': 'flex', 'flexWrap': 'wrap', 'justifyContent': 'space-between', 'gap': '20px'}, children=[

            html.Div([html.H4("Valor Recebido"), html.P(f"R$ {dados['valor_recebido']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("Valor Pago"), html.P(f"R$ {dados['valor_pago']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("Saldo Real"), html.P(f"R$ {dados['saldo_real']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("Total a Receber"), html.P(f"R$ {dados['valor_total_receber']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("Total a Pagar"), html.P(f"R$ {dados['valor_total_pagar']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("Atraso a Receber"), html.P(f"R$ {dados['valor_atrasado_receber']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("Atraso a Pagar"), html.P(f"R$ {dados['valor_atrasado_pagar']:,.2f}", className='card-value')], className='card'),
            html.Div([html.H4("% Recebido"), html.P(f"{dados['percentual_recebido']:.2f}%", className='card-value')], className='card'),
            html.Div([html.H4("% Pago"), html.P(f"{dados['percentual_pago']:.2f}%", className='card-value')], className='card'),
        ]),

        html.Div([dcc.Graph(figure=fig_fluxo)], className='card', style={'width': '100%', 'marginTop': '30px'}),

        html.Div(style={'display': 'flex', 'gap': '20px', 'marginTop': '30px'}, children=[
            html.Div([dcc.Graph(figure=gauge_recebido)], className='card'),
            html.Div([dcc.Graph(figure=gauge_pago)], className='card'),
        ])
    ])

    # CSS
    app.index_string = '''
    <!DOCTYPE html>
    <html>
        <head>
            {%metas%}
            <title>Dashboard Financeiro</title>
            {%favicon%}
            {%css%}
            <style>
                .card {
                    background-color: white;
                    padding: 20px;
                    border-radius: 12px;
                    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
                    flex: 1;
                    min-width: 200px;
                }
                .card-value {
                    font-size: 24px;
                    font-weight: bold;
                    color: #222;
                }
                @media (max-width: 900px) {
                    .card {
                        width: 100% !important;
                        margin-bottom: 20px;
                    }
                }
            </style>
        </head>
        <body>
            {%app_entry%}
            <footer>
                {%config%}
                {%scripts%}
                {%renderer%}
            </footer>
        </body>
    </html>
    '''

    # Rodar app
    if __name__ == '__main__':
        app.run_server(debug=True)

 

4. Layout Moderno e Responsivo com Dash

Utilizamos Dash Bootstrap Components para estilizar os cards e organizar os elementos em colunas, facilitando a leitura dos dados e mantendo o painel responsivo.

Os filtros por data no topo do dashboard permitem selecionar intervalos dinâmicos para análise.

5. Resultado Final

O resultado é um dashboard robusto, ideal para a gestão financeira estratégica:

  • Visual moderno
  • Dados em tempo real via SQL Server
  • Indicadores essenciais em cards
  • Gráficos intuitivos

Você pode rodar o app com:


python app.py