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:
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
