# FASE3-QWEN — medição do Qwen (mac mini) como programador de Blade/CSS

Divisão de trabalho da Fase 3 (docs/PLANO.md): Claude escreve tudo com lógica ou segurança; o Qwen (skill `delegar-mini`) escreve Blade e CSS sem lógica sensível. Registo por pedido: o que foi pedido, quantas idas, o que foi corrigido.

## Pedido 1 — layout + 9 páginas Blade + CSS

Prompt: guardado no scratchpad da sessão (`mini-prompt-blade.txt`), com o mockup HTML aprovado (`mockups-assistente.html`, 189 linhas) como ficheiro de contexto. Pedido único cobrindo os 10 ficheiros: `layouts/app.blade.php`, `auth/login.blade.php`, `fichas/index.blade.php`, `fichas/novo.blade.php`, `fichas/lote.blade.php`, `fichas/mostrar.blade.php`, `users/index.blade.php`, `users/criar.blade.php`, `fiabilidade/index.blade.php`, `public/css/app.css`.

**Idas: 1** (não foi preciso repetir — resposta completa, `finish=stop`, sem cortar). `prompt_tokens=9808 completion_tokens=11012`, ~11 minutos a gerar (~17 tok/s), corrido em background enquanto o Claude escrevia os controladores/testes em paralelo.

**O que o Qwen entregou bem, sem correção:**
- Tokens CSS do mockup (claro + escuro, `@media prefers-color-scheme` + `[data-theme]`) copiados fielmente, incluindo a troca de fonte para `system-ui`/monoespaçada do sistema (sem Google Fonts, como pedido).
- Estrutura das 9 páginas fiel ao mockup: sidebar, cartões, tabelas, pills de estado, formulários com `@csrf`, `old()` para preservar valores após erro.
- Lógica de apresentação condicional (estados, confiança, checklist) correta na maior parte dos `@if`/`@foreach`.

**Correções feitas na revisão (bugs reais, não só estilo):**
1. `$errors->get('campo')` usado dentro de `{{ }}` em 3 ficheiros (`fichas/novo.blade.php`, `users/criar.blade.php`, `auth/login.blade.php`) — `get()` devolve um array, não uma string; ia imprimir "Array" (aviso PHP "Array to string conversion"). Corrigido para `$errors->has()` no `@if` e `$errors->first()` no `{{ }}`.
2. Filtro de estado da fila (`fichas/index.blade.php`): a opção "todos" tinha `value=""` e comparava com `$estadoAtual === ''`, mas o controlador normaliza sempre para `'todos'` — o filtro nunca aparecia selecionado. Corrigido para `value="todos"`.
3. `fichas/mostrar.blade.php`: checkbox de cartão a ler `$cartao->ativo` (campo inexistente no modelo `FichaCartao`) em vez de `conta_como_especialidade`. Corrigido.
4. **Bug de domínio que o Qwen não podia prever** (não estava na especificação do prompt, apanhado só pelos testes Feature): uma checkbox HTML desmarcada não é submetida no POST — o form de "Guardar edições" não tinha como o controlador distinguir "cartão não estava no formulário" de "cartão desmarcado pelo utilizador". Corrigido pelo Claude: campo oculto `cartoes_ids[]` por cartão na view + lógica em `FichasController::guardar()` a comparar a lista completa com o que veio marcado. Teste novo (`AprovacaoTest`) cobre o caso.
5. Botão `[img]` dos cartões: o Qwen gerou um `<a target="_blank">` direto para a rota de documentos (funcionalmente correto, mas fora do sistema visual do mockup, que usa um painel lateral fechado por omissão). O Claude trocou para `data-img-url` + o painel de `public/js/app.js` (script próprio, sem dependências), consistente com "PLANO.md: painel lateral da imagem fecha por omissão e abre pelo botão".

**Conclusão desta medição:** o Qwen produziu ~700 linhas de Blade/CSS utilizáveis num único pedido, com erros mecânicos previsíveis (API do `MessageBag` mal usada, um campo de modelo inventado) e sem nenhum erro de segurança — nada que tocasse em autorização, validação ou dados sensíveis, exatamente a fronteira que a divisão de trabalho do PLANO.md definiu. Todas as correções foram feitas pelo Claude antes de sincronizar para o CT; nenhum código do Qwen foi aplicado sem revisão e sem os 106 testes Pest a passar.

## Pedido 2 — correções do primeiro teste em browser (22/09/2026)

**Não delegado ao Qwen.** As 6 correções pedidas pelo utilizador depois do primeiro teste em browser (botão `[img]` por campo + secção "Documentos da pasta", esconder apelido/nome_proprio, só campos do manifesto na tabela pela ordem certa, datas dd/mm/aaaa, ncc só com 8 dígitos, normalização de filho_de/nome/naturalidade/concelho/nacionalidade) são todas alterações de **lógica** (novas colunas em `fichas_campos`, cálculo de páginas de PDF, conversão de datas, ordenação/filtro de campos vindos do controlador, extração do CC) com uma única mudança de Blade (`fichas/mostrar.blade.php`: reordenar a tabela, esconder linhas não-manifesto, mostrar `[img]` por linha, nova secção de documentos) tão entrelaçada com os dados que o controlador já lhe entrega (`camposTabela`, `camposData`) que separar num pedido ao Qwen só duplicaria trabalho de especificação sem poupar tokens a sério — a fronteira do PLANO.md é "Blade/CSS sem lógica sensível", e aqui a view é pura consequência da lógica escrita ao lado. Feito diretamente pelo Claude, revisto pelos 121 testes Pest.

## Pedido 3 — visualizador de zoom no painel lateral (22/09/2026)

Divisão: o Claude escreveu a rota/controlador (resolução do `pdftoppm`, qualidade JPEG), o HTML do painel em `layouts/app.blade.php` e a integração em `public/js/app.js`; foi pedido ao Qwen só o componente sem lógica de negócio — `public/js/viewer.js` (zoom, pan, ecrã inteiro, redimensionamento do painel) e `public/css/viewer.css` — contra um contrato de HTML e uma API pública (`window.DocumentoViewer`, `window.PainelResizer`) fechados no prompt, para o Claude poder integrar sem adivinhar nomes.

**Idas: 1** (resposta completa, `finish=stop`). `prompt_tokens=5846 completion_tokens=6408`, corrido em background enquanto o Claude tratava do controlador/testes/blade em paralelo.

**O que o Qwen entregou bem, sem correção:**
- Zoom 25%-400% em passos de 25%, "Ajustar"/"100%" calculados corretamente (`computeFitScale`, `centerOffset`), zoom centrado no cursor com Ctrl+roda (ratio matemático correto), arrastar/touch, duplo clique a alternar Ajustar/200%, `prefers-reduced-motion`, sem inline handlers, sem CDNs, `aria-label` em todos os botões, `:focus-visible` visível.
- `PainelResizer`: arrasto + teclado (setas) + persistência em `localStorage` com clamp 320-900px, e já removia os próprios listeners antigos antes de reatar (o único sítio onde acertou a gestão de listeners à primeira).

**Correções feitas na revisão (bugs reais, não só estilo):**
1. **Fuga de memória de listeners (a mais grave):** `DocumentoViewer.load()` criava um `state` novo e chamava `attachStageListeners`/`rootEl.addEventListener('click', ...)` a cada chamada — ou seja, a cada mudança de página do documento, sem nunca remover os listeners da chamada anterior. `detachStageListeners` existia mas era inofensivo (passava funções anónimas *novas* a `removeEventListener`, que nunca correspondem às que foram de facto registadas — não remove nada). Reescrito: os listeners do stage/root são ligados **uma única vez** por instância (guardados em `state.stageHandlers`), e `load()` seguinte só troca a `<img>`, sem reatar nada.
2. **Mesma fuga no ecrã inteiro:** `openFullscreen()` acrescentava `document.addEventListener('keydown'|'mousemove'|'mouseup', ...)` de novo cada vez que se abria o ecrã inteiro, e `closeFullscreen()` nunca os removia — abrir/fechar várias vezes ia acumulando listeners globais no `document` para sempre. Corrigido: os três handlers do document são guardados em `state.overlayHandlers` e removidos em `closeFullscreen()`.
3. **Zoom não preservado entre páginas** (contra a especificação — "páginas seguinte/anterior mantêm o zoom"): como o `state` era recriado do zero a cada `load()`, o zoom voltava sempre a "Ajustar" ao mudar de página. Corrigido: a instância e o seu `state` (incluindo `scale`/`fitMode`) só são criados a primeira vez que o painel abre; chamadas seguintes de `load()` só substituem a imagem e preservam o zoom, exceto na primeira abertura da sessão (que continua a abrir em "Ajustar", como pedido).
4. **Atalhos de teclado não chegavam à toolbar:** o `keydown` só estava ligado ao `.viewer-stage`; com o foco num botão da toolbar (`.viewer-toolbar`, irmão do stage, não descendente), `+`/`-`/`0`/Esc não faziam nada — o evento nunca borbulhava até ao stage. Corrigido: o `keydown` passou para o elemento raiz do visualizador (`#documento-viewer`), que é ancestral comum de toolbar e stage.
5. **CSS:** `.viewer{height:100%}` dentro do `.panel-inner` (coluna flex, com o `<h4>` do título acima) ultrapassava o espaço disponível em vez de o partilhar — a imagem ficava cortada por baixo. Trocado por `flex:1;min-height:0`, o padrão já usado pelo `.viewer-stage` no mesmo ficheiro.
6. Ecrã inteiro fechado passou a devolver o zoom/posição ao painel lateral (o Qwen deixava os dois estados desligados um do outro depois de fechar — não era um bug funcional grave, mas ficava inconsistente com "os controlos são os mesmos").

**Conclusão desta medição:** o Qwen acertou toda a matemática do zoom/pan/fit (a parte "difícil" da especificação) à primeira, e a estrutura dos ficheiros seguiu o contrato de HTML/API à letra — zero API inventada, ao contrário do Pedido 1. Mas errou sistematicamente a gestão do ciclo de vida dos listeners (o único requisito de segurança/qualidade explícito no pedido: "sem fugas de memória de listeners") em três sítios diferentes do mesmo ficheiro, e não implementou a persistência de estado entre páginas pedida na especificação funcional. Todas as correções foram feitas pelo Claude antes de sincronizar para o CT; nenhum código do Qwen foi aplicado sem revisão nem sem a suite de testes Pest a passar.

## Pedido 4 — botões/tabelas da Fase 4 (apply em DEV, conferência, histórico, PDF/email) (23/09/2026)

Divisão: o Claude escreveu toda a lógica nova da Fase 4 (`Aplicador`, `Conferencia`, os métodos novos de `FichasController`, o wrapper); foi pedido ao Qwen só o Blade — ligar os botões `Aplicar em DEV`/`Verificar/Reconciliar`/`Descarregar ficha PDF`/`Enviar ficha por email` às rotas certas conforme flags booleanas já calculadas pelo controlador (`$podeAplicarDev`, `$podeReconciliar`, `$podePdfEmail`, `$aplicarAtivo`), mais duas tabelas novas (conferência pós-inserção, histórico de execuções) em `fichas/mostrar.blade.php`, e o formulário de "enviar por email em lote" em `fichas/index.blade.php`. Prompt (`mini-prompt-fase4-blade.txt`, no scratchpad) fechava nomes de rotas, nomes de variáveis e classes CSS a reutilizar (`pill`/`p-ok`/`p-rev`/`p-blk`/`btn`/`b1`/`b2`/`bx`/`card`/`wrap`), com os dois ficheiros atuais como contexto.

**Idas: 1** (resposta completa, `finish=stop`). `prompt_tokens=6390 completion_tokens=5334`, corrido em background enquanto o Claude escrevia os testes Pest da Fase 4 em paralelo.

**O que o Qwen entregou bem, sem correção:**
- Todos os botões condicionais (`@if($podeAplicarDev)` etc.) com o texto, as classes e os `title` exatamente como pedidos, incluindo o estado desativado com o motivo certo ("Aplicar em DEV desativado (WRAPPER_APPLY_ATIVO)" vs "Só disponível a partir de dry-run ok").
- Tabela de conferência: acesso correto por chave de array (`$linha['campo']`, não `->campo` — o prompt avisou que `$conferenciaLinhas` não é uma coleção Eloquent) e os 3 badges de estado (`ok`/`equivalente` com o texto "(confirmado pelo modelo)"/`diferente`) certos.
- Tabela de histórico de execuções: os 3 casos do exit code (`0` a verde, outro valor a "rever", `null` como "sem resposta" em vez de mostrar vazio) exatamente como especificado — este era o ponto mais fácil de esquecer (confundir `null` com `0`) e o Qwen não confundiu.
- `Aplicar em PROD` manteve-se sempre desativado, sem nenhuma tentativa de lhe dar uma rota (o prompt foi explícito nisso).

**Correção feita na revisão (bug real, não só estilo):**
1. **Checkboxes fora do `<form>`** em `fichas/index.blade.php`: o Qwen pôs o novo `<form action="fichas.email-lote">` a envolver só o botão final, deixando a tabela com as checkboxes `casos[]` fora dele — um clique em "Enviar fichas selecionadas por email" submeteria sempre uma lista vazia (o controlador já trata isso sem rebentar, mas a funcionalidade ficava inútil). Corrigido pelo Claude: o `<form>` passou a envolver a tabela inteira (checkboxes incluídas) até ao botão, sem tocar em mais nada da tabela.

**Conclusão desta medição:** pedido mecânico e bem especificado (rotas, variáveis e classes todas fechadas de antemão) — o Qwen não inventou nenhuma API nem classe CSS, e acertou toda a lógica condicional dos badges/estados à letra. O único erro real foi estrutural em HTML (âmbito do `<form>`), do tipo que o próprio SKILL.md deste projeto já esperava do modelo local (ver Pedido 1, "erros mecânicos previsíveis"). Corrigido pelo Claude antes de sincronizar; os 163 testes Pest (145 anteriores + 18 novos da Fase 4) passam com as views aplicadas, incluindo os testes que renderizam `fichas.mostrar` a sério (`MostrarTest`, `ChecklistTest`).

## Pedido 5 — módulo Contratos: nenhum pedido ao Qwen (23/09/2026)

Divisão pedida pelo plano: as duas views novas do módulo Contratos
(`resources/views/contratos/index.blade.php` e
`resources/views/contratos/mostrar.blade.php`) podiam ser pedidas ao Qwen,
revistas pelo Claude.

**Não delegado.** A meio deste trabalho o utilizador avisou que ia desligar o
mac mini (10.0.30.231) e pediu explicitamente para não usar `delegar-mini`
nem o leitor neste bloco. As duas views foram escritas diretamente pelo
Claude, seguindo as mesmas convenções já medidas nos Pedidos 1-4 (tokens de
cor `var(--...)`, classes `pill`/`p-ok`/`p-rev`/`p-blk`/`card`/`wrap`/`btn
b2`/`b3`, datas em `dd/mm/aaaa`, formulário a envolver a tabela inteira até
ao botão — lição do Pedido 4). Sem medição de qualidade do Qwen a
acrescentar aqui porque não houve rascunho do Qwen nesta entrega.
