[
  {
    "chave": "CS-168",
    "territorio": "frontend",
    "titulo": "🩸 O ECRÃ DAS QUESTÕES PASSA AO MODELO DA MARCA DE MINUTO LIVRE **SEM PERDER** O ERRO POR CAMPO NEM O PAINEL DO MATERIAL — ⛔ não é escolher um lado e ⛔ não é união de texto: é o `main` e o `epic/CS-32` a caberem no MESMO ecrã. É o ÚNICO motivo que falta para o CS-32 fundir na pasta das questões. ⟹ O MODELO VENCEDOR É O DA MARCA e ⛔ NÃO se reabre: decisão do dono de 29/09 (pedido 67), com o exemplo DELE «120 min com perguntas aos 30, 75 e 118». O `Record<bloco, EstadoDoLugar>` do `main` (`[aulaId]/page.tsx:477`) ⛔ NÃO TEM onde guardar «uma pergunta no minuto 37» ⟹ é o superseded.\n\n🩸 O MECANISMO QUE TORNA O CRITÉRIO (7) ALCANÇÁVEL, e sem ele o card é impossível — MEDIDO, ⛔ não suposto: ⛔ NÃO se funde `main` dentro do epic (isso traria os outros 17 conflitos, que ⛔ NÃO são deste card) e ⛔ NÃO se faz ramo novo a partir do `main` (criava uma TERCEIRA versão do ecrã, o frankenstein que o integrador recusou escrever à mão). ⟹ FAZ-SE DENTRO DE `epic/CS-32`, e a regra é esta: **cada um dos 5 ficheiros tem de passar a CONTER AS ALTERAÇÕES DO `main` TAL E QUAL, mais o modelo da marca em regiões OUTRAS.** O `git merge-tree` casa por HUNK: onde os dois lados fizerem a MESMA alteração ⟹ ⛔ sem conflito; onde só o epic alterar ⟹ toma-se o epic. É por isso que o critério (7) se cumpre tocando SÓ nestes 5 ficheiros. ⟹ Um hunk do `main` reescrito «à minha maneira» ⛔ NÃO funde, e o card volta.\n\n=== AS TRÊS CORRECÇÕES DO TEXTO, TODAS RE-MEDIDAS POR MIM HOJE (04/10) E TODAS CONFIRMADAS\n(1) 🩸 O NÚMERO É **400**, ⛔ NÃO 422. `questao.servico.ts:113-118` do epic lança `BadRequestException({codigo: MARCA_FORA_DA_AULA, duracaoMin})` e `questao.tipos.ts:24-26` di-lo à letra: «sai em **400** (`BadRequestException`), ⛔ não no 422 de `valida.recusar()`». ⚠️ E a precisão que falta ao texto e que o QA precisa: **o 422 CONTINUA A EXISTIR e é de OUTRA coisa** — enunciado e alternativas continuam a sair em 422 por `valida.recusar()` («os ensaios que já existem dependem disso»). ⟹ O erro por campo do critério (3) é 422; a marca fora da aula é 400. ⛔ Não se troca um pelo outro.\n(2) 🩸 QUEM ESCREVE É O **ESPECIALISTA RESPONSÁVEL**, ⛔ NÃO a secretaria. Medido nos DOIS lados e **IDÊNTICO** ⟹ ⛔ NÃO é conflito deste card e ⛔ não se toca no controlador: `questao.controlador.ts:29` `@Papeis(Papel.ESPECIALISTA)` + `@Post()` para ESCREVER; `:45` `@Papeis(Papel.ESPECIALISTA, Papel.SECRETARIA)` + `@Get()` para LER. A secretaria que tente gravar leva 403, e isso está CERTO (regra 54: as questões são escritas pelo especialista; regra 112: a secretaria VÊ tudo mas ⛔ não faz o acto de outro papel). ⟹ O QA entra como ESPECIALISTA RESPONSÁVEL para provar o gravar, e como SECRETARIA para provar que a lista continua a ler. Sem isto o QA reprova o card com um 403 que é a regra a funcionar.\n(3) 🩸 SÃO **4 + 1 = 5** FICHEIROS, e o território são 4190 linhas do `main` contra 3922 do epic. Re-medido hoje: `git merge-tree --write-tree main epic/CS-32` dá **22 ficheiros em conflito** — `[aulaId]/page.tsx` (main 865 / epic 720) · `dados.ts` (542/298) · `page.tsx` (344/259) · `questoes.module.css` (888/529) · `e2e/questoes.spec.ts` (1551/2116). ⚠️ Os OUTROS são **17**, ⛔ não 18 (o texto conta o `questoes.spec.ts` duas vezes): 22 − 5 = 17. ⛔ Não contam contra este card.\n\n=== 🩸 DUAS MEDIÇÕES MINHAS QUE O TEXTO ⛔ NÃO TEM E QUE MUDAM O CARD\n(A) **O PAINEL DO MATERIAL ⛔ NÃO É SÓ «1 IMPORT E 4 CHAMADAS» — SÃO TAMBÉM 42 CLASSES DE CSS, E É AQUI QUE ELE SE PERDE EM SILÊNCIO.** Medido: `PainelDeMaterial.tsx` usa **44 classes** de `questoes.module.css`, das quais **42 começam por `material*` e existem SÓ no `main`** (css do main: 88 classes · do epic: 53 · partilhadas: 42 · só no main: 46 · só no epic: 11). ⟹ Quem resolver o css «tomando o lado do epic» (o ficheiro MENOR, 529 linhas) deixa o painel a aparecer 5 vezes, com TODOS os greps verdes, e **COMPLETAMENTE SEM ESTILO**. ⟹ O css resolvido é a **UNIÃO: 99 classes** (42 partilhadas + 46 do main + 11 do epic), e ⛔ nenhuma das 11 do epic se perde (`campoDaMarca`, `compositorEsqueleto`, `enunciado`, `grupo`, `grupoTitulo`, `alternativaLida`, `alternativaOrdem`, `alternativaTexto`, `alternativasLidas`, `esqueletoLugar`, `rodapeDoCartao`). ⚠️ E ⛔ não se perde `escolhaErro`, que é do main e é o erro de GRUPO das alternativas («nenhuma está marcada como correcta»).\n(B) **4 FICHEIROS DO MATERIAL ⛔ NÃO EXISTEM NO RAMO DO EPIC, E SEM ELES O RAMO ⛔ NÃO COMPILA.** `PainelDeMaterial.tsx`, `material-api.ts`, `material-regras.ts` e `material-textos.ts` são adições SÓ DO MAIN (o epic ⛔ nunca lhes tocou ⟹ ⛔ não conflituam, e entram no merge inteiras). ⟹ Este card **traz os 4 para dentro do epic, verbatim**, senão o `import { PainelDeMaterial } from '../PainelDeMaterial'` do critério (4) ⛔ não resolve. MEDI AS DEPENDÊNCIAS DELES E AS 3 EXISTEM NO EPIC ⟹ o card é viável sem sair da cerca: `@/componentes/base` já exporta `Botao`, `ChipDeEstado`, `Dialogo`, `EstadoDoChip` e `Icone`; `@/componentes/casca/contexto` já tem `useSessao`; `@/lib/api` existe. ⟹ Traz-se também `e2e/material-especialista.spec.ts` (13 casos, ⛔ inexistente no epic), que é a ÚNICA prova a sério do critério (4): ele faz `goto('/especialista/questoes/<aula>')` (`:517`) e exercita o painel **ATRAVÉS DESTE ECRÃ**.\n⚠️ E O LIMITE HONESTO DO «PONTA A PONTA» DO CRITÉRIO (4), medido e dito em voz alta para ⛔ ninguém o descobrir no QA: **o backend do material ⛔ NÃO EXISTE no ramo do epic** — 11 ficheiros em `backend/src/modules/material/` e o `model Material` do `schema.prisma` são SÓ do `main`. ⟹ Neste card o painel prova-se contra o CONTRATO, pela e2e hermética (`page.route` em `**/api/v1/**`), que é o que a casa já usa. O «ponta a ponta com o servidor verdadeiro» chega com o merge do integrador, que traz os 11 ficheiros SEM conflito (o epic ⛔ nunca lhes tocou) — e ⛔ NÃO é trabalho deste card: `schema.prisma` é um dos 17 e ⛔ não se toca.\n\n=== 🩸 O TRABALHO REAL É O RECHAVEAMENTO, E ⛔ NÃO O PAINEL\nMedido: no `main` o erro por campo está chaveado PELO NÚMERO DO BLOCO, de ponta a ponta — `errosDoRascunho(numero, rascunho)` (`dados.ts:504`), `primeiroCampoEmErro(numero, erros)` (`:536`), e os ids dos campos saem de `nomeDoEnunciado(numero)` → `enunciado-${numero}` (`:523`) e `nomeDaAlternativa(numero, ordem)` → `alternativa-${numero}-${ordem}` (`:527`), que é por onde o foco encontra o primeiro erro (`document.getElementById(primeiro)?.focus()`, `[aulaId]/page.tsx:632`). As frases também: «A pergunta **do bloco 1** ficou em branco». ⟹ **O CARD RECHAVEIA ISTO DO BLOCO PARA A IDENTIDADE DA QUESTÃO SOB MARCA LIVRE.** ⚠️ E a identidade ⛔ NÃO é «a marca» sozinha: medido, `marcaMinuto` é `number | null` e `null` quer dizer LEGADO (`dados.ts:89 marcaDe`, `:113 legadasEmOrdem`) ⟹ duas questões legadas têm a MESMA marca nula, e `QuestaoDoServidor.bloco` ainda vem do servidor (`:42`). A chave é escolha do especificador; a TRAVA é observável e está no aceite: **o erro de uma questão ⛔ nunca acende o campo de outra.** ⚠️ E ⛔ não se repõe o denominador: o total do epic ⛔ NÃO tem «de 4» (R10, `dados.ts:180`) porque sob marca livre ⛔ não há denominador — `contagem(escritas)` leva UM argumento, ⛔ não dois.\n⚠️ AS FRASES (aviso e erros) SÃO FORMA DO ESPECIFICADOR, ⛔ NÃO DO DONO, e ⛔ não é palpite: o pedido 67 fecha-o à letra («a frase literal do aviso continua a ser forma do especificador, ⛔ não subiu ao dono e ⛔ não devia ter subido»). O que ⛔ não é negociável é a regra 85, cláusula CAMPOS: «O TEXTO DE ERRO DIZ O QUE ACONTECEU E O QUE FAZER — ⛔ nunca 'campo inválido'».\n\n=== OS CASOS DE ENSAIO SÃO **NOME EXACTO**, porque os aceites casam por `-g`. ⛔ NENHUM CASO SE APAGA: o epic tem 22 e o main 14; os dois do main que ⛔ NÃO têm equivalente no epic ENTRAM («o especialista escreve a questao pelo Campo do sistema…» = o erro por campo, e «o esqueleto da lista das aulas…»), e nascem 4 novos ⟹ o ficheiro acaba com **≥24**. Os 4 novos, com o nome à letra: `os minutos 30 75 e 118 gravam e o ecra volta a mostra-los nesses minutos depois de recarregar` · `a marca 0 e a marca 121 numa aula de 120 min levam 400 MARCA_FORA_DA_AULA e a frase diz o que aconteceu e o que fazer` · `enunciado vazio e sem alternativa correcta mostram os dois erros de uma vez e o foco salta para o primeiro campo em erro` · `duas questoes nos minutos 30 e 75 no mesmo ecra: o erro de uma nao acende o campo da outra`. E **7 CAPTURAS `q46-*.png`**, uma por estado e todas DISTINTAS (⛔ nunca por curl: foi curl que deixou passar três medições erradas na demanda 19): `q46-marca-grava` · `q46-marca-recusada-400` · `q46-erro-por-campo` · `q46-duas-marcas-sem-contaminacao` · `q46-painel-material` · `q46-lista-secretaria` · `q46-lista-especialista`.\n\n=== A LISTA IRMÃ (`questoes/page.tsx`) GANHA OS DOIS PAPÉIS POR CIMA DO LADO DO EPIC — ⛔ não é arquitectura, é acréscimo. Medido: `main` tem `papel` em **9 linhas** e `tituloDaLista` em **2**; o epic tem **0 e 0** ⟹ sem isto o porte faz a SECRETARIA PERDER A LISTA. Voltam `tituloDaLista`/`explicacaoDaLista`/`fraseSemAulas`/`aulasQueVe`/`resumoDaAula` e as constantes `TITULO_DA_LISTA_DA_REDE`/`FRASE_SEM_AULAS_DA_REDE`/`EXPLICACAO_DA_LISTA_DA_REDE` (regra 112: a secretaria vê as aulas da rede com o nome do responsável de cada uma; o especialista vê as suas).\n\n⛔ O QUE ESTE CARD ⛔ NÃO FAZ, e ⛔ não se alarga: ⛔ não toca nos outros **17** ficheiros em conflito do CS-32 (são resolução de integração) · ⛔ não toca em `backend/` NENHUM — o servidor da marca JÁ está certo e ⛔ não está em conflito (medido: o `main` ⛔ nunca tocou `backend/src/modules/questao/`, só o epic ⟹ auto-funde sem perda) · ⛔ não toca `schema.prisma`, `participacao`, `aula`, `leitor`, `secretaria/aulas`, `ao-vivo`, `gravacao` · ⛔ não toca `componentes/base/` nem `lib/navegacao.ts` · ⛔ não toca o portão do PUBLICAR nem o aviso do trecho final (são cards do CS-32) · ⛔ não toca ranking nem percurso do médico · ⛔ NENHUM ficheiro de `_produto/design/` nem `tokens.css` · ⛔ não traz `material-medico.spec.ts` (medido: visita `/ao-vivo`, `/gravacao` e `/calendario`, ⛔ nunca o ecrã das questões) · e ⛔ NÃO dispensa o aceite do dono da regra 298 no merge que vem a seguir (`test/permanente-minutos.e2e-spec.ts` ⛔ não devolve zero), que é do INTEGRADOR e ⛔ não deste card.",
    "escopo_paths": [
      "frontend/src/app/especialista/questoes/[aulaId]/page.tsx",
      "frontend/src/app/especialista/questoes/dados.ts",
      "frontend/src/app/especialista/questoes/page.tsx",
      "frontend/src/app/especialista/questoes/questoes.module.css",
      "frontend/src/app/especialista/questoes/PainelDeMaterial.tsx",
      "frontend/src/app/especialista/questoes/material-api.ts",
      "frontend/src/app/especialista/questoes/material-regras.ts",
      "frontend/src/app/especialista/questoes/material-textos.ts",
      "frontend/e2e/questoes.spec.ts",
      "frontend/e2e/material-especialista.spec.ts"
    ],
    "aceites": [
      "cd frontend && grep -qw marcaDe src/app/especialista/questoes/dados.ts && grep -qw lerMarca src/app/especialista/questoes/dados.ts && grep -qw errosDoRascunho src/app/especialista/questoes/dados.ts && grep -qw primeiroCampoEmErro src/app/especialista/questoes/dados.ts && grep -qw nomeDoEnunciado src/app/especialista/questoes/dados.ts && grep -qw nomeDaAlternativa src/app/especialista/questoes/dados.ts && grep -qw tituloDaLista src/app/especialista/questoes/dados.ts && grep -qw TITULO_DA_LISTA_DA_REDE src/app/especialista/questoes/dados.ts",
      "cd frontend && grep -qw marcaMinuto 'src/app/especialista/questoes/[aulaId]/page.tsx' && grep -qw errosDoRascunho 'src/app/especialista/questoes/[aulaId]/page.tsx' && grep -qw primeiroCampoEmErro 'src/app/especialista/questoes/[aulaId]/page.tsx' && grep -q campo-erro 'src/app/especialista/questoes/[aulaId]/page.tsx' && grep -q escolha-erro 'src/app/especialista/questoes/[aulaId]/page.tsx'",
      "cd frontend && test $(grep -cw PainelDeMaterial 'src/app/especialista/questoes/[aulaId]/page.tsx') -eq 5",
      "cd frontend && test -f src/app/especialista/questoes/PainelDeMaterial.tsx && for c in $(grep -o 'estilos\\.[A-Za-z0-9_]*' src/app/especialista/questoes/PainelDeMaterial.tsx | sed 's/estilos\\.//' | sort -u); do grep -qE \"^\\.$c[ ,{:]\" src/app/especialista/questoes/questoes.module.css || exit 1; done",
      "cd frontend && test $(grep -cE '^\\.material' src/app/especialista/questoes/questoes.module.css) -ge 42 && grep -qE '^\\.escolhaErro[ ,{:]' src/app/especialista/questoes/questoes.module.css && grep -qE '^\\.campoDaMarca[ ,{:]' src/app/especialista/questoes/questoes.module.css && grep -qE '^\\.enunciado[ ,{:]' src/app/especialista/questoes/questoes.module.css && grep -qE '^\\.erroDoLugar[ ,{:]' src/app/especialista/questoes/questoes.module.css && test $(grep -cE '^\\.[A-Za-z0-9_-]+' src/app/especialista/questoes/questoes.module.css) -ge 99",
      "cd frontend && test $(grep -cw papel src/app/especialista/questoes/page.tsx) -ge 9 && test $(grep -cw tituloDaLista src/app/especialista/questoes/page.tsx) -eq 2",
      "cd frontend && test $(grep -cE '^[[:space:]]*test\\(' e2e/questoes.spec.ts) -ge 24",
      "cd frontend && npx playwright test e2e/questoes.spec.ts -g 'os minutos 30 75 e 118 gravam e o ecra volta a mostra-los nesses minutos depois de recarregar' --reporter=line",
      "cd frontend && npx playwright test e2e/questoes.spec.ts -g 'a marca 0 e a marca 121 numa aula de 120 min levam 400 MARCA_FORA_DA_AULA e a frase diz o que aconteceu e o que fazer' --reporter=line",
      "cd frontend && npx playwright test e2e/questoes.spec.ts -g 'enunciado vazio e sem alternativa correcta mostram os dois erros de uma vez e o foco salta para o primeiro campo em erro' --reporter=line",
      "cd frontend && npx playwright test e2e/questoes.spec.ts -g 'duas questoes nos minutos 30 e 75 no mesmo ecra: o erro de uma nao acende o campo da outra' --reporter=line",
      "cd frontend && npx playwright test e2e/material-especialista.spec.ts --reporter=line",
      "cd frontend && rm -f e2e/capturas/q46-*.png && CAPTURAS=1 npx playwright test e2e/questoes.spec.ts e2e/material-especialista.spec.ts --reporter=line && test $(ls e2e/capturas/q46-*.png | wc -l) -eq 7 && test $(md5sum e2e/capturas/q46-*.png | cut -c1-32 | sort -u | wc -l) -eq 7",
      "! git merge-tree --write-tree main HEAD 2>&1 | grep -qE 'CONFLICT.*(especialista/questoes|e2e/questoes\\.spec\\.ts)'",
      "cd frontend && ! grep -qE '^export function (blocosDaDuracao|numerosDosBlocos|horaDoBloco|horaDeAbertura)\\(' src/app/especialista/questoes/dados.ts",
      "cd frontend && grep -qE '^export function contagem\\(escritas: number\\)' src/app/especialista/questoes/dados.ts",
      "cd frontend && test $(grep -cw marcaMinuto e2e/questoes.spec.ts) -ge 16",
      "cd frontend && npx playwright test e2e/questoes.spec.ts --reporter=line",
      "cd frontend && npx next build",
      "cd frontend && npx playwright test e2e/alcance.spec.ts --reporter=line"
    ],
    "depende_de": []
  }
]