← Projetos
BI2026

Dashboard: Copa do Mundo 2026

Como transformar quatro planilhas soltas de um Mundial de 48 seleções num relatório onde artilharia, resultado e premiação respondem ao mesmo filtro.

  • Power BI
  • DAX
  • TMDL
  • Modelagem Dimensional
  • JSON
Tela do projeto Dashboard: Copa do Mundo 2026
01

O problema

A Copa de 2026 é a primeira com 48 seleções e 104 jogos — quase o dobro do formato anterior. Os dados públicos vêm repartidos por assunto: uma lista de jogos, uma de artilheiros, uma de premiações, uma das fases finais. Cada uma com o nome da seleção escrito do seu jeito, e nenhuma conversando com as outras.

Perguntas simples ficavam caras. “Quem foi o artilheiro da seleção campeã” exigia abrir duas abas e cruzar na mão. E como as quatro listas não compartilhavam nenhuma chave, qualquer visual novo significava refazer o cruzamento do zero.

O objetivo era montar um modelo onde essas perguntas — e as próximas, que eu ainda não sabia quais seriam — fossem respondidas por um clique de filtro.

02

As decisões

Quatro fatos compartilhando dimensões, não uma tabela gigante

Cada assunto virou sua própria tabela fato — Jogos, Artilheiros, Premiações e Fase Final — porque o grão de cada um é diferente: uma linha de Jogos é uma partida, uma de Artilheiros é um jogador. Juntar isso numa tabela só obrigaria a repetir dado ou inventar linha vazia. Em vez disso, as quatro apontam para as mesmas dimensões (Seleção, Calendário, Fase), então filtrar por seleção propaga para os quatro assuntos ao mesmo tempo.

O que eu descartei

A saída rápida seria um PROCV gigante consolidando tudo numa aba só. Funciona para exibir uma vez e quebra na primeira pergunta nova — além de multiplicar linha errado no primeiro jogador que marcou em dois jogos.

A dimensão de seleção é calculada a partir dos próprios fatos

Não existia uma lista canônica de seleções. Em vez de digitar as 48 na mão e torcer para bater, montei a Dim_Selecao com DISTINCT(UNION(...)) puxando de quatro origens: mandante e visitante de Jogos, mais a seleção de Artilheiros e de Premiações. Assim é impossível existir uma seleção num fato que não exista na dimensão — o modelo se garante sozinho.

O que eu descartei

Digitar a lista manualmente criaria um ponto de manutenção silencioso: qualquer divergência de grafia entre as planilhas viraria um filtro que devolve vazio sem avisar.

Mandante e visitante sem relacionamento — resolvido na medida

Um jogo tem duas seleções, e as duas precisariam filtrar o mesmo fato. Deixei Fato_Jogos sem relacionamento direto com Dim_Selecao e resolvi o papel-duplo dentro das medidas, somando os dois lados explicitamente com SUMX sobre FILTER. O modelo fica sem relacionamento inativo escondido, e quem lê a medida vê os dois lados sendo somados.

O que eu descartei

As alternativas eram relacionamento inativo com USERELATIONSHIP, ou duplicar a dimensão em Dim_Mandante e Dim_Visitante. A segunda obrigaria quem usa o relatório a saber em qual das duas filtrar — empurra a complexidade do modelo para dentro da cabeça do usuário.

Ordenação de fase resolvida na dimensão, não no visual

“Semifinal” vem antes de “Final” no alfabeto, mas depois no torneio. A Dim_Fase é calculada com um SWITCH que atribui Ordem Fase de 1 a 9 e marca cada fase como Grupos ou Mata-mata. A coluna de ordem fica oculta e serve de sortByColumn — então todo visual do relatório já nasce na ordem certa, sem eu configurar um por um.

O que eu descartei

Ordenar manualmente em cada visual funciona até você criar o décimo, e quebra silenciosamente quando alguém adiciona uma fase nova.

Cinco cards de KPI em uma única medida DAX que devolve HTML

A capa mostra cinco indicadores num visual só: uma medida monta HTML e CSS com os valores vindos do modelo. O código é dividido em CONFIG e MOTOR — trocar a identidade visual inteira é editar quatro cores em hexadecimal no topo, e os cards se realinham sozinhos porque o layout é grid CSS. É um mini design system reaproveitável entre relatórios.

O que eu descartei

Cinco cards nativos separados dão o mesmo resultado na tela e um alinhamento que precisa ser refeito à mão a cada ajuste de posição.

Projeto em .pbip: modelo em TMDL, relatório em PBIR

Salvei como Power BI Project em vez de .pbix. O modelo vira TMDL e o relatório vira JSON — os dois em texto. Isso põe o dashboard no Git de verdade: dá para ver num diff que uma medida mudou, revisar antes de aplicar e voltar atrás sem depender de “cópia final v3”.

O que eu descartei

O .pbix é um binário. Versionar significa guardar arquivos inteiros lado a lado, sem histórico do que mudou dentro deles.

03

O modelo

FATOSDIMENSÕESDataFaseFato_Jogos12 colunasFato_Artilheiros6 colunasFato_Premiacoes4 colunasFato_FaseFinal4 colunasDim_Selecao1 colunasDim_Calendario8 colunasDim_Fase3 colunas_KPIs Copatabela de medidas

Fato_Jogos

tabela fato

Grão: uma linha por partida da competição

Excel via Power Query · 3 colunas calculadas extraem os gols do texto do placar

  • ID DO JOGO
  • Fase
  • Data
  • Local
  • Mandante
  • Visitante
  • Placar
  • Vencedor
  • Grupo
  • Gols Mandante
  • Gols Visitante
  • Total de Gols

clique numa tabela para ver o grão, a origem e as colunas · lido do TMDL do projeto

O DAX que sustenta o modelo

Dim_Selecao (tabela calculada)

Dim_Selecao =
DISTINCT (
    UNION (
        SELECTCOLUMNS ( 'Fato_Jogos',       "Seleção", 'Fato_Jogos'[Mandante] ),
        SELECTCOLUMNS ( 'Fato_Jogos',       "Seleção", 'Fato_Jogos'[Visitante] ),
        SELECTCOLUMNS ( 'Fato_Artilheiros', "Seleção", 'Fato_Artilheiros'[Seleção] ),
        SELECTCOLUMNS ( 'Fato_Premiacoes',  "Seleção", 'Fato_Premiacoes'[Seleção] )
    )
)

Por que assim

É a peça que segura o modelo inteiro. Como a dimensão nasce da união dos fatos, nenhuma seleção pode existir num fato sem existir aqui — o erro de integridade fica impossível por construção, em vez de depender de conferência.

Seleção + Goleadora

Seleção + Goleadora =
VAR _tbl =
    ADDCOLUMNS (
        VALUES ( 'Dim_Selecao'[Seleção] ),
        "@Gols",
            VAR _s = 'Dim_Selecao'[Seleção]
            RETURN
                SUMX ( FILTER ( ALL ( 'Fato_Jogos' ), 'Fato_Jogos'[Mandante]  = _s ), 'Fato_Jogos'[Gols Mandante] )
              + SUMX ( FILTER ( ALL ( 'Fato_Jogos' ), 'Fato_Jogos'[Visitante] = _s ), 'Fato_Jogos'[Gols Visitante] )
    )
VAR _top = TOPN ( 1, _tbl, [@Gols], DESC )
RETURN
    CONCATENATEX ( _top, 'Dim_Selecao'[Seleção], ", " )

Por que assim

É onde o papel-duplo se resolve. Os dois SUMX somam os gols da seleção como mandante e como visitante — sem isso, o número sairia pela metade e continuaria parecendo plausível, que é o tipo de erro que ninguém percebe. O CONCATENATEX no fim existe para o caso de empate: em vez de escolher uma arbitrariamente, mostra as duas.

Gols (coluna calculada em Fato_Artilheiros)

Gols =
VAR t        = TRIM ( 'Fato_Artilheiros'[Gols na Copa 2026] )
VAR posSpace = SEARCH ( " ", t, 1, LEN ( t ) + 1 )
VAR posParen = SEARCH ( "(", t, 1, LEN ( t ) + 1 )
VAR cut      = MIN ( posSpace, posParen ) - 1
RETURN
    VALUE ( LEFT ( t, cut ) )

Por que assim

A planilha traz o número junto de observação — casos como "3 (só na disputa de 3º lugar)". A medida corta no que vier primeiro, espaço ou parêntese, e o quarto argumento do SEARCH devolve o fim do texto quando não acha, em vez de erro. Deixei documentado no modelo que o lugar certo disso é o Power Query: resolver na coluna calculada funciona, mas empurra o tratamento para depois da carga.

04

O resultado

104
jogos mapeados

48 seleções

20
medidas DAX

em 3 display folders

8
tabelas no modelo

4 fatos · 3 dimensões

39
visuais

em 5 páginas navegáveis