# Demanda 32 — O PERFIL DO MÉDICO · enquadramento

**11 cards · 6 backend · 5 frontend · 2 raízes independentes · profundidade máxima 6 · 0 ciclos ·
0 órfãos · 0 cercas largas.** Os cards estão em `ENQUADRAMENTO-D32.cards.json`; quem os entrega é
`ENQUADRAR-D32.py --entregar`, que lê a chave NO INSTANTE DA ENTREGA.

---

## 1 · 🩸 A MEDIÇÃO QUE MUDA O ENQUADRAMENTO: A FOTO ⛔ NÃO ESTÁ RETIDA — O DONO JÁ RESPONDEU

O texto da demanda diz que **só a foto** fica retida, à espera do **pedido 107** («quem pode ver a
foto?»), e que o card dela espera. **Medido na base da fábrica, e ⛔ não adivinhado:**

| | quando | o quê |
|---|---|---|
| **regra 134** | `2026-09-30 16:24:24`, `origem=dono` | «**A FOTO DO MEDICO SEGUE O NOME: quem já vê o nome, vê o rosto**» — escolhida por ele **entre quatro** formas |
| **pedido 107** | `2026-09-30 16:26:28`, aberto | «QUEM PODE VER A FOTO DELE?» — as **três** primeiras dessas quatro |

⟹ **A pergunta 107 nasceu DOIS MINUTOS DEPOIS de o dono a ter respondido.** É a mesma pergunta, e
a resposta dele é mais larga do que qualquer das três opções que o 107 lhe volta a oferecer. O meu
pacote manda que, se o que eu escrever colidir com uma regra, **a regra ganha** — e a regra 132,
que travava a foto, travava-a «antes dessa resposta». **A resposta existe.**

⟹ **O card da FOTO parte com os outros.** E ⛔ não foi por optimismo: o critério de pronto desta
demanda tem **nove** itens e o **7 é a foto**. Entregar oito de nove era repetir à letra o pecado da
regra 121 — «uma demanda ⛔ não se fecha enquanto o seu próprio título prometer coisa que os cards
⛔ não entregam».

⚠️ **E ⛔ NÃO VOLTO A PERGUNTAR NADA.** O que faço é **dizer-lhe, no pedido de aprovação, que o 107
pode fechar com `fab responder 107` a apontar para a regra 134.** A fila dele é a medida de saúde
desta casa, e um item que já está respondido ⛔ não devia estar lá.

### O que a regra 134 ⛔ NÃO autoriza neste epic
A regra diz que a foto **segue o nome**, e o corolário dela é explícito: a visibilidade da foto é
**exactamente** a do nome, ⛔ nunca maior. Medido, os sítios onde o nome do médico JÁ aparece:

- **o chat da aula ao vivo** — `chat.servico.ts:38-40` e `:52-57` devolvem `autorNome` a todos, e
  `:229-234` deixa entrar qualquer médico com ≥1 UPA `ACEITO`;
- **a lista do coordenador** — `/coordenador`;
- **o ranking e o top 5** — que ⛔ **ainda ⛔ não existem** (regra 121: nenhum dos 21 cards do CS-1 é
  o ranking; é a demanda 29).

⟹ **Nada disso entra aqui.** O título desta demanda é «**ecrã novo `/perfil`**», e é esse o
contrato. A foto nos outros ecrãs é **consequência da regra 134 e demanda própria** — fica escrita
em §6 (observação 1) com ficheiro e linha, para quem vier a seguir ⛔ não ter de a medir outra vez.
O aceite `CS-118` **conta no disco** que a foto ⛔ não apareceu em mais nenhum ecrã deste epic.

---

## 2 · A chave ⛔ não foi improvisada

O rastreador deste produto é **o livro da própria fábrica** — ⛔ **não há Jira aqui**: a regra 3
(`MSS-<n>` vindo do Jira) tem âmbito `produto:maissaude`, e a sigla `CS` foi dada pelo dono em
25/09. Precedentes escritos: `ENQUADRAMENTO-D28.md:122-125`, `ENQUADRAMENTO-D39.md:109-118`,
`ENQUADRAMENTO-D40.md:5-14`.

🩸 **E o número lê-se NO INSTANTE DA ENTREGA, ⛔ não no início do trabalho** — é a cicatriz do D40,
onde outros enquadradores tomaram `CS-76`, `CS-82`, `CS-88` e `CS-94` *entre* a medição e o
`fab enquadrar`, e o verbo recusou com `UNIQUE constraint failed: demanda.chave`. Por isso ⛔ **não
há uma única chave escrita à mão**: `ENQUADRAR-D32.py:proxima_livre()` mede o máximo em `card` ∪
`demanda` no momento de correr. Máximo medido ao escrever isto: **`CS-107`** ⟹ epic **`CS-108`**,
cards **`CS-109`…`CS-119`**.

---

## 3 · Os cards e o grafo

| card | terr. | o que entrega | depois de |
|---|---|---|---|
| CS-109 | backend | **UMA migração, ⛔ zero rotas**: `Especialidade` no molde exacto do `Nucleo`, `especialidadeId` + `fotoId` nuláveis no `Utilizador`, e `AlteracaoDePerfil` | — |
| CS-110 | backend | a **SECRETARIA** cria, renomeia, **arquiva e DESARQUIVA** especialidades, sem deploy | CS-109 |
| CS-111 | backend | **`GET /perfil`** — os quatro dados + as UPAs por extenso, e o **PENDENTE alcança-o** | CS-109 |
| CS-112 | backend | **`PATCH /perfil`** vale na hora + o **rasto na mesma transacção** + `GET /perfil/alteracoes` | CS-111 |
| CS-113 | backend | **pedir uma UPA** na linha que já existe, e ⛔ nenhum caminho dele escreve `ACEITO` | — |
| CS-114 | backend | **`POST /perfil/foto`** reusando o carregamento provado, e o PENDENTE alcança a dele | CS-112 |
| CS-115 | frontend | **`/perfil` nasce e é PORTA do médico**: quatro dados, UPAs por extenso, **seis estados** | CS-111 |
| CS-116 | frontend | corrigir CRM + escolher especialidade da lista **+ o extracto lido no ecrã** | CS-115, CS-112, CS-110 |
| CS-117 | frontend | **pedir uma UPA** a partir do perfil, e o ecrã DIZ porque é diferente do CRM | CS-116, CS-113 |
| CS-118 | frontend | **a foto** — três formatos, recusa que diz o que fazer, e ⛔ em mais nenhum ecrã | CS-117, CS-114 |
| CS-119 | frontend | **`/secretaria/especialidades`** — a lista dela, sem deploy, arquivar com volta | CS-110, CS-115 |

**Duas raízes que arrancam ao mesmo tempo: `CS-109` e `CS-113`.**

---

## 4 · As sete medições que mandaram na forma dos cards

**1 · ⛔ `GET /identidade/eu` ⛔ NÃO SE TOCA — e isto corrige, com medição, a decisão 5 do quadro.**
O quadro decidiu «a leitura reaproveita `GET /identidade/eu`». Medido: `frontend/src/lib/sessao.ts:48`
chama-a em **TODA** a página do produto, **12 ficheiros de e2e** simulam-na, e
`frontend/e2e/navegacao.spec.ts:533` e `:1023` exigem que ela seja o **ÚNICO** pedido de um ecrã
mudo. ⟹ Engordá-la com a especialidade, a foto e o **join dos vínculos** fazia **cada página** do
produto pagar esse join, e mudava a forma do objecto que 12 ficheiros simulam.
🩸 **O que se reaproveita é o MECANISMO, ⛔ não a rota**: o `@PermitePendente()` e o padrão «a porta
do próprio». `GET /perfil` nasce à parte, com `@PermitePendente()` **no método** e
`@Papeis(Papel.MEDICO)` — e `/identidade/eu` fica **byte por byte** como está (é aceite do CS-111).
⚠️ Isto é **mecanismo, reversível, e ⛔ não é regra de negócio** — fica à vista do dono aqui, como o
quadro pediu para as dele.

**2 · ⛔ `GET /upas` ⛔ NÃO SE ALARGA AO MÉDICO.** Ele precisa da lista de UPAs para pedir uma, e o
caminho óbvio era juntar `Papel.MEDICO` ao `@Papeis` de `upa.controlador.ts:67`. **Medido:**
`frontend/e2e/navegacao.spec.ts:709` fixa `expect(doServidor.get('GET /upas')).toEqual(['SECRETARIA','COORDENADOR'])`
— **lendo o ficheiro do backend do disco**. ⟹ Um card de **backend** que alargasse essa rota
**reprovava um teste de FRONTEND**, que é uma quebra que atravessa territórios e ⛔ não tem culpado
reversível. ⟹ `CS-113` traz a sua própria leitura dentro do módulo `vinculo`, e tem **aceite
dedicado** a contar que `GET /upas` continua com os dois papéis.

**3 · O `@PermitePendente()` ⛔ NÃO é usado «por UMA rota só».** O texto da demanda di-lo; medido, são
**três** usos e um deles é a armadilha: `identidade.controlador.ts:64` (método),
`aula.controlador.ts:80` (método) e `gravacao.controlador.ts:84` — **`PermitePendente()(ControladorGravacao)`,
aplicado à CLASSE INTEIRA** e depois **re-apertado à mão** dentro de cada método
(`exigirAtivo`, `:69-80`). ⟹ Isto **fortalece** a decisão 4 do quadro (o mecanismo está provado três
vezes, ⛔ não uma) **e** dá-lhe a trava: no `perfil` o decorador vai **no método**, ⛔ nunca na
classe — senão cada rota nova que ali nascer herda-o em silêncio.

**4 · O REGISTO DA ALTERAÇÃO ⛔ NÃO se separa da alteração, e é por isso que `CS-112` e `CS-116`
fazem as duas coisas.** Medido: ⛔ **não existe tabela de histórico nem de auditoria** no schema (13
models, e nenhum é Alteração, Histórico ou Auditoria). E a regra 131 diz, à letra, que o registo «⛔
NÃO É OPCIONAL: é a ÚNICA rede que sobra entre a alteração e a confiança no dado». ⟹ Se fossem
cards separados, o card da correcção podia ser **aprovado e integrado sozinho** — e o produto ficava
com correcção livre de CRM **sem rasto legível**, que é exactamente o dano que a regra veio evitar.
🩸 **Um card é uma tarefa; e aqui a tarefa é «corrige, e a correcção fica escrita».**

**5 · A TABELA DA NAVEGAÇÃO É FECHADA NOS DOIS SENTIDOS, e é ela que serializa `CS-119` atrás de
`CS-115`.** `frontend/e2e/navegacao.spec.ts:569` faz
`expect([...dasPortas, ...dasOrfas].sort()).toEqual(noDisco)` — **igualdade exacta** contra as
`page.tsx` varridas do disco. E `frontend/e2e/alcance.spec.ts:127` (`ESPERADO`) varre o disco
outra vez, como **oráculo independente**. ⟹ Uma porta sem página parte o teste, e uma página sem
declaração também. ⟹ **A entrada na tabela e a página nascem no MESMO card**, e os **dois** cards
que trazem página nova (`CS-115` e `CS-119`) tocam os **mesmos dois ficheiros de e2e** — logo ⛔ não
podem correr ao mesmo tempo. A dependência `CS-119 → CS-115` ⛔ não é lógica: é a cerca a impedir
duas mãos no mesmo ficheiro.
⚠️ E `CS-115` tem de reescrever mais três asserções que hoje estão verdes e ⛔ não podem ficar:
`navegacao.spec.ts:450`, `:483` e `:1000`, todas `rotulos(medico) === ['Calendário']`.

**6 · `GET /marcas/:id` ⛔ NÃO tem `@PermitePendente()`.** Medido em `marca.controlador.ts:77-78`
(⛔ sem `@Papeis`, logo qualquer sessão **ATIVO**) contra `guarda-estado.ts:41-44`. ⟹ Hoje um médico
**PENDENTE** podia carregar a sua foto e levar **403 do guarda de estado** ao ir buscar os bytes
dela — e o critério 6 exige precisamente que o PENDENTE alcance o seu perfil. É `CS-114` que fecha
isto, e ⛔ **sem abrir `POST /marcas` ao médico**: a marca do núcleo continua SECRETARIA-só, e tem
aceite a contá-lo.

**7 · A lista de especialidades **NASCE VAZIA** e a prova disso é aceite.** O critério 3 exige que
⛔ **nenhum dos dez núcleos apareça nela por herança**. ⟹ `CS-109` ⛔ **não leva semente nenhuma**
(ao contrário de `nucleo.semente.ts`) e tem aceite a medir «a migração deixa ZERO especialidades e
os dez núcleos continuam dez e intactos». E ⛔ não se toca no `Aula.especialidade String?`
(`schema.prisma:139`) — é o texto livre que já foi condenado e que obrigou ao
`src/modules/aula/migracao-nucleo.ts`; a aula aponta ao **Núcleo** desde o CS-47.

---

## 5 · Os aceites

Todos são **comandos executáveis** na forma medida deste produto (a mesma dos enquadramentos D39 e
D40):
- backend — `cd backend && npx jest test/<x>.e2e-spec.ts --runInBand --ci [-t '<nome>']`
- frontend — `cd frontend && npx playwright test e2e/<x>.spec.ts --reporter=line [-g '<nome>']`

🩸 **⛔ NENHUM aceite aponta para um ficheiro de teste que JÁ EXISTA.** O portão corre o mesmo
aceite na base e **reprova o card se ele já passava sem o card** — logo um aceite em
`navegacao.spec.ts`, `alcance.spec.ts` ou `botoes.spec.ts` (que hoje estão verdes) **reprovava um
card que está certo**. ⟹ Cada card mede-se no **seu** ficheiro novo, que na base ⛔ nem existe. O
que os cards têm de ⛔ não quebrar nos ficheiros antigos está escrito no **título** e em §4.5 —
para o especificador o pôr na spec, e para o portão o apanhar como regressão.

⚠️ E os nomes dos testes vão **SEM ACENTOS**, de propósito: `-t` e `-g` são expressão regular, e um
aceite que dependesse da codificação do terminal mentia (é a R38 do `nucleos.spec.ts`).

**Os seis estados, o vermelho e a régua do desenho** entram como aceite em **cada** card de
frontend: os seis num teste só (como `nucleos.spec.ts:645` já faz), `error` ⛔ nunca a marcar o dado
do médico (regra 84b), foco de teclado **visível** e alvo de **48dp** (regra 81.1 e 81.2), tabular
nos números (regra 85), e `prefers-reduced-motion` a zero com **o mesmo pixel** (regra 82).

---

## 6 · OBSERVAÇÕES — ⛔ não são achados: ⛔ nada disto me impediu de continuar

1. ⚠️ **A FOTO NOS OUTROS ECRÃS É DEMANDA PRÓPRIA, e a regra 134 já a autoriza.** Medido para quem a
   enquadrar: o **chat da aula ao vivo** já mostra o nome de todos a todos
   (`backend/src/modules/chat/chat.servico.ts:38-40`, `:52-57`; entra qualquer médico com ≥1 UPA
   `ACEITO`, `:229-234`) — é ali, ⛔ não no ranking, que uma foto chega aos colegas. A **lista do
   coordenador** é `frontend/src/app/coordenador/`. O **ranking e o top 5** ⛔ ainda ⛔ não existem
   (regra 121; demanda 29) — quem os construir nasce já com a foto por causa da regra 134.

2. ⚠️ **`CS-110` e `CS-111` correm em paralelo e os dois registam um módulo em
   `backend/src/app.module.ts`.** É **uma linha** cada, e o precedente é o `ENQUADRAMENTO-D40`
   (CS-102 e CS-103, os dois com o ficheiro na cerca e os dois sem pai). A resolução é o **rebase do
   epic**, ⛔ não um card — mas ⛔ nenhum aceite meu **conta módulos** («⛔ não há 14» / «há 15»):
   medem-se os **nomes**, exactamente como a observação 1 do D39 mandou.

3. ⚠️ **O ecrã `/perfil` é do MÉDICO e ⛔ mais nenhum papel o alcança** — os outros três levam
   **SEM PERMISSÃO** (é aceite do `CS-115`). Medido: `crm` só existe para o médico
   (`schema.prisma:40-42`, «só o médico declara CRM»), e os outros três dados são dele. ⟹ Um perfil
   para a secretaria, o coordenador ou o especialista ⛔ **não está decidido** e ⛔ não se adivinha —
   e ⛔ **não colide** com a regra 112 («a secretaria vê tudo»), que ela própria se limita: «o que
   ela ⛔ não passa a fazer é ACTO de outro papel no lugar dele».

4. ⚠️ **O CRM REPETIDO fica nomeado e ⛔ FORA do critério, e ⛔ não sobe.** Medido: ⛔ nada impede
   dois médicos de terem o mesmo CRM hoje, e com a correcção livre um pode escrever o de outro. A
   regra 131 manda subi-lo por `fab perguntar` — e ⛔ **não o faço**, pelas duas razões que o quadro
   já escreveu e que eu confirmei: ⛔ **não muda a forma de nenhum destes 11 cards**, e a fila do
   dono tem itens a mais para o alvo dela. ⟹ Fica aqui, com a medição, para o dia em que mudar
   alguma coisa.

5. ⚠️ **`vinculo.servico.ts` — o número da linha muda.** O texto da demanda aponta
   `recalcularEstadoDoMedico` a `:217`; está em **`:198`**. Importa porque é a função que `CS-112`
   tem de ⛔ **NÃO** chamar: é ela que faria o CRM corrigido devolver o médico a `PENDENTE`, e é
   isso que a regra 131 proíbe.

6. ⚠️ **`AvatarUpa` existe e avatar de PESSOA ⛔ não.** `frontend/src/componentes/base/AvatarUpa.tsx`
   e `monograma.ts` resolvem o monograma **da unidade**; ⛔ nenhuma das 39 peças aprovadas é avatar
   de pessoa (regra 132). ⟹ `CS-115` faz o monograma **do médico** derivado em código, no molde já
   aprovado (regra 83.3: mesma pessoa ⟹ sempre o mesmo monograma, ⛔ zero imagens pagas, ⛔ zero
   chamadas de rede), **com o nome sempre a uma linha dele** (regra 83a). ⛔ Não há card de estúdio
   nesta demanda e ⛔ não se gasta um cêntimo.
