Quatro empresas, um só produto.
Este guia mostra como Z Future, Myappnow, MTM e a quarta empresa vão construir a plataforma da FAPESB. O objetivo é que ela tenha um login, uma identidade visual e uma qualidade, sem virar um Frankenstein. Cada regra traz o trecho do edital que a fundamenta, e a trilha de estudo no fim leva do básico ao avançado.
@fapesb/ui.1 · O mapa
O caminho de um projeto, do edital à prestação de contas
Cada etapa é um módulo com dono. O proponente não sabe nem precisa saber que quatro empresas trabalham por trás: para ele, é um sistema só.
Quem faz o quê
- Z Future: submissão, enquadramento e mérito (módulos F1–F6), e o pacote visual
@fapesb/ui. Desafio 5.1.2.1 R1 p.7. - Myappnow: contratação e monitoramento ("Contratos 4.0"). Desafio 5.1.2.2 R1 p.7. Contato: Silvio.
- MTM Tecnologia: prestação de contas e o Keycloak da FAPESB. Responsável pela autenticação: Gustavo.
- Empresa D: provavelmente bolsas, a confirmar. O desafio de bolsas da Rodada 2 "envie dados para o sistema de prestação de contas" R2 p.8-9 · §5.1.4.1.
Por que precisamos de regras comuns
Os sistemas trocam dados e compartilham login, perfis e chaves (CPF, código do edital, número do pedido e do instrumento). Uma mudança feita sozinha quebra outro sistema, duplica cadastro ou obriga o usuário a entrar duas vezes. E o código fica com a FAPESB depois: ela recebe "acesso ao código-fonte, documentação técnica, modelos de IA" R1 p.32 · §23.4. Quatro jeitos diferentes de construir multiplicam o custo de quem vai manter.
2 · A ideia central
Ninguém constrói o sistema inteiro: cada empresa entrega pacotes
Cada empresa trabalha no próprio GitHub ou GitLab e publica o seu módulo como pacote npm num
registry privado, o Verdaccio. Um app hospedeiro, a plataforma-fapesb, instala os pacotes e monta o site
inteiro. As APIs de cada empresa rodam como serviços separados.
O que a sua empresa entrega
- O pacote de front do seu módulo (uma Nuxt Layer), publicado no Verdaccio.
- A API do seu módulo, como imagem de contêiner.
- Os contratos (OpenAPI) das APIs que outros consomem, em
fapesb-contratos. - Testes, documentação e diagramas C4 do seu módulo.
O que a sua empresa não faz
- Tela de login, cadastro ou senha: é o Keycloak.
- Cabeçalho, menu, rodapé, layout geral: vêm do
@fapesb/uie do app hospedeiro. - Domínio, deploy do site e certificado: são do app hospedeiro.
- Acesso ao banco de outra empresa: só por API.
Os pacotes
| Pacote | O que tem | Quem publica |
|---|---|---|
@fapesb/ui | shell (cabeçalho, menu, rodapé, login), modelos de página, ponte Sass do DSGov, textos e glossário | Z Future |
@fapesb/submissao | telas do módulo de submissão (Nuxt Layer) | Z Future |
@fapesb/contratacao | telas de contratação e monitoramento (Nuxt Layer) | Myappnow |
@fapesb/prestacao | telas da prestação de contas (Nuxt Layer) | MTM |
@fapesb/bolsas | telas de bolsas (Nuxt Layer), se confirmado | Empresa D |
@fapesb/contratos | tipos e validações (Zod) gerados dos contratos OpenAPI | CI de fapesb-contratos |
@fapesb/base | validação do token do Keycloak, slug, formato de erro, ID de correlação, logs | a definir |
Passo a passo: publicar o seu módulo
- Crie o pacote no seu próprio Git, a partir do modelo oficial de layer do Nuxt.
# cria um layer com a pasta .playground para rodar sozinho bunx nuxi@latest init -t layer fapesb-submissao-web - Desenvolva isolado.
bun run devsobe o módulo na pasta.playground, com o@fapesb/uiinstalado. Você não precisa do código das outras empresas. - Aponte o escopo
@fapesbpara o Verdaccio. O token fica no cofre do CI, nunca no Git.# bunfig.toml [install.scopes] "@fapesb" = { token = "$FAPESB_NPM_TOKEN", url = "https://pacotes.<domínio-da-plataforma>/" }
- Garanta a qualidade:
bun check,bun test, lint e testes de acessibilidade passam no CI. - Publique por tag. Crie a tag
v0.3.0; o CI rodabun publishe o pacote vai para o Verdaccio. - Peça a atualização no app hospedeiro. Um PR em
plataforma-fapesbsobe a versão nopackage.json. Ele vai para a homologação integrada e, na review da sprint, para produção.
Regras para o pacote do seu módulo (para não brigar com os outros)
| Item | Regra | Exemplo (Submissão) |
|---|---|---|
| Páginas | só debaixo do prefixo do módulo | pages/submissao/** → /submissao/... |
| Componentes | prefixo do módulo | SubmissaoCartaoEdital.vue |
| Composables | prefixo do módulo | useSubmissaoProposta() |
| Configuração | só no seu namespace | runtimeConfig.public.submissao.apiUrl |
Layout, app.vue, CSS global, plugins globais | proibidos: são do app hospedeiro e do @fapesb/ui | — |
| Dependências | nuxt e @fapesb/ui como peerDependencies | "@fapesb/ui": "^1.0.0" |
| Versão | SemVer; quebra de compatibilidade só com RFC aprovada | 1.4.0 → 2.0.0 |
E se alguma empresa preferir Next (React) em vez de Nuxt?
Resposta curta: os pacotes comuns continuam funcionando, mas a montagem muda. Uma Nuxt Layer só entra num app Nuxt. Com Next na mesa, cada módulo vira um app próprio, e um proxy junta todos no mesmo domínio.
| Peça | Todos em Nuxt (proposta) | Nuxt e Next misturados |
|---|---|---|
@fapesb/ui | componentes Vue + Sass | precisa virar Web Components (<fapesb-cabecalho>) + Sass, usados em Vue e React. O DSGov já faz assim: @govbr-ds/webcomponents-vue e -react 2.6.0 |
@fapesb/contratos, @fapesb/base | funcionam | funcionam igual (TypeScript puro, sem framework) |
| Módulos | pacotes (Nuxt Layers) num app hospedeiro | cada módulo é um app (imagem de contêiner). O Nginx encaminha /submissao para um e /contratacao para outro. No Next isso se chama Multi-Zones |
| Sass | sim | sim: o Next suporta Sass nativamente |
| Login | uma sessão | continua um login só (Keycloak), mas trocar de módulo recarrega a página |
| Deploy | 1 site | 1 por módulo |
| Risco de Frankenstein | baixo | médio: o shell é o mesmo, mas navegação, estado e erros são feitos 2 vezes |
Recomendação: um framework só para todos é o caminho mais simples (KISS). Mesmo assim, o shell do @fapesb/ui
(cabeçalho, menu, rodapé e login) nasce em Web Components. É pouca coisa, e com isso o modelo misto fica possível sem refazer o pacote.
A escolha do framework é decisão do comitê (critério A12).
# config.yaml do Verdaccio — quem lê e quem publica packages: '@fapesb/ui': { access: $authenticated, publish: zfuture } '@fapesb/submissao': { access: $authenticated, publish: zfuture } '@fapesb/contratacao': { access: $authenticated, publish: myappnow } '@fapesb/prestacao': { access: $authenticated, publish: mtm } '@fapesb/contratos': { access: $authenticated, publish: ci-contratos } '**': { access: $all, proxy: npmjs } # o resto vem do npm público, com cache uplinks: npmjs: { url: https://registry.npmjs.org/ }
3 · Governança
Autonomia dentro do módulo, acordo nas fronteiras
A regra-mãe é simples. O que só afeta o seu módulo, a sua empresa decide. O que afeta outra empresa passa pelo Comitê de Arquitetura, com uma pessoa de cada empresa.
Quando a mudança vai ao comitê? Basta um critério
| # | A mudança… | Exemplo |
|---|---|---|
| A1 | altera API consumida por outra empresa | mudar o formato do edital enviado à prestação de contas |
| A2 | altera dado enviado ou chave de ligação (CPF, edital, pedido, instrumento) | trocar o número do pedido por um ID interno |
| A3 | altera o catálogo de 38 blocos | enviar um campo novo no bloco 3 |
| A4 | altera o Keycloak (Realm, perfis, claims, sessão) | pedir um perfil transversal novo |
| A5 | renomeia ou remove perfil que outro sistema lê | renomear "solicitante" |
| A6 | cria integração ou muda o tipo dela | passar a enviar em lote noturno |
| A7 | altera status compartilhado | status novo do monitoramento |
| A8 | altera disponibilidade, volume ou prazo | manutenção da API em horário útil |
| A9 | altera dado pessoal que trafega (LGPD) | enviar dados da equipe que hoje não vão |
| A10 | altera a experiência comum (login, sessão, shell visual) | tela de login própria; menu diferente |
| A11 | altera ambiente ou credencial compartilhados | mudar a URL do Keycloak de homologação |
| A12 | sai da base comum | adotar MongoDB; criar componente visual próprio |
O A9 tem base direta no Termo de Outorga, que manda "cumprir o disposto na Lei nº 13.709" (LGPD) R1 p.68 · §6.2.33.1.
O caminho de uma mudança
Comitê
1 titular + 1 suplente por empresa. Decisão padrão: consenso silencioso em 3 dias úteis. Reunião só quando há objeção.Coordenador técnico
Indicado pela FAPESB, sem vínculo com as empresas. Só vota para desempatar. O edital prevê esse interlocutor: grupos "que interagirão diretamente com a FAPESB e com as empresas" R1 p.31 · §22.6.Limite do comitê
Não muda escopo de contrato nem decide negócio. Mudança que afeta o mérito técnico vai à FAPESB: só pode ser implementada "após a aprovação formal" R1 p.31 · §22.3.4 · Identidade
Um login para tudo: o Keycloak da FAPESB
A MTM configura o Realm FAPESB. Cada módulo é um client dele. O Keycloak diz quem é a pessoa e quais perfis ela tem; o que cada perfil pode fazer é decisão de cada módulo.
Obrigações de cada módulo
- Login e logout só pelo Keycloak (OpenID Connect).
- Tratar o batimento: Conferido entra com perfis; Fora da base entra sem perfil; Não se aplica é servidor.
- Atribuir os perfis do próprio client pela Admin API.
- Fazer os controles de acesso da FAPESB, como a restrição de horário.
Na prática, no código
- O botão de login vem pronto no
<FapesbLogin>do@fapesb/ui. - As APIs validam o token com o
@fapesb/base(bibliotecajose). - O serviço de IA em Python não tem usuário: recebe token de serviço.
- Login gov.br, se a FAPESB quiser, entra como federação dentro do Keycloak, sem mexer nos módulos.
Fundamento: as soluções devem "observar os requisitos de segurança, proteção de dados e conformidade tecnológica" R2 p.5-6 · §4.4.
5 · Integração
Contrato antes de código
Módulos de empresas diferentes conversam só por API, com JSON. O contrato é escrito e aprovado antes da implementação, e nenhum módulo acessa o banco de outro. O edital pede "Arquitetura baseada em microsserviços e integração via API RESTful" R1 p.57 · Anexo 3 §2.2.
| Regra | Padrão |
|---|---|
| Chaves de ligação | CPF (só dígitos), código do edital, número do pedido ou do instrumento, como emitidos pela origem |
| Versão | na URL: /api/v1/; quebrar o contrato cria v2 (critério A1) |
| Erro | recusa com descrição no formato RFC 9457 (application/problem+json) e código estável |
| Rastreio | cabeçalho X-Correlation-Id em toda chamada, registrado nos dois lados |
| Datas e dinheiro | ISO 8601 em UTC; valores em texto decimal ("1500000.00"), nunca número com vírgula flutuante |
| Eventos | outbox na mesma transação + webhook assinado no formato CloudEvents (ex.: submissao.proposta.aprovada) |
// Recusa com descrição: o sistema de origem corrige e reenvia { "type": "https://plataforma.fapesb.ba.gov.br/erros/prestacao/campo-fora-do-catalogo", "title": "Campo não previsto no catálogo", "status": 422, "detail": "O bloco 3 não aceita o campo 'patente_depositada'. Atualize o catálogo por RFC (A3).", "codigo": "PRESTACAO_BLOCO_CAMPO_FORA_DO_CATALOGO", "correlation_id": "01JC..." }
6 · Identidade visual
A cara do governo, em Sass, num pacote só
Usamos o Padrão Digital de Governo (DSGov). Ele é escrito em Sass (SCSS), sem Bootstrap nem Tailwind, por isso todo estilo da plataforma é Sass: assim reaproveitamos direto as variáveis, funções e mixins do governo. A acessibilidade é exigência do edital: "Compatibilidade com padrões de acessibilidade e usabilidade institucional (ex: e-MAG)" R1 p.58 · Anexo 3 §2.2.
Usamos do DSGov
- Tokens, grade, fonte Rawline, componentes e modelos de página.
- Mixins e funções Sass, sem copiar valores.
Não usamos
- A barra e a marca do governo federal. O DSGov só é obrigatório para órgãos federais; a FAPESB é estadual e usa a própria marca com a do Governo da Bahia.
Tokens principais
- Primária
#1351B4· sucesso#168821· alerta#FFCD07· erro#E52207 - Espaço em base 8 px:
var(--spacing-scale-2x) - Raios de 4, 8 e 16 px · foco sempre visível
Como se escreve um componente
<style lang="scss" scoped> @use "@fapesb/ui/scss" as govbr; // ponte para os mixins e funções do DSGov .submissao-cartao { padding: var(--spacing-scale-2x); // sempre token, nunca valor solto font-size: var(--font-size-scale-up-01); border: var(--surface-width-sm) solid var(--border-color); &__titulo { font-weight: var(--font-weight-semi-bold); } &:focus-visible { @include govbr.focus; } // foco acessível do próprio DSGov } </style>
@import antigo do Sass, então só a ponte
dentro do @fapesb/ui usa @import e o resto do código usa @use. Os avisos do Sass vinham todos do código do DSGov
e somem com quietDeps. O Stylelint 17 com stylelint-config-standard-scss barra cor hexadecimal solta.7 · Stack
TypeScript em primeiro lugar, Python só para IA
O edital diz que Python é a "principal linguagem para IA e NLP" e pede "Node.js e Vue.js/React.js para frontend" R1 p.57 · Anexo 3 §2.2. O resto da plataforma é TypeScript, de ponta a ponta.
| Camada | Padrão | Por quê |
|---|---|---|
| Runtime | Node 24 LTS em produção | roda TypeScript direto, sem build (testado com 24.22) |
| Ferramenta | Bun: bun install, bun test, bun check, bun publish | rápido; bun check dá os mesmos erros do tsc (testado) |
| Front | Vue 3 + Nuxt 4, módulos como Nuxt Layers | uma montagem só na plataforma-fapesb |
| Estilo | Sass (SCSS) + DSGov + @fapesb/ui | mesma linguagem do design system |
| APIs | Fastify 5 + Zod; Drizzle no banco; pg-boss para filas | tipo e validação de uma só fonte |
| IA e NLP | Python 3.12 + FastAPI; MLflow para versões de modelo | "DVC ou MLFlow" R1 p.58 |
| Banco | PostgreSQL 17 só (JSONB, pgvector, busca textual, fila) | "PostgreSQL (preferencial)" R1 p.57 |
| Alta disponibilidade | Patroni 4.1 + etcd (3 nós) + HAProxy + PgBouncer + pgBackRest | entrega em produção, "TRL 8" R1 p.5 · §4.3 |
// tsconfig.json base — testado com bun check e Node 24 { "compilerOptions": { "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "verbatimModuleSyntax": true, "erasableSyntaxOnly": true, // sem enum: o Node 24 executa o .ts direto "allowImportingTsExtensions": true, "noEmit": true } } // package.json precisa de "type": "module" — sem isso o bun check acusa TS1295 (testado)
Banco com alta disponibilidade
8 · Nomes, URLs e slugs
A mesma coisa tem o mesmo nome em todo lugar
Em time grande, nome é metade da documentação. O código vai para a FAPESB, que vai mantê-lo R1 p.58 · Anexo 3 §2.3.2; nomes iguais em todos os módulos tornam tudo legível para quem chegar depois.
| O quê | Padrão | Exemplo |
|---|---|---|
| Idioma | domínio em português, sem acento nem ç; termos técnicos ficam em inglês | proposta, avaliacao, id, status |
| Repositórios | fapesb-<modulo>-<peca> | fapesb-submissao-web, fapesb-submissao-api |
| Pacotes | @fapesb/<modulo> | @fapesb/contratacao |
| URLs | minúsculas, kebab-case, plural; o 1º nível é o módulo | /submissao/editais/chamada-universal-2026 |
| Slugs | ASCII, até 80 caracteres, não mudam depois de publicados (o antigo responde 301) | "Chamada Universal 2026 — Inovação" → chamada-universal-2026-inovacao |
| API | /api/v1/ + recurso no plural; JSON em snake_case | POST /api/v1/editais/{codigo_edital}/propostas |
| Banco | snake_case, tabelas no plural, criado_em/atualizado_em | submissao.propostas.numero_pedido |
| Eventos | <modulo>.<entidade>.<fato_no_passado> | contratacao.instrumento.assinado |
| Classes CSS | BEM com prefixo do módulo | submissao-formulario__etapa--ativa |
| Variáveis de ambiente | FAPESB_<MODULO>_<NOME> | FAPESB_SUBMISSAO_DATABASE_URL |
| Commits | Conventional Commits, em português | feat(submissao): adiciona enquadramento por CNAE |
| Logs | JSON; proibido logar CPF, token, senha ou conteúdo de proposta | {"nivel":"info","modulo":"submissao","correlation_id":"…"} |
9 · Metodologia
O mesmo ritmo nas quatro empresas
O edital pede "Princípios da metodologia ágil (Scrum)" e "DevOps e CI/CD com versionamento Git" R1 p.57 · Anexo 3 §2.2. Com quatro empresas, o que evita surpresa é a sprint nas mesmas datas e uma demonstração conjunta a cada duas semanas.
Pronto quer dizer
- CI verde:
bun check, lint,bun test, cobertura ≥ 90% no domínio, acessibilidade sem erro. - Revisado por outra pessoa; ADR citado se tocou fronteira.
- Só componentes DSGov e
@fapesb/ui; estilo só com tokens. - Contrato e eventos atualizados; migração reversível.
- Nenhum dado pessoal em log.
Fluxo de código
- Trunk-based: branch de até 2 dias, PR pequeno (≈ 400 linhas),
mainsempre pronta. - Conventional Commits e SemVer: o CHANGELOG sai dos commits.
- ADR para cada decisão que cruza módulos; C4 para os diagramas.
- Defeito entre módulos: issue no módulo suspeito, com o
X-Correlation-Id; resposta em 1 dia útil.
Princípios de código: o básico bem feito
| Princípio | Aqui significa | Como se verifica |
|---|---|---|
| KISS | a solução mais simples que atende a história | revisão: "dá para fazer com menos?" |
| YAGNI | só o que está na sprint; pacote ou serviço novo só por RFC | revisão; RFC |
| DRY com moderação | regra de negócio num lugar só; repetiu 3 vezes, extrai | revisão |
| SOLID: S e D | uma responsabilidade por módulo e função; Keycloak, SEI, LLM e banco atrás de uma interface | testes sem rede |
| Clean Code essencial | nomes do glossário, funções curtas, sem código morto, erro nunca engolido | ESLint complexity ≤ 10; ruff C901 |
| Pastas por assunto | src/propostas/, não controllers/ e services/ | modelo de projeto |
Fica de fora, de propósito: Clean Architecture em camadas, DDD tático completo e padrão de projeto aplicado por obrigação.
10 · Trilha de estudo
Do básico ao avançado, no seu ritmo
Quatro níveis, na ordem em que você vai precisar. Marque o que já estudou: o progresso fica salvo neste navegador. PT indica material em português.
1Fundamentos
Git, pacotes, TypeScript, Scrum e a web acessível0/0
2A nossa stack
Node 24, Bun, Vue, Nuxt Layers, Sass e DSGov, Fastify, PostgreSQL0/0
3Arquitetura e integração
Contratos, login, eventos, diagramas e decisões0/0
4Qualidade, segurança e operação
Acessibilidade, OWASP, testes, carga, LGPD e banco em alta disponibilidade0/0
11 · Para decidir
Decisões e pendências
Propostas para o comitê. Cada uma vira um ADR depois de aprovada.
| # | Decisão | Proposta | Fundamento |
|---|---|---|---|
| D1 | Governança | comitê das 4 empresas, quórum 4/4, consenso silencioso em 3 dias úteis | R1 p.31 · §22.6 |
| D2 | Login | Keycloak da FAPESB (MTM); nada de login próprio | R2 p.5-6 · §4.4 |
| D3 | Integração | OpenAPI antes do código, RFC 9457, versão na URL, Pact | R1 p.57 · Anexo 3 §2.2 |
| D4 | Visual | DSGov + Sass + @fapesb/ui; marca FAPESB e Governo da Bahia | R1 p.58 · Anexo 3 §2.2 |
| D5 | Linguagem | TypeScript primeiro; Python só para IA | R1 p.57 |
| D6 | Ferramentas | Node 24 LTS + Bun (bun check) | R1 p.57 |
| D7 | Front | Vue 3 + Nuxt 4; módulos como Nuxt Layers | R1 p.57 |
| D8 | Banco | só PostgreSQL, com Patroni | R1 p.57 · R1 p.5 · §4.3 |
| D9 | Nomes | repositórios, URLs, slugs, API, banco, código num padrão só | R1 p.58 · §2.3.2 |
| D10 | Método | Scrum sincronizado, trunk-based, Conventional Commits, ADR, C4 | R1 p.57 |
| D11 | Pacotes | cada empresa no seu Git; módulos e peças comuns como pacotes no Verdaccio; app hospedeiro único | R1 p.53 · §1.1(ii) |
| D12 | Aceite da FAPESB | ofício conjunto pedindo aprovação da base comum antes de implementar | R1 p.31 · §22.3 · R1 p.65 · §6.2.2 |
Pendências que precisam de dono
Imediato
- Coordenador técnico indicado pela FAPESB
- ADR-0001: Keycloak (MTM)
- Escopo da Empresa D
- Acesso de todos à pasta de decisões
Em até 3 semanas
- Diagramas C1 e C2 de cada módulo
- Padrão de integração e catálogo de 38 blocos como contrato
- Verdaccio,
fapesb-contratos,fapesb-governancaeplataforma-fapesb - Dono do app hospedeiro
Antes de integrar
- Modelo comum de orçamento (3 módulos)
- Evento "aprovado e finalizado" R1 p.50 · §2.2.1
- Integração com o SEI
- Homologação integrada; onde roda o Patroni
Fontes
- Edital FAPESB nº 027/2025, Rodada 1: PDF oficial (78 p.) · página do edital. As citações "R1 p.N" abrem o PDF na página N.
- Edital FAPESB nº 017/2026, Rodada 2: PDF oficial (100 p.).
- Documentos da MTM Tecnologia, versão 4 (05/11/2026): cadastro e autenticação integrados; visão geral dos dados da prestação de contas; estrutura de formulários dos editais.
- Portaria SECOM-MCOM nº 7.508/2022 (Padrão Digital de Governo): legislação do gov.br · Manual de Aplicação de Marcas do Governo da Bahia.