Em produção2023-11 – atualDesenvolvedor full-stack, responsável pela camada de integração

Metas

Dashboards financeiros de várias empresas, cada uma com um acesso diferente pra dar: API, banco, ou nada.

AnonimizadoUma plataforma de consultoria que acompanha indicadores financeiros e operacionais de várias empresas-cliente, de porte médio a grandes multinacionais, em setores diferentes. O nome fica de fora porque autorização nunca foi pedida. Toda restrição, decisão e número neste case reflete o que de fato foi construído.

  • rails
  • react
  • postgresql
  • oracle
  • devise

O problema e as restrições

O Metas existe pra transformar os próprios números financeiros e operacionais de uma empresa-cliente numa nota única e comparável, mês a mês, em toda área do negócio e em toda empresa de um grupo. O cliente define uma meta e um peso pra cada indicador; uma pessoa ou um sistema reporta o que de fato aconteceu; a plataforma transforma isso numa nota que a gestão consegue comparar entre dezenas de áreas e, num cliente com várias empresas, entre toda subsidiária ao mesmo tempo.

A primeira restrição era que “pegar o número” significa uma coisa diferente pra cada cliente, porque nenhum cliente roda os mesmos sistemas nem concede o mesmo acesso. Alguns clientes têm uma API moderna e não têm problema em entregar uma chave. Alguns rodam software antigo, on-premises, sem API nenhuma, mas permitem uma conta de banco somente leitura atrás de uma VPN. Alguns, por razão regulatória ou de infraestrutura, não abrem porta nenhuma além do que uma pessoa já vê na própria tela. Uma plataforma que só soubesse chamar API simplesmente não conseguiria atender uma fatia relevante dos clientes pra quem esse produto existe.

A segunda restrição era que a organização de um cliente raramente cabe numa lista plana de “empresas”. Uma holding é dona de subsidiárias, uma subsidiária tem as próprias unidades de negócio, e cada nível precisa dos próprios indicadores, da própria meta e do próprio fechamento pro nível acima, enquanto uma única pessoa gerenciando o grupo inteiro precisa ver tudo isso de uma vez, sem trocar de login pra cada entidade.

A terceira restrição era que uma nota alimenta decisão real que a própria gestão do cliente toma sobre desempenho de time e, em alguns clientes, sobre a remuneração desse time, bem além de um número cosmético de dashboard. Errar uma fórmula sutilmente, ou deixar passar sem perceber um número velho ou manualmente maquiado, não só fica feio num gráfico, produz uma decisão real errada lá na frente.

A última restrição era o próprio acesso, no sentido mais literal. Chegar aos números reais de um cliente às vezes significa trabalho de software normal (chamar um endpoint), às vezes significa agir como engenheiro de rede (subir e manter um túnel VPN pra dentro de uma infraestrutura que ninguém do lado do cliente consegue explicar por completo), e às vezes significa agir como arqueólogo de dado (reconstituir o que as próprias telas de um sistema legado de fato mostram, quando o sistema em si não conta)

Arquitetura

A organização de um cliente vive como uma árvore autorreferente de linhas Company: uma holding, suas subsidiárias e as unidades de negócio delas são todas o mesmo tipo de registro, distinguidas só por um campo de categoria e um ponteiro de pai, então o mesmo schema atende um cliente com uma entidade legal só e um cliente com um grupo de quatro níveis, sem nenhum caso especial. Usuários se ligam a uma ou mais empresas por uma tabela de junção, e o acesso de um usuário comum fica estritamente restrito às empresas às quais ele de fato está ligado. Um papel fica inteiramente fora desse escopo: um administrador cross-company que vê a estrutura de todo cliente de uma vez, o papel que um consultor gerenciando várias relações de cliente de fato usa no dia a dia.

Debaixo de toda empresa fica um conjunto de Areas, e debaixo de toda área um conjunto de Indicators, cada um com um peso e uma de cinco fórmulas de pontuação (uma razão crescente simples, uma decrescente, uma variante de economia em despesas, e duas fórmulas de desvio pra estourar ou ficar abaixo de uma meta). O valor mensal de um indicador pode chegar de um de dois jeitos, rastreado por uma única flag is_manual: alguém digita no dashboard na mão, ou é escrito por qualquer mecanismo de integração que aquele cliente específico usa. Os dois caminhos alimentam exatamente o mesmo motor de fórmula lá na frente, então a plataforma nunca precisa saber nem se importar, na hora de pontuar, de onde um número veio originalmente.

Esse segundo caminho, a própria integração, é onde mora a maior parte do custo real de engenharia, e são três mecanismos separados, escolhidos por cliente conforme o que os próprios sistemas daquele cliente de fato permitem que um terceiro faça. Onde um cliente expõe uma API, a integração é uma chamada agendada direta. Onde um cliente permite acesso ao banco mas não tem API, a integração é uma consulta somente leitura rodando por um túnel VPN construído especificamente pra aquele cliente. Onde nenhum dos dois existe, e a própria equipe do cliente só vê número pelas telas do próprio software, a integração é RPA: operando aquele software do jeito que uma pessoa operaria, pra extrair os mesmos números que um operador humano teria digitado na mão de qualquer forma.

Assim que um valor chega, a própria nota de uma área segue uma regra deliberadamente direta: o resultado ponderado só conta alguma coisa se o resultado agregado da área bater 80% da meta; abaixo disso, a nota da área é exatamente zero, em vez de uma fração de um. Esse zero, ou essa nota, sobe pela árvore de empresas do mesmo jeito que a própria estrutura de empresa sobe, então o dashboard de uma holding reflete desempenho real de toda subsidiária debaixo dela, além do que por acaso foi digitado mais recentemente.

Uma meta perdida também não fica só num número seco. Quem é dono de um indicador que ficou abaixo anexa uma justificativa escrita pra aquele mês específico, e pode sustentar com evidência de apoio, um documento, uma captura de tela, uma planilha, enviada e ligada àquele mesmo indicador e mês. Nem a justificativa nem a evidência mudam a nota em si; a fórmula e o piso de 80% rodam exatamente igual dos dois jeitos. O que mudam é o que um revisor vê ao lado de um número vermelho: não só que uma meta foi perdida, mas a explicação específica e datada que o próprio dono da área deu pro motivo, registrada no mesmo mês em que aconteceu em vez de reconstituída de memória depois.

Arquitetura

Metas: how an indicator's value reaches a company's score An indicator's monthly value comes from one of two sources: someone types it in by hand, or it arrives through an integration, an API call, a read-only database query over a VPN, or RPA driving the client's own software, chosen per client depending on what that client's systems allow. Either way, the value is scored against its weight and one of five formulas. That score rolls up into its area, which is zeroed out entirely if it falls short of 80% of target, and areas roll up into a self-referential company tree (holding, subsidiary, business unit). One role sees every company at once; every other role stays scoped to its own. Manual entry typed in by hand Integration API, DB query + VPN, or RPA picked per client's access Indicator weight × one of 5 formulas monthly value, is_manual flag Area zeroes below 80% of target Company self-referential tree holding → subsidiary → unit one role sees every company at once; every other role stays scoped to its own

Decisões

DECISION 01/04 · Um de três caminhos pra entrar, escolhido por cliente

EscolhidoBuscar os números reais de um cliente por qual dos três mecanismos a TI daquele cliente de fato permite: uma chamada de API simples, uma consulta somente leitura no banco por um túnel VPN IPsec, ou RPA operando o próprio software de relatório do cliente quando nem API nem acesso direto ao banco existem

DescartadoExigir que todo cliente exponha o mesmo tipo de acesso (uma API) antes de embarcar, e recusar quem não consegue

A maturidade e a política de TI de cada cliente varia demais na prática: alguns expõem uma API limpa, alguns só permitem VPN mais uma conta de banco somente leitura, e alguns (um hospital, no caso mais difícil até agora) mal conseguem sustentar isso. Recusar um cliente por não ter o tipo certo de acesso significaria perder exatamente o negócio que esse produto existe pra atender

Custo aceitoTrês caminhos de integração inteiramente separados pra construir, testar e manter funcionando, cada um com seu próprio jeito de quebrar: limite de taxa e mudança de schema no caminho de API, manutenção de túnel e credencial no caminho de VPN, fragilidade a qualquer mudança de tela no caminho de RPA

DECISION 02/04 · Hierarquia de empresa como árvore autorreferente

EscolhidoModelar a organização de um cliente como uma única tabela Company com um parent_id apontando pra outra linha da mesma tabela, mais uma categoria (holding, subsidiária, unidade de negócio), em vez de uma tabela própria por nível

DescartadoTabelas separadas pra holding, subsidiária e unidade de negócio, cada uma com o próprio schema e as próprias consultas

Organogramas de cliente de verdade não compartilham um formato só: alguns clientes são uma entidade legal só, outros são estruturas de holding com três ou quatro níveis de profundidade. Um número fixo de níveis embutido no código quebra na primeira vez que a estrutura de um cliente não couber nele

Custo aceitoQualquer consulta que precise de 'toda empresa abaixo dessa' tem que percorrer a árvore recursivamente em vez de rodar um join simples, e nada no nível do banco impede uma empresa de acidentalmente virar sua própria ancestral

DECISION 03/04 · Nota abaixo do piso da meta é zero

EscolhidoA nota total de uma área é exatamente 0 sempre que o resultado agregado dela fica abaixo de 80% da meta, mesmo quando vários indicadores individuais dentro dessa área foram bem

DescartadoDar uma nota proporcional a quão perto a área chegou da meta, então quase bater ainda rende a maior parte dos pontos

Essa nota alimenta decisão de avaliação e de remuneração que a própria gestão do cliente toma sobre o time daquela área. Uma escala graduada borra exatamente a linha que essa decisão precisa; o cliente escolheu um piso explícito em vez disso

Custo aceitoUma área a 79% da meta pontua igual a uma a 10%, o que pode parecer implacável pra um time que errou por muito pouco. Esse é um trade-off deliberado do próprio cliente

DECISION 04/04 · Valor manual e valor integrado dividem um pipeline só

EscolhidoTodo indicador carrega uma única flag is_manual em vez de viver em tabela separada dependendo de onde o valor dele vem: um indicador manual é digitado por uma pessoa no dashboard, um não manual é escrito por qualquer mecanismo de integração que aquele cliente usa, e os dois passam exatamente pela mesma lógica de peso e fórmula depois

DescartadoManter indicador digitado manualmente e indicador vindo de integração em tabelas separadas, cada uma com o próprio código de pontuação

Do ponto de vista da fórmula de pontuação, o valor de um indicador é o valor de um indicador, não importa de onde veio. Uma tabela só significa um motor de fórmula só, uma validação de peso só, e um fechamento mensal só, em vez de manter a mesma lógica duas vezes e as duas cópias sincronizadas pra sempre

Custo aceitoUma escrita ruim de integração parece idêntica a uma entrada manual ruim no nível do banco. Perceber que o feed automatizado de um cliente parou de atualizar depende inteiramente de alguém de fato revisar os números; o schema sozinho não tem como impor isso

Invariantes

  • Um grupo de indicadores irmãos cujos pesos não somam exatamente 100 é detectado e exposto, nunca aceito em silêncio

    Garantido porUser#have_goals_incompleteds? agrupa um conjunto de indicadores por nível, soma o peso de cada grupo e sinaliza qualquer grupo que não feche em exatamente 100

  • O status de admin ou gerente de um papel é calculado pela posição dele na hierarquia, nunca guardado como uma flag independente que poderia dessincronizar

    Garantido porRole#admin? e Role#manager? leem parent_id e label no momento da chamada (um papel raiz que não é 'analista' é gerente; um papel cujo pai é 'admin' é ele mesmo admin) em vez de guardar a resposta em cache em algum lugar

  • Um usuário comum, com escopo de empresa, nunca vê uma empresa pra qual não tem uma linha ativa em company_users

    Garantido porUser#company e User#companies resolvem estritamente pelo join com company_users, filtrado por active: true; a única exceção deliberada é o escopo separado saga_admin, construído especificamente pra cruzar essa fronteira

  • A nota de uma área é o resultado ponderado completo ou exatamente zero, nunca um valor intermediário quando a área fica abaixo da meta

    Garantido porArea#calcular_indicators só calcula total_nota quando total_real bate o piso de premissa de 0.8; caso contrário devolve 0 tanto pra total_nota quanto pra total_geral

  • Um indicador que é filho de outro indicador nunca é obrigado a declarar o próprio peso

    Garantido porvalidates_presence_of :weight, unless: :child? no modelo Indicator, app/models/indicator.rb

O que quebrou

Sintoma
A integração com o hospital, a mais difícil construída até agora, precisou de uma conexão direta com o banco por um túnel VPN IPsec em vez da chamada de API de costume: a própria equipe de rede do hospital nunca conseguiu abrir uma porta pra expor uma API, então um caminho de leitura direto no banco Oracle deles foi a única opção que sobrou
Causa raiz
O hospital não tinha DBA nem engenheiro de rede próprio, então ninguém lá conseguia de fato documentar como a rede ou o banco estavam montados; os dois precisaram ser mapeados de fora, do zero, sem ajuda de quem originalmente construiu os sistemas deles. Pior, assim que a conexão subiu os números não batiam: o software do próprio hospital calculava vários valores na hora, dentro de views do banco, e nunca persistia o resultado calculado como uma coluna comum. Consultar as tabelas de baixo direto, o único caminho que uma conexão externa somente leitura tem, devolvia as entradas brutas desses cálculos em vez dos valores que as telas do próprio hospital de fato mostravam
Correção
O cálculo de cada view afetada foi reconstituído na mão e reescrito como uma query equivalente, conferida campo a campo contra o que o sistema do hospital mostrava pro mesmo período, uma query reescrita e reconferida por dia durante cerca de um mês até toda saída bater exatamente
Prevenção
A topologia de rede e a estrutura do banco, as duas sem nenhuma documentação do lado do hospital no início, agora estão completamente mapeadas e registradas deste lado, pra essa mesma integração não precisar ser reconstituída do zero de novo se algum dia precisar mudar

Resultados

3mecanismos de integração, escolhidos pelo acesso de cada clienterelato direto do autor: API, consulta em banco via VPN, ou RPA; nenhuma das três implementações vive neste repositório
5fórmulas de cálculo suportadasenum Indicator.calculation_type, app/models/indicator.rb: asc, desc, cost_savings, positive_deviation, negative_deviation
80%da meta que uma área precisa bater pra pontuarArea#calcular_indicators, app/models/area.rb: atende_premissa = total_real >= 0.8
35migrations de bancocontagem de arquivos em db/migrate/, 2025-09-01
10modelos de domíniocontagem de arquivos em app/models/, excluindo a classe base ApplicationRecord, 2025-09-01
45componentes Reactcontagem de arquivos em app/javascript/components/, 2025-09-01

Stack completa

Backend

  • Ruby on Rails
  • Devise (autenticação)
  • Pundit (políticas de autorização)

Frontend

  • React
  • Chakra UI
  • Redux Toolkit
  • ApexCharts / Highcharts

Dados

  • PostgreSQL (banco da aplicação)
  • Oracle (somente leitura, via integração com cliente)

Infra

  • Túneis VPN IPsec (por cliente, sob demanda)
  • RPA contra o software do próprio cliente

Tem interesse em um projeto como esse? Entre em contato.

Entrar em contato