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.
1. Conectando ao Banco de Dados SQL Server
Utilizamos o pyodbc
para acessar as tabelas financeiras do sistema Protheus, como SE1, SE2 e SE5:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 7 8 |
fig = go.Figure(go.Indicator( mode="gauge+number", value=percentual_recebido, title={"text": "% Recebido"}, gauge={"axis": {"range": [0, 100]}} )) |
Código completo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
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:
1 2 3 |
python app.py |
Fernando Bueno
Atuando desde 2005 no mercado de tecnologia, desenvolvendo e implantando e sistemas gerenciais, sistemas e sites web e ecommerce.
Siga-me no Linked In