# D29 — O RANKING INTEIRO · enquadramento

**Epic `CS-120` · 26 cards · 13 backend · 13 frontend · 554 aceites · 1 raiz (`CS-121`) · profundidade 15 · 0 ciclos.**
Cards em `ENQUADRAMENTO-D29.cards.json`, gerados e conferidos por `ENQUADRAR-D29.py`.

## A chave ⛔ não foi improvisada
O rastreador deste produto ⛔ não tem espaço de chaves próprio (medido no pedido 35 e repetido no
D40): as chaves `CS-<n>` vivem no registo da própria casa. **Remedido 30/09 antes de escrever:**
`select max(...) from card` ⟹ **119**; `from demanda` ⟹ 108; a união das duas é **contígua de 1 a
119, sem buracos**. ⟹ **epic `CS-120`, cards `CS-121`..`CS-146`**. ⛔ Nenhuma chave inventada,
⛔ nenhuma reutilizada. 🩸 Quem enquadrar em paralelo nesta casa **remede antes de escrever** — o
D40 levou `UNIQUE constraint failed: demanda.chave` por não o ter feito.

## 🩸 DUAS PRÉ-CONDIÇÕES DE PUBLICAÇÃO, E SÃO ACTO DO DONO (`fab publicar`), ⛔ NÃO MINHAS
Este ranking **soma factos que nascem noutras demandas e ⛔ não os refaz** — é a decisão do quadro.
Medi onde esses factos estão hoje, e ⛔ não estão em `main`:

| facto | onde vive | estado medido |
|---|---|---|
| `model SegmentoValidado` + `enum CaminhoDeValidacao` | `epic/CS-32` (demanda 23) | 9 cards **todos `aprovado`**, demanda em **`aprovada`** e ⛔ **não `publicada`** |
| `modules/curso` + `modules/inscricao` (`compareceu`) | `epic/CS-94` (demanda 39) | `CS-95` **`em_curso`**, `CS-96` **`a_espera_do_pai`** |

E medi o mecanismo: `bin/motorbase.py:217` faz **`git rebase main`** no worktree do epic ⟹ o chão
deste epic é `main`, e `main` tem **13 models** e **zero** ocorrências de `segmento` no schema.

⟹ **`CS-124` exige `epic/CS-32` publicado** e **`CS-125` exige `epic/CS-94` publicado**, e está
escrito no título de cada um, em maiúsculas, para chegar ao seu aviso. Sem o merge, o dev devolve-os
por chão que ⛔ não existe. **Os outros 24 cards ⛔ não dependem disto** — foi por isso que o
lançamento aponta o facto por `factoTipo`+`factoId` **em TEXTO** (ver abaixo): é o que faz este epic
compilar sozinho.

## O que fez o `CS-121` ser UM card e ⛔ não seis
Seis models novos ⟹ a tentação era seis cards de esquema. **Medido o custo:** dois cards que
apendam ao fim de `schema.prisma` a partir do mesmo HEAD do epic conflituam ao integrar, e isso é a
cicatriz do `MSS-180` (**6 voltas** com «conflito ao integrar no epic», `motorbase.py:203`).
⟹ **um card, uma migração, e os 12 cards seguintes correm em PARALELO.** É também a leitura do
critério (1), que nomeia «⛔ nenhum Ponto, Certificado ou Obrigatoriedade» como **um** buraco.

## As quatro decisões que tomei e que são minhas — nomeadas para o senhor corrigir no aceite
1. **A prova ao lado é `factoTipo` + `factoId` em TEXTO, ⛔ sem relação Prisma.** ⛔ Não inventei o
   padrão: está **duas vezes** neste schema, com o motivo escrito pelo autor —
   `RespostaQuestao.alternativaEscolhida` («⛔ nao um apontador vivo») e `Parametro.atualizadoPorId`
   («⛔ nao fixo aqui um nome de modelo que ⛔ nao e' meu»). Ganha-se o que a tabela acima diz: o epic
   compila sem os dois epics não publicados. E `@@unique([factoTipo, factoId])` **do banco** dá de
   graça «a gravada permanente pontua SÓ NA PRIMEIRA VEZ» (resposta 90).
2. **O `pontos_por_minuto_validado` é chave nova de `parametro`; os TIPOS de certificado são
   TABELA.** ⛔ Não é incoerência: `chaves.ts:7` é um `as const` de literais que o código valida —
   nele a secretaria ⛔ **não pode criar** um tipo, e a regra 53 diz que ela cria e tira tipos sem
   deploy. ⟹ lista dinâmica pede tabela, no molde medido do `Nucleo` (`schema.prisma:510`).
3. **«2 de 6» lê-se como CONTAGEM de aulas por trimestre, ⛔ não como um campo `obrigatoria` na
   `Aula`.** É o caminho reversível: de uma contagem chega-se a uma lista; de uma lista marcada ⛔ não
   se volta atrás sem tocar em toda a `Aula`. (`CS-128`.)
4. **O armazém do certificado é PRÓPRIO e ⛔ não é o da marca.** Medido: `marca.tipos.ts:11`
   `TECTO_BYTES = 524288` e `FORMATOS = ['svg','png','webp']` — 🩸 **um certificado digitalizado é
   PDF ou JPEG e ⛔ não cabe nem no tecto nem na lista**, e foi por isso que o `CS-67` fez
   `modules/documento` para o protocolo. Segue-se o **molde** de `modules/marca` (que está em `main`)
   e ⛔ não se reusa `modules/documento` (epic `CS-65`, ⛔ não publicado — seria uma **terceira**
   pré-condição). (`CS-127`.)

## A peça em espera é UMA, e a espera está declarada no grafo
O pedido **117** (top 5) decide **o que o servidor devolve a um médico**, e isso é guarda e corpo
diferentes. ⟹ **`CS-133`** (backend) é o card que ⛔ não se especifica antes da resposta; **`CS-146`**
(o ecrã `/pontuacao`) **depende dele** e por isso espera pelo mecanismo, ⛔ não por um aviso.
⚠️ E o critério (5) fica provado **hoje**: o `GET /ranking` do `CS-131` dá **403 ao médico**, contado
lá ⟹ ⛔ nenhum nome de colega chega a um médico antes da resposta.

## 🩸 O FRONTEND É UMA CADEIA, E A CAUSA É MEDIDA — ⛔ NÃO É ZELO MEU
`frontend/src/lib/navegacao.ts` é a **fonte única** das portas (CS-29, R1) e
`navegacao.spec.ts:570` exige **igualdade exacta nos dois sentidos**:
`[...portas, ...órfãs].sort() === todas as page.tsx do disco`. ⟹ **cada página nova obriga a editar
aquele ficheiro**, e ⛔ não há como pré-declarar (uma porta sem página parte o teste no próprio card).
Medido também: **outros quatro epics já têm `navegacao.ts` na cerca** — `CS-92` (epic CS-88),
`CS-68` (CS-65), `CS-98` (CS-94), `CS-115` (CS-108).
⟹ Os 11 cards de ecrã que criam página estão **serializados de propósito**. Profundidade 15.
**Os dois que ⛔ não criam página (`CS-144`, `CS-145`) ⛔ não lhe tocam e correm em paralelo** — e cada
um tem um aceite que o prova: `git diff --name-only main -- src/lib/navegacao.ts` tem de dar **0**.

⚠️ **OBSERVAÇÃO, ⛔ não achado** (⛔ não fui impedido de continuar): achatar isto pedia partir a fonte
única num ficheiro por assunto. ⛔ **Não o proponho como card**: quatro cards de outros epics têm
aquele ficheiro na cerca AGORA, e o refactor rebentava-lhes o rebase. Fica escrito para quando esses
quatro estiverem publicados.

⚠️ E a segunda invariante que parte se for esquecida, e está no título do `CS-134` e do `CS-146`:
`navegacao.spec.ts:597` exige `oferecidas[0] === INICIO[papel]` ⟹ **«Calendário» tem de continuar a
ser a PRIMEIRA porta do médico**, porque `inicios.ts:17` diz `MEDICO: '/calendario'` — e ⛔ não se
toca em `inicios.ts` (⛔ nenhum ecrã velho morre, decisão (e)).

## O fuso, que o backend ⛔ não tinha
O trimestre é **CIVIL e igual para a rede toda**, e por isso precisa de fuso. Medido: `America/Bahia`
está em **três ficheiros do frontend** (`app/ao-vivo/dados.ts:50` `FUSO_DA_AULA`,
`componentes/chat/dados.ts:42`, `especialista/questoes/dados.ts:196`) e o **backend tem ZERO
ocorrências de fuso**. ⟹ `src/modules/ponto/trimestre.ts`, função **pura**, com spec própria: 31/12
23:00 em Salvador é 01/01 02:00 em UTC, e sem isto o limite do critério (3) cai no trimestre errado.

## As três travas, e onde cada uma se mede
- **(9) ⛔ nenhum castigo na cara** — `CS-123` prova pela positiva no servidor (errar ⛔ não gera linha
  negativa, ⛔ não anula, ⛔ não desce o total); nos 13 cards de ecrã o bloco `SEIS` de aceites leva
  «descer é ▼ `onSurfaceVariant` COM o número escrito» e há grep contra `BA1A1A`/`--cor-error` nos
  ecrãs onde o vermelho ⛔ nunca entra.
- **(10) a cerca** — `CS-124` tem aceite de que ⛔ não toca `minutosValidados`; `CS-132` tem
  `! grep -rqE 'prisma\..*\.(create|update|...)'` no módulo inteiro (o percurso **LÊ**); oito cards
  de backend têm `git diff --name-only main -- prisma/ | wc -l | grep -qx 0` (⛔ zero migrações fora
  do `CS-121` e do `CS-124`); os 13 de ecrã têm grep contra `_produto/design` e `tokens.css`;
  `CS-127` e `CS-141` têm grep contra `brasao|assinatura|emitir`; `CS-130` e `CS-145` contra
  `email|nodemailer|aviso`. **⛔ Nenhum teste se constrói** (regra 125): ⛔ não há card de teste, e os
  `.spec` que se tocam são os que **já existem** ou o do próprio card.
- **(11) os seis estados e as três provas** — todo card de ecrã leva os oito aceites do bloco `SEIS`
  (com o **parcial** a mostrar as três coisas e o total **ao lado**) e **fecha com a corrida das
  três provas juntas**: `npx playwright test e2e/navegacao.spec.ts e2e/alcance.spec.ts
  e2e/botoes.spec.ts`. ⛔ Nenhum contorno: as três estão na cerca de quem as muda.

## O que ⛔ não entrou, e fica nomeado
O **certificado que a plataforma emite** (brasão e assinatura da Secretaria) — falta o ACTO que lhe
dá direito, é regra de negócio dele e é demanda própria com a sua pergunta. A **cara dos ecrãs**
(demanda 30). **Artigos, gravadas permanentes, protocolos e cursos** (CS-82, CS-88, CS-65, CS-94, já
enquadradas). **Testes** (regra 125, e a 121 §(5) explica porquê). **Aviso ou email novo** (regra
133). A **fila de espera** do curso (epic CS-94).

## ⛔ Nada foi perguntado ao dono neste enquadramento
A fila dele tem 3 itens (115, 116, 117) e o quadro já fechou o escopo com «⛔ nada aqui é pergunta
nova · o TOP 5 é a ÚNICA peça em espera». Conferi as regras uma a uma: a **data de corte** que a
regra 117 deixara aberta foi **dissolvida** pela 118 (trimestre civil ⟹ ⛔ não há corte a escolher) e
o «traço e ⛔ não zero» foi **respondido** pela 118 §1. O que sobrou era reversível, e está nas quatro
decisões nomeadas acima — para ele corrigir **no aceite**, que é onde custa menos.
