- Para quem é (personas)
- Histórias das personas
- Como acessar
- Permissões e acesso
- Classificações: o rótulo na tela × o valor guardado na API
- Caminho principal
- Modo somente leitura (exame assinado)
- Aviso do portal de entrega (classificação "Img. Exames")
- Botões desabilitados de propósito
- Regras de negócio
- Caminhos alternativos e falhas
- Validações
- Informações técnicas
- Relacionado
Anexos do exame
Modal onde você vê, anexa, reclassifica e exclui os arquivos ligados a um exame — pedido médico, exame anterior, resultado, documento do paciente e outros. É o mesmo modal, aberto de dois lugares diferentes: pelo ícone de clipe na linha da worklist e pelo ícone de anexos dentro do laudário.
:::info Atualização (24/09/2026) — fluxo completo confirmado ao vivo, upload/reclassificação/exclusão funcionando
Com o exame de teste da unidade ("Paciente Teste Docs Portal2") disponível, o modal foi validado de
ponta a ponta como administrator: estado vazio ("Nenhum anexo."), menu de anexar mostrando só
Outros e Img. Exames (nunca as 8 classificações, confirmando RN-A4), upload real com sucesso
("1 anexo(s) enviado(s) com sucesso."), preview do PDF no carrossel, badge de contagem atualizado na
worklist sem recarregar (RN-A6), menu de reclassificar com as 8 classificações exceto a atual
(RN-A5), copiar link e exclusão com o diálogo de confirmação — todos confirmados ao vivo.
Uma tentativa de upload anterior havia falhado com "Falha ao enviar <nome>."
(Servidor indisponível) — não era um bug deste código de tela, e sim uma lacuna real no ambiente
de testes: o schema do banco diagnosis deste ambiente estava com migrations travadas atrás de uma
migration antiga que tentava recriar uma coluna já existente (aplicada fora do fluxo normal, sem
registro em diagnosis_migrations), o que impedia a tabela diagnosis_unit_exam_preferences de
existir e derrubava com 503 qualquer leitura de preferências da unidade — inclusive a que baseia o
menu de "Criar link externo" (ver Convite de anexo externo). As
migrations pendentes foram aplicadas e o upload real passou a funcionar; o mesmo ambiente também
resolveu o erro equivalente ao salvar comentários (ver Comentários).
:::
Para quem é (personas)
| Persona | Quem é | O que faz aqui |
|---|---|---|
| Médico / Técnico / Residente / Digitador | opera o exame no dia a dia | anexa documentos, consulta anexos existentes, baixa e copia o link |
| Administrador / Gerente da unidade | gestor do exame | tudo o que o operador faz, mais excluir anexos e criar o link de convite externo |
| Financeiro / Solicitante / Read only | sem exam:add-attachment na unidade | não vê o ícone de anexar (ver Permissões e acesso) |
Histórias das personas
💬 Como técnico, quero anexar o pedido médico ao exame para que o médico o veja ao laudar.
- Técnico: "Recebo o pedido médico escaneado e preciso colocá-lo no exame antes que o médico abra o laudário."
- Gerente: "Preciso excluir um anexo que foi enviado no exame errado — só eu e o administrador temos esse botão."
- Médico (laudário): "Depois que assino o laudo, só quero poder ver os anexos, não mais mexer neles."
Como acessar
Não existe uma rota própria para este modal — ele abre por cima da tela atual.
| Onde | Ação que abre o modal | Rota de fundo |
|---|---|---|
| Worklist | Clique no ícone de clipe (com contador, se já houver anexos) na linha do exame | /exams |
| Laudário | Clique no ícone de anexos da barra de ações do laudo | /exams/:examId/report |
| Caminho na tela | Rota |
|---|---|
| Worklist › ícone de clipe da linha | /exams |
| Laudário › ícone de anexos | /exams/:examId/report |
:::info Quem vê o ícone
O ícone só aparece para quem tem a permissão exam:add-attachment na unidade daquele exame
específico (não é uma permissão global — é avaliada exame a exame, pela unidade dona do exame).
Sem ela, e sem o exame estar excluído, o ícone aparece desabilitado, com tooltip de "sem
permissão" (ver linha attachDenied abaixo) — ele nunca some por completo enquanto o exame existir,
ao contrário de outros módulos onde a ausência de permissão esconde o item de menu.
:::
Permissões e acesso
| Ação | Elemento | Condição de visibilidade / habilitação |
|---|---|---|
| Ver o ícone de anexar/anexos ativo, na worklist | ícone de clipe da linha | `!exam.isDeleted && (exam:add-attachment na unidade do exame |
| Ver o ícone desabilitado (negado) | ícone de clipe cinza + tooltip "Você não possui permissão para anexar" | exame não excluído e sem exam:add-attachment — attachDenied, exam-worklist.component.ts:944 |
| Abrir o modal, ver anexos, fazer upload | app-exam-attachments com readOnly=false | mesma condição acima (o próprio ícone já barra quem não pode) |
| Excluir um anexo, a partir da worklist | botão de lixeira dentro do modal | canDeleteAttachments(exam): platform admin, ou (exam:update e exam:delete na unidade) — chamado de "gestor do exame"; se o exame está concluído, só platform admin — exam-worklist.component.ts:1013 |
| Ver e mexer no modal, a partir do laudário, exame não assinado | readOnly=false, canDelete=true | readOnly() = isSigned() || signedCheckFailed() — falso aqui |
| Ver o modal em modo consulta, a partir do laudário, exame assinado | readOnly=true, canDelete=false | readOnly() verdadeiro — ver Modo somente leitura |
| Criar link de anexo externo | item "Criar link externo" no menu de anexar | !readOnly && unidade.externalAttachmentsEnabled === true — ver Convite de anexo externo |
O modal em si não decide essas permissões — ele só respeita o que os inputs readOnly e
canDelete mandam. Quem decide é sempre a página que o abre (worklist ou laudário), cada uma com
sua própria regra (ver tabela acima). Isso é intencional: o comentário no código chama de "a
policy da entidade é aplicada pela página; aqui apenas se respeita a decisão".
:::caution Divergência entre o gate da tela e a permissão do backend
O backend autoriza DELETE /exams/:examId/attachments/:attachmentId pela permissão granular
exam:delete-attachment no escopo do exame (ver
Anexos do exame — API, seção Permissões).
O frontend, porém, não lê essa permissão: ele decide mostrar o botão de excluir usando
isExamManager (platform admin, ou exam:update e exam:delete juntos) na worklist, e
simplesmente !readOnly() (exame não assinado) no laudário — nenhum dos dois caminhos consulta
exam:delete-attachment. Na prática, alguém com exam:delete-attachment mas sem exam:update
e exam:delete juntos não veria o botão na worklist, mesmo que a chamada à API funcionasse se
ele a disparasse de outra forma; e um perfil isExamManager que não tenha exam:delete-attachment
veria o botão, mas a exclusão falharia com 403 FORBIDDEN_ACTION ao confirmar. A confirmar — responsável: time de frontend; data: 24/09/2026. para o comportamento observado ao vivo.
:::
Classificações: o rótulo na tela × o valor guardado na API
As 8 classificações de anexo são as mesmas dos dois lados (frontend e backend), mas o rótulo em
português que a pessoa vê não corresponde ao nome semântico do valor gravado — confirmado
comparando EXAM_ATTACHMENT_CLASSIFICATION_LIST
(src/app/entities/exam/model/exam-attachment.ts:27-36) com as chaves de tradução em
public/i18n/pt-BR.json:
| Rótulo mostrado na tela | Valor gravado (classification na API) | Nome que o valor sugeriria |
|---|---|---|
| Outros | OTHERS | Outros |
| Img. Exames | PRIOR_EXAM | "Exame anterior" |
| Pedido Médico | MEDICAL_REQUEST | Pedido médico |
| Questionário | EXTERNAL_REPORT | "Laudo externo" |
| Prescrição Médica | EXAM_RESULTS | "Resultado do exame" |
| Exame Anterior | PATIENT_DOCUMENT | "Documento do paciente" |
| Documentos Pessoais | CONSENT_FORM | "Termo de consentimento" |
| Guia Convênio | OTHER_ATTACHMENTS | "Outros anexos" |
Só as duas primeiras linhas batem com o que o nome do valor sugere. Nas outras seis, o rótulo que a
pessoa escolhe na tela não descreve o que o valor da API significa — por exemplo, quem quer
guardar o "resultado do exame" (EXAM_RESULTS) escolhe Prescrição Médica na tela, e quem quer
guardar um "documento do paciente" (PATIENT_DOCUMENT) escolhe Exame Anterior. Isso importa
especialmente para quem lê a API — Anexos do exame
junto desta página: o escudo de exame assinado (RN-05 do backend) libera exclusão só para
EXAM_RESULTS — na tela, isso aparece como Prescrição Médica, não como algo relacionado a
"resultado". A confirmar — responsável: time de produto/frontend; data: 24/09/2026. se essa
troca de rótulos é intencional (ex.: mapeamento 1:1 com nomes de classificação do sistema legado) ou
um desalinhamento a corrigir.
Caminho principal
1. Abrir o modal
Clique no ícone de clipe da linha (worklist) ou no ícone de anexos do laudário. O modal abre em
tela quase cheia (96vw × 92vh), com o título "Anexos do exame", carregando a lista de
anexos do exame. Sem nenhum anexo, o corpo do modal mostra "Nenhum anexo.".

2. Anexar um arquivo
Clique no botão de anexar (ícone de clipe na barra de ações do modal). Um menu abre com as
classificações disponíveis para upload — a lista completa (8 opções) só aparece se
requireClassification for true; hoje nenhum dos dois pontos de entrada passa esse input como
true (ambos usam [requireClassification]="false" — exam-worklist.component.html:908 e
exam-report-page.component.html:669), então na prática o menu de upload sempre mostra só as duas
primeiras opções: Outros e Img. Exames. Escolher uma classificação abre o seletor de
arquivo do sistema operacional (múltiplos arquivos).

Cada arquivo do lote é enviado em uma requisição separada, um de cada vez (não em paralelo); uma barra de progresso mostra "X de Y enviados" enquanto isso acontece. Em sucesso, um toast confirma "1 anexo(s) enviado(s) com sucesso." e o contador de anexos do ícone de clipe na worklist atualiza sem recarregar a lista inteira (RN-A6).
3. Ver, navegar e filtrar
Os anexos aparecem em um carrossel (p-galleria) com miniaturas à esquerda (até 6 visíveis por
vez) e o preview grande à direita. Imagem mostra com zoom/rotação (local, não persiste no
servidor); PDF mostra em um <iframe>; qualquer outro tipo (ZIP, planilha, docx) mostra um
placeholder com botão de download. Um filtro por classificação, no canto superior direito,
restringe a lista visível sem recarregar do servidor.

4. Reclassificar
Clique no ícone de lápis (editar classificação): um menu lista todas as 8 classificações, exceto a atual do anexo ativo. Escolher uma delas chama a reclassificação imediatamente — não existe um modo de edição em lote nem um botão "Salvar" para isso (ver Botões desabilitados de propósito).
5. Excluir
Clique no ícone de lixeira (só visível para quem pode excluir — ver
Permissões e acesso). Um diálogo de confirmação, título "Excluir
anexo", pergunta "Deseja realmente excluir o anexo <nome do arquivo>?" antes de excluir. A
exclusão é sempre de um anexo por vez — o legado tinha uma opção "excluir todos os duplicados"
que não tem equivalente aqui (ver nota abaixo).
Modo somente leitura (exame assinado)
Quando o modal abre a partir do laudário de um exame já assinado (readOnly=true):
- a lista visível não é todos os anexos — é filtrada para mostrar só os de classificação
Img. Exames (
PRIOR_EXAM); - os botões de anexar, editar classificação e excluir somem da barra de ações;
- o filtro por classificação também some (não faz sentido filtrar uma lista já filtrada);
- só ficam disponíveis navegação, zoom/rotação, copiar link, abrir em nova guia e download.
Isso é o oposto do "escudo de exame assinado" do backend (que protege a exclusão por
classificação, permitindo excluir só EXAM_RESULTS em exame assinado — ver
Anexos do exame — API, RN-05):
aqui, o frontend nem mostra o botão de excluir nesse cenário, então a regra do backend nunca chega
a ser testada por esse caminho. A confirmar — responsável: time de frontend; data: 24/09/2026.
se existe algum fluxo de UI que ainda permita tentar excluir um EXAM_RESULTS de exame assinado
(o escudo do backend sugere que sim, em algum lugar).
Aviso do portal de entrega (classificação "Img. Exames")
Quando o anexo ativo (ou a classificação escolhida para upload) é Img. Exames
(PRIOR_EXAM), uma faixa vermelha aparece com o aviso: "Anexos com esta classificação ficam
visíveis no portal de entrega do paciente." (EXAM.ATTACHMENTS.PATIENT_PORTAL_WARNING, texto
confirmado em public/i18n/pt-BR.json).
Botões desabilitados de propósito
Dois botões do modal aparecem sempre desabilitados, com tooltip "indisponível" — não são bugs nem telas faltando, são decisões documentadas no próprio código:
| Botão | Por quê está desabilitado |
|---|---|
| Imprimir | depende do laudário/viewer, ainda não portado para o Portal 2.0 |
| Salvar Alterações (rodapé) | o legado tinha edição de classificação em lote (PUT /exame/anexo); o BFF atual não expõe essa rota — a reclassificação agora é imediata, anexo a anexo (ver passo 4), então não sobra nada para este botão salvar |
Anexos duplicados (não portado)
O legado oferecia, na exclusão, escolher entre "apagar só este" ou "apagar todos os duplicados"
(anexos que compartilham a mesma origem, via anexo_original_id — ver card Bitrix F-008 "Gerenciar
anexos duplicados", contexto histórico apenas). O BFF atual não expõe esse endpoint, então a
exclusão neste modal é sempre unitária — não existe a opção de lote.
Regras de negócio
| ID | Regra | O que a tela faz |
|---|---|---|
| RN-A1 | Modo somente leitura mostra só a classificação Img. Exames, sem nenhuma ação de edição | aplicado quando o modal abre a partir de um laudário de exame assinado |
| RN-A2 | Excluir é decidido por quem abre o modal, não pelo modal em si | worklist: gestor do exame (exceto concluído, só platform admin); laudário: qualquer um, desde que não assinado |
| RN-A3 | Upload valida tipo, tamanho e quantidade antes de enviar | tipos aceitos: PDF, imagem, ZIP, xls/xlsx/csv, docx; até 20 arquivos por lote; cada arquivo até 50 MB (constante do frontend, não configurável); envio um a um, com barra de progresso |
| RN-A4 | Lista de classificações do upload depende de uma preferência da empresa | requireClassification; hoje sempre false nos dois pontos de entrada (worklist e laudário) — o menu de upload nunca mostra as 8 opções, só as 2 primeiras (Outros, Img. Exames). A confirmar — responsável: time de frontend; data: 24/09/2026. se isso é intencional ou uma preferência ainda não conectada |
| RN-A5 | Filtro e edição de classificação sempre mostram as 8 opções completas | só o upload é limitado por RN-A4; filtrar e reclassificar um anexo já existente sempre oferece as 8 |
| RN-A6 | O contador de anexos da worklist atualiza sem fechar o modal | toda vez que um upload ou exclusão termina, o modal emite a nova contagem total, que a worklist usa para atualizar o badge do ícone sem recarregar a lista inteira |
Caminhos alternativos e falhas
| Situação | O que acontece | Como se recupera |
|---|---|---|
Sem exam:add-attachment na unidade do exame | Ícone aparece desabilitado, tooltip "Você não tem permissão para anexar arquivos a este exame"; exame excluído esconde o ícone por completo | Pedir a permissão a quem administra a unidade |
| Upload com mais de 20 arquivos no lote | Todo o lote é rejeitado, toast "Máximo de 20 arquivos por envio." | Selecionar até 20 por vez |
| Arquivo de tipo não aceito | Aquele arquivo é rejeitado, toast "Tipo de arquivo não permitido: <nome>"; os demais do lote seguem | Reenviar sem o arquivo recusado |
| Arquivo maior que o limite | Aquele arquivo é rejeitado, toast "Arquivo excede o tamanho máximo: <nome>"; os demais do lote seguem | Reenviar sem o arquivo recusado |
| Upload de um arquivo específico falha no meio do lote | Toast "Falha ao enviar <nome>." só daquele arquivo; os demais continuam sendo enviados | Reenviar só o que falhou |
| Listagem falha ao carregar | Toast "Falha ao carregar anexos." | Reabrir o modal |
| Reclassificar falha na API | Toast "Falha ao atualizar a classificação do anexo."; a lista não muda | Tentar de novo |
| Excluir falha na API | Toast "Falha ao excluir o anexo."; a lista não muda | Tentar de novo |
Tentar excluir sem canDelete | Botão nem aparece | Pedir a quem é gestor do exame |
| Copiar link de um anexo | Busca um link assinado novo antes de copiar (o link anterior pode ter expirado — ver validade do link, API); sucesso mostra toast "Link copiado." | Colar de novo se o link copiado já não funcionar |
Validações
Frontend
Ver a tabela RN-A3 acima — tipo, tamanho e quantidade são checados antes do envio, no navegador.
Backend
O upload em si segue POST /exams/:examId/attachments, incluindo a validação de tamanho (50 MB) e
o escudo de exame assinado na exclusão — ver
Anexos do exame — API, seções Body e Erros.
O frontend não reproduz o escudo de exame assinado (RN-05 do backend) porque, como descrito em
Modo somente leitura, ele nem mostra o botão de excluir
quando o exame está assinado.
Informações técnicas
Para o time de desenvolvimento.
Componente: ExamAttachmentsComponent (app-exam-attachments,
src/app/features/exam-attachments/ui/exam-attachments.component.ts) · Sem rota própria —
inputs visible, examId, readOnly, canDelete, requireClassification, isKeyImage.
Rotas de API/BFF consumidas:
| Operação | Transporte | Quando é chamada |
|---|---|---|
| Listar anexos | GraphQL examAttachments(examId) | ao abrir o modal e após upload/exclusão/reclassificação |
| Anexar arquivo | REST POST /bff/exams/:examId/attachments (BFF, cookie de sessão) | ao escolher arquivo(s) no seletor |
| Reclassificar | GraphQL changeExamAttachmentClassification(examId, attachmentId, classification) | ao escolher uma classificação no menu de editar |
| Excluir | GraphQL deleteExamAttachment(examId, attachmentId) | ao confirmar no diálogo de exclusão |
| Buscar 1 anexo (link fresco) | reaproveita a listagem completa, filtrando pelo id | ao clicar em "copiar link" |
O upload usa REST (não GraphQL) pelo mesmo motivo do upload de áudio do exame: o multipart/form-data
não é representável em GraphQL, e a rota do domínio diagnosis só aceita Bearer, enquanto o
navegador só carrega o cookie de sessão — por isso o upload passa pelo BFF (/bff/exams/...), que
faz a ponte de autenticação, e não pela rota /v1/exams/... documentada como API do domínio.
→ Detalhe do contrato do domínio: Anexos do exame (Back)
Relacionado
- ⚙️ API: Anexos do exame (Back)
- 🔗 Convite de anexo externo — como criar o link, dentro deste mesmo modal
- 📂 Módulo: Anexos — visão geral