Skip to main content

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)​

PersonaQuem éO que faz aqui
Médico / Técnico / Residente / Digitadoropera o exame no dia a diaanexa documentos, consulta anexos existentes, baixa e copia o link
Administrador / Gerente da unidadegestor do exametudo o que o operador faz, mais excluir anexos e criar o link de convite externo
Financeiro / Solicitante / Read onlysem exam:add-attachment na unidadenã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.

OndeAção que abre o modalRota de fundo
WorklistClique no ícone de clipe (com contador, se já houver anexos) na linha do exame/exams
LaudárioClique no ícone de anexos da barra de ações do laudo/exams/:examId/report
Caminho na telaRota
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çãoElementoCondiçã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 uploadapp-exam-attachments com readOnly=falsemesma condição acima (o próprio ícone já barra quem não pode)
Excluir um anexo, a partir da worklistbotão de lixeira dentro do modalcanDeleteAttachments(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 assinadoreadOnly=false, canDelete=truereadOnly() = isSigned() || signedCheckFailed() — falso aqui
Ver o modal em modo consulta, a partir do laudário, exame assinadoreadOnly=true, canDelete=falsereadOnly() verdadeiro — ver Modo somente leitura
Criar link de anexo externoitem "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 telaValor gravado (classification na API)Nome que o valor sugeriria
OutrosOTHERSOutros
Img. ExamesPRIOR_EXAM"Exame anterior"
Pedido MédicoMEDICAL_REQUESTPedido médico
QuestionárioEXTERNAL_REPORT"Laudo externo"
Prescrição MédicaEXAM_RESULTS"Resultado do exame"
Exame AnteriorPATIENT_DOCUMENT"Documento do paciente"
Documentos PessoaisCONSENT_FORM"Termo de consentimento"
Guia ConvênioOTHER_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.".

Modal &quot;Anexos do exame&quot; vazio, perfil administrator

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).

Menu de anexar mostrando só &quot;Outros&quot; e &quot;Img. Exames&quot;, confirmado ao vivo

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.

Lista com um anexo real (PDF), miniatura à esquerda e preview grande à direita, perfil administrator

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ãoPor quê está desabilitado
Imprimirdepende 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​

IDRegraO que a tela faz
RN-A1Modo somente leitura mostra só a classificação Img. Exames, sem nenhuma ação de ediçãoaplicado quando o modal abre a partir de um laudário de exame assinado
RN-A2Excluir é decidido por quem abre o modal, não pelo modal em siworklist: gestor do exame (exceto concluído, só platform admin); laudário: qualquer um, desde que não assinado
RN-A3Upload valida tipo, tamanho e quantidade antes de enviartipos 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-A4Lista de classificações do upload depende de uma preferência da empresarequireClassification; 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-A5Filtro e edição de classificação sempre mostram as 8 opções completassó o upload é limitado por RN-A4; filtrar e reclassificar um anexo já existente sempre oferece as 8
RN-A6O contador de anexos da worklist atualiza sem fechar o modaltoda 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çãoO que aconteceComo 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 completoPedir a permissão a quem administra a unidade
Upload com mais de 20 arquivos no loteTodo o lote é rejeitado, toast "Máximo de 20 arquivos por envio."Selecionar até 20 por vez
Arquivo de tipo não aceitoAquele arquivo é rejeitado, toast "Tipo de arquivo não permitido: <nome>"; os demais do lote seguemReenviar sem o arquivo recusado
Arquivo maior que o limiteAquele arquivo é rejeitado, toast "Arquivo excede o tamanho máximo: <nome>"; os demais do lote seguemReenviar sem o arquivo recusado
Upload de um arquivo específico falha no meio do loteToast "Falha ao enviar <nome>." só daquele arquivo; os demais continuam sendo enviadosReenviar só o que falhou
Listagem falha ao carregarToast "Falha ao carregar anexos."Reabrir o modal
Reclassificar falha na APIToast "Falha ao atualizar a classificação do anexo."; a lista não mudaTentar de novo
Excluir falha na APIToast "Falha ao excluir o anexo."; a lista não mudaTentar de novo
Tentar excluir sem canDeleteBotão nem aparecePedir a quem é gestor do exame
Copiar link de um anexoBusca 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çãoTransporteQuando é chamada
Listar anexosGraphQL examAttachments(examId)ao abrir o modal e após upload/exclusão/reclassificação
Anexar arquivoREST POST /bff/exams/:examId/attachments (BFF, cookie de sessão)ao escolher arquivo(s) no seletor
ReclassificarGraphQL changeExamAttachmentClassification(examId, attachmentId, classification)ao escolher uma classificação no menu de editar
ExcluirGraphQL deleteExamAttachment(examId, attachmentId)ao confirmar no diálogo de exclusão
Buscar 1 anexo (link fresco)reaproveita a listagem completa, filtrando pelo idao 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​