DOCUMENTO DE ESPECIFICAÇÃO FUNCIONAL CONSOLIDADO

Central do Médico (Cockpit) — Versão 4.21

28 de agosto de 2026


SEÇÃO 1 — DEFINIÇÃO

Um dia na Central do Médico

Você chega à clínica para iniciar seu turno. Ao abrir o Gemed Onco, a primeira tela que surge é o seu Cockpit. No topo, quatro indicadores rápidos mostram que você tem 12 consultas hoje, 5 prescrições aguardando sua assinatura e 3 pacientes já em tratamento nas poltronas.

Ao longo da manhã, você não precisa “caçar” informações. Se um paciente chega na recepção, o painel de Consultas atualiza sozinho, mostrando quem está com status Recepcionado (fez check-in, pronto para atendimento). Se surge uma dúvida da enfermagem, uma nova mensagem pisca no painel de Comunicação. Antes de chamar o próximo paciente, você bate o olho no painel de Procedimentos na Clínica e vê, por uma barra de progresso, que a infusão da Dona Maria está correndo dentro do tempo previsto. O painel mostra todos os pacientes em tratamento na clínica, não apenas os seus, permitindo uma visão global da operação da unidade. Quando sobra um minuto entre atendimentos, você acessa o painel de Prescrições Pendentes e, com um clique, assina e revisa as prescrições do dia, garantindo que a farmácia e a enfermagem sigam o fluxo sem interrupções. É o seu dia controlado em uma única visão, sem cliques desnecessários.

Objetivo

Prover ao médico oncologista uma interface centralizada que consolide todas as informações críticas para a gestão do seu turno de trabalho em tela única. O foco é eliminar a navegação excessiva entre menus, reduzir a carga cognitiva e aumentar a segurança do paciente através de alertas visuais de atrasos e pendências críticas.

Escopo

  • Incluído: Header com busca inteligente (Nome, CPF, Prontuário); Indicadores de desempenho (KPIs); Painel de Consultas cronológico; Painel de Prescrições para assinatura rápida; Chat de comunicação interna; Monitoramento de tempo de infusão dos pacientes; Atalhos para o Prontuário Eletrônico (PEP).
  • Excluído: Digitação de evoluções clínicas (feito no PEP); Criação de novos protocolos; Gestão de horários da agenda; Comunicação externa (E-mail/WhatsApp).

Personas

PersonaPapel no SistemaNecessidade Principal
Médico OncologistaUsuário PrincipalVisão 360º do dia, agilidade na assinatura e monitoramento de segurança.
RecepcionistaStakeholder operacionalMantém as agendas atualizadas (status Agendado e Recepcionado). Recebe resultados de exames dos pacientes para encaminhar aos médicos. O status “Aguardando Recepção” pode ser definido manualmente pela recepcionista ou via integração com totem de fila.
Enfermeira e FarmacêuticaStakeholders clínicos (não usam o Cockpit diretamente)Recebem prescrições assinadas para liberar o tratamento. A enfermagem só administra a prescrição depois que ela tiver sido revisada por um médico — revisão essa que o sistema exige automaticamente a partir de 1 dia antes da aplicação agendada (RN-CM-020). Enfermagem e Farmácia são tratadas como uma persona só nesta tabela porque nenhuma das duas interage com o Cockpit em si — aparecem aqui apenas como stakeholders impactados pelas decisões tomadas nele.

Workflows do Profissional

A Central do Médico é um hub que toca múltiplos workflows do profissional. Cada workflow é documentado abaixo com seu resumo e os touchpoints específicos da Central.

Workflow 1 — Atendimento de Consulta

Resumo: O paciente chega à clínica, pega número na fila de espera e aguarda ser chamado pela recepcionista, que faz o check-in. O paciente aguarda na sala de espera. O médico, antes de chamar o paciente, analisa o prontuário, verifica anotações e exames anteriores. Então chama o paciente, inicia o atendimento (perguntas, constatação do estado atual, análise de exames trazidos, solicitação de novos exames, prescrições). O médico pode registrar a evolução clínica durante ou após a consulta. Ao finalizar, fecha o atendimento e foca no próximo paciente. O paciente sai do consultório e vai embora ou passa na recepção para marcar retorno.

Touchpoints da Central:

Momento do workflowNecessidade do profissionalO que a Central ofereceDecisão de design justificada
Antes de chamar o próximo pacienteSaber rapidamente quem está recepcionado e pronto para atendimentoPainel de Consultas com status “Recepcionado” em destaque (chip azul + borda lateral)Toggle default é “Pendentes” — no workflow, o médico só precisa ver quem falta atender, não quem já atendeu. Consultas finalizadas ficam esmaecidas ao alternar para “Todas”.
Antes de chamar o próximo pacienteIdentificar se o paciente está atrasado (horário passou e não iniciou)Badge vermelho pulsante “Atrasado · Xmin” no card de consultaO atraso é uma exceção do workflow que exige ação imediata — o pulsante chama atenção sem que o médico precise procurar.
Entre atendimentosAcessar o prontuário do próximo paciente sem navegar por menusClique no card de consulta abre diretamente o PEPNo workflow, o médico precisa do prontuário imediatamente antes de chamar o paciente — um clique é o menor caminho possível.
Início do turnoTer um panorama geral do dia (quantas consultas, quantas pendentes, quantas atrasadas)KPI “Consultas Hoje” com número “finalizadas/total” e subtexto “pendentes · atrasadas”No workflow, o médico precisa calibrar expectativas de carga de trabalho logo ao logar — o KPI dá isso sem abrir nenhum painel.
Durante o atendimentoNão ser interrompido por informações irrelevantesConsultas finalizadas aparecem esmaecidas na sua posição originalNo workflow, o médico em atendimento só se preocupa com o que vem a seguir — finalizadas são histórico imediato, não prioridade.

Workflow 2 — Tratamento Oncológico (Monitoramento)

Resumo: O paciente em tratamento oncológico tem um plano terapêutico com ciclos e sessões previstos. Em cada dia de tratamento, o paciente chega à clínica, faz check-in, é preparado pela enfermagem (acesso venoso, sinais vitais) e inicia a infusão do quimioterápico. A enfermagem monitora o paciente durante a infusão (checagens periódicas). O médico acompanha o progresso do tratamento, avalia resposta, ajusta protocolo quando necessário e prescreve a próxima sessão. O tratamento pode ser suspenso por intercorrências (febre neutropênica, reação alérgica). O paciente pode não aderir ao planejamento (descompasso de intervalos entre ciclos).

Touchpoints da Central:

Momento do workflowNecessidade do profissionalO que a Central ofereceDecisão de design justificada
Entre consultas / durante o turnoSaber quais pacientes estão em procedimento na clínica e em qual etapaPainel de Procedimentos na Clínica com status (Em Atendimento / Aguardando) e local na clínicaNo workflow de monitoramento, o médico precisa saber onde cada paciente está no fluxo de infusão sem sair da sua tela — o painel consolida isso em um olhar.
Durante o monitoramentoSaber se uma infusão está dentro do tempo previsto ou atrasadaBarra de progresso inline (3px) na linha do nome, com porcentagemNo workflow, o tempo de infusão é um parâmetro crítico de segurança — a barra permite monitoramento periférico sem abrir o prontuário. A barra é discreta para não competir com nome/status, mas fica vermelha se ultrapassar 100%.
Entre consultasDecidir se precisa intervir em um procedimento em andamentoClique no card abre a prescrição do procedimento; botão separado “PEP” abre o prontuárioNo workflow, o médico primeiro verifica a prescrição (o que está sendo infundido, dose, diluente) antes de decidir se precisa intervir — abrir a prescrição é a ação primária; o PEP é a ação secundária para contexto clínico completo.
Início do turnoSaber quantos pacientes estão em tratamento sob sua responsabilidade e quantos não estão aderentesKPI “Em Tratamento” com número principal (em tratamento) e subtexto “não aderentes”No workflow, o médico precisa identificar rapidamente pacientes em risco de não adesão — o subtexto traz apenas essa informação complementar, sem redundância.
Durante o turnoFiltrar apenas seus pacientes vs. ver todos os da clínicaToggle pill “Meus / Todos” (default: Meus)No workflow, o médico monitora seus próprios pacientes, mas pode precisar ver a operação global da clínica (ex: quando outro médico está em plantão) — o toggle permite alternar sem mudar de tela.

Workflow 3 — Prescrição e Dispensação

Resumo: O médico define o protocolo de tratamento e prescreve os medicamentos para cada ciclo/sessão. A prescrição fica pendente de assinatura. O médico assina (com ou sem certificado digital) na véspera ou no dia da aplicação. A farmácia recebe a prescrição assinada e manipula o medicamento. Como segunda barreira de segurança, o sistema marca automaticamente a prescrição como pendente de revisão a partir de 1 dia antes da aplicação agendada — um médico (preferencialmente o prescritor ou o médico assistente do paciente) precisa abrir a prescrição e confirmá-la como revisada antes que a enfermagem possa administrar o medicamento. A prioridade das prescrições não revisadas cresce da mesma forma que a das não assinadas, conforme a hora da aplicação se aproxima.

Touchpoints da Central:

Momento do workflowNecessidade do profissionalO que a Central ofereceDecisão de design justificada
Início do turno / entre atendimentosAssinar prescrições pendentes antes que a farmácia possa manipularPainel de Prescrições Pendentes com botão “Assinar” no card que abre a tela de assinatura com a prescrição completaNo workflow, a assinatura é um gargalo — a farmácia não pode manipular sem ela. O botão no card abre a tela de assinatura com a prescrição completa para que o médico revise e se certifique de que é a prescrição correta antes de assinar. É responsabilidade do médico saber o que está assinando — a assinatura não pode ser um clique direto sem revisão.
Início do turnoPriorizar quais prescrições assinar primeiro (as mais urgentes)Chips de urgência: vermelho (<12h) e laranja (12-24h) com tempo restanteNo workflow, a prescrição com aplicação agendada para breve tem prioridade máxima — a cor vermelha sinaliza que a janela de ação está fechando.
A partir de 1 dia antes da aplicação agendadaConfirmar que a prescrição foi revisada por um médico antes da administração ao pacienteCard de prescrição tipo “Revisar”, gerado automaticamente pelo sistema quando falta 1 dia ou menos para a aplicação e a prescrição ainda não foi revisada por nenhum médico. Botão “Revisar” abre a prescrição completa para o médico validar ou ajustar antes de confirmarNo workflow, a revisão é uma segunda barreira de segurança, independente e posterior à assinatura — garante que algum médico (idealmente o prescritor ou o assistente do paciente) validou a prescrição pouco antes da administração. Por ser automática e baseada em tempo, ganha a mesma escalada de prioridade visual (chip vermelho/laranja) das prescrições não assinadas, refletindo que o risco cresce conforme a aplicação se aproxima.
Início do turnoSaber quantas prescrições urgentes (menos de 12h) precisam de sua atençãoKPI “Alertas de Prescrição” com número principal (urgentes) em vermelho e subtexto (p/ revisar · p/ assinar próximos 7 dias)No workflow, prescrições urgentes são o item de maior risco — o número em vermelho chama atenção imediata ao logar. O subtexto dá contexto adicional sem competir com o número principal.
Ao assinar uma prescriçãoConfirmar que a assinatura foi processada e retornar à CentralToast de sucesso “Prescrição assinada com sucesso” + retorno automático à Central, focada no painel de Prescrições Pendentes + atualização do KPINo workflow, o médico precisa confirmação visual de que cumpriu a etapa — o toast + retorno à Central + decremento do KPI fecham o loop de feedback. O retorno automático evita que o médico precise navegar manualmente de volta.
Ao revisar prescriçõesFocar primeiro nas prescrições que é preferencialmente responsável por revisar (seus pacientes), mas poder ajudar a revisar as de colegas quando necessárioToggle pill “Meus / Todos” no painel de Prescrições Pendentes. “Meus” mostra somente as prescrições a revisar em que o médico logado é o prescritor ou o médico assistente do paciente. “Todos” mostra todas as prescrições a revisar (de qualquer médico, já que RN-CM-020 permite que qualquer médico revise) somadas às prescrições a assinar do próprio médico logadoNo workflow, o médico prioriza naturalmente seus próprios pacientes, mas pode ajudar a revisar as de colegas quando necessário — daí o toggle. O default é “Meus”: as prescrições a assinar nunca são afetadas pelo toggle (já são sempre do médico logado, RN-CM-012), então não há risco de escondê-las por trás de um filtro — o padrão “Meus” apenas prioriza a revisão dos próprios pacientes, consistente com o padrão do painel de Procedimentos.

Workflow 4 — Comunicação Interna

Resumo: Diferentes profissionais da clínica (organizados em equipes, como recepção, farmácia e enfermagem) precisam comunicar informações ao médico durante o turno: recados, solicitações, resultados de exames, dúvidas sobre prescrições, notificações de intercorrências. Módulos do sistema também podem gerar alertas automáticos (ex: prescrição urgente, atraso de infusão). O emissor — pessoa ou módulo — pode definir um prazo para resposta. O médico recebe essas conversas, precisa priorizá-las, pode responder diretamente pelo Cockpit, ou resolvê-las/descartá-las e arquivar as encerradas. Algumas são urgentes (intercorrência durante infusão), outras são informativas (confirmação de manipulação). Diferente de uma notificação avulsa, cada item é uma conversa (thread) que pode acumular respostas de ambos os lados.

Touchpoints da Central:

Momento do workflowNecessidade do profissionalO que a Central ofereceDecisão de design justificada
Durante todo o turnoReceber conversas e alertas da equipe sem sair da CentralPainel de Comunicação com conversas em aberto (não resolvidas nem descartadas)No workflow, o médico não pode alternar entre telas para checar mensagens — o painel integra comunicação ao cockpit, permitindo resposta rápida entre atendimentos.
Ao receber uma conversa novaIdentificar rapidamente que há conversa não lidaConversas não lidas com ícone colorido e borda lateral destacadaNo workflow, conversas não lidas são pendências — o ícone colorido + borda destacada permitem identificação periférica sem ler o conteúdo. Ao ser lida, passa ao estado normal.
Ao revisar a lista de conversasPriorizar conversas com prazo de respostaPrazo exibido no card (“Responder até 14:00”) + ordenação por prazo primeiroNo workflow, conversas com prazo são compromissos — a ordenação coloca as mais urgentes no topo, e o prazo visível no card permite triagem sem abrir a conversa.
Ao precisar esclarecer algo rapidamenteResponder sem sair do Cockpit, sem precisar telefonar ou ir até a equipeCampo de resposta no modal da conversa — envia uma nova mensagem na thread e muda o status para “Respondida”No workflow, muitas dúvidas simples (confirmar uma dose, avisar que já viu o recado) podem ser resolvidas com uma frase — abrir uma tela separada de chat quebraria o fluxo entre atendimentos.
Ao resolver uma solicitaçãoArquivar a conversa com um cliqueBotão “Resolvido” no card que remove a conversa e atualiza o contadorNo workflow, resolver é uma ação frequente e repetitiva — um clique é o menor caminho. A remoção imediata mantém a lista enxuta.
Durante todo o turnoDistinguir se a conversa é de uma pessoa ou um alerta automáticoÍcone identificador (pessoa vs. bell do Lucide para alertas automáticos)No workflow, a origem determina a natureza da mensagem — o ícone permite categorização visual instantânea sem ler o texto. Não há distinção visual por equipe (recepção/farmácia/enfermagem) — o nome do autor já cumpre esse papel.

Regras de Negócio Consolidadas

Cada regra abaixo tem um código (ex: RN-CM-020) que permite localizá-la e rastreá-la até sua implementação na Seção 2. A numeração tem saltos (ex: de RN-CM-006 para RN-CM-011) porque regras intermediárias foram consolidadas ou removidas ao longo de revisões anteriores deste documento — não é um sinal de conteúdo faltando.

Layout e Visualização

  • RN-CM-001 — Tela única, sem rolagem geral: A Central ocupa a tela inteira — o médico vê os 4 painéis sem precisar rolar a página principal. Cada painel rola de forma independente quando tem mais itens do que cabem no espaço disponível. (Detalhamento técnico de implementação: Seção 2.8.)
  • RN-CM-002 — Quatro painéis fixos: A interface é dividida em quatro quadrantes fixos, cada um com uma função específica:
    • Quadrante superior esquerdo — Consultas: Agenda de consultas do médico no dia, com status visual de cada paciente (Agendado, Aguardando Recepção, Recepcionado, Em Atendimento, Finalizado).
    • Quadrante superior direito — Prescrições Pendentes: Lista de prescrições que aguardam ação do médico, divididas em duas categorias: prescrições para assinar (aguardando assinatura digital) e prescrições para revisar (marcadas automaticamente pelo sistema quando a aplicação agendada está a 1 dia ou menos e a prescrição ainda não foi revisada por nenhum médico — RN-CM-020).
    • Quadrante inferior esquerdo — Comunicação: Conversas e alertas não lidos, de pessoas/equipes ou do sistema, com ação de responder, resolver ou descartar.
    • Quadrante inferior direito — Procedimentos na Clínica: Monitoramento de pacientes previstos e presentes na unidade para procedimentos e tratamentos (infusões, aplicações, checagens) — não inclui consultas médicas, apenas procedimentos terapêuticos e diagnósticos.
    • Diferenciação visual: O cabeçalho de cada painel possui um fundo cinza claro para se diferenciar do corpo do painel (fundo branco), orientando o olhar do usuário entre a área estrutural e a área de conteúdo. (Token de cor: Seção 2.8.)
  • RN-CM-003 — Títulos como links de navegação: Os títulos dos 4 painéis são clicáveis e funcionam como links para as telas dedicadas correspondentes. No estado default, o título aparece em peso regular (sem negrito) com cor neutra. No hover, a cor muda para o azul secundário e uma seta (→) aparece discretamente ao lado do título, deslizando da esquerda. (Token de cor: Seção 2.8.) Esta abordagem substitui links separados (‘Ver agenda →’, ‘Ver todas →’, etc.), reduzindo ruído visual no cabeçalho. Motivador: o título é o elemento mais natural para clicar; a seta no hover garante descobribilidade sem poluir o estado default.

Busca Inteligente

  • RN-CM-004 — Lógica de Busca: A busca é acionada quando o usuário digita os primeiros 2 caracteres e para de digitar por 1 segundo (esse intervalo de pausa se repete a cada novo caractere digitado, sem exceção). A busca usa o mecanismo de correspondência aproximada já existente no sistema (pesos de proximidade fonológica e semântica, fora do escopo desta especificação) — por isso sempre retorna a lista mais provável, mesmo sem um resultado exato. Se a entrada começar com dígito, o sistema busca por CPF ou Data de Nascimento. Se começar com letra, busca por Nome do Paciente ou Nome da Mãe. O placeholder do campo de busca deve ser ‘Procurar paciente…’. Ao abrir o dropdown de resultados, exibir uma mensagem de orientação no rodapé da lista: ‘Digite parte do nome, CPF, data de nascimento ou nome da mãe para filtrar’.

Consultas e Atendimento

  • RN-CM-005 — Status de Atendimento: As consultas devem refletir o status real:
    • Agendado = consulta marcada, mas sem mais interações
    • Aguardando Recepção = paciente está presente na clínica, mas ainda não fez o check-in
    • Recepcionado = paciente fez o check-in com a recepção e está pronto para ser atendido
    • Em Atendimento = paciente está em atendimento com o profissional (médico)
    • Finalizado = paciente já realizou a consulta
  • RN-CM-006 — Ordenação: A lista de consultas é sempre cronológica, priorizando o próximo atendimento.

Prescrições e Assinatura

  • RN-CM-011 — Alertas de Urgência: Prescrições com menos de 12 horas para a aplicação ganham um alerta vermelho. Entre 12h e 24h, o alerta é laranja. Esses limiares (12h/24h) são parâmetros configuráveis por clínica — os valores-padrão vêm da experiência de uso do sistema legado, não são fixos no código. Esta regra vale tanto para prescrições pendentes de assinatura quanto para prescrições pendentes de revisão (RN-CM-020) — a urgência é sempre calculada em relação ao tempo restante até a aplicação agendada, independentemente do tipo de pendência.
  • RN-CM-012 — Assinatura Digital: A assinatura de prescrições pode ocorrer de 2 modos, definidos por parâmetro de configuração do cliente: (a) assinatura com certificado digital — exige que o médico possua certificado digital ativo vinculado ao seu perfil; (b) assinatura sem certificado — validada apenas pela sessão do usuário logado, sem exigência de certificado. O modo ativo é determinado pela configuração da clínica. Motivador: nem toda clínica tem infraestrutura ou exige assinatura com certificado digital (ICP-Brasil) para prescrições — por isso o modo é configurável por clínica, não fixo no sistema. Quando o modo exige certificado e o médico não tem um certificado ativo, ele não pode assinar digitalmente e segue o processo de assinatura física em papel (fora do escopo deste documento).
  • RN-CM-020 — Revisão Automática Pré-Aplicação: O sistema marca automaticamente uma prescrição já assinada como “a revisar” quando a data/hora agendada da aplicação está a 1 dia (24h) ou menos da data/hora atual E não existe assinatura do médico (original ou de revisão) com menos de 7 dias. Em outras palavras: se a assinatura original do médico tiver menos de 7 dias no momento em que a janela de D-1 se abre, ela já vale como revisão implícita — não é exigida uma confirmação separada. Se a assinatura original tiver mais de 7 dias, é necessária uma confirmação explícita de revisão dentro dos últimos 7 dias. O objetivo é garantir que um médico tenha validado a prescrição recentemente antes da administração ao paciente pela enfermagem — é uma segunda barreira de segurança, independente e desvinculada de qualquer notificação de problema pela farmácia ou enfermagem. Qualquer médico pode revisar, mas o sistema deve sinalizar preferencialmente o médico prescritor e o médico assistente do paciente como revisores ideais. Revisar não exige necessariamente editar a prescrição — o médico pode apenas confirmá-la como revisada (“Marcar como Revisada”) ou, se identificar necessidade de ajuste, editá-la, o que gera uma nova versão da prescrição (novo registro, com a versão anterior cancelada) que precisa ser assinada novamente. Prescrições “a revisar” seguem a mesma escala de urgência de RN-CM-011 conforme o tempo restante até a aplicação. A janela de 7 dias também é um parâmetro configurável por clínica, com valor-padrão herdado da experiência do sistema legado.

Comunicação Interna

  • RN-CM-015 — Conversas, Alertas e Priorização: Toda comunicação no Cockpit (mensagens entre profissionais e alertas automáticos do sistema) é tratada como uma conversa, que pode acumular várias mensagens — como uma thread. Uma conversa fica marcada como “não lida” até que o médico a abra; abrir não a remove do painel, só tira o destaque visual e a empurra para depois das ainda não vistas na ordenação (ver “Ordenação” na Seção 2.5). O painel compacto do Cockpit (Seção 2.5) mostra todas as conversas em aberto do médico logado — lidas e não lidas — e uma conversa só desaparece dali quando é marcada como Resolvida ou Descartada; essa ação é definitiva (não pode ser desfeita) e fica registrada no histórico para auditoria. O histórico completo (incluindo as já resolvidas/descartadas) fica reservado a uma tela dedicada, fora do escopo desta versão. Quem inicia a conversa — uma pessoa ou um módulo automático do sistema — pode definir um prazo para resposta ou resolução. O médico pode responder diretamente pelo Cockpit, sem precisar telefonar ou ir até quem enviou; responder também marca a conversa como lida. Se o mesmo tipo de alerta automático acontecer de novo depois de uma conversa já encerrada, o sistema abre uma conversa nova (não reabre a antiga), mas mantém o vínculo com a anterior para quem for consultar o histórico depois. (Detalhamento técnico — nomes de campo, fórmula de “não lida”, modelo de dados: Seção 2.5 e Seção 4.8.)

Procedimentos na Clínica (Monitoramento)

  • RN-CM-016 — Barra de Progresso: Exibe o tempo decorrido da infusão versus o tempo estimado pelo protocolo. Se ultrapassar 100%, a barra torna-se vermelha. Motivador: o tempo de infusão é um parâmetro de segurança — uma infusão que ultrapassa o tempo previsto pode indicar problema no acesso venoso, na bomba de infusão ou reação do paciente; a barra vermelha chama a atenção do médico sem que ele precise abrir o prontuário para checar.

Atualização de Dados

  • RN-CM-017 — Atualização Frequente: A Central deve ser um espelho confiável da realidade da clínica. Os dados de todos os painéis devem ser atualizados com frequência suficiente para refletir o estado atual das operações, sem que o médico precise recarregar a página manualmente. [A DEFINIR] O intervalo/mecanismo técnico exato (e se é igual para os 4 painéis) ainda não tem um número concreto — fica como pendência técnica para a Seção 2/4, a decidir com o time técnico e validar com o solicitante antes de virar critério de aceitação testável.
  • RN-CM-019 — Hierarquia visual sem negrito: A Central não utiliza negrito em nenhum elemento estrutural. A hierarquia visual é construída exclusivamente através de tamanho tipográfico, cor e espaçamento. Destaques importantes são feitos por contraste cromático (chips coloridos, borda lateral dos KPIs, número vermelho de alertas) e por tamanho de fonte. Motivador: o negrito deve ser economizado para destaques verdadeiramente importantes; informações estruturais devem orientar o olhar discretamente, não competindo com as informações clínicas que importam. (Valores exatos de peso e tamanho de fonte: Seção 2.8.)

Fluxo Principal (O Ciclo do Dia)

  1. Início do Turno: O médico faz login e visualiza o panorama geral nos KPIs.
  2. Triagem de Pendências: Antes do primeiro paciente, o médico assina as prescrições pendentes de assinatura e confirma a revisão das prescrições marcadas automaticamente como “a revisar” (aplicação agendada em até 1 dia), priorizando as mais urgentes.
  3. Atendimento: Ao identificar um paciente “Recepcionado” no painel de Consultas, o médico clica no nome para abrir o PEP e iniciar o atendimento.
  4. Monitoramento: Entre consultas, o médico observa o painel de Procedimentos na Clínica para verificar se algum tratamento está atrasado ou finalizado.
  5. Comunicação: O médico responde dúvidas rápidas da equipe via painel de Comunicação sem sair da tela principal.

Premissas

  • A assinatura de prescrições pode exigir certificado digital ou não, dependendo da configuração do cliente. Se a clínica está configurada para assinatura com certificado digital, o médico que possuir certificado assina digitalmente; o médico que não possuir certificado deve imprimir e assinar fisicamente. Se a clínica não está configurada para certificado digital, a assinatura é validada apenas pela sessão do usuário logado.
  • A resolução mínima de tela recomendada é de 1920x1080 (Full HD).

Dependências

Sistema/MóduloDependênciaImpacto
Módulo de AgendaStatus da agendaSem isso, o painel de consultas não atualiza.
Módulo de AgendaData/hora da aplicação da prescriçãoA marcação automática de “a revisar” (RN-CM-020) depende da data/hora de aplicação agendada na prescrição — sem essa informação, o sistema não consegue calcular a janela de 1 dia e disparar o estado de revisão.
Serviço de AssinaturaIntegração com CertificadoraNecessário para a validade jurídica da assinatura (quando aplicável).
RecepcionistaAtualização de status na agendaDefine os status “Agendado” e “Recepcionado” no painel de Consultas.

Requisitos SBIS Aplicáveis

ID SBISDescrição AcessívelImplementação no Cockpit
ECF.03.11Busca multifatorial de pacientesImplementado na busca do Header (Nome, CPF, Mãe).
ECF.16.01Listagem de pendências do profissionalImplementado no Painel de Prescrições Pendentes.
ECF.17.04Registro de eventos em ordem cronológicaAplicado na ordenação de Consultas e Mensagens.

SEÇÃO 2 — ESPECIFICAÇÃO

Premissa de design: Toda decisão de design nesta especificação é justificada pelos touchpoints de workflow documentados na Seção 1. Ao escolher um componente, definir uma ordenação, priorizar uma informação visualmente ou estabelecer um comportamento de navegação, a decisão apoia o fluxo real do profissional naquele momento específico do workflow. Consulte a seção “Workflows do Profissional” na Seção 1 para o contexto completo de cada touchpoint.

2.1 Cabeçalho (Header) com Busca de Pacientes

US-CM-000 — Buscar pacientes rapidamente

Como médico, quero buscar qualquer paciente pelo nome, CPF, data de nascimento ou nome da mãe diretamente do cabeçalho, para acessar o prontuário sem precisar navegar por menus.

Descrição Funcional:

O cabeçalho é a barra superior fixa da Central do Médico e contém 3 blocos:

  1. Identificação da clínica: Nome da clínica onde o médico está logado (sem a unidade/local de atendimento — removido por decisão do responsável do projeto).
  2. Campo de busca de pacientes: Campo de texto. A busca é inteligente e contextual:
    • O placeholder do campo deve ser MSG_searchPac para deixar explícito que é uma busca por pacientes.
    • Ao abrir o dropdown de resultados, exibir uma mensagem de orientação no rodapé da lista: MSG_searchFoot.
    • A primeira busca é acionada quando o usuário digita os primeiros 2 caracteres e para de digitar por 1 segundo. A partir desse primeiro disparo, a lista vai sendo filtrada dinamicamente conforme o usuário continua digitando, sem necessidade de novo intervalo de pausa.
    • A busca sempre retorna uma lista, pois o algoritmo busca por aproximação de termos. Se o usuário digitar “ay”, o sistema busca termos em torno de “ay”: “ay” será o mais prioritário, mas se não encontrar, retornará “ai”, “aj” e assim por diante.
    • Se a entrada começar com dígito, o sistema busca por CPF ou Data de Nascimento. Se começar com letra, busca por Nome do Paciente ou Nome da Mãe.
    • Os resultados da busca devem conter minimamente: nome completo, número de prontuário, sexo, data de nascimento, nome da mãe e CPF (conforme requisito SBIS ECF.03.14).
    • Ao selecionar um paciente na lista de resultados, o sistema abre o Prontuário Eletrônico do Paciente (PEP).
  3. Menu do usuário: Avatar com foto do profissional (quando disponível) ou iniciais, com um ícone de dropdown (seta) ao lado. Não exibe nome, CRM ou especialidade do médico no cabeçalho (removido por decisão do responsável do projeto) — essas informações vivem dentro do menu, não como texto fixo visível. Clicar no avatar ou na seta abre um menu suspenso abaixo, à direita, com ações específicas do usuário. Por enquanto, o menu contém apenas “Sair”. Itens previstos para versões futuras (fora do escopo desta versão, ainda não especificados): autocadastro/edição de perfil, dados de faturamento e pagamento (conta bancária, PIX) e dados de especialidade (CRM, RQE). Clicar fora do menu ou selecionar um item o fecha.

Justificativa de workflow: A busca por pacientes no cabeçalho atende ao touchpoint ‘Antes de chamar o próximo paciente’ (Workflow 1) e ao touchpoint ‘Decidir se precisa intervir em um procedimento’ (Workflow 2). Em ambos os momentos, o médico pode precisar acessar um prontuário que não está visível nos painéis — a busca global no cabeçalho é o atalho universal, disponível em qualquer momento do turno.

Critérios de Aceitação:

  • Dado que o médico digita “Ma” no campo de busca, quando ele para de digitar por 1 segundo, então o sistema deve retornar pacientes cujo nome ou nome da mãe contenham “Ma” ou termos aproximados, exibindo nome completo, número de prontuário, sexo, data de nascimento, nome da mãe e CPF.
  • Dado que o médico digita “12” no campo de busca, quando ele para de digitar por 1 segundo, então o sistema deve buscar por CPF ou data de nascimento que contenham “12”.
  • Dado que o médico digita apenas 1 caractere, então o sistema não deve disparar a busca, exibindo a mensagem MSG_searchMin no rodapé do dropdown.
  • Dado que o médico digita “ay” e não existe paciente com esse termo exato, então o sistema deve retornar pacientes com termos próximos (aproximação), como “Ayla”, “Aymoré”, ordenados por relevância.
  • Dado o médico seleciona um paciente na lista de resultados, então o sistema deve abrir o Prontuário Eletrônico do Paciente (PEP), preservando o contexto do profissional logado.
    • Dado que a busca não retorna resultados, então o sistema deve exibir MSG_searchNone
  • Dado que o médico clica no avatar ou na seta de dropdown, então o sistema deve abrir o menu do usuário abaixo, à direita, contendo ao menos o item “Sair”.
  • Dado que o menu do usuário está aberto e o médico clica fora dele, então o sistema deve fechar o menu.
  • Dado que o médico clica em “Sair” no menu, então o sistema deve encerrar a sessão (fluxo de logout fora do escopo deste documento).
  • Dado que o cabeçalho é renderizado, então ele não deve exibir nome do médico, CRM, especialidade ou unidade/local de atendimento — apenas o nome da clínica à esquerda e o avatar com dropdown à direita.

Internacionalização:

Termopt-BRen-USes-419
MSG_searchPacProcurar paciente…Search Pacient…Buscar paciente…
MSG_searchFootDigite parte do nome, CPF, data de nascimento ou nome da mãe para filtrarEnter parte of the name, ID, mother’s name or DOB to filterIngrese parte del busque por nombre, ID, nombre de la madre o fecha de nacimiento
MSG_searchMinDigite pelo menos 2 caracteres para iniciar a busca.Type at least 2 characters to start searching.Escriba al menos 2 caracteres para iniciar la búsqueda.
MSG_searchNoneNenhum paciente encontrado com os dados informados.No patient found with the provided data.Ningún paciente encontrado con los datos informados.

2.2 KPI Bar (Indicadores)

US-CM-001 — Visualizar indicadores do dia

Como médico, quero ver um resumo numérico do meu dia (consultas, tratamentos, procedimentos na clínica e alertas) em uma barra no topo, para ter um panorama rápido sem precisar analisar cada painel.

Descrição Funcional:

Layout dos cards: Cada KPI card possui uma borda lateral esquerda colorida (4px) que identifica visualmente a categoria:

  • Consultas Hoje → azul (color-secondary-pure),
  • Em Tratamento → verde (color-primary-pure),
  • Procedimentos Hoje → âmbar (color-highlight-pure),
  • Alertas de Prescrição → vermelho (color-error-pure). O ícone do KPI fica posicionado à direita do texto. Motivador: a borda colorida permite identificação rápida da categoria mesmo em uma olhada periférica, sem necessidade de leitura do título.

Justificativa de workflow: Os 4 KPIs correspondem aos 4 workflows que a Central toca: Consultas Hoje (Workflow 1), Em Tratamento (Workflow 2), Alertas de Prescrição (Workflow 3) e Procedimentos Hoje (Workflow 2). No início do turno (touchpoint comum a todos os workflows), o médico precisa calibrar expectativas de carga de trabalho — os KPIs dão isso em um olhar, sem abrir nenhum painel. Cada KPI é clicável porque, após o panorama inicial, o médico pode precisar aprofundar em um workflow específico.

A KPI Bar é uma faixa horizontal entre o cabeçalho e o grid 2×2, contendo 4 cartões de indicadores fixos. Cada cartão é clicável e navega para a tela dedicada correspondente.

  1. Consultas Hoje: Exibe “finalizadas/total” (ex: “4/6”). Subtexto: “X pendentes · Y atrasadas”.
    • Definição dos termos:
      • Pendentes: agendas que ainda não foram finalizadas
      • Atrasadas: agendas pendentes cuja hora marcada já passou
    • Ao clicar: abre a tela de Agenda de Consultas
  2. Em Tratamento: Exibe o total de pacientes em tratamento (número principal) cujo médico assistente é o médico logado. Subtexto: ‘Y não aderentes’.
    • Definição dos termos:
      • Em tratamento (número principal): pacientes que estão em tratamento cujo médico assistente é o médico logado
      • Não aderentes: pacientes em tratamento que não estão conformes com o planejamento do tratamento (plano terapêutico) — pode ser por descompasso com os intervalos entre sessões e/ou ciclos previstos
    • Ao clicar: abre a tela de Pacientes do Médico
  3. Procedimentos Hoje: Exibe o total de pacientes do médico logado com procedimentos (não consultas) previstos ou em andamento na unidade hoje. Subtexto: “X em atendimento · Y aguardando · Z previstos”.
    • Definição dos termos:
      • Em atendimento: pacientes do médico logado que já estão em procedimento (iniciaram quimioterapia, estão realizando cateterismo ou limpeza de cateter, iniciaram sessão radioterápica, iniciaram cirurgia, etc.)
      • Aguardando: pacientes do médico logado que estão na clínica (em check-in ou sendo preparados para o procedimento) mas ainda não iniciaram o procedimento
      • Previstos: pacientes do médico logado que ainda não chegaram à clínica mas têm procedimento previsto para o dia
    • Ao clicar: abre a tela de Procedimentos na Clínica
  4. Alertas de Prescrição: Exibe o total de prescrições pendentes urgentes (menos de 12h para aplicação). Subtexto: “X p/ revisar · Y p/ assinar (próx. 7 dias)”.
    • Definição dos termos:
      • Urgentes (número principal): prescrições pendentes de assinatura E pendentes de revisão com menos de 12h para a aplicação agendada
      • P/ revisar: prescrições marcadas automaticamente pelo sistema como “a revisar” (agenda de aplicação a 1 dia ou menos e ainda sem revisão de nenhum médico — ver RN-CM-020)
      • P/ assinar (próx. 7 dias): prescrições pendentes de assinatura com aplicação prevista nos próximos 7 dias
    • Quando o valor principal (urgentes) for maior que zero, o número deve aparecer em vermelho (cor error-pure, f44336).
    • Ao clicar: abre a tela de Prescrições Pendentes

Critérios de Aceitação:

  • Dado que o médico abre a Central, quando os dados são carregados, então a KPI Bar deve exibir os 4 indicadores com seus respectivos subtextos.
  • Dado que o KPI de Alertas de Prescrição tem 3 prescrições urgentes, então o número “3” deve ser exibido em vermelho (#F44336).
  • Dado que o KPI de Alertas de Prescrição tem 0 urgentes, então o número “0” deve ser exibido na cor padrão (não vermelho).
  • Dado que o médico clica no card “Consultas Hoje”, então o sistema deve navegar para a tela de Agenda de Consultas.
  • Dado que o médico clica no card “Em Tratamento”, então o sistema deve navegar para a tela de Pacientes do Médico.
  • Dado que o médico clica no card “Procedimentos Hoje”, então o sistema deve navegar para a tela de Procedimentos na Clínica.
  • Dado que o médico clica no card “Alertas de Prescrição”, então o sistema deve navegar para a tela de Prescrições Pendentes.
  • Dado que o endpoint de KPIs falha, então a KPI Bar deve exibir ”—” nos valores e os demais painéis devem continuar operando normalmente.

Internacionalização

Termo/Localizaçãopt-BRen-USes-419
Card Consultas Hoje - títuloCONSULTAS HOJETODAY’S APPOINTMENTSCONSULTAS DE HOY
Card Consultas Hoje - pendentespendentespendingpendientes
Card Consultas Hoje - atrasadasatrasadasdelayedatrasada
Card Em tratamento - títuloEM TRATAMENTOIN TREATMENTEN TRATAMIENTO
Card Em tratamento - não aderentesnão aderentesnon-adherentno adherentes
Card Procedimentos hoje - títuloPROCEDIMENTOS HOJETODAY’S PROCEDURESPROCEDIMIENTOS HOY
Card Procedimentos hoje - em atendimentoem atendimentoin progressen atención
Card Procedimentos hoje - aguardandoaguardandowaitingen espera
Card Procedimentos hoje - previstosprevistosscheduledprogramados
Card Alertas de prescrição = títuloALERTAS DE PRESCRIÇÃOPRESCRIPTION ALERTSALERTAS DE PRESCRIPCIÓN
Card Alertas de prescrição - revisarp/revisarto reviewp/revisar
Card Alertas de prescrição - assinarp/assinar (próx. 7 dias)to sign (next 7 days)p/firmar (próx. 7 dias)

2.3 Painel de Consultas (Quadrante Superior Esquerdo)

US-CM-002 — Visualizar consultas do dia

Como médico, quero ver minhas consultas do dia em ordem cronológica com informações clínicas relevantes, para organizar meu fluxo de atendimento.

US-CM-003 — Filtrar consultas pendentes

Como médico, quero alternar entre “Todas” e “Pendentes” no painel de consultas, para focar apenas nas consultas que ainda não foram realizadas.

Descrição Funcional:

O painel de Consultas exibe a agenda do médico para o dia, composta por cards de consulta. Cada card contém:

  • Hora prevista da consulta e hora prevista de término (ou tempo previsto da consulta)
  • Nome do paciente com foto (avatar com iniciais ou foto, quando disponível)
  • Diagnóstico(s) oncológico(s) (se houver — ex: “Ca. Mama”; um paciente pode ter mais de um diagnóstico oncológico ativo, e todos são exibidos)
  • Tipo de consulta (Presencial / Teleatendimento)
  • Tags de atenção (se houver — ex: VIP, Quimioterapia, 1ª Consulta)
  • Status da agenda (Agendado, Aguardando Recepção, Recepcionado, Em Atendimento, Finalizado)

Ordenação: A lista deve estar ordenada pela hora agendada. Consultas finalizadas permanecem na sua posição original na ordem cronológica.

Filtro: Deve haver um toggle em estilo pill (botões com borda discreta e fundo branco) entre ‘Todas’ e ‘Pendentes’. O default é mostrar apenas as pendentes (oculta finalizadas). Ao alternar para ‘Todas’, as finalizadas aparecem na sua posição original esmaecidas. Motivador: o estilo pill é mais discreto que o bloco cinza anterior, competindo menos com o conteúdo dos cards.

Tratamento visual de finalizadas: Consultas finalizadas devem aparecer esmaecidas (cor de fundo mais clara e opacidade reduzida), mantendo a ordem cronológica da agenda. Não são movidas para o final da lista — permanecem na sua posição original, apenas com sinalização visual de que já foram concluídas.

Atraso: Quando o paciente está atrasado (horário agendado já passou e a consulta não foi iniciada nem finalizada), mostrar um sinalizador de “Atrasado” com o tempo de atraso em minutos (ex: ”🔴 15min”).

Cores de status: Agendado ffe0b3, Aguardando Recepção ffc5c1, Recepcionado b7e3ff, Em Atendimento d1df9d, Finalizado 9679e1.

Destaque visual por status: Além dos chips coloridos, cards com status “Recepcionado” recebem uma borda lateral esquerda (3px) na cor secondary-pure (azul), sinalizando que o paciente está pronto para atendimento. Cards com status “Aguardando Recepção” recebem borda lateral esquerda (3px) na cor ff8a80 (vermelho-claro), sinalizando que o paciente está presente na clínica. Outros status não recebem borda adicional. Motivador: a borda colorida é consistente com o padrão de KPI cards e permite identificação periférica dos pacientes prontos para atendimento sem necessidade de leitura do chip.

Navegação: O título do painel (‘Consultas’) é um link clicável para a tela dedicada da Agenda do Médico. No hover, o título muda de cor e uma seta (→) aparece ao lado. Ao clicar em qualquer card de consulta, o sistema abre o Prontuário Eletrônico do Paciente (PEP). Motivador: o título como link elimina a necessidade de um botão separado ‘Ver agenda →’, reduzindo ruído visual no cabeçalho do painel.

Justificativa de workflow: Este painel atende ao touchpoint ‘Antes de chamar o próximo paciente’ (Workflow 1). O toggle default ‘Pendentes’ reflete que, neste momento do workflow, o médico só precisa ver quem falta atender. O badge de atraso reflete a exceção do workflow onde o horário passou e o paciente não foi chamado. O clique no card abrindo o PEP reflete que, neste momento, o médico precisa do prontuário para se preparar antes de chamar o paciente.

Estado vazio: Quando não há consultas agendadas, exibir ícone de calendário vazio com a mensagem MSG_appointmentNone.

Critérios de Aceitação:

  • Dado que o médico abre a Central, quando o painel de Consultas carrega, então o default deve mostrar apenas consultas pendentes, ordenadas por hora agendada.
  • Dado que o médico clica no toggle “Todas”, então as consultas finalizadas devem aparecer na sua posição original esmaecidas (cor de fundo mais clara e opacidade reduzida).
  • Dado que uma consulta agendada para 09:00 não foi iniciada e são 09:15, então o sistema deve exibir o sinalizador ”🔴 Atrasado · 15min” no card da consulta.
  • Dado que o médico clica em qualquer card de consulta, então o sistema deve abrir o Prontuário Eletrônico do Paciente (PEP).
  • Dado que o médico clica no título do painel, então o sistema deve navegar para a tela dedicada da Agenda do Médico.
  • Dado que não há consultas agendadas no dia, então o painel deve exibir “Nenhuma agenda para este dia” (MSG_appointmentNone).
  • Dado que uma consulta tem status “Recepcionado”, então o card deve exibir borda lateral esquerda na cor secondary-pure (azul).
  • Dado que uma consulta tem status “Aguardando Recepção”, então o card deve exibir borda lateral esquerda na cor ff8a80.

Internacionalização

Termo/Localizaçãopt-BRen-USes-419
painel-títuloConsultasAppointmentsConsultas
toggle-todasTodasAllTodas
toggle-pendentesPendentesPendingPendientes
chip-atrasadoAtrasadoDelayedAtrasado
status-agendadoAgendadoShceduledAgendada
status-aguardandoAguardando recepçãoWaiting receptionEsperando recepción
status-recepcionadoRecepcionadoChecked-inRecepcionado
status-atendimentoEm AtendimentoIn consulationEn atención
status-finalizadoFinalizadoCompletedFinalizado
MSG_appointmentNoneNenhuma agenda para este diaNo appointments for this dayNo hay citas para este día

2.4 Painel de Prescrições Pendentes (Quadrante Superior Direito)

US-CM-004 — Visualizar prescrições pendentes

Como médico, quero ver as prescrições pendentes de assinatura ou revisão ordenadas por prioridade, para priorizar as que têm menos tempo até a aplicação agendada.

US-CM-005 — Assinar prescrição

Como médico, quero revisar e assinar prescrições pendentes diretamente no Cockpit, para que a farmácia possa liberar o medicamento a tempo.

US-CM-006 — Revisar prescrição

Como médico, quero revisar e confirmar prescrições que o sistema marcou automaticamente como pendentes de revisão perto da aplicação, para garantir que algum médico validou a prescrição antes da administração ao paciente.

Descrição Funcional:

O painel de Prescrições Pendentes lista as prescrições que aguardam ação do médico, divididas em duas categorias:

  1. Prescrições para assinar — aguardando assinatura digital do médico
  2. Prescrições para revisar — marcadas automaticamente pelo sistema quando a agenda de aplicação está a 1 dia (24h) ou menos da data/hora atual e a prescrição ainda não foi revisada por nenhum médico (RN-CM-020). Uma prescrição só entra nessa categoria depois de já assinada — é uma segunda barreira de segurança, independente e posterior à assinatura, não relacionada a notificações de farmácia ou enfermagem.

Cada card de prescrição contém:

  • Nome do paciente (nome social, se houver; caso contrário nome civil) — o endpoint envia ambos (preferredName/legalName, mesmo padrão do painel de Consultas e de Procedimentos) para o médico perceber uma eventual questão de nome social/gênero antes de abrir a prescrição
  • Código da prescrição, no formato “Protocolo CxDy” (ex: “CARBO-TAXOL C3D1”, onde C = ciclo e D = dia/sessão) — vem pronto do banco (Prescricao.Identificacao), sem montagem de protocolo e ciclo como campos separados
  • Data da agenda da aplicação agendada (ex: “22/07 18:00”) — importante principalmente para prescrições pendentes de revisão, pois a data é o próprio motivo pelo qual a prescrição está nessa lista
  • Tempo restante até a aplicação agendada (ex: “8h restantes”)
  • Tipo de ação (Assinar ou Revisar)
  • No caso de revisões: destaque visual (não texto com nomes de terceiros) quando o médico logado é revisor preferencial — prescritor ou médico assistente do paciente (RN-CM-020)

Ordenação: As prescrições devem ser ordenadas por prioridade — quanto mais próxima da data de aplicação agendada, mais prioritária é. As prescrições com menos de 12 horas restantes são classificadas como “Críticas” e marcadas com chip vermelho (fundo ffebee, texto d32f2f). Entre 12h e 24h, são “Atenção” e marcadas com chip laranja (fundo fff8e1, texto ff8f00).

Filtro “Meus / Todos”: O painel tem um toggle em estilo pill entre “Meus” e “Todos”, que afeta apenas as prescrições para revisar — as prescrições para assinar já são, por definição, sempre do médico logado (RN-CM-012: só o próprio prescritor assina) e por isso aparecem em ambos os estados do toggle, sem alternância. “Meus” mostra apenas as prescrições a revisar em que o médico logado é o prescritor ou o médico assistente do paciente (revisor preferencial, RN-CM-020). “Todos” mostra todas as prescrições a revisar de qualquer médico da clínica, somadas às prescrições a assinar do próprio médico logado. O default é “Meus” — consistente com o padrão do painel de Procedimentos — porque as prescrições a assinar nunca são escondidas pelo toggle; o filtro só decide o quanto o médico quer ver do trabalho de revisão de colegas.

Assinatura (tela de assinatura): Ao clicar em “Assinar”, o sistema abre a tela de assinatura com a prescrição completa para que o médico revise e se certifique de que é a prescrição correta. É responsabilidade do médico saber o que está assinando — a assinatura não pode ser um clique direto sem revisão. A tela exibe todos os medicamentos, doses, vias, diluentes e instruções. Após revisão, o médico clica em “Assinar” na tela, que processa a assinatura conforme o modo configurado (com ou sem certificado digital). Em caso de sucesso, o sistema exibe um toast de sucesso “Prescrição assinada com sucesso” e retorna automaticamente à Central do Médico, focada no painel de Prescrições Pendentes, com o KPI de Alertas atualizado. Em caso de erro, exibir toast de erro na tela de assinatura.

Revisão (tela de revisão): Ao clicar em “Revisar”, o sistema abre a prescrição completa (medicamentos, doses, vias, diluentes e instruções) para o médico validar. O médico tem duas opções: (a) confirmar que a prescrição está correta, clicando em “Marcar como Revisada” — o sistema registra o médico revisor e o timestamp, e a prescrição sai da lista de pendentes; ou (b) identificar necessidade de ajuste e editar a prescrição, recalculando, adicionando ou retirando medicamentos. Nesse segundo caso, o sistema cancela a prescrição atual e cria uma nova versão com os itens ajustados — como é uma prescrição nova, ela precisa ser assinada novamente pelo médico (mesmo fluxo de assinatura de uma prescrição inédita) antes de sair da lista de pendentes; a edição por si só não conta como revisão. Em ambos os casos, o sistema retorna automaticamente à Central do Médico, focada no painel de Prescrições Pendentes, com o contador atualizado.

Assinatura digital: A assinatura pode ocorrer de 2 modos, conforme configuração do cliente:

  • (a) Assinatura com certificado digital — exige certificado ativo vinculado ao perfil do médico
  • (b) Assinatura sem certificado — validada pela sessão do usuário logado

Navegação: O título do painel (‘Prescrições Pendentes’) é um link clicável para a tela dedicada. No hover, o título muda de cor e uma seta (→) aparece ao lado.

Justificativa de workflow: Este painel atende aos touchpoints ‘Assinar prescrições pendentes’ e ‘Confirmar que a prescrição foi revisada por um médico antes da administração ao paciente’ (Workflow 3). O botão ‘Assinar’ abre a tela de assinatura com a prescrição completa porque é responsabilidade do médico saber o que está assinando — a assinatura não pode ser um clique direto sem revisão. Os chips de urgência (vermelho/laranja) refletem que, no workflow, a prioridade é cronológica: quanto menos tempo até a aplicação, mais urgente — e essa lógica vale igualmente para prescrições pendentes de assinatura e de revisão (RN-CM-020), já que ambas representam risco crescente conforme a aplicação se aproxima. A prescrição completa é sempre exibida para revisão porque o médico precisa validar o que está confirmando, não apenas um resumo. O retorno automático à Central após assinar ou revisar reflete que o médico precisa voltar ao seu fluxo de trabalho sem navegação manual. O toggle “Meus/Todos” atende ao touchpoint ‘Ao revisar prescrições’: no workflow, o médico prioriza naturalmente seus próprios pacientes, mas precisa poder ajudar a revisar as de colegas ausentes — daí a possibilidade de alternar sem sair da tela.

Estado vazio: Quando não há prescrições pendentes, exibir ícone de check com a mensagem MSG_prescriptionNone.

Critérios de Aceitação:

  • Dado que uma prescrição possui 10 horas restantes para aplicação, então o sistema deve renderizar o chip de tempo com fundo vermelho claro (#FFEBEE) e texto em vermelho (#D32F2F), e o KPI de Alertas deve exibir o valor em vermelho.
  • Dado que uma prescrição possui 18 horas restantes, então o sistema deve renderizar o chip com fundo laranja claro (#FFF8E1) e texto laranja (#FF8F00).
  • Dado que o médico clica em “Assinar” em uma prescrição, então o sistema deve abrir a tela de assinatura com a prescrição completa para revisão.
  • Dado que o médico clica em “Assinar” na tela de assinatura, quando a assinatura é validada (via certificado ou sessão, conforme configuração), então o sistema deve exibir toast de sucesso, retornar automaticamente à Central do Médico focada no painel de Prescrições Pendentes, e o KPI de Alertas deve subtrair uma unidade.
  • Dado que o médico clica em “Assinar” na tela de assinatura e a assinatura falha, então o sistema deve exibir um toast de erro na tela de assinatura: “Falha na validação da assinatura. Verifique seu certificado ou tente novamente.”
  • Dado que uma prescrição assinada tem aplicação agendada para 1 dia (24h) ou menos a partir de agora e ainda não foi revisada, então o sistema deve marcá-la automaticamente como “a revisar” e exibi-la no painel com ação “Revisar” (RN-CM-020).
  • Dado que o médico clica em “Revisar”, então o sistema deve abrir a prescrição completa, permitindo confirmar a revisão (“Marcar como Revisada”) ou editar a prescrição.
  • Dado que o médico clica em “Marcar como Revisada”, então o sistema deve registrar o médico revisor e o timestamp, exibir toast de sucesso, remover a prescrição da lista de pendentes e retornar automaticamente à Central do Médico focada no painel de Prescrições Pendentes.
  • Dado que o médico edita a prescrição na tela de revisão e clica em “Gravar”, então o sistema deve cancelar a prescrição atual, criar uma nova versão com os itens ajustados e exigir que o médico assine essa nova versão (mesmo fluxo de assinatura), retornando automaticamente à Central do Médico focada no painel de Prescrições Pendentes após a assinatura.
  • Dado que o médico clica no título do painel, então o sistema deve navegar para a tela dedicada de Prescrições Pendentes.
  • Dado que não há prescrições pendentes, então o painel deve exibir “Sem prescrições pendentes” (MSG_prescriptionNone).
  • Dado que a prescrição tem protocolo “CARBO-TAXOL” no Ciclo 3, Dia 1, então o sistema deve exibir o código da prescrição como recebido do endpoint (prescriptionCode), ex: “CARBO-TAXOL C3D1”, sem remontar a string a partir de campos separados.
  • Dado que o card de prescrição exibe a data da agenda, então o formato deve ser “dd/MM HH:mm” (ex: “22/07 18:00”).
  • Dado que o painel é carregado, então o toggle “Meus/Todos” deve iniciar na posição “Meus”.
  • Dado que o médico ativa o toggle “Meus”, então o painel deve exibir apenas as prescrições a revisar em que o médico logado é o prescritor ou o médico assistente do paciente, mantendo visíveis todas as prescrições a assinar do médico logado (estas não são afetadas pelo toggle).
  • Dado que o médico ativa o toggle “Todos”, então o painel deve exibir todas as prescrições a revisar da clínica, somadas às prescrições a assinar do médico logado.

Internacionalização

Termo/Localizaçãopt-BRen-USes-419
painel-títuloPrescrições PendentesPending PrescriptionsPrescripciones Pendientes
botão-assinarAssinarSignFirmar
botão-revisarRevisarReviewRevisar
chip-T_restanteT RESTANTEST REMAININGT RESTANTES
MSG_prescriptionNoneSem prescrições pendentesNo pending prescriptionsSin prescripciones pendientes
MSG_prescriptionSignedPrescrição assinada com sucessoPrescription signed successfullyPrescripción firmada con éxito
MSG_prescriptionSignErrorFalha na validação da assinatura. Verifique seu certificado ou tente novamente.Signature validation failed. Check your certificate or try again.Fallo en la validación de la firma. Verifique su certificado o intente nuevamente.
toggle-meusMeusMineMíos
toggle-todosTodosAllTodos

2.5 Painel de Notificações (Quadrante Inferior Esquerdo)

Modelo de dados de Conversas e Alertas (IP.Mensageria, ver documento “Conversas-e-Alertas”): o que seria uma “mensagem” avulsa é uma Conversa (thread), que pode acumular várias Mensagem — o médico pode responder, não só resolver/descartar. Escopo do painel: este painel compacto do Cockpit mostra todas as conversas em aberto do médico logado — lidas e não lidas —, priorizando as não lidas na ordenação; uma conversa só deixa de aparecer aqui quando é resolvida ou descartada. Uma tela dedicada, ainda não especificada neste documento, mostrará também as conversas já finalizadas (resolvidas ou descartadas), formando o histórico completo.

US-CM-007 — Receber conversas e alertas internos

Como médico, quero ver conversas e alertas da equipe (recepção, farmácia, enfermagem) e do próprio sistema, ordenados por prioridade, para responder solicitações e recados sem precisar acessar outra tela.

US-CM-008 — Arquivar conversa resolvida

Como médico, quero marcar uma conversa como resolvida ou descartá-la com um clique, para manter minha lista enxuta e o foco nas pendentes.

US-CM-012 — Responder rapidamente

Como médico, quero responder uma conversa com uma frase curta direto do Cockpit, para esclarecer algo simples sem precisar telefonar ou sair do meu fluxo de trabalho.

Descrição Funcional:

O painel de Notificações exibe todas as Conversa do médico logado que ainda não estão em estado final (Status diferente de Resolvida e Descartada), estejam lidas ou não lidas para ele (ver definição de “não lida” em RN-CM-015). Ler uma conversa (abrir o modal, ou responder) não a remove do painel — ela permanece visível, mas passa para o grupo de prioridade reduzida na ordenação (ver “Ordenação” abaixo), perdendo o destaque visual de não lida. Motivador: uma conversa aberta ainda pode estar pendente de alguma ação do médico (resolver, descartar, aguardar retorno) mesmo depois de lida — escondê-la do painel faria o médico perder o rastro dela até a próxima atividade, obrigando-o a confiar na memória para lembrar do que ficou em aberto. O histórico completo (incluindo as já resolvidas/descartadas) continua reservado à tela dedicada mencionada acima. Uma conversa chega ao médico de duas formas: enviada diretamente a ele (DestinoUsuarioId), ou enviada a uma equipe da qual ele é membro (EquipeId, snapshot em ConversaDestinatario). Cada card contém:

  • Ícone identificador: bell da biblioteca Lucide para alertas automáticos (OrigemTipo = sistema); ícone genérico de pessoa para qualquer conversa aberta por um usuário (OrigemTipo = usuario), seja ela endereçada a um indivíduo ou a uma equipe. Não há distinção visual por equipe (recepção/farmácia/enfermagem) — o modelo de dados não tem um campo de tipo/ícone em Equipe, então abolimos essa distinção em vez de inventar uma camada de compatibilidade por nome.
  • Texto da última mensagem da thread (não necessariamente a inicial — se a equipe respondeu depois do médico, o card mostra a resposta mais recente)
  • Nome de quem escreveu a última mensagem, com timestamp (ex: “Há 15 min · Sandra”; para alertas automáticos, “Há 15 min · Sistema”) — sem o nome da equipe junto, apenas o nome da pessoa
  • Prazo (opcional): quando Conversa.PrazoEmUtc está definido, exibir no card (ex: ”⏰ Responder até 14:00”)
  • Selo de status quando a conversa já foi respondida pelo médico em algum momento (Status = Respondida) mas voltou a ficar não lida por nova atividade — sinaliza que a resposta já foi dada e há algo novo desde então, diferenciando de uma conversa nunca respondida
  • Quando Conversa.PacienteId está definido, um link com o nome do paciente relacionado, permitindo abrir o contexto clínico diretamente (o painel não concede acesso ao prontuário por si só — a autorização continua sendo validada pelo módulo de destino, conforme a fronteira de responsabilidade do IP.Mensageria)

Conversas não lidas: Uma conversa está não lida para o médico logado quando Conversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida (ou quando não existe ConversaLeitura para ele ainda) — essa condição não filtra quais conversas aparecem no painel (todas as não finalizadas aparecem, lidas ou não), mas determina dois efeitos: (a) o destaque visual — ícone na cor de destaque (primary-pure) e borda lateral esquerda em secondary-pure, aplicados somente aos cards não lidos; cards já lidos usam a borda neutra padrão (transparente) e o ícone na cor neutra; (b) a camada de prioridade na ordenação (ver “Ordenação” abaixo). Ao abrir o modal da conversa, o sistema grava ConversaLeitura.UltimaSequenciaLida = Conversa.UltimaSequencia para aquele médico; o card perde o destaque visual e migra para a camada de prioridade reduzida na próxima atualização da lista, mas permanece no painel — para os demais membros de uma equipe, o indicador de não lida permanece intacto (leitura é individual). Não é utilizado negrito no texto — a distinção visual vem exclusivamente do ícone colorido e da borda lateral (RN-CM-019).

Ordenação: As conversas são ordenadas em duas camadas. Primeiro, por estado de leitura: todas as conversas não lidas aparecem antes de todas as lidas — uma conversa lida nunca aparece à frente de uma não lida, mesmo que tenha prazo mais próximo ou atividade mais recente. Dentro de cada camada, os mesmos dois critérios decidem a ordem (proposta do Designer para manter consistência entre as duas camadas — critério de desempate das lidas a confirmar com o solicitante):

  1. Prazo (PrazoEmUtc, quando definido) — conversas com prazo mais próximo aparecem primeiro
  2. Atividade mais recente (LIFO) — para conversas sem prazo, a com a mensagem mais recente aparece primeiro

Ação no card: Cada card exibe um botão “Resolvido” que muda Conversa.Status para Resolvida, removendo o card da lista. O contador do painel deve ser atualizado. Isso é uma mudança de estado, não uma exclusão — a conversa continua existindo (auditável), só sai da visão de pendentes.

Ação ao clicar no card: Ao clicar no card, o sistema abre um modal com a thread completa (todas as Mensagem da conversa, em ordem, cada uma com autor e timestamp), o prazo (se houver) e três ações:

  • Campo de texto + botão “Responder” — envia uma nova Mensagem (Tipo=Resposta), atualiza Conversa.UltimaSequencia e muda Status para Respondida. A conversa permanece na lista (responder não é uma ação terminal) — o médico pode continuar acompanhando até que a equipe confirme ou até resolver/descartar.
  • “Resolvido” — mesma função do botão no card.
  • “Descartado” — muda Conversa.Status para Descartada. Isso não exclui a conversa (o modelo de dados é append-only, preserva histórico/auditoria) — apenas sai da lista de pendentes, com o mesmo efeito visual de “Resolvido” para o médico. Motivador: o descarte permite remover conversas informativas ou irrelevantes que não exigem resposta, sem marcá-las como resolvidas — a distinção fica registrada para quem precisar auditar depois.

Conversas do sistema (alertas automáticos): Geradas por módulos da plataforma (ex: Prescrição, Infusão, Agenda) via AlertaPublicacao, com chave de idempotência garantindo que retries não dupliquem o alerta. Usam o ícone bell da biblioteca Lucide e seguem as mesmas regras de ordenação, destaque de não lidas e ações — exceto “Responder”, que não se aplica a alertas puramente informativos sem um destinatário humano do outro lado (o front deve ocultar o campo de resposta quando OrigemTipo = sistema, mantendo apenas Resolvido/Descartado). O remetente exibido é “Sistema”. Se a mesma anomalia recorrer depois que a conversa anterior foi resolvida ou descartada, uma nova Conversa é criada apontando para a anterior via ConversaAnteriorId — o médico vê um alerta novo, não a reabertura do antigo.

Padrão de internacionalização (i18n) para mensagens do sistema:

As mensagens geradas pelo sistema devem ser documentadas com suas versões em português (pt-BR), inglês (en-US) e espanhol (es-419), conforme tabela padrão abaixo:

Código da mensagempt-BRen-USes-419
SYS_PRESC_URGENTPrescrição urgente: menos de 12h para aplicação de {paciente}.Urgent prescription: less than 12h until application for {patient}.Prescripción urgente: menos de 12h para aplicación de {paciente}.
SYS_PRESC_REVIEWPrescrição de {paciente} pendente de revisão antes da aplicação.Prescription for {patient} pending review before application.Prescripción de {paciente} pendiente de revisión antes de la aplicación.
SYS_INFUSION_DELAYInfusão de {paciente} ultrapassou o tempo previsto.Infusion for {patient} exceeded expected time.Infusión de {paciente} superó el tiempo previsto.
SYS_PATIENT_CHECKIN{paciente} fez check-in na recepção.{patient} checked in at reception.{paciente} hizo check-in en recepción.
SYS_SESSION_REMINDERLembrete: {paciente} tem sessão prevista para {horario}.Reminder: {patient} has session scheduled for {time}.Recordatorio: {paciente} tiene sesión programada para {horario}.

Convenções da tabela:

  • Variáveis entre chaves {} são substituídas dinamicamente pelo sistema
  • O código da mensagem (SYS_*) é usado internamente para identificação e não é exibido ao usuário
  • Cada mensagem de sistema corresponde ao Conteudo da Mensagem inicial de uma Conversa com OrigemTipo = sistema
  • Novas mensagens do sistema devem ser adicionadas a esta tabela como padrão de documentação
  • A tradução para o idioma do usuário é feita no backend; o frontend recebe a mensagem já traduzida

Navegação: O título do painel (‘Notificações’) é um link clicável para a tela dedicada de Mensagens. No hover, o título muda de cor e uma seta (→) aparece ao lado.

Justificativa de workflow: Este painel atende aos touchpoints ‘Receber conversas e alertas da equipe’ e ‘Responder sem sair do Cockpit’ (Workflow 4). O painel manter conversas lidas visíveis, mas em prioridade reduzida, reflete que, neste espaço compacto do Cockpit, o médico precisa ver “o que está pendente de atenção agora” sem perder de vista o que já leu mas ainda não encerrou — uma conversa já lida deixa de competir pela atenção imediata dele, mas continua acessível até ser resolvida ou descartada, evitando que ele precise confiar na memória para lembrar do que ficou em aberto. O destaque visual e a prioridade das não lidas refletem que, no workflow, conversas não lidas são as pendências que mais competem com a atenção do médico agora. A ordenação por prazo reflete que conversas com prazo são compromissos no workflow. O campo de resposta no modal reflete que muitas dúvidas simples podem ser resolvidas com uma frase, sem justificar uma tela de chat separada. O botão ‘Resolvido’ com um clique reflete que resolver é uma ação frequente e repetitiva no dia do médico. O botão ‘Descartado’ reflete que algumas conversas são puramente informativas e não exigem resposta — o médico precisa removê-las da lista sem marcá-las como resolvidas.

Estado vazio: Quando não há nenhuma conversa não finalizada (lida ou não lida) para o médico logado, exibir ícone de balão vazio com a mensagem MSG_notificationNone. Conversas já lidas, mas ainda em aberto, contam como conteúdo do painel — não disparam o estado vazio.

Critérios de Aceitação:

  • Dado que chega uma nova conversa da farmácia, quando os dados são atualizados, então o card deve aparecer com o ícone na cor primary-pure e borda lateral esquerda na cor secondary-pure (sem negrito no texto).
  • Dado que o médico abre o modal de uma conversa não lida, então o sistema deve gravar ConversaLeitura.UltimaSequenciaLida = Conversa.UltimaSequencia para aquele médico, e o card deve perder o destaque visual de não lida e migrar para depois das conversas não lidas na atualização seguinte da lista — sem sair do painel.
  • Dado que duas conversas da mesma camada de leitura (ambas não lidas, ou ambas lidas) têm prazo definido, quando a lista é renderizada, então a conversa com prazo mais próximo deve aparecer primeiro dentro daquela camada.
  • Dado que conversas sem prazo existem dentro de uma mesma camada de leitura, então elas devem ser ordenadas pela atividade mais recente (LIFO), após as conversas com prazo daquela camada.
  • Dado que existem conversas lidas e não lidas ao mesmo tempo, quando a lista é renderizada, então todas as não lidas devem aparecer antes de todas as lidas, independentemente de prazo ou atividade.
  • Dado que o médico digita uma resposta no modal e clica em “Responder”, então o sistema deve gravar uma nova mensagem na thread, mudar o status da conversa para “Respondida”, marcar a conversa como lida para o médico (a própria resposta conta como leitura) e migrá-la para a camada de prioridade reduzida do painel, sem removê-la — a menos que já exista atividade não vista por ele antes da resposta, caso em que ela permanece na camada de não lidas.
  • Dado que uma conversa já foi respondida pelo médico e uma nova mensagem chega depois disso, então a conversa deve retornar à camada de não lidas do painel (destaque visual restaurado), com o selo indicando que já houve resposta anterior.
  • Dado que o médico clica em “Resolvido” no card ou no modal, então o status da conversa deve mudar para “Resolvida”, o card deve ser removido da lista e o contador do painel deve ser atualizado.
  • Dado que o médico clica no card de uma conversa, então o sistema deve abrir um modal com a thread completa, contendo o campo de resposta e os botões “Resolvido” e “Descartado”.
  • Dado que o médico clica em “Descartado” no modal, então o status da conversa deve mudar para “Descartada” (sem excluir o registro), o card deve ser removido da lista e o contador do painel deve ser atualizado.
  • Dado que a conversa é um alerta automático (OrigemTipo = sistema), então o modal não deve exibir o campo de resposta, apenas “Resolvido” e “Descartado”.
  • Dado que o médico clica no título do painel, então o sistema deve navegar para a tela dedicada de Comunicação/Mensagens (fora do escopo desta versão — mostrará também as conversas já finalizadas, resolvidas ou descartadas).
  • Dado que não há nenhuma conversa não finalizada (lida ou não lida) para o médico logado, então o painel deve exibir “Sem notificações pendentes” (MSG_notificationNone).

Internacionalização

Termo/Localizaçãopt-BRen-USes-419
painel-títuloNotificaçõesNotificationsNotificaciones
botão-resolvidoResolvidoResolvedResuelto
botão-descartadoDescartadoDiscardedDescartado
botão-responderResponderReplyResponder
placeholder-respostaEscreva uma resposta…Write a reply…Escriba una respuesta…
chip-respondidaRespondidaRepliedRespondida
label-háHá TT agoHace T
label-prazo-responderResponder até TReply by TResponder antes de T
MSG_notificationNoneSem notificações pendentesNo pending notificationsSin notificaciones pendientes
MSG_conversationResolvedConversa marcada como resolvidaConversation marked as resolvedConversación marcada como resuelta
MSG_conversationDiscardedConversa descartadaConversation discardedConversación descartada
MSG_replySentResposta enviadaReply sentRespuesta enviada

2.6 Painel de Procedimentos na Clínica (Quadrante Inferior Direito)

US-CM-009 — Monitorar procedimentos na clínica

Como médico, quero ver todos os pacientes presentes na unidade com seus status e localização, para planejar visitas e saber quem está disponível para atendimento.

US-CM-010 — Acompanhar tempo de atendimento

Como médico, quero ver o progresso do atendimento de pacientes em tratamento com barra de tempo, para saber quanto tempo já passou e se está dentro do previsto.

US-CM-011 — Filtrar meus pacientes na clínica

Como médico, quero filtrar a lista de pacientes na clínica para mostrar apenas os meus, para me concentrar nos pacientes sob minha responsabilidade.

Descrição Funcional:

O painel de Procedimentos na Clínica monitora pacientes previstos e presentes na unidade para procedimentos terapêuticos e diagnósticos (infusões, aplicações, checagens) — não inclui consultas médicas, apenas procedimentos.

Cada card de paciente contém:

  • Nome do paciente com avatar
  • Prescrição do procedimento (ex: “AC-T C2D14”, “Paclitaxel C1D8”)
  • Local na clínica (ex: “Consultório 4”, “Sala de Infusão 2”)
  • Status do procedimento (Agendado, Aguardando recepção, Recepcionado, Preparo, Em administração, Completo ou Finalizado — domínio completo em ProcedureStatus(), Seção 4.3/4.4)
  • Horário de início do atendimento (quando aplicável)
  • Duração estimada do procedimento (quando aplicável)
  • Tags do paciente (se houver)

Barra de progresso: Pacientes com status ‘Em Atendimento’ exibem uma barra de progresso inline (50px de largura, 3px de altura) na mesma linha do nome do paciente, acompanhada da porcentagem em fonte reduzida (9px). A barra é extremamente discreta e não ocupa uma linha separada, evitando que pareça uma divisão entre cards. Se o tempo decorrido ultrapassar 100% da estimativa, a barra torna-se vermelha (cor error-pure, f44336). Motivador: a barra na linha do nome é percebida como parte do card, não como separador. O destaque visual fica reservado apenas para o estado de atraso (vermelho).

Filtro ‘Meus pacientes’: O toggle em estilo pill (não checkbox) alterna entre ‘Meus’ e ‘Todos’. O filtro ‘Meus’ está ativado por padrão. Motivador: o estilo pill é consistente com o toggle do painel de Consultas e mais discreto que um checkbox.

Navegação: O título do painel (‘Procedimentos na Clínica’) é um link clicável para a tela dedicada. No hover, o título muda de cor e uma seta (→) aparece ao lado. Ao clicar no card de paciente, o sistema abre a prescrição do procedimento (se houver). O card contém um botão separado ‘PEP’ para abrir o Prontuário Eletrônico. Se não houver prescrição associada, o clique no card abre diretamente o PEP.

Justificativa de workflow: Este painel atende aos touchpoints ‘Saber quais pacientes estão em procedimento’ e ‘Decidir se precisa intervir’ (Workflow 2). A barra de progresso discreta reflete que, no workflow de monitoramento, o médico faz checagem periférica entre atendimentos — não precisa de detalhe, só de saber se está dentro ou fora do tempo. O clique no card abrindo a prescrição (e não o PEP) reflete que, neste momento do workflow, o médico primeiro verifica o que está sendo infundido antes de decidir se precisa do prontuário completo. O botão ‘PEP’ separado reflete que o prontuário é a ação secundária neste momento.

Estado vazio: Quando não há pacientes na unidade, exibir ícone de prédio vazio com a mensagem MSG_proceduresNone.

Critérios de Aceitação:

  • Dado que um paciente está com status “Em Atendimento”, então o painel deve exibir uma barra de progresso inline na linha do nome representando o tempo decorrido vs. a duração estimada.
  • Dado que o tempo decorrido ultrapassa 100% da duração estimada, então a barra de progresso deve mudar para a cor vermelha (#F44336).
  • Dado que o toggle “Meus” está ativado, então o painel deve exibir apenas pacientes com vínculo direto com o médico logado.
  • Dado que o médico desativa o toggle “Meus”, então o painel deve exibir todos os pacientes presentes na unidade.
  • Dado que o médico clica no card de paciente, então o sistema deve abrir a prescrição do procedimento (se houver). Se não houver prescrição associada, abre diretamente o PEP.
  • Dado que o médico clica no botão de PEP no card, então o sistema deve abrir o Prontuário Eletrônico do Paciente (PEP).
  • Dado que não há pacientes na unidade, então o painel deve exibir MSG_proceduresNone.

Internacionalização

Termo/Localizaçãopt-BRen-USes-419
painel-títuloProcedimentos na clínicaProcedures at the clinicProcedimientos en la clínica
toggle-todosTodosAllTodos
toggle-meusMeusMineMios
botão-PEPPEPEHRHCe
status-agendadoAgendadoScheduledProgramado
status-aguardandoAguardando recepçãoWaiting receptionEsperando recepción
status-recepcionadoRecepcionadoChecked-inRecepcionado
status-preparoPreparoPreparationPreparación
status-administrandoEm administraçãoAdministeringEn administración
status-completoCompletoCompletedCompleto
status-finalizadoFinalizadoCompletedFinalizado
status-canceladoCanceladoCanceledCancelado
MSG_proceduresNoneSem pacientes para o diaNo patients for todaySin pacientes para el día

2.7 Fluxos de Exceção e Estados (Consolidado)

PainelCondiçãoExibição
Qualquer painelFalha de endpointMSG_endpointFail
Qualquer painelQueda de conexãoManter últimos dados na tela + ícone discreto de “Offline” no canto do painel
KPI BarFalha de endpointExibir ”—” nos valores; demais painéis continuam operando

Estados de loading: Cada painel exibe skeleton loading independente quando os dados estão sendo carregados. A busca exibe um spinner dentro do campo de input durante a consulta à API.

Internacionalização

Termo/Localizaçãopt-BRen-USes-419
MSG_endpointFailDados temporariamente indisponíveis. Tente novamenteData temporarily unavailable. Try againDatos temporalmente no disponibles. Reintentar

2.8 Mapeamento de Componentes do Design System

ComponenteVarianteEstadosTokens aplicados
ButtonPrimary (Assinar)default, hover, disabled, loadingbg: color-primary-pure; text: color-base-pure; height: btn-height-sm; radius: radius-m
ButtonSecondary (Revisar)default, hover, disabledbg: transparent; border: color-primary-pure; text: color-primary-pure
ButtonTertiary (Resolvido)default, hoverbg: transparent; text: color-primary-pure
ButtonDanger (Descartado)default, hoverbg: color-error-pure; text: color-base-pure
ChipActive (Crítico)defaultbg: color-error-light; text: color-error-dark
ChipActive (Atenção)defaultbg: color-highlight-light; text: color-highlight-dark
ChipStatus (consulta)defaultVer cores por status na RN-CM-005
BadgeError (KPI Alertas)defaultbg: color-error-pure; text: color-base-pure
BadgePrimary (contadores)defaultbg: color-primary-pure; text: color-base-pure
InputBordered (busca)default, focus, disabledborder: color-neutral-light → focus: color-neutral-dark (2px); height: input-height
AvatarSM/MD (paciente)defaultbg: color-primary-light; text: color-primary-dark
ProgressBar (atendimento)default, criticalbg track: color-neutral-lighter; bg bar: color-primary-pure → critical: color-error-pure
ToastSuccess/ErrordefaultVer Design System para tokens de toast
TooltipDefaulthoverbg: color-neutral-dark; text: color-base-pure
KPI CardBorda colorida lateraldefault, hoverborder-left: 4px solid; cores: secondary-pure (consultas), primary-pure (tratamento), highlight-pure (procedimentos), error-pure (alertas)
Panel Title LinkLink com seta no hoverdefault, hoverfont-weight: 400; color: neutral-darker → hover: secondary-dark; arrow: opacity 0 → hover: opacity 1
Toggle PillFiltro de painelactive, inactiveborder: 1px solid neutral-lighter; active: bg secondary-light, text secondary-dark; inactive: bg transparent, text neutral-pure
Progress Bar InlineBarra na linha do nomedefault, dangerwidth: 50px; height: 3px; bg track: neutral-lighter; bg fill: primary-pure → danger: error-pure; pct font: 9px
Card Highlight (Consulta)Borda colorida por statusrecepcionado, aguardando, defaultrecepcionado: border-left 3px solid secondary-pure; aguardando: border-left 3px solid ff8a80; default: sem borda
Message Icon (Lucide)Origem da conversaunread, readunread: color primary-pure; read: color neutral-pure. Apenas 2 ícones: bell (sistema/alerta automático) e um ícone genérico de pessoa (conversa com usuário) — não há ícone por equipe
User Menu DropdownAvatar + caretclosed, openavatar: bg neutral-dark, text base-pure, radius-full; caret: rotate 180° quando open; menu: bg base-pure, shadow-medium, radius-m, min-width 160px; item: hover bg base-dark

2.9 Integração com Backend

Decisão arquitetural: 1 endpoint por painel + 1 endpoint por KPI card, em vez de um único endpoint consolidado. Justificativa: paralelismo (frontend dispara chamadas simultâneas), resiliência (falha em um endpoint não bloqueia os demais — especialmente importante para os KPIs, onde um problema em um indicador não trava os outros três), granularidade de atualização.

Endpoints de KPI (1 por card):

  • [GET] /api/v1/cockpit/doctor/kpis/appointments-today
    • Função: Retornar o indicador de Consultas Hoje
    • Response 200: { "total": 6, "finished": 4, "pending": 2, "delayed": 1 }
  • [GET] /api/v1/cockpit/doctor/kpis/in-treatment
    • Função: Retornar o indicador de Em Tratamento
    • Response 200: { "inTreatment": 43, "nonAdherent": 2 }
  • [GET] /api/v1/cockpit/doctor/kpis/procedures-today
    • Função: Retornar o indicador de Procedimentos Hoje
    • Response 200: { "total": 8, "inProgress": 2, "waiting": 4, "scheduled": 2 }
  • [GET] /api/v1/cockpit/doctor/kpis/prescription-alerts
    • Função: Retornar o indicador de Alertas de Prescrição
    • Response 200: { "urgent": 3, "toReview": 1, "toSignNext7Days": 5 }
    • Detalhamento dos campos: urgent = total de prescrições pendentes de assinatura OU pendentes de revisão (RN-CM-020) com hoursRemaining < 12h (RN-CM-011). toReview = total de prescrições atualmente marcadas como “a revisar”, independente da urgência. toSignNext7Days = total de prescrições pendentes de assinatura com aplicação agendada nos próximos 7 dias. Mapeamento de BD: Seção 4.6.

Endpoints de leitura (painéis):

  • [GET] /api/v1/cockpit/doctor/appointments
    • Response 200: { "appointments": [ { "id": "uuid", "patient": { "id": "uuid", "preferredName": "", "legalName": "Maria da Silva", "photo": "url|null", "diagnosis": ["Ca. Mama"] }, "startTime": "08:00", "endTime": "08:30", "status": "finished", "type": "in_person", "delayMinutes": 0, "firstTime": false, "tags": ["vip"] } ] }
    • diagnosis: lista de strings — um paciente pode ter mais de um diagnóstico oncológico ativo; todos são retornados e exibidos (ver mapeamento na Seção 4.1).
    • tags: lista de tags de atenção do paciente (ex: vip, quimioterapia, 1a_consulta) — ver origem de cada uma na Seção 4.1.
  • [GET] /api/v1/cockpit/doctor/prescriptions
    • Query params: type (opcional: to_sign | to_review) — usado pela tela dedicada de Prescrições Pendentes para filtrar por categoria. O painel do Cockpit não envia o parâmetro e recebe as duas categorias combinadas, já ordenadas por hoursRemaining crescente (RN-CM-011). reviewScope (opcional: mine | all, default mine) — controla o toggle “Meus/Todos”: afeta somente as prescrições to_review; as to_sign nunca são filtradas por este parâmetro, pois já são sempre do médico logado (RN-CM-012).
    • Response 200: { "prescriptions": [ { "id": "uuid", "patient": { "id": "uuid", "preferredName": "", "legalName": "Roberto Oliveira" }, "prescriptionCode": "CARBO-TAXOL C3D1", "scheduleDate": "2026-07-22T18:00:00Z", "hoursRemaining": 8, "type": "to_sign" }, { "id": "uuid", "patient": { "id": "uuid", "preferredName": "", "legalName": "Carlos Mendes" }, "prescriptionCode": "FOLFOX C1D1", "scheduleDate": "2026-07-22T14:00:00Z", "hoursRemaining": 18, "type": "to_review", "isPreferredReviewer": true } ] }
    • patient.preferredName/patient.legalName: mesmo padrão de appointments e procedures — o front decide qual exibir como nome principal (nome social se houver), mas ambos são enviados para o médico perceber questões de gênero/identidade antes do atendimento.
    • prescriptionCode: substitui os antigos campos protocol/cycle por um único campo já formatado (protocolo + ciclo/sessão, ex: “CARBO-TAXOL C3D1”), espelhando Prescricao.Identificacao diretamente — sem necessidade de montar a string no backend.
    • Campo exclusivo do tipo to_review (RN-CM-020): isPreferredReviewer = true quando o médico logado é o prescritor OU o médico assistente do paciente — usado pelo front para destacar visualmente que este médico é revisor preferencial, sem bloquear a ação para os demais (qualquer médico pode revisar). Os campos prescriberName/attendingDoctorName cogitados numa versão anterior foram removidos — o booleano já é suficiente para o front filtrar/destacar, sem expor nomes de terceiros nesta lista.
    • type = to_sign só retorna prescrições com aplicação agendada nos próximos 7 dias — evita inflar a lista com prescrições distantes no tempo, sem relevância imediata.
    • Mapeamento de BD: Seção 4.7.
  • [GET] /api/v1/cockpit/doctor/conversations
    • Alinhado à entidade Conversa do modelo de mensageria (IP.Mensageria). Retorna todas as conversas do médico logado com Status não-terminal (Enviada, Lida ou Respondida), lidas ou não. Este é o comportamento do painel compacto do Cockpit; uma tela dedicada (fora do escopo desta versão) listará também as conversas já finalizadas (Resolvida/Descartada).
    • Response 200: { "conversations": [ { "id": "uuid", "sourceType": "person | system", "authorName": "Sandra", "lastMessage": "Medicamento Carboplatina em falta.", "lastMessageAt": "2026-07-22T13:45:00Z", "deadline": "2026-07-22T14:00:00Z|null", "status": "sent | read | replied", "unread": true, "patient": { "id": "uuid", "preferredName": "", "legalName": "Roberto Oliveira" } | null } ] }
    • sourceType: system quando Conversa.OrigemTipo = sistema (alerta automático via ModuloOrigem); person quando OrigemTipo = usuario — vale tanto para conversa endereçada a um indivíduo quanto a uma equipe, já que não há distinção visual por equipe.
    • authorName: nome de quem escreveu a última mensagem (Usuario.Apelido, ver 4.8), ou “Sistema” quando sourceType = system. Não inclui nome de equipe.
    • status: sent = Enviada, read = Lida, replied = Respondida. Resolvida/Descartada nunca aparecem aqui (já saíram da lista). Um item com status = replied pode estar lido ou não lido, dependendo se houve atividade nova desde a resposta do médico — ver campo unread.
    • unread (boolean): true quando Conversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida (ou não existe ConversaLeitura para o médico). Usado pelo front para o destaque visual e para a camada de prioridade na ordenação (RN-CM-015) — não filtra mais quais itens são retornados.
    • Mapeamento de BD: Seção 4.8.
  • [GET] /api/v1/cockpit/doctor/procedures
    • Response 200: { "procedures": [ { "id": "uuid", "prescriptionCode": "AC-T C1D21", "patient": { "id": "uuid", "legalName": "Roberto Oliveira", "preferredName": "", "photo": "url|null" }, "location": "Sala de Infusão 2", "status": "administering", "startTime": "2026-07-22T09:30:00Z", "progress": 65, "mine": true, "tags": ["quimioterapia"] } ] }
    • status: um dos valores de ProcedureStatus() (Seção 4.3/4.4) — scheduled | waiting | checked | preparation | administering | administered | completed | canceled.
    • tags: lista de tags de atenção do paciente (ex: vip, quimioterapia) — mesma origem de appointments.tags, ver Seção 4.1/4.3.
    • prescriptionCode: substitui o campo prescriptionId de uma versão anterior deste documento — o valor nunca foi um identificador único, sempre foi o código formatado da prescrição (protocolo + ciclo/sessão), mesmo campo já usado em prescriptions (Seção 2.9).
  • [GET] /api/v1/patients/search
    • Query params: q (string, mínimo 2 caracteres)
    • Response 200: { "patients": [ { "id": "uuid", "name": "Maria da Silva", "medicalRecordNumber": "0012345", "gender": "F", "birthDate": "1972-08-15", "motherName": "Joana da Silva", "cpf": "12345678901" } ] }

Endpoints de ação:

Nota: os endpoints de ação sobre uma prescrição individual (abrir prescrição completa, assinar, marcar como revisada, editar/gravar nova versão) saíram do escopo deste documento. Eles pertencem à tela de detalhe da prescrição, que é chamada a partir deste Cockpit mas faz parte de um processo mais amplo de Prescrições (fora do domínio “Central do Médico”) e deve ser especificada em documento próprio. RN-CM-020 continua aqui porque define a regra de negócio usada no cálculo do KPI e do painel (Seções 4.6/4.7), mesmo que a execução da ação em si esteja documentada em outro lugar.

  • [GET] /api/v1/conversations/{id} — Retorna a thread completa (todas as Mensagem, em ordem) para exibição no modal.
    • Response 200: { "id": "uuid", "status": "sent | read | replied", "deadline": "2026-07-22T14:00:00Z|null", "patient": { ... } | null, "messages": [ { "id": "uuid", "type": "initial | reply | update", "author": "Sandra | Sistema", "content": "string", "createdAt": "2026-07-22T13:30:00Z" } ] }
  • [POST] /api/v1/conversations/{id}/read — Marca a conversa como lida pelo médico logado (chamado ao abrir o modal).
    • Response 200: { "id": "uuid", "status": "read" } — idempotente; se a conversa já estiver Respondida, o status não regride para read.
  • [POST] /api/v1/conversations/{id}/reply — Envia uma resposta na thread (não disponível para conversas com sourceType = system, ver RN-CM-015).
    • Request: { "content": "string" }
    • Response 200: { "id": "uuid", "status": "replied", "message": { "id": "uuid", "content": "string", "createdAt": "2026-07-22T13:50:00Z" } }
  • [PATCH] /api/v1/conversations/{id}/resolve — Muda o status da conversa para Resolvida. Substitui o antigo PATCH /messages/{id}/resolve.
    • Response 200: { "id": "uuid", "status": "resolved" }
  • [PATCH] /api/v1/conversations/{id}/discard — Muda o status da conversa para Descartada. Substitui o antigo DELETE /messages/{id} — o método mudou de DELETE para PATCH porque o registro nunca é excluído fisicamente, só muda de status (modelo append-only, ver RN-CM-015).
    • Response 200: { "id": "uuid", "status": "discarded" }

2.10 Conformidade SBIS (Detalhamento)

RequisitoComo a Central do Médico atende
ECF.03.11O campo de busca no header permite pesquisar pacientes por nome, CPF, data de nascimento e nome da mãe. A busca inteligente (RN-CM-004) interpreta automaticamente o tipo de entrada.
ECF.03.14O endpoint GET /api/v1/patients/search retorna os campos obrigatórios: nome completo, número de prontuário, sexo, data de nascimento, nome da mãe e CPF em cada resultado.
ECF.16.01(e)Após o login, o painel de Prescrições Pendentes exibe as prescrições em aberto de responsabilidade do médico, permitindo acesso direto para assinatura ou revisão.
ECF.17.01Todos os cards de paciente identificam univocamente o paciente (nome + ID). As conversas identificam o autor de cada mensagem (nome da pessoa ou “Sistema”, ver authorName na Seção 2.9). As prescrições identificam o paciente e o médico prescritor.
ECF.17.02As mensagens internas exibem timestamp de criação (criadaEm). As ações de assinatura e resolução registram data/hora automaticamente no backend.
ECF.17.04As consultas são ordenadas cronologicamente pelo horário agendado. As mensagens seguem ordenação por data limite e LIFO. As prescrições são ordenadas por urgência cronológica (tempo restante).
ECF.17.18Toda a interface, incluindo rótulos, mensagens, títulos de tela e descritivos, está em português do Brasil.
ECF.17.19Mensagens de erro e feedback ao usuário (toasts, alerts) são em linguagem não técnica. Erros técnicos do backend são tratados e traduzidos para mensagens amigáveis.

SEÇÃO 3 — PROTÓTIPO HTML

Estado: Protótipo funcional v3.13, com HTML semântico, CSS (Flexbox/Grid, tokens do Design System) e JavaScript de interação (render dinâmico dos 4 painéis, busca com debounce, modal de mensagens, toasts de feedback, filtros por toggle). Dados mockados em memória para demonstração.

Cobertura de touchpoints: O protótipo permite percorrer os touchpoints de workflow de ponta a ponta:

  • Workflow 1: ver consultas pendentes → identificar atraso (badge pulsante) → clicar no card → abrir PEP
  • Workflow 2: ver pacientes em procedimento → monitorar barra de progresso inline → clicar no card → abrir prescrição → clicar em PEP
  • Workflow 3: ver prescrições urgentes → clicar em Assinar → tela de assinatura (simulada via toast) → assinar → retorno automático à Central com contador atualizado. Clicar em Revisar → tela de edição (simulada via toast)
  • Workflow 4: ver mensagens não lidas em destaque, à frente das já lidas (borda lateral colorida, prioridade na lista) → identificar data limite → clicar no card → modal com mensagem integral (leitura rebaixa a conversa de prioridade, sem removê-la do painel) → Resolvido ou Descartado → contador atualizado

Pendências identificadas no protótipo em relação à Seção 2 (ver lista completa no Checklist de acompanhamento):

  • Ações de Assinar/Revisar e Resolver/Descartar/Responder mensagens estão simuladas com showToast()/manipulação de array em memória — os endpoints reais de prescrições (assinar, revisar, editar) saíram do escopo deste documento (ver nota na Seção 2.9); os de conversas (/read, /reply, /resolve, /discard) estão especificados na 2.9/4.9, mas o protótipo ainda não os chama de fato.
  • Ícones dos KPI cards ainda são placeholders (texto/emoji), pendente substituição pela biblioteca Lucide (ver Metadados Finais, item 4).
  • O botão “Revisar” do painel de Prescrições Pendentes simula uma única ação genérica — o protótipo ainda não distingue visualmente os dois caminhos de RN-CM-020 (“Marcar como Revisada” sem editar vs. editar, que gera nova versão); a Seção 2.4 já descreve os dois critérios de aceitação, mas a demonstração interativa de ambos fica pendente.

Código-fonte completo:

<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemed Onco v3.12 - Central do Médico</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root {
  --font-family: 'Roboto', sans-serif;
  --color-primary-pure: #00E676;
  --color-primary-dark: #00B01C;
  --color-primary-light: #E8F5E9;
  --color-secondary-pure: #40C4FF;
  --color-secondary-dark: #0091EA;
  --color-secondary-light: #E1F5FE;
  --color-error-pure: #F44336;
  --color-error-dark: #D32F2F;
  --color-error-light: #FFEBEE;
  --color-highlight-pure: #FFC400;
  --color-highlight-dark: #FF8F00;
  --color-highlight-light: #FFF8E1;
  --color-base-pure: #FFFFFF;
  --color-base-dark: #F5F6FA;
  --color-neutral-darker: #01013B;
  --color-neutral-dark: #012856;
  --color-neutral-pure: #6B85AC;
  --color-neutral-medium: #94A7C4;
  --color-neutral-light: #BECADC;
  --color-neutral-lighter: #E5EAF0;
  --spacing-xs: 8px;
  --spacing-sm: 12px;
  --spacing-m: 16px;
  --spacing-lg: 20px;
  --spacing-xl: 24px;
  --radius-s: 4px;
  --radius-m: 8px;
  --radius-l: 12px;
  --radius-pill: 999px;
  --shadow-light: 0px 1px 4px rgba(0,0,0,0.08);
  --shadow-medium: 0px 2px 8px rgba(0,0,0,0.12);
}
* { box-sizing: border-box; margin: 0; padding: 0; font-weight: 400; }
h1, h2, h3, h4, h5, h6, strong, b { font-weight: 400; }
body {
  font-family: var(--font-family);
  background-color: var(--color-base-dark);
  color: var(--color-neutral-darker);
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.header {
  height: 64px;
  background-color: var(--color-base-pure);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--spacing-xl);
  box-shadow: var(--shadow-light);
  z-index: 100;
  flex-shrink: 0;
}
.header-left { display: flex; align-items: center; gap: var(--spacing-m); }
.logo-circle {
  width: 36px; height: 36px;
  background-color: var(--color-primary-pure);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: white; font-size: 18px;
}
.clinic-info h1 { font-size: 14px; }
.clinic-info p { font-size: 11px; color: var(--color-neutral-pure); }
.header-center { flex: 0 1 450px; position: relative; }
.search-container { position: relative; }
.search-input {
  width: 100%; height: 40px;
  background-color: var(--color-base-dark);
  border: 1px solid var(--color-neutral-lighter);
  border-radius: var(--radius-m);
  padding: 0 12px 0 40px;
  font-size: 14px; outline: none;
  transition: border-color 0.2s;
}
.search-input:focus { border-color: var(--color-secondary-pure); }
.search-icon {
  position: absolute; left: 12px; top: 10px;
  color: var(--color-neutral-pure); font-size: 16px;
}
.search-dropdown {
  position: absolute; top: 46px; left: 0; right: 0;
  background: white; border-radius: var(--radius-m);
  box-shadow: var(--shadow-medium);
  display: none; max-height: 320px; overflow-y: auto; z-index: 200;
}
.search-hint {
  padding: var(--spacing-sm) var(--spacing-m);
  font-size: 11px; color: var(--color-neutral-medium);
  border-top: 1px solid var(--color-base-dark);
  background: #fafafa; font-style: italic;
}
.search-item {
  padding: var(--spacing-sm);
  border-bottom: 1px solid var(--color-base-dark);
  cursor: pointer;
}
.search-item:hover { background-color: var(--color-secondary-light); }
.search-item h4 { font-size: 14px; margin-bottom: 4px; }
.search-item p { font-size: 11px; color: var(--color-neutral-pure); }
.header-right { display: flex; align-items: center; gap: var(--spacing-m); }
.user-menu-wrapper { position: relative; }
.doctor-avatar-btn {
  display: flex; align-items: center; gap: 4px;
  background: none; border: none; cursor: pointer; padding: 0;
}
.doctor-avatar {
  width: 40px; height: 40px;
  background-color: var(--color-neutral-dark);
  color: white; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.dropdown-caret { font-size: 10px; color: var(--color-neutral-pure); transition: transform 0.2s; }
.user-menu-wrapper.open .dropdown-caret { transform: rotate(180deg); }
.user-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  background: white; border-radius: var(--radius-m);
  box-shadow: var(--shadow-medium);
  min-width: 160px; display: none; z-index: 200;
  overflow: hidden;
}
.user-menu.show { display: block; }
.user-menu-item {
  display: block; width: 100%; text-align: left;
  padding: var(--spacing-sm) var(--spacing-m);
  font-size: 13px; background: none; border: none; cursor: pointer;
  color: var(--color-neutral-darker);
}
.user-menu-item:hover { background-color: var(--color-base-dark); }
.kpi-bar {
  min-height: 96px;
  display: flex; gap: var(--spacing-m);
  padding: var(--spacing-sm) var(--spacing-xl);
  flex-shrink: 0;
}
.kpi-card {
  flex: 1; background: white;
  border-radius: var(--radius-m);
  padding: 10px var(--spacing-m);
  box-shadow: var(--shadow-light);
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;
  display: flex; align-items: center; gap: var(--spacing-sm);
  overflow: hidden;
  border-left: 4px solid var(--color-neutral-light);
}
.kpi-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-medium); }
.kpi-card.kpi-consultas { border-left-color: var(--color-secondary-pure); }
.kpi-card.kpi-tratamento { border-left-color: var(--color-primary-pure); }
.kpi-card.kpi-procedimentos { border-left-color: var(--color-highlight-pure); }
.kpi-card.kpi-alertas { border-left-color: var(--color-error-pure); }
.kpi-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.kpi-title { font-size: 11px; text-transform: uppercase; color: var(--color-neutral-pure); letter-spacing: 0.5px; }
.kpi-value { font-size: 22px; line-height: 1.2; }
.kpi-value.alert { color: var(--color-error-pure); }
.kpi-subtext { font-size: 11px; color: var(--color-neutral-medium); line-height: 1.3; }
.kpi-icon-placeholder {
  width: 36px; height: 36px;
  border-radius: var(--radius-m);
  background: var(--color-base-dark);
  border: 1px dashed var(--color-neutral-light);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; font-size: 10px; color: var(--color-neutral-light);
}
.dashboard-grid {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: var(--spacing-m);
  padding: 0 var(--spacing-xl) var(--spacing-xl);
  overflow: hidden;
  min-height: 0;
}
.panel {
  background: white; border-radius: var(--radius-m);
  display: flex; flex-direction: column;
  overflow: hidden; box-shadow: var(--shadow-light);
  min-height: 0;
}
.panel-header {
  padding: var(--spacing-sm) var(--spacing-m);
  background-color: var(--color-base-dark);
  border-bottom: 1px solid var(--color-neutral-lighter);
  display: flex; align-items: center; justify-content: space-between;
  flex-shrink: 0;
}
.panel-title-group { display: flex; align-items: center; gap: var(--spacing-xs); }
.panel-title-link {
  font-size: 15px;
  color: var(--color-neutral-darker);
  text-decoration: none; cursor: pointer;
  display: flex; align-items: center; gap: 4px;
  transition: color 0.2s;
}
.panel-title-link:hover { color: var(--color-secondary-dark); }
.panel-title-arrow {
  font-size: 14px; opacity: 0;
  transition: opacity 0.2s, transform 0.2s;
  transform: translateX(-4px);
}
.panel-title-link:hover .panel-title-arrow { opacity: 1; transform: translateX(0); }
.badge-count {
  background: var(--color-neutral-lighter);
  padding: 2px 8px; border-radius: var(--radius-pill);
  font-size: 12px;
}
.panel-actions { display: flex; align-items: center; gap: var(--spacing-sm); }
.toggle-group {
  display: flex; background: white;
  padding: 2px; border-radius: var(--radius-pill);
  border: 1px solid var(--color-neutral-lighter);
}
.toggle-btn {
  padding: 3px 10px; font-size: 11px;
  border: none; background: transparent;
  cursor: pointer; border-radius: var(--radius-pill);
  color: var(--color-neutral-pure);
  transition: all 0.2s;
}
.toggle-btn.active { background: var(--color-secondary-light); color: var(--color-secondary-dark); }
.panel-body { flex: 1; overflow-y: auto; padding: var(--spacing-sm); min-height: 0; }
.card-item {
  border: 1px solid var(--color-base-dark);
  border-radius: var(--radius-m);
  padding: var(--spacing-sm);
  margin-bottom: var(--spacing-xs);
  display: flex; align-items: flex-start; gap: var(--spacing-sm);
  transition: background 0.2s; cursor: pointer;
  position: relative;
}
.card-item:hover { background-color: var(--color-base-dark); }
.card-item.finalized { opacity: 0.55; background-color: #fafafa; }
.card-item.highlight-recepcionado { border-left: 3px solid var(--color-secondary-pure); }
.card-item.highlight-aguardando { border-left: 3px solid #FF8A80; }
.avatar-circle {
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: white; font-size: 13px; flex-shrink: 0;
}
.card-content { flex: 1; min-width: 0; }
.card-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.card-name { font-size: 14px; }
.card-time { font-size: 12px; color: var(--color-neutral-pure); }
.card-meta { font-size: 12px; color: var(--color-neutral-medium); display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.chip {
  padding: 2px 8px; border-radius: var(--radius-pill);
  font-size: 10px; text-transform: uppercase; white-space: nowrap;
}
.chip-agendado { background: #FFE0B3; color: #855D00; }
.chip-recepcionado { background: #B7E3FF; color: #004D7A; }
.chip-aguardandorecepcao { background: #FFC5C1; color: #8B0000; }
.chip-ematendimento { background: #D1DF9D; color: #3E4D00; }
.chip-finalizado { background: #E5EAF0; color: #6B85AC; }
.chip-urgente { background: var(--color-error-light); color: var(--color-error-dark); }
.chip-atencao { background: var(--color-highlight-light); color: var(--color-highlight-dark); }
.chip-vip { background: var(--color-secondary-light); color: var(--color-secondary-dark); }
.chip-1aconsulta { background: #EDE7F6; color: #4527A0; }
.badge-atraso {
  background: var(--color-error-pure); color: white;
  font-size: 10px; padding: 2px 6px; border-radius: var(--radius-s);
  animation: pulse 2s infinite; white-space: nowrap;
}
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
.progress-name-inline {
  display: flex; align-items: center; gap: 4px;
  flex: 0 0 auto;
}
.progress-bar-mini {
  width: 50px; height: 3px;
  background: var(--color-neutral-lighter);
  border-radius: 2px; overflow: hidden;
}
.progress-fill { height: 100%; background: var(--color-primary-pure); border-radius: 2px; transition: width 0.5s; }
.progress-fill.danger { background: var(--color-error-pure); }
.progress-pct { font-size: 9px; color: var(--color-neutral-medium); white-space: nowrap; }
.btn {
  padding: 6px 12px; border-radius: var(--radius-s);
  font-size: 12px; cursor: pointer;
  border: none; transition: filter 0.2s; white-space: nowrap;
}
.btn-primary { background: var(--color-primary-pure); color: white; }
.btn-secondary { background: var(--color-secondary-light); color: var(--color-secondary-dark); }
.btn-tertiary { background: transparent; color: var(--color-neutral-pure); border: 1px solid var(--color-neutral-lighter); }
.btn-danger { background: transparent; color: var(--color-error-dark); border: 1px solid var(--color-error-light); }
.btn:hover { filter: brightness(0.9); }
.msg-item { border-left: 4px solid transparent; }
.msg-unread { border-left-color: var(--color-secondary-pure); }
.msg-icon { font-size: 20px; flex-shrink: 0; color: var(--color-neutral-pure); }
.msg-unread .msg-icon { color: var(--color-primary-pure); }
#toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 1000; }
.toast {
  background: var(--color-neutral-darker); color: white;
  padding: 12px 24px; border-radius: var(--radius-m);
  margin-top: 8px; font-size: 14px;
  box-shadow: var(--shadow-medium);
  animation: slideIn 0.3s ease-out;
}
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
.empty-state {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  height: 100%; color: var(--color-neutral-medium); text-align: center;
}
.empty-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.3; }
.panel-body::-webkit-scrollbar { width: 6px; }
.panel-body::-webkit-scrollbar-track { background: transparent; }
.panel-body::-webkit-scrollbar-thumb { background: var(--color-neutral-light); border-radius: 3px; }
.modal-overlay {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.4);
  display: none; align-items: center; justify-content: center;
  z-index: 500;
}
.modal-overlay.show { display: flex; }
.modal-box {
  background: white; border-radius: var(--radius-l);
  padding: var(--spacing-xl);
  max-width: 500px; width: 90%;
  box-shadow: var(--shadow-medium);
  display: flex; flex-direction: column; gap: var(--spacing-m);
}
.modal-title { font-size: 16px; color: var(--color-neutral-darker); }
.modal-body { font-size: 14px; color: var(--color-neutral-dark); line-height: 1.5; }
.modal-meta { font-size: 12px; color: var(--color-neutral-pure); }
.modal-actions { display: flex; gap: var(--spacing-sm); justify-content: flex-end; }
@media (max-width: 1024px) {
  .dashboard-grid { grid-template-columns: 1fr; overflow-y: auto; }
  body { overflow-y: auto; }
}
</style>
</head>
<body>
 
<header class="header">
  <div class="header-left">
    <div class="logo-circle">G</div>
    <div class="clinic-info">
      <h1>Hospital São Lucas</h1>
    </div>
  </div>
  <div class="header-center">
    <div class="search-container">
      <span class="search-icon">🔍</span>
      <input type="text" class="search-input" id="main-search" placeholder="Procurar paciente…">
      <div class="search-dropdown" id="search-results"></div>
    </div>
  </div>
  <div class="header-right">
    <div class="user-menu-wrapper" id="user-menu-wrapper">
      <button class="doctor-avatar-btn" onclick="toggleUserMenu(event)">
        <div class="doctor-avatar">FA</div>
        <span class="dropdown-caret">▾</span>
      </button>
      <div class="user-menu" id="user-menu">
        <button class="user-menu-item" onclick="showToast('Saindo...')">Sair</button>
      </div>
    </div>
  </div>
</header>
 
<div class="kpi-bar">
  <div class="kpi-card kpi-consultas" onclick="showToast('Navegando para Agenda de Consultas…')">
    <div class="kpi-text">
      <span class="kpi-title">Consultas Hoje</span>
      <span class="kpi-value">4/6</span>
      <span class="kpi-subtext">2 pendentes · 1 atrasada</span>
    </div>
    <div class="kpi-icon-placeholder">ícone</div>
  </div>
  <div class="kpi-card kpi-tratamento" onclick="showToast('Navegando para Pacientes do Médico…')">
    <div class="kpi-text">
      <span class="kpi-title">Em Tratamento</span>
      <span class="kpi-value">43</span>
      <span class="kpi-subtext">2 não aderentes</span>
    </div>
    <div class="kpi-icon-placeholder">ícone</div>
  </div>
  <div class="kpi-card kpi-procedimentos" onclick="showToast('Navegando para Procedimentos na Clínica…')">
    <div class="kpi-text">
      <span class="kpi-title">Procedimentos Hoje</span>
      <span class="kpi-value">8</span>
      <span class="kpi-subtext">2 em atendimento · 4 aguardando · 2 previstos</span>
    </div>
    <div class="kpi-icon-placeholder">ícone</div>
  </div>
  <div class="kpi-card kpi-alertas" onclick="showToast('Navegando para Prescrições Pendentes…')">
    <div class="kpi-text">
      <span class="kpi-title">Alertas de Prescrição</span>
      <span class="kpi-value alert" id="kpi-alert-count">3</span>
      <span class="kpi-subtext">1 p/ revisar · 5 p/ assinar (próx. 7 dias)</span>
    </div>
    <div class="kpi-icon-placeholder">ícone</div>
  </div>
</div>
 
<main class="dashboard-grid">
 
  <section class="panel">
    <div class="panel-header">
      <div class="panel-title-group">
        <a class="panel-title-link" onclick="showToast('Abrindo Agenda do Médico…')">Consultas <span class="panel-title-arrow">→</span></a>
        <span class="badge-count" id="count-consultas">6</span>
      </div>
      <div class="panel-actions">
        <div class="toggle-group">
          <button class="toggle-btn" onclick="filterConsultas('all', this)">Todas</button>
          <button class="toggle-btn active" onclick="filterConsultas('pending', this)">Pendentes</button>
        </div>
      </div>
    </div>
    <div class="panel-body" id="list-consultas"></div>
  </section>
 
  <section class="panel">
    <div class="panel-header">
      <div class="panel-title-group">
        <a class="panel-title-link" onclick="showToast('Abrindo Prescrições Pendentes…')">Prescrições Pendentes <span class="panel-title-arrow">→</span></a>
        <span class="badge-count" id="count-prescricoes">3</span>
      </div>
      <div class="panel-actions">
        <div class="toggle-group">
          <button class="toggle-btn active" onclick="filterPrescricoes('mine', this)">Meus</button>
          <button class="toggle-btn" onclick="filterPrescricoes('all', this)">Todos</button>
        </div>
      </div>
    </div>
    <div class="panel-body" id="list-prescricoes"></div>
  </section>
 
  <section class="panel">
    <div class="panel-header">
      <div class="panel-title-group">
        <a class="panel-title-link" onclick="showToast('Abrindo Mensagens…')">Comunicação <span class="panel-title-arrow">→</span></a>
        <span class="badge-count" id="count-mensagens">4</span>
      </div>
    </div>
    <div class="panel-body" id="list-mensagens"></div>
  </section>
 
  <section class="panel">
    <div class="panel-header">
      <div class="panel-title-group">
        <a class="panel-title-link" onclick="showToast('Abrindo Procedimentos na Clínica…')">Procedimentos na Clínica <span class="panel-title-arrow">→</span></a>
        <span class="badge-count" id="count-clinica">4</span>
      </div>
      <div class="panel-actions">
        <div class="toggle-group">
          <button class="toggle-btn active" onclick="toggleMeusPacientes(true, this)">Meus</button>
          <button class="toggle-btn" onclick="toggleMeusPacientes(false, this)">Todos</button>
        </div>
      </div>
    </div>
    <div class="panel-body" id="list-clinica"></div>
  </section>
 
</main>
 
<div id="toast-container"></div>
 
<div class="modal-overlay" id="msg-modal-overlay">
  <div class="modal-box">
    <div class="modal-title" id="msg-modal-title"></div>
    <div class="modal-meta" id="msg-modal-meta"></div>
    <div class="modal-body" id="msg-modal-body"></div>
    <div class="modal-meta" id="msg-modal-deadline"></div>
    <div id="msg-modal-reply" style="display: flex; gap: 8px; margin-top: 10px;">
      <input type="text" id="msg-modal-reply-input" placeholder="Escreva uma resposta..." style="flex: 1; padding: 8px; border: 1px solid var(--color-neutral-lighter); border-radius: var(--radius-m); font-family: var(--font-family);" onkeydown="if(event.key==='Enter') sendReply()" />
      <button class="btn btn-secondary" onclick="sendReply()">Responder</button>
    </div>
    <div class="modal-actions">
      <button class="btn btn-danger" onclick="discardMessage()">Descartado</button>
      <button class="btn btn-tertiary" onclick="resolveMessageFromModal()">Resolvido</button>
    </div>
  </div>
</div>
 
<script>
var pacientesMock = [
  { id: 1, nome: "Maria da Silva", prontuario: "12345", cpf: "123.456.789-00", mae: "Josefa Silva", nasc: "12/05/1965", sexo: "F" },
  { id: 2, nome: "Roberto Oliveira", prontuario: "67890", cpf: "987.654.321-11", mae: "Maria Oliveira", nasc: "20/10/1970", sexo: "M" },
  { id: 3, nome: "Ana Paula Costa", prontuario: "11223", cpf: "444.555.666-77", mae: "Lucia Costa", nasc: "05/01/1985", sexo: "F" },
  { id: 4, nome: "Carlos Mendes", prontuario: "44556", cpf: "888.999.000-11", mae: "Beatriz Mendes", nasc: "15/08/1958", sexo: "M" },
  { id: 5, nome: "Aymore Lima", prontuario: "77889", cpf: "222.333.444-55", mae: "Helena Lima", nasc: "03/03/1972", sexo: "M" }
];
 
var consultas = [
  { id: 1, hora: "08:00 - 08:30", nome: "Maria da Silva", diags: ["Ca. Mama"], tipo: "Presencial", tags: ["vip"], status: "Finalizado" },
  { id: 2, hora: "08:30 - 09:00", nome: "Roberto Oliveira", diags: ["Ca. Prostata"], tipo: "Presencial", tags: [], status: "Recepcionado" },
  { id: 3, hora: "09:00 - 09:30", nome: "Ana Paula Costa", diags: ["Ca. Mama", "Ca. Ovario"], tipo: "Teleatendimento", tags: ["1aconsulta"], status: "Agendado", atraso: "15min" },
  { id: 4, hora: "09:30 - 10:00", nome: "Carlos Mendes", diags: ["Ca. Colon"], tipo: "Presencial", tags: [], status: "Aguardando Recepcao" },
  { id: 5, hora: "10:00 - 10:30", nome: "Joana Ferreira", diags: ["Ca. Pulmao"], tipo: "Presencial", tags: [], status: "Agendado" },
  { id: 6, hora: "10:30 - 11:00", nome: "Pedro Santos", diags: [], tipo: "Presencial", tags: ["1aconsulta"], status: "Em Atendimento" }
];
// nota v4.21: item 3 tem 2 diagnosticos oncologicos ativos, para demonstrar que o card exibe todos, nao so um (Secao 4.1)
 
// Cor por status (RN-CM-005 / Secao 2.3) - fonte unica, evita cores divergentes entre avatar e chip
function statusColor(status) {
  var map = {
    'Agendado': '#FFE0B3',
    'Aguardando Recepcao': '#FFC5C1',
    'Recepcionado': '#B7E3FF',
    'Em Atendimento': '#D1DF9D',
    'Finalizado': '#9679E1'
  };
  return map[status] || '#E0E0E0';
}
 
var prescricoes = [
  { id: 1, paciente: "Roberto Oliveira", prescricaoCodigo: "CARBO-TAXOL C3D1", dataAgenda: "22/07 18:00", tempo: "8h restantes", urgencia: "urgente", acao: "Assinar" },
  { id: 2, paciente: "Maria da Silva", prescricaoCodigo: "AC-T C2D1", dataAgenda: "23/07 09:00", tempo: "18h restantes", urgencia: "atencao", acao: "Assinar" },
  { id: 3, paciente: "Carlos Mendes", prescricaoCodigo: "FOLFOX C1D1", dataAgenda: "22/07 14:00", tempo: "18h restantes", urgencia: "atencao", acao: "Revisar", meu: true, obs: "Revisao automatica: aplicacao em menos de 24h. Voce e revisor preferencial." },
  { id: 4, paciente: "Aymore Lima", prescricaoCodigo: "FOLFIRI C4D1", dataAgenda: "23/07 07:00", tempo: "23h restantes", urgencia: "atencao", acao: "Revisar", meu: false, obs: "Revisao automatica: aplicacao em menos de 24h." }
];
 
// item id 4 (unread:false) demonstra: fica no painel, mas em prioridade reduzida (depois das nao lidas)
var mensagens = [
  { id: 1, origemTipo: "sistema", origemNome: "Sistema", texto: "Prescricao urgente: menos de 12h para aplicacao de Roberto Oliveira.", meta: "Ha 10 min - Sistema", limite: "14:00", unread: true, status: "sent",
    thread: [{ autor: "Sistema", texto: "Prescricao urgente: menos de 12h para aplicacao de Roberto Oliveira.", tipo: "inicial", hora: "13:50" }] },
  { id: 2, origemTipo: "pessoa", origemNome: "Sandra", texto: "Medicamento Carboplatina em falta para o ciclo de amanha.", meta: "Ha 15 min - Sandra", limite: "14:00", unread: true, status: "sent",
    thread: [{ autor: "Sandra", texto: "Medicamento Carboplatina em falta para o ciclo de amanha.", tipo: "inicial", hora: "13:45" }] },
  { id: 3, origemTipo: "pessoa", origemNome: "Carla", texto: "Paciente Roberto Oliveira relatou nausea durante a infusao.", meta: "Ha 32 min - Carla", unread: true, status: "sent",
    thread: [{ autor: "Carla", texto: "Paciente Roberto Oliveira relatou nausea durante a infusao.", tipo: "inicial", hora: "13:28" }] },
  { id: 4, origemTipo: "pessoa", origemNome: "Juliana", texto: "Combinado, aguardo confirmacao apos a consulta.", meta: "Ha 1h - Juliana", unread: false, status: "replied",
    thread: [
      { autor: "Juliana", texto: "Resultado de exame de imagem recebido para Ana Paula Costa.", tipo: "inicial", hora: "12:30" },
      { autor: "Voce", texto: "Obrigado, vou avaliar na consulta de hoje.", tipo: "resposta", hora: "12:40" },
      { autor: "Juliana", texto: "Combinado, aguardo confirmacao apos a consulta.", tipo: "resposta", hora: "12:45" }
    ] }
];
 
var clinica = [
  { id: 1, nome: "Roberto Oliveira", local: "Sala de Infusao 2", status: "Em administracao", inicio: "09:30", progresso: 65, tags: ["quimioterapia"], meu: true },
  { id: 2, nome: "Maria da Silva", local: "Sala de Infusao 1", status: "Em administracao", inicio: "09:00", progresso: 110, tags: ["vip"], meu: true },
  { id: 3, nome: "Ana Paula Costa", local: "Consultorio 3", status: "Aguardando recepcao", inicio: "-", progresso: 0, tags: [], meu: true },
  { id: 4, nome: "Pedro Santos", local: "Consultorio 4", status: "Em administracao", inicio: "10:30", progresso: 30, tags: [], meu: true },
  { id: 5, nome: "Joao Silva", local: "Sala de Infusao 3", status: "Em administracao", inicio: "08:00", progresso: 80, tags: [], meu: false },
  { id: 6, nome: "Fernanda Lima", local: "Sala de Repouso 1", status: "Aguardando recepcao", inicio: "-", progresso: 0, tags: [], meu: false },
  { id: 7, nome: "Jose Pereira", local: "Consultorio 2", status: "Em administracao", inicio: "09:45", progresso: 50, tags: [], meu: false },
  { id: 8, nome: "Lucia Santos", local: "Sala de Infusao 4", status: "Em administracao", inicio: "10:00", progresso: 95, tags: [], meu: false }
];
// nota v4.21: rotulos de status alinhados ao dominio canonico de ProcedureStatus() (Secao 4.3/4.4)
 
var currentMsgId = null;
 
function renderConsultas(filter) {
  var container = document.getElementById('list-consultas');
  var countEl = document.getElementById('count-consultas');
  var filtered = filter === 'pending' ? consultas.filter(function(c) { return c.status !== 'Finalizado'; }) : consultas;
  countEl.innerText = filtered.length;
  if (filtered.length === 0) {
    container.innerHTML = '<div class="empty-state"><div class="empty-icon">📅</div><p>Nenhuma agenda para este dia</p></div>';
    return;
  }
  container.innerHTML = filtered.map(function(c) {
    var initials = c.nome.split(' ').map(function(n) { return n[0]; }).join('').substring(0, 2);
    var tagsHtml = c.tags.map(function(t) {
      var label = t === '1aconsulta' ? '1a Consulta' : t.toUpperCase();
      return '<span class="chip chip-' + t + '">' + label + '</span>';
    }).join('');
    var statusClass = c.status.toLowerCase().replace(/\s/g, '').replace(/[^a-z]/g, '');
    var highlightClass = '';
    if (c.status === 'Recepcionado') highlightClass = 'highlight-recepcionado';
    else if (c.status === 'Aguardando Recepcao') highlightClass = 'highlight-aguardando';
    return '<div class="card-item ' + (c.status === 'Finalizado' ? 'finalized' : '') + ' ' + highlightClass + '" onclick="showToast(\'Abrindo PEP: ' + c.nome + '\')">' +
      '<div class="avatar-circle" style="background-color: ' + statusColor(c.status) + '">' + initials + '</div>' +
      '<div class="card-content">' +
        '<div class="card-row"><span class="card-name">' + c.nome + '</span><span class="card-time">' + c.hora + '</span></div>' +
        '<div class="card-row">' +
          '<div class="card-meta">' +
            (c.diags && c.diags.length ? '<span>' + c.diags.join(', ') + '</span><span>-</span>' : '') +
            '<span>' + c.tipo + '</span>' + tagsHtml +
          '</div>' +
          '<div style="display: flex; gap: 6px; align-items: center;">' +
            (c.atraso ? '<span class="badge-atraso">Atrasado ' + c.atraso + '</span>' : '') +
            '<span class="chip chip-' + statusClass + '">' + c.status + '</span>' +
          '</div>' +
        '</div>' +
      '</div>' +
    '</div>';
  }).join('');
}
 
var currentPrescricaoScope = 'mine';
 
function renderPrescricoes(scope) {
  currentPrescricaoScope = scope || currentPrescricaoScope;
  scope = currentPrescricaoScope;
  var container = document.getElementById('list-prescricoes');
  var countEl = document.getElementById('count-prescricoes');
  var kpiEl = document.getElementById('kpi-alert-count');
  var urgentCount = prescricoes.filter(function(p) { return p.urgencia === 'urgente'; }).length;
  kpiEl.innerText = urgentCount;
  kpiEl.className = urgentCount > 0 ? 'kpi-value alert' : 'kpi-value';
  // Toggle Meus/Todos afeta somente os itens "Revisar" — os itens "Assinar" ja sao sempre do medico logado (RN-CM-012)
  var filtered = prescricoes.filter(function(p) {
    if (p.acao === 'Assinar') return true;
    return scope === 'all' ? true : p.meu === true;
  });
  countEl.innerText = filtered.length;
  if (filtered.length === 0) {
    container.innerHTML = '<div class="empty-state"><div class="empty-icon">✅</div><p>Sem prescricoes pendentes</p></div>';
    return;
  }
  container.innerHTML = filtered.map(function(p) {
    return '<div class="card-item">' +
      '<div class="card-content">' +
        '<div class="card-row"><span class="card-name">' + p.paciente + '</span><span class="chip chip-' + p.urgencia + '">' + p.tempo + '</span></div>' +
        '<div class="card-row">' +
          '<div class="card-meta"><span>' + p.prescricaoCodigo + '</span><span>-</span><span>Agenda: ' + p.dataAgenda + '</span></div>' +
          '<button class="btn ' + (p.acao === 'Assinar' ? 'btn-primary' : 'btn-secondary') + '" onclick="handlePrescricao(' + p.id + ', \'' + p.acao + '\', event)">' + p.acao + '</button>' +
        '</div>' +
        (p.obs ? '<p style="font-size: 11px; font-style: italic; color: var(--color-neutral-pure); margin-top: 6px;">' + p.obs + '</p>' : '') +
      '</div>' +
    '</div>';
  }).join('');
}
 
function filterPrescricoes(scope, btn) {
  document.querySelectorAll('.panel:nth-child(2) .toggle-btn').forEach(function(b) { b.classList.remove('active'); });
  btn.classList.add('active');
  renderPrescricoes(scope);
}
 
function iconForOrigem(m) {
  return m.origemTipo === 'sistema' ? '🔔' : '👤'; // binario: sistema vs pessoa - sem icone por equipe
}
 
function renderMensagens() {
  var container = document.getElementById('list-mensagens');
  var countEl = document.getElementById('count-mensagens');
  // o painel mostra TODAS as conversas nao finalizadas (lidas e nao lidas), nao so as nao lidas.
  // Ler uma conversa nao a remove do painel, apenas a rebaixa de prioridade (fica sempre depois das nao lidas).
  // A lista completa (incluindo resolvidas/descartadas) fica para a futura tela dedicada.
  countEl.innerText = mensagens.length;
  var sorted = mensagens.slice().sort(function(a, b) {
    if (a.unread !== b.unread) return a.unread ? -1 : 1;
    if (a.limite && !b.limite) return -1;
    if (!a.limite && b.limite) return 1;
    return b.id - a.id;
  });
  if (sorted.length === 0) {
    container.innerHTML = '<div class="empty-state"><div class="empty-icon">💬</div><p>Sem notificacoes pendentes</p></div>';
    return;
  }
  container.innerHTML = sorted.map(function(m) {
    return '<div class="card-item msg-item' + (m.unread ? ' msg-unread' : '') + '" onclick="openMsgModal(' + m.id + ')">' +
      '<div class="msg-icon">' + iconForOrigem(m) + '</div>' +
      '<div class="card-content">' +
        '<p style="font-size: 13px; margin-bottom: 4px;">' + m.texto + (m.status === 'replied' ? ' <span class="chip chip-atencao" style="font-size: 10px;">Respondida</span>' : '') + '</p>' +
        '<div class="card-row">' +
          '<span class="card-meta">' + m.meta + '</span>' +
          (m.limite ? '<span style="font-size: 11px; color: var(--color-highlight-dark);">⏰ Responder até ' + m.limite + '</span>' : '') +
          '<button class="btn btn-tertiary" onclick="resolveMsg(' + m.id + ', event)">Resolvido</button>' +
        '</div>' +
      '</div>' +
    '</div>';
  }).join('');
}
 
function renderClinica(onlyMe) {
  var container = document.getElementById('list-clinica');
  var countEl = document.getElementById('count-clinica');
  var filtered = onlyMe ? clinica.filter(function(c) { return c.meu; }) : clinica;
  countEl.innerText = filtered.length;
  if (filtered.length === 0) {
    container.innerHTML = '<div class="empty-state"><div class="empty-icon">🏥</div><p>Sem pacientes para o dia</p></div>';
    return;
  }
  container.innerHTML = filtered.map(function(c) {
    var initials = c.nome.split(' ').map(function(n) { return n[0]; }).join('').substring(0, 2);
    var progressHtml = '';
    if (c.progresso > 0) {
      progressHtml = '<div class="progress-name-inline">' +
        '<div class="progress-bar-mini">' +
          '<div class="progress-fill ' + (c.progresso > 100 ? 'danger' : '') + '" style="width: ' + Math.min(c.progresso, 100) + '%"></div>' +
        '</div>' +
        '<span class="progress-pct">' + c.progresso + '%</span>' +
      '</div>';
    }
    var tagsHtml = c.tags.map(function(t) {
      var label = t === 'vip' ? 'VIP' : (t.charAt(0).toUpperCase() + t.slice(1));
      return '<span class="chip chip-' + t + '">' + label + '</span>';
    }).join('');
    return '<div class="card-item" onclick="showToast(\'Abrindo prescricao: ' + c.nome + '\')">' +
      '<div class="avatar-circle" style="background-color: var(--color-neutral-light); color: var(--color-neutral-dark);">' + initials + '</div>' +
      '<div class="card-content">' +
        '<div class="card-row">' +
          '<span class="card-name">' + c.nome + '</span>' +
          progressHtml +
          '<button class="btn btn-secondary" style="height: 24px; padding: 0 8px; font-size: 11px;" onclick="showToast(\'Abrindo PEP: ' + c.nome + '\'); event.stopPropagation();">PEP</button>' +
        '</div>' +
        '<div class="card-meta">' +
          '<span>' + c.local + '</span><span>-</span>' +
          '<span>' + c.status + (c.inicio !== '-' ? ' (' + c.inicio + ')' : '') + '</span>' + tagsHtml +
        '</div>' +
      '</div>' +
    '</div>';
  }).join('');
}
 
function filterConsultas(type, btn) {
  document.querySelectorAll('.panel:nth-child(1) .toggle-btn').forEach(function(b) { b.classList.remove('active'); });
  btn.classList.add('active');
  renderConsultas(type);
}
 
function toggleMeusPacientes(onlyMe, btn) {
  document.querySelectorAll('.panel:nth-child(4) .toggle-btn').forEach(function(b) { b.classList.remove('active'); });
  btn.classList.add('active');
  renderClinica(onlyMe);
}
 
function handlePrescricao(id, acao, e) {
  e.stopPropagation();
  if (acao === 'Assinar') {
    showToast('Abrindo tela de assinatura...');
    setTimeout(function() {
      prescricoes = prescricoes.filter(function(p) { return p.id !== id; });
      showToast('Prescricao assinada com sucesso. Retornando a Central...');
      renderPrescricoes();
    }, 2000);
  } else {
    showToast('Abrindo tela de edicao para revisao...');
  }
}
 
function resolveMsg(id, e) {
  e.stopPropagation();
  mensagens = mensagens.filter(function(m) { return m.id !== id; });
  renderMensagens();
  showToast('Conversa marcada como resolvida');
}
 
function openMsgModal(id) {
  var msg = mensagens.find(function(m) { return m.id === id; });
  if (!msg) return;
  currentMsgId = id;
  if (msg.unread) {
    msg.unread = false;
    if (msg.status === 'sent') msg.status = 'read';
    renderMensagens();
  }
  document.getElementById('msg-modal-title').innerText = msg.origemNome || 'Mensagem';
  document.getElementById('msg-modal-meta').innerText = msg.meta;
  document.getElementById('msg-modal-body').innerHTML = msg.thread.map(function(t) {
    var isMine = t.autor === 'Voce';
    return '<div style="margin-bottom: 10px; ' + (isMine ? 'text-align: right;' : '') + '">' +
      '<div style="font-size: 11px; color: var(--color-neutral-pure);">' + t.autor + ' - ' + t.hora + '</div>' +
      '<div style="font-size: 13px;">' + t.texto + '</div>' +
    '</div>';
  }).join('');
  document.getElementById('msg-modal-deadline').innerText = msg.limite ? '⏰ Responder ate ' + msg.limite : '';
  var replyBox = document.getElementById('msg-modal-reply');
  replyBox.style.display = msg.origemTipo === 'sistema' ? 'none' : 'flex';
  document.getElementById('msg-modal-reply-input').value = '';
  document.getElementById('msg-modal-overlay').classList.add('show');
}
 
function sendReply() {
  var input = document.getElementById('msg-modal-reply-input');
  var texto = input.value.trim();
  if (!texto) return;
  var msg = mensagens.find(function(m) { return m.id === currentMsgId; });
  if (!msg) return;
  msg.thread.push({ autor: 'Voce', texto: texto, tipo: 'resposta', hora: 'agora' });
  msg.texto = texto;
  msg.status = 'replied';
  msg.meta = 'Ha 1 min - Voce';
  input.value = '';
  openMsgModal(currentMsgId);
  renderMensagens();
  showToast('Resposta enviada');
}
 
function resolveMessageFromModal() {
  mensagens = mensagens.filter(function(m) { return m.id !== currentMsgId; });
  renderMensagens();
  document.getElementById('msg-modal-overlay').classList.remove('show');
  showToast('Conversa marcada como resolvida');
}
 
function discardMessage() {
  mensagens = mensagens.filter(function(m) { return m.id !== currentMsgId; });
  renderMensagens();
  document.getElementById('msg-modal-overlay').classList.remove('show');
  showToast('Conversa descartada');
}
 
function initSearch() {
  var input = document.getElementById('main-search');
  var dropdown = document.getElementById('search-results');
  var timeout = null;
  input.addEventListener('input', function(e) {
    var val = e.target.value;
    clearTimeout(timeout);
    if (val.length === 0) {
      dropdown.style.display = 'none';
      return;
    }
    if (val.length === 1) {
      dropdown.innerHTML = '<div class="search-hint">Digite pelo menos 2 caracteres para iniciar a busca.</div>';
      dropdown.style.display = 'block';
      return;
    }
    timeout = setTimeout(function() {
      // Mock local por substring - o motor real de correspondencia aproximada
      // (pesos foneticos/semanticos, RN-CM-004) e externo a este prototipo.
      var valLower = val.toLowerCase();
      var results = pacientesMock.filter(function(p) {
        return p.nome.toLowerCase().indexOf(valLower) !== -1 ||
               p.cpf.indexOf(val) !== -1 ||
               p.prontuario.indexOf(val) !== -1 ||
               p.nasc.indexOf(val) !== -1 ||
               p.mae.toLowerCase().indexOf(valLower) !== -1;
      });
      var hint = '<div class="search-hint">Digite parte do nome, CPF, data de nascimento ou nome da mae para filtrar</div>';
      if (results.length > 0) {
        dropdown.innerHTML = results.map(function(p) {
          return '<div class="search-item" onclick="showToast(\'Abrindo PEP: ' + p.nome + '\'); document.getElementById(\'search-results\').style.display=\'none\';">' +
            '<h4>' + p.nome + '</h4>' +
            '<p>Prontuario: ' + p.prontuario + ' - Sexo: ' + p.sexo + ' - Nasc: ' + p.nasc + ' - Mae: ' + p.mae + ' - CPF: ' + p.cpf + '</p>' +
          '</div>';
        }).join('') + hint;
      } else {
        dropdown.innerHTML = '<div class="search-item"><p>Nenhum paciente encontrado com os dados informados.</p></div>' + hint;
      }
      dropdown.style.display = 'block';
    }, 1000);
  });
  document.addEventListener('click', function(e) {
    if (!e.target.closest('.search-container')) {
      dropdown.style.display = 'none';
    }
  });
  document.getElementById('msg-modal-overlay').addEventListener('click', function(e) {
    if (e.target === this) {
      this.classList.remove('show');
    }
  });
  document.addEventListener('click', function(e) {
    if (!e.target.closest('.user-menu-wrapper')) {
      closeUserMenu();
    }
  });
}
 
function toggleUserMenu(e) {
  e.stopPropagation();
  var wrapper = document.getElementById('user-menu-wrapper');
  var menu = document.getElementById('user-menu');
  var isOpen = menu.classList.contains('show');
  if (isOpen) { closeUserMenu(); } else {
    menu.classList.add('show');
    wrapper.classList.add('open');
  }
}
 
function closeUserMenu() {
  document.getElementById('user-menu').classList.remove('show');
  document.getElementById('user-menu-wrapper').classList.remove('open');
}
 
function showToast(msg) {
  var container = document.getElementById('toast-container');
  var toast = document.createElement('div');
  toast.className = 'toast';
  toast.innerText = msg;
  container.appendChild(toast);
  setTimeout(function() { toast.remove(); }, 3000);
}
 
window.onload = function() {
  renderConsultas('pending');
  renderPrescricoes('mine');
  renderMensagens();
  renderClinica(true);
  initSearch();
};
</script>
</body>
</html>

SEÇÃO 4 — MAPEAMENTO DE BANCO DE DADOS

4.1 Endpoint: GET /api/v1/cockpit/doctor/appointments

Retorna a lista de consultas do médico logado para o dia atual.

Tabelas envolvidas

TabelaBancoFunção no endpoint
AgendaGescomClienteAlfaTabela principal — dados da agenda
PacienteGescomClienteAlfaDados do paciente (nome, nome social)
PacienteFotoGescomClienteAlfaFoto do paciente
PEPDiagnosticoGescomClienteAlfaDiagnóstico oncológico do paciente
TermoTraducaoIpTerminologiaTradução do modo de atendimento
ClienteEmpresaIpSegurancaIdioma e fuso horário da clínica

Mapa de campos

CampoTipoBancoObservação
idintAgenda.IdAgenda
startTimehorárioAgenda.DhAgendaIniUTCsó horário, convertido para o fuso e idioma da clínica
endTimehorárioAgenda.DhAgendaFimUTCsó horário, convertido para o fuso e idioma da clínica
statusstringcalculadovide AppointmentStatus()
typestringAgenda.Atendimento -> TermoTraducao.TermoValorTermo = Agenda.Atendimento, Origem=“Indicadores
firstTimecharAgenda.PrimeiraConsultadomínio = ‘S’=sim, ‘N’=não
delayMinutesintNow(no fuso da clínica)-Agenda.DhAgendaIniUTC(no fuso da clínica)Se o valor for negativo (horário previsto ainda não chegou), retorna 0 — só é positivo quando o horário previsto já passou e a consulta não foi iniciada
patient.idintAgenda.IdPaciente
patient.prefferedNamestringPaciente.NomeSocialse existir, usar este como nome principal
patient.legalNamestringPaciente.Nomese NomeSocial não existir, usar este como nome
patient.photourlPacienteFoto.NomeArquivo, busca IdPaciente e Padrao = ‘S’, se multiplos, retorna o mais recenteurl gerada pelo MinIO
patient.diagnosisarray de stringPEPDiagnostico.Diagnostico para Tipo = ‘O’ e PEP.IdPacienteretorna TODOS os diagnósticos oncológicos ativos do paciente (não só um) — o card exibe a lista completa
tagsarray de stringPaciente.PacienteVip (bit=1 → tag vip) UNION ProgramaApoioPaciente filtrado por IdPaciente (ativo quando DataInicio <= hoje e DataFim IS NULL ou DataFim >= hoje) join ProgramaApoio (Nome/Sigla/Cor)Cor de cada tag vem de ProgramaApoio.Cor (exceto vip, que usa o token de destaque do Design System, Seção 2.8). Consultado no EsquemaGemed21.csv: ProgramaApoioPaciente/ProgramaApoio existem; PEPProgramaApoioPaciente (vínculo por PEP, em vez de por paciente) existe mas não é usado aqui — as tags do Cockpit são no nível do paciente, não do atendimento.

Funções de cálculo

AppointmentStatus() — calcula o status da consulta a partir dos campos da Agenda:

Status retornadoCondição
scheduled (Agendado)Agenda.Situacao = ‘A’ AND DhChegadaUTC IS NULL
waiting (Aguardando recepção)Agenda.Situacao = ‘A’ AND DhChegadaUTC IS NOT NULL
checked (Recepcionado)Agenda.Situacao = ‘V’ AND DhAtendimentoIniUTC IS NULL
consultation (Em atendimento)Agenda.Situacao = ‘V’ AND DhAtendimentoIniUTC IS NOT NULL AND DhAtendimentoFimUTC IS NULL
`finished’ (Finalizado)Agenda.Situacao = ‘V’ AND DhAtendimentoFimUTC IS NOT NULL
’canceled’ (Cancelado)Agenda.Situacao IN (‘C’,‘D’,‘F’,‘I’,‘M’,‘O’,‘T’)

Domínios

TipoAgenda (domínio de Agenda.TipoAgenda) na tabela Indicadores:

ValorSignificado
CConsulta
QQuimioterapia
RRadioterapia
SReserva de sala para assuntos não assistenciais

ModoAtendimento (domínio de Agenda.Atendimento) na tabela Indicadores:

ValorSignificado
PPadrão (Presencial)
TTeleatendimento
CCustomizado
RReservado

SituacaoAgenda (domínio de Agenda.Situacao) na tabela Indicadores:

ValorSignificado
AAberta
VConfirmada (check-in realizado)
CCancelada pelo usuário
DCancelada pelo paciente
MCancelada pelo médico
FFalta
ICancelado por internação
OCancelada por óbito
TTransferido (cancelado)

TipoDiagnostico (domínio de Diagnostico.Tipo):

ValorSignificado
CComorbidade
ODoença oncológica

Infraestrutura de apoio

MinIO (Storage de fotos):

  • Bucket global: gemed
  • Bucket por ambiente do cliente: nome = banco de dados do cliente
  • 1 bucket por banco de dados (cliente pode ter múltiplos bancos)
  • Pasta de fotos: FotosPacientes/ dentro do bucket do cliente
  • Seleção da foto: PacienteFoto.Padrao = 'S'; se múltiplas, selecionar a mais recente (futuro: vamos usar o campo CriadoEm da infraestrutura)

Internacionalização (i18n):

  • Idioma da clínica: ClienteEmpresa.IdiomaOficial (IpSeguranca), o termo segue padrão BCP 47
  • Fuso horário da clínica: ClienteEmpresa.FusoOficial (IpSeguranca)
  • Traduções: tabela TermoTraducao (IpTerminologia) usa chave composta por (Termo, Origem, Idioma)
    • Origem: nome da tabela de origem ou Indicadores|<NomeIndicador> quando o domínio está na tabela Indicadores
    • Idioma: Idioma do cliente
    • Termo: o termo usado como chave na tabela de origem

Filtros do endpoint

  • Agenda.IdChRecurso = {IdChProfissional do médico logado} — apenas consultas do médico
  • Agenda.TipoAgenda = 'C' - Agendas de consulta
  • Agenda.DhAgendaIniUTC(no fuso da clínica) = Hoje(no fuso da clinica) — apenas consultas do dia atual
  • Toggle “Pendentes”: Agenda.Situacao IN ('A','V')ANDDhAtendimentoFimUTC IS NULL — exclui canceladas e finalizadas
  • Toggle “Todas”: inclui finalizadas (canceladas não aparecem no painel — apenas finalizadas esmaecidas)

4.2 Endpoint: GET /api/v1/cockpit/doctor/kpis/appointments-today

Retornar o indicador de Consultas Hoje

Tabelas envolvidas

TabelaBancoFunção no endpoint
AgendaGescomClienteAlfaTabela principal — dados da agenda

Mapa de campos

CampoTipoBanco
totalintcount(Agenda.Situacao IN (‘A’, ‘V’))
finishedintcount(Agenda.Situacao=‘V’, Agenda.DhAtendimentoFimUTC<>NULL)
pendingintcount(Agenda.Situacao IN (‘A’, ‘V’), Agenda.DhAtendimentoFimUTC=NULL)
delayedintcount(Agenda.Situacao IN (‘A’, ‘V’), Agenda.DhAtendimentoFimUTC=NULL, Now(no fuso da clínica)-Agenda.DhAgendaIniUTC(no fuso da clinica)>0)

Domínios

** os mesmos do endpoint acima

Filtros do endpoint

  • Agenda.IdChRecurso = {IdChProfissional do médico logado} — apenas consultas do médico
  • Agenda.TipoAgenda = 'C' - agendas de consulta
  • Agenda.DhAgendaIniUTC(no fuso da clínica) = Hoje(no fuso da clinica) — apenas consultas do dia atual

4.3 Endpoint: [GET] /api/v1/cockpit/doctor/procedures

`{ "procedures":
    [ { "id": "uuid",
        "patient":
            { "id": "uuid",
              "preferredName": "",
              "legalName": "Roberto Oliveira"
            },
        "location": "Sala de Infusão 2",
        "status": "administering",
        "startTime": "2026-07-22T09:30:00Z",
        "progress": 65,
        "mine": true,
        "tags": ["quimioterapia"]
      }
    ]
}`

Retorna a lista de pacientes em procedimentos no dia atual.

Tabelas envolvidas

TabelaBancoFunção no endpoint
AgendaGescomClienteAlfaTabela principal — dados da agenda
PacienteGescomClienteAlfaDados do paciente (nome, nome social)
PacienteFotoGescomClienteAlfaFoto do paciente
PrescricaoGescomClienteAlfaDiagnóstico oncológico do paciente
TermoTraducaoIpTerminologiaTradução do modo de atendimento
ClienteEmpresaIpSegurancaIdioma e fuso horário da clínica

Mapa de campos

CampoTipoBancoObservação
idintPrescricao.IdPrescricao
prescriptionCodestringPrescricao.Identificacao
locationstringPrescricao.IdAgenda->Agenda.IdUAtendimento->UnidadeAtendimento.Descricao + Agenda.IdChRecurso->Chave.DescricaoIdUAtendimento pode ser NULL
startTimehoráriose Situacao=‘V’, Agenda.DhAtendimentoIniUTC(no fuso da clíncia) senão Agenda.DhAgendaIniUTC(no fuso da clínica)só horário, convertido para o fuso e idioma da clínica
statusstringcalculadovide ProcedureStatus()
progressint(Now()-Agenda.DhAtendimentoIniUTC(no fuso do servidor))/Agenda.Tempo*100
minebooleanPrescricao.IdChProfissionalMed={IdChProfissional do médico logado}
patient.idintPrescricao.IdPaciente
patient.prefferedNamestringPaciente.NomeSocialse existir, usar este como nome principal
patient.legalNamestringPaciente.Nomese NomeSocial não existir, usar este como nome
patient.photourlPacienteFoto.NomeArquivo, busca IdPaciente e Padrao = ‘S’, se multiplos, retorna o mais recenteurl gerada pelo MinIO
tagsarray de stringmesma origem de appointments.tags, ver Seção 4.1 (Paciente.PacienteVip + ProgramaApoioPaciente/ProgramaApoio)

Funções de cálculo

ProcedureStatus() — calcula o status do procedimento a partir dos campos da Agenda:

Status retornadoCondição
scheduled (Agendado)Agenda.Situacao = ‘A’ AND DhChegadaUTC IS NULL
waiting (Aguardando recepção)Agenda.Situacao = ‘A’ AND DhChegadaUTC IS NOT NULL
checked (Recepcionado)Agenda.Situacao = ‘V’ AND DhAtendimentoIniUTC IS NULL
preparation (Preparo)Agenda.Situacao = ‘V’ AND DhAtendimentoIniUTC IS NOT NULL AND DhInfusaoIniUTC IS NULL
administering (Em administração)Agenda.Situacao = ‘V’ AND DhInfusaoIniUTC IS NOT NULL AND DhInfusaoFimUTC IS NULL
administered (Completo)Agenda.Situacao = ‘V’ AND DhInfusaoFimUTC IS NOT NULL AND DhAtendimentoFim IS NULL
finished (Finalizado)Agenda.Situacao = ‘V’ AND DhAtendimentoFimUTC IS NOT NULL
canceled (Cancelado)Agenda.Situacao IN (‘C’,‘D’,‘F’,‘I’,‘M’,‘O’,‘T’)

Domínios

TipoAgenda (domínio de Agenda.TipoAgenda) na tabela Indicadores:

ValorSignificado
CConsulta
QQuimioterapia
RRadioterapia
SReserva de sala para assuntos não assistenciais

SituacaoAgenda (domínio de Agenda.Situacao) na tabela Indicadores:

ValorSignificado
AAberta
VConfirmada (check-in realizado)
CCancelada pelo usuário
DCancelada pelo paciente
MCancelada pelo médico
FFalta
ICancelado por internação
OCancelada por óbito
TTransferido (cancelado)

Infraestrutura de apoio

MinIO (Storage de fotos):

  • Bucket global: gemed
  • Bucket por ambiente do cliente: nome = banco de dados do cliente
  • 1 bucket por banco de dados (cliente pode ter múltiplos bancos)
  • Pasta de fotos: FotosPacientes/ dentro do bucket do cliente
  • Seleção da foto: PacienteFoto.Padrao = 'S'; se múltiplas, selecionar a mais recente (futuro: vamos usar o campo CriadoEm da infraestrutura)

Internacionalização (i18n):

  • Idioma da clínica: ClienteEmpresa.IdiomaOficial (IpSeguranca), o termo segue padrão BCP 47
  • Fuso horário da clínica: ClienteEmpresa.FusoOficial (IpSeguranca)
  • Traduções: tabela TermoTraducao (IpTerminologia) usa chave composta por (Termo, Origem, Idioma)
    • Origem: nome da tabela de origem ou Indicadores|<NomeIndicador> quando o domínio está na tabela Indicadores
    • Idioma: Idioma do cliente
    • Termo: o termo usado como chave na tabela de origem

Filtros do endpoint

  • Prescricao.IdAgenda <> NULL
  • Agenda.TipoAgenda IN 'Q', 'R' - Agendas de quimio, radio
  • Agenda.DhAgendaIniUTC(no fuso da clínica) = Hoje(no fuso da clinica) — apenas procedimentos do dia atual
  • Toggle “Meus”: Prescricao.IdChProfissionalMed={IdChProfissional do médico logado}
  • Toggle “Todos”: sem restrições

4.4 Endpoint GET /api/v1/cockpit/doctor/kpis/procedures-today

GET] /api/v1/cockpit/doctor/kpis/procedures-today { "total": 8, "inProgress": 2, "waiting": 4, "scheduled": 2 } Função: Retornar o indicador de Procedimentos Hoje

Tabelas envolvidas

TabelaBancoFunção no endpoint
AgendaGescomClienteAlfaTabela principal — dados da agenda
PacienteGescomClienteAlfaDados do paciente (nome, nome social)
PacienteFotoGescomClienteAlfaFoto do paciente
PrescricaoGescomClienteAlfaDiagnóstico oncológico do paciente
TermoTraducaoIpTerminologiaTradução do modo de atendimento
ClienteEmpresaIpSegurancaIdioma e fuso horário da clínica

Mapa de campos

CampoTipoBanco
totalintcount(Agenda.Situacao IN (‘A’, ‘V’))
inProgressintcount(ProcedureStatus()=administering)
waitingintcount(ProcedureStatus()=waiting OR checked OR preparation)
scheduledintcount(ProcedureStatus()=scheduled)

Funções de cálculo

ProcedureStatus() — calcula o status do procedimento a partir dos campos da Agenda:

Status retornadoCondição
scheduled (Agendado)Agenda.Situacao = ‘A’ AND DhChegadaUTC IS NULL
waiting (Aguardando recepção)Agenda.Situacao = ‘A’ AND DhChegadaUTC IS NOT NULL
checked (Recepcionado)Agenda.Situacao = ‘V’ AND DhAtendimentoIniUTC IS NULL
preparation (Preparo)Agenda.Situacao = ‘V’ AND DhAtendimentoIniUTC IS NOT NULL AND DhInfusaoIniUTC IS NULL
administering (Em administração)Agenda.Situacao = ‘V’ AND DhInfusaoIniUTC IS NOT NULL AND DhInfusaoFimUTC IS NULL
administered (Completo)Agenda.Situacao = ‘V’ AND DhInfusaoFimUTC IS NOT NULL AND DhAtendimentoFim IS NULL
completed (Finalizado)Agenda.Situacao = ‘V’ AND DhAtendimentoFimUTC IS NOT NULL
canceled (Cancelado)Agenda.Situacao IN (‘C’,‘D’,‘F’,‘I’,‘M’,‘O’,‘T’)

Domínios

TipoAgenda (domínio de Agenda.TipoAgenda) na tabela Indicadores:

ValorSignificado
CConsulta
QQuimioterapia
RRadioterapia
SReserva de sala para assuntos não assistenciais

SituacaoAgenda (domínio de Agenda.Situacao) na tabela Indicadores:

ValorSignificado
AAberta
VConfirmada (check-in realizado)
CCancelada pelo usuário
DCancelada pelo paciente
MCancelada pelo médico
FFalta
ICancelado por internação
OCancelada por óbito
TTransferido (cancelado)

Infraestrutura de apoio

Internacionalização (i18n):

  • Idioma da clínica: ClienteEmpresa.IdiomaOficial (IpSeguranca), o termo segue padrão BCP 47
  • Fuso horário da clínica: ClienteEmpresa.FusoOficial (IpSeguranca)
  • Traduções: tabela TermoTraducao (IpTerminologia) usa chave composta por (Termo, Origem, Idioma)
    • Origem: nome da tabela de origem ou Indicadores|<NomeIndicador> quando o domínio está na tabela Indicadores
    • Idioma: Idioma do cliente
    • Termo: o termo usado como chave na tabela de origem

Filtros do endpoint

  • Agenda.TipoAgenda IN 'Q', 'R' - Agendas de quimio, radio
  • Agenda.DhAgendaIniUTC(no fuso da clínica) = Hoje(no fuso da clinica) — apenas procedimentos do dia atual
  • `Prescricao.IdChProfissionalMed={IdChProfissional do médico logado} - só os meus pacientes

4.5 Endpoint [GET] /api/v1/cockpit/doctor/kpis/in-treatment

/api/v1/cockpit/doctor/kpis/in-treatment
`{  "inTreatment": 43, 
    "nonAdherent": 2 }`

Função: Retornar o indicador de Em Tratamento

Tabelas envolvidas

TabelaBancoFunção no endpoint
AgendaGescomClienteAlfaTabela principal — dados da agenda
PacienteGescomClienteAlfaDados do paciente (nome, nome social)
PrescricaoGescomClienteAlfaDiagnóstico oncológico do paciente
TermoTraducaoIpTerminologiaTradução do modo de atendimento

Mapa de campos

CampoTipoBanco
inTreatmentintPatientInTreatmentCount()
nonAdherentintTreatmentPlanAdherenceCount()

Funções de cálculo

PatientInTreatmentCount() — calcula a quantidade de pacientes em tratamento do médico

  • refere-se aos pacientes com agenda realizada há menos de 6 meses e/ou com prescrições realizadas há menos de 6 meses)
  • esses 6 meses referem-se a um parâmetro da clínica que diz qual o tempo máximo de inatividade para considerar o paciente inativo:
    • quanto às agendas: IpParametro.TempoPacienteAtivoAgenda -> IpParametroChave.Valor
    • quanto às prescrições: IpParametroTempoPacienteAtivoPrescricao -> IpParametroChave.Valor
  • Nas agendas obter as agendas com:
    • Situação ‘V’
    • DhAgendaIniUTC > Hoje()-IpParametroChave.Valor (onde IpParametro=TempoPacienteAtivoAgenda)
  • Nas prescrições obter as prescrições com:
    • Situação <> ‘C’ (canceladas)
    • Data > Hoje()-IpParametroChave.Valor (onde IpParametro=TempoPacienteAtivoPrescricao)
  • A query fica mais ou menos assim: SELECT DISTINCT p.IdChProfissional, p.IdPaciente FROM paciente p INNER JOIN agenda a ON a.IdPaciente = p.IdPaciente AND a.IdChRecurso = p.IdChProfissional WHERE a.Situacao = 'V' AND a.DhAgendaIniUTC > dateadd(day,-180, getdate()) -- há 6 meses UNION SELECT DISTINCT p.IdChProfissional , p.IdPaciente FROM Paciente p INNER JOIN Prescricao p2 on p2.IdPaciente = p.IdPaciente AND p.IdChProfissional = p2.IdChProfissionalMed WHERE p2.Situacao <> 'C' AND p2.[Data] > dateadd(day,-180, getdate()) -- há 6 meses Então conta a quantidade de pacientes do profissional logado.

TreatmentPlanAdherenceCount() — calcula os pacientes com plano terapêutico (PEPProjetoTerapeutico) que estão em descompasso com o planejado; quem não tem plano terapêutico não entra na contagem. Um paciente conta como não aderente quando QUALQUER UMA das três condições abaixo é verdadeira (lógica OU — confirmado com o solicitante: basta uma delas estar fora do parametrizado) para algum PEPProjetoTerapeuticoProtocolo do seu plano que tenha IdPrescricao:

  • A data do C1D1 realizado difere da data prevista em mais de Protocolo.ToleranciaDiasAdesao dias [PROPOSTA]
  • A prescrição passada não foi realizada (Prescricao.Situacao IN (‘P’,‘G’,‘A’,‘C’))
  • O intervalo entre sessões da prescrição diverge do intervalo programado no protocolo (Prescricao.Sessao = Prescricao.Data - Prescricao.Data_da_prescrição_anterior, comparado a Protocolo.IntervaloCiclosEmDias — já existe no schema — com a mesma tolerância Protocolo.ToleranciaDiasAdesao acima)

[PROPOSTA] Coluna nova em Protocolo (GescomClienteAlfa): ToleranciaDiasAdesao (int, dias de tolerância para desvio de datas previstas — reaproveitada tanto para o C1D1 quanto para o intervalo entre sessões). Não existe hoje no EsquemaGemed21.csv — confirmado com o solicitante que os valores de tolerância são parâmetros do protocolo, ainda não modelados na tabela Protocolo; a criar antes da implementação deste KPI.

Filtros do endpoint

  • Paciente.IdChProfissional={IdChProfissional do médico logado} - Pacientes em que o médico é o médico assistente

4.6 Endpoint: GET /api/v1/cockpit/doctor/kpis/prescription-alerts

Retorna o indicador de Alertas de Prescrição (urgentes, a revisar, a assinar nos próximos 7 dias).

Tabelas envolvidas

TabelaBancoFunção no endpoint
PrescricaoGescomClienteAlfaTabela principal — versão ativa (Situacao <> 'C'), médico prescritor
PrescricaoAssinaturaGescomClienteAlfaHistórico de assinaturas/revisões — usada para determinar se a prescrição está revisada
AgendaGescomClienteAlfaData/hora agendada da aplicação (via Prescricao.IdAgenda)

Mapa de campos

CampoTipoBancoObservação
urgentintPrescriptionUrgentCount()
toReviewintPrescriptionToReviewCount()
toSignNext7DaysintPrescriptionToSignNext7DaysCount()

Funções de cálculo

PrescriptionToReviewCount() — conta as prescrições “a revisar” do médico logado (RN-CM-020), restritas a prescrições em que o médico logado é o prescritor ou o médico assistente do paciente. Reaproveita PrescricaoAssinatura: um registro com TipoAssinatura = 'M' é a assinatura original do médico; um registro com TipoAssinatura = 'R' é uma confirmação de revisão. Qualquer um dos dois, se teve Data dentro dos últimos 7 dias, satisfaz o requisito — não é necessária uma tabela nova. Para “está assinada”, verifica tanto PrescricaoAssinatura quanto o campo legado Prescricao.IdChProfissionalAssinou, por compatibilidade com prescrições gravadas antes da adoção da tabela de assinaturas:

SELECT COUNT(*)
FROM Prescricao p
INNER JOIN Agenda a ON a.IdAgenda = p.IdAgenda
WHERE p.Situacao <> 'C'                                          -- apenas a versão ativa
  AND (
        EXISTS (SELECT 1 FROM PrescricaoAssinatura pa
                WHERE pa.IdPrescricao = p.IdPrescricao AND pa.TipoAssinatura = 'M')
        OR p.IdChProfissionalAssinou IS NOT NULL                 -- compatibilidade com legado
      )                                                          -- já assinada, por um dos dois caminhos
  AND a.DhAgendaIniUTC <= DATEADD(HOUR, 24, SYSDATETIMEOFFSET())  -- a 1 dia ou menos da aplicação
  AND NOT EXISTS (
        SELECT 1 FROM PrescricaoAssinatura pa2
        WHERE pa2.IdPrescricao = p.IdPrescricao
          AND pa2.TipoAssinatura IN ('M','R')
          AND pa2.Data > DATEADD(DAY, -7, SYSDATETIMEOFFSET())    -- assinatura/revisão válida por 7 dias
      )
  AND (p.IdChProfissionalMed = {IdChProfissional do médico logado}
       OR EXISTS (SELECT 1 FROM Paciente pac WHERE pac.IdPaciente = p.IdPaciente
                  AND pac.IdChProfissional = {IdChProfissional do médico logado}))

Nota: a cláusula de escopo acima é fechada (só prescritor OU assistente) — o KPI é uma contagem pessoal de “quanto precisa da sua atenção”, diferente do toggle “Todos” do painel (Seção 4.7), que deliberadamente amplia para qualquer médico da clínica porque ali o médico está decidindo ajudar a revisar colegas. Não existe (nem deveria existir) uma permissão RBAC de “revisor geral” que amplie o KPI — isso inflaria a contagem pessoal com prescrições de pacientes sem nenhum vínculo com o médico logado.

PrescriptionUrgentCount() — soma duas contagens, ambas restritas ao médico logado (prescritor ou assistente) e filtradas por hoursRemaining < 12 (RN-CM-011): (a) PrescriptionToReviewCount() restrita a hoursRemaining < 12; (b) prescrições pendentes de assinatura do médico logado (Prescricao.IdChProfissionalMed = {médico logado}, sem PrescricaoAssinatura.TipoAssinatura = 'M' E Prescricao.IdChProfissionalAssinou IS NULL) com hoursRemaining < 12. hoursRemaining = DATEDIFF(MINUTE, SYSDATETIMEOFFSET(), a.DhAgendaIniUTC) / 60.0.

PrescriptionToSignNext7DaysCount() — prescrições com Prescricao.Situacao <> 'C', Prescricao.IdChProfissionalMed = {IdChProfissional do médico logado} (só o próprio prescritor assina, RN-CM-012 — não há equivalente de “assistente” aqui), sem PrescricaoAssinatura.TipoAssinatura = 'M' E sem Prescricao.IdChProfissionalAssinou preenchido (compatibilidade com legado), e Agenda.DhAgendaIniUTC entre agora e +7 dias.

Filtros do endpoint

  • Escopo do médico logado, fechado nas 3 métricas: prescritor (Prescricao.IdChProfissionalMed) OU médico assistente do paciente (Paciente.IdChProfissional) para toReview e a parcela de revisão de urgent; apenas prescritor para toSignNext7Days e a parcela de assinatura de urgent. O KPI nunca inclui prescrições de outros médicos sem esse vínculo — ver nota acima sobre a diferença em relação ao toggle “Todos” do painel (Seção 4.7).

4.7 Endpoint: GET /api/v1/cockpit/doctor/prescriptions

Retorna a lista de prescrições pendentes (assinatura e revisão) do médico logado, para o painel e para a tela dedicada.

Tabelas envolvidas

TabelaBancoFunção no endpoint
PrescricaoGescomClienteAlfaTabela principal — versão ativa (Situacao <> 'C'); Identificacao já traz protocolo + ciclo/sessão formatados
PrescricaoAssinaturaGescomClienteAlfaDetermina se a prescrição está assinada e/ou revisada (ver 4.6)
PacienteGescomClienteAlfaNome social/civil do paciente e médico assistente
AgendaGescomClienteAlfaData/hora agendada da aplicação

Domínio — PrescricaoAssinatura.TipoAssinatura

ValorSignificado
MAssinatura do médico (assinatura original da prescrição ou de uma nova versão gerada por edição)
RConfirmação de revisão sem alteração da prescrição (“Marcar como Revisada”, RN-CM-020)

Nota: o esquema também sugere os tipos F (farmácia) e outros, usados por outras funcionalidades fora do escopo do Cockpit — não detalhados aqui.

Mapa de campos

CampoTipoBancoObservação
iduuidPrescricao.IdPrescricaoidentifica diretamente a versão ativa — não há campo de número de versão a considerar à parte
patient.iduuidPrescricao.IdPaciente
patient.preferredNamestringPaciente.NomeSocialmesmo padrão de appointments (4.1) e procedures (4.3) — enviado sempre, mesmo vazio, para o front sinalizar nome social ao médico
patient.legalNamestringPaciente.Nomeenviado sempre, junto com preferredName
prescriptionCodestringPrescricao.Identificacaosubstitui os antigos protocol/cycle — já vem formatado do banco (ex: “CARBO-TAXOL C3D1”), sem necessidade de montar a string a partir de Protocolo/PrescricaoProtocolo
scheduleDatedatetimeAgenda.DhAgendaIniUTC, via Prescricao.IdAgendaconvertida para o fuso da clínica na exibição
hoursRemainingintcalculado: Agenda.DhAgendaIniUTC - Now() em horas
typestringcalculadoto_sign se não existe PrescricaoAssinatura.TipoAssinatura = 'M' E Prescricao.IdChProfissionalAssinou IS NULL (checagem dupla por compatibilidade com legado, ver 4.6); to_review se assinada por qualquer um dos dois caminhos, mas nenhum registro M/R com Data nos últimos 7 dias (ver PrescriptionToReviewCount, 4.6)
isPreferredReviewerboolcalculadotrue se médico logado = Prescricao.IdChProfissionalMed OU = Paciente.IdChProfissional; presente somente quando type = to_review. Os campos prescriberName/attendingDoctorName cogitados antes foram removidos — o booleano basta para o front

Filtros do endpoint

  • type = to_sign: (não existe PrescricaoAssinatura.TipoAssinatura = 'M' E Prescricao.IdChProfissionalAssinou IS NULL) AND Prescricao.IdChProfissionalMed = médico logado AND Agenda.DhAgendaIniUTC até 7 dias a partir de agora — nunca afetado por reviewScope. O corte de 7 dias evita inflar a lista com prescrições distantes no tempo, sem relevância imediata para o médico.
  • type = to_review: condição temporal e de assinatura de PrescriptionToReviewCount() (Seção 4.6) — i.e., Situacao <> 'C', assinada (por qualquer um dos dois caminhos), dentro da janela D-1, sem assinatura/revisão válida nos últimos 7 dias — mas com a condição de escopo de médico da 4.6 substituída pelo reviewScope abaixo (a 4.6 é sempre “mine” porque é uma contagem pessoal do KPI; o painel de prescrições permite alternar):
    • reviewScope = mine (default): adiciona (Prescricao.IdChProfissionalMed = {médico logado} OR Paciente.IdChProfissional = {médico logado})
    • reviewScope = all: sem restrição adicional de médico — todas as prescrições a revisar da clínica
  • Sem parâmetro type: união das duas categorias, ordenada por hoursRemaining ascendente (RN-CM-011); reviewScope aplicado apenas à parte to_review da união, conforme acima
  • Prescricao.Situacao <> 'C' em ambos os casos — como há no máximo 1 prescrição ativa por cadeia de versões, este filtro já garante que apenas a versão vigente apareça na lista, sem risco de duplicidade

Nota: os endpoints de ação de prescrições (GET /prescriptions/{id}, POST /sign, PATCH /review, PUT /prescriptions/{id}) pertencem a um documento de processo de Prescrições à parte, fora do escopo deste documento. RN-CM-020 continua na Seção 2 porque alimenta o KPI/painel deste Cockpit (Seções 4.6/4.7). O esquema já suporta assinatura/revisão via PrescricaoAssinatura.TipoAssinatura (M/R) e versionamento via PrescricaoVersao, sem necessidade de tabela nova.

4.8 Endpoint: GET /api/v1/cockpit/doctor/conversations

Retorna as conversas não finalizadas do médico logado — lidas e não lidas (painel compacto do Cockpit — ver escopo em RN-CM-015 e Seção 2.5), com base no modelo de Conversas e Alertas (IP.Mensageria, documento “Conversas-e-Alertas”). Diferente das demais tabelas mapeadas neste documento, as tabelas de mensageria abaixo pertencem ao mesmo banco operacional do cliente (GescomClienteAlfa), mas são de um domínio separado (IP.Mensageria), não do domínio assistencial.

Tabelas envolvidas

TabelaBancoFunção no endpoint
ConversaGescomClienteAlfaTabela principal — thread, status, prazo, origem, paciente relacionado
ConversaDestinatarioGescomClienteAlfaSnapshot de quem recebeu a conversa — usada para filtrar pelo médico logado
ConversaLeituraGescomClienteAlfaDetermina se a conversa está lida para o médico logado
MensagemGescomClienteAlfaConteúdo e autor da última mensagem da thread (preview do card)
SegUsuarioGescomClienteAlfaPonte legada entre o GUID do IPSeguranca e o usuário local (ver resolução de nome abaixo)
PacienteGescomClienteAlfaNome social/civil do paciente relacionado, quando Conversa.PacienteId existe

Resolução do nome do autor: Mensagem.AutorUsuarioId e Conversa.RemetenteUsuarioId armazenam o GUID = IPSeguranca.dbo.Usuario.Id. Como não há FK física entre bancos, a triangulação correta é: AutorUsuarioId (GUID) = SegUsuario.SegurancaUsuarioId (join dentro do próprio GescomClienteAlfa) → nome de exibição em IPSeguranca.dbo.Usuario.Apelido (mesmo GUID) — não Profissional.IdUsuario; a fonte do nome é o apelido do usuário, não o nome do profissional, e a ponte é SegUsuario, não Profissional. Como efeito colateral, SegUsuario.IdUsuario (smallint) expõe o identificador legado local, o mesmo tipo de valor já usado em Profissional.IdUsuario/Prescricao.IdUsuarioAssinatura, útil se for necessário cruzar com tabelas assistenciais no futuro.

O ícone é binário: bell (Lucide) para OrigemTipo = sistema, ícone genérico de pessoa para OrigemTipo = usuario, sem depender do nome da equipe — não há distinção visual por equipe (recepção/farmácia/enfermagem). A tabela Equipe não é necessária neste endpoint — ela é relevante apenas no backend de roteamento de conversas (quem recebe, via EquipeMembro), não na exibição.

Mapa de campos

CampoTipoBancoObservação
iduuidConversa.Id
sourceTypestringcalculado a partir de Conversa.OrigemTiposystem se sistema; person se usuario
authorNamestringIPSeguranca.dbo.Usuario.Apelido, via SegUsuario (ver resolução acima), do autor da mensagem de maior Sequencia; ou “Sistema”
lastMessagestringMensagem.Conteudo (maior Sequencia da Conversa)
lastMessageAtdatetimeMensagem.CriadoEm (mesma linha)campo não listado explicitamente no documento de origem — assume-se equivalente a um campo de auditoria padrão do projeto
deadlinedatetimeConversa.PrazoEmUtcpode ser nulo
statusstringConversa.Statusmapeado para sent/read/replied (ver 2.9)
unreadboolcalculado: Conversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida (ou sem ConversaLeitura)usado para destaque visual e camada de prioridade (RN-CM-015); não filtra o resultado
patientobjectnullPaciente, via Conversa.PacienteId

Filtros do endpoint

  • ConversaDestinatario.UsuarioId = {médico logado} — recebida diretamente ou via snapshot de equipe
  • Conversa.ClienteEmpresaId = {filial da sessão validada} — nunca escolhido livremente pelo cliente da API (conforme fronteira de responsabilidade do IPSeguranca)
  • Conversa.Status IN ('Enviada', 'Lida', 'Respondida') — exclui Resolvida e Descartada
  • Conversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida, OU não existe ConversaLeitura para o médico logado — calcula o campo unread, usado como critério de ordenação (não filtra o WHERE).
  • Ordenação: unread descendente (não lidas primeiro), depois Conversa.PrazoEmUtc ascendente (nulos por último), depois lastMessageAt descendente — RN-CM-015

4.9 Endpoints de ação: GET /conversations/{id}, POST /read, POST /reply, PATCH /resolve, PATCH /discard

Tabelas envolvidas

TabelaBancoFunção no endpoint
ConversaGescomClienteAlfaStatus atual, UltimaSequencia
MensagemGescomClienteAlfaThread completa (GET detalhe); nova linha inserida em POST /reply
ConversaLeituraGescomClienteAlfaAtualizada em POST /read
ConversaAcaoGescomClienteAlfaHistórico append-only — nova linha em toda ação (leitura, resposta, resolução, descarte)
OutboxEventoGescomClienteAlfaEvento assíncrono gravado na mesma transação de qualquer mudança de estado (fora do escopo deste front, mencionado por completude)

Efeitos em BD por endpoint de ação

  • GET /conversations/{id}: somente leitura — retorna todas as Mensagem da conversa ordenadas por Sequencia, mais os dados de Conversa (status, prazo, paciente).
  • POST /read: insere ou atualiza ConversaLeitura (chave ConversaId + UsuarioId) com UltimaSequenciaLida = Conversa.UltimaSequencia; insere uma linha em ConversaAcao (Tipo=Leitura); se Conversa.Status = 'Enviada', atualiza para 'Lida' — nunca regride um status já mais avançado (Respondida permanece Respondida).
  • POST /reply: insere uma nova linha em Mensagem (Tipo=Resposta, Sequencia = Conversa.UltimaSequencia + 1, AutorUsuarioId = médico logado, Conteudo do request); atualiza Conversa.UltimaSequencia; atualiza Conversa.Status para 'Respondida'; insere uma linha em ConversaAcao (Tipo=Resposta, MensagemId da nova mensagem); grava OutboxEvento (TipoEvento=ConversaRespondida).
  • PATCH /resolve: atualiza Conversa.Status para 'Resolvida'; insere uma linha em ConversaAcao (Tipo=Resolução); grava OutboxEvento (TipoEvento=ConversaResolvida). Não altera nem exclui Mensagem.
  • PATCH /discard: atualiza Conversa.Status para 'Descartada'; insere uma linha em ConversaAcao (Tipo=Descarte); grava OutboxEvento (TipoEvento=ConversaDescartada). Mesma observação — nada é excluído fisicamente.

Fora do escopo deste documento

A criação de conversas (envio inicial por um usuário) e a publicação de alertas automáticos (AlertaPublicacao, com chave de idempotência) partem de outros módulos ou telas — o Cockpit é consumidor/participante das conversas, não o ponto de origem. Da mesma forma, Equipe/EquipeMembro (cadastro de equipes) e o processamento assíncrono via OutboxEvento pertencem ao domínio do IP.Mensageria e não são especificados aqui.


4.10 Endpoint: GET /api/v1/patients/search

Retorna pacientes que casam com o termo digitado no campo de busca do cabeçalho (RN-CM-004). A correspondência aproximada (pesos de proximidade fonológica e semântica) usa um mecanismo já existente no sistema, fora do escopo desta especificação — este mapeamento cobre apenas os campos de busca direta e de resposta que esse motor já existente consome/retorna.

Tabelas envolvidas

TabelaBancoFunção no endpoint
PacienteGescomClienteAlfaTabela única consultada — todos os campos de busca e resposta vêm dela

Mapa de campos

CampoTipoBancoObservação
idintPaciente.IdPaciente
namestringPaciente.Nomesegue o mesmo padrão de preferredName/legalName dos demais endpoints quando Paciente.NomeSocial existir
medicalRecordNumberstringPaciente.NumProntuario
gendercharPaciente.Sexodomínio ‘M’/‘F’
birthDatedataPaciente.Nascimentocampo de busca quando a entrada começa com dígito (RN-CM-004)
motherNamestringPaciente.NomeMaecampo de busca quando a entrada começa com letra (RN-CM-004)
cpfstringPaciente.CPFcampo de busca quando a entrada começa com dígito (RN-CM-004)

Filtros do endpoint

  • Sem filtro por médico logado — diferente dos demais painéis do Cockpit, a busca do cabeçalho não é restrita aos pacientes do médico, pois ele pode precisar localizar qualquer paciente da clínica.
  • Mínimo de 2 caracteres (RN-CM-004) antes de disparar a busca.

Nota sobre o motor de correspondência aproximada

O EsquemaGemed21.csv não tem uma coluna dedicada de correspondência fonética/semântica em Paciente — o motor de busca aproximada citado em RN-CM-004 já existe e está em produção fora deste documento; presume-se que ele consulta os mesmos campos mapeados acima, mas o algoritmo em si (ranking, tolerância, índice usado) não é redefinido aqui.


METADADOS FINAIS

  • Prioridade: Alta (Core do Sistema)
  • Complexidade: Média/Alta (Devido às integrações em tempo real)
  • Próximos Passos:
    1. Homologação do serviço de atualização automática.
    2. Teste de carga para múltiplos médicos acessando simultaneamente.
    3. Validação da assinatura digital em ambiente de sandbox.
    4. Definir ícones definitivos para os KPI cards (biblioteca Lucide Icons).
    5. Substituir ícones placeholder de comunicação por SVGs da biblioteca Lucide — apenas 2 ícones (bell para alertas de sistema, ícone de pessoa genérico para conversas com usuário; não há ícone por equipe/farmácia/enfermagem/recepção).
    6. Validar acessibilidade (WCAG, navegação por teclado) dos títulos como links e toggles pill.
    7. Testar usabilidade do título como link com seta no hover com médicos.
    8. Validar touchpoints de workflow com médicos em ambiente real (shadowing).
    9. Mapear touchpoints de workflows ainda não documentados (ex: workflow de alta, workflow de intercorrência).
    10. Especificar os demais itens do menu do usuário: autocadastro/edição de perfil, dados de faturamento e pagamento (conta bancária, PIX) e dados de especialidade (CRM, RQE) — hoje o menu só tem “Sair”.

HISTÓRICO DE VERSÕES

  • v1.0 (21/07/2026) — Versão inicial da Definição.

  • v1.1 (22/07/2026) — Atualização com busca, KPIs e grid 2×2.

  • v2.0 (22/07/2026) — Especificação completa com histórias e regras.

  • v3.0 (27/07/2026) — Consolidação narrativa e técnica; inclusão de SBIS.

  • v3.1 (28/07/2026) — Atualização de regras de busca, assinatura e status.

  • v3.2 (29/07/2026) — Reestruturação da Seção 2 com descrição funcional detalhada por painel.

  • v3.3 (30/07/2026) — Correções de busca, KPIs, prescrições, comunicação, procedimentos e validações.

  • v3.4 (30/07/2026) — Correções de dependências, avatar, KPI Em Tratamento e navegação.

  • v3.5 (31/07/2026) — Protótipo HTML: títulos como links, sem negrito, KPIs com borda colorida, barra de progresso discreta, toggles pill, busca com hint.

  • v3.6 (31/07/2026) — KPI Em Tratamento: número principal agora é “em tratamento” (removido conceito de “ativos”).

  • v3.7 (09/08/2026) — Incorporação de Análise de Workflow com touchpoints detalhados por workflow.

  • v3.8 (10/08/2026) — Narrativa de prescrições, hint no rodapé do dropdown, urgentes incluem revisão, ícone de sistema, mensagens não lidas sem negrito, tabela de i18n.

  • v3.9 (10/08/2026) — Data da agenda em prescrições, barra de progresso inline, consultas finalizadas esmaecidas em ordem, destaque visual para Recepcionado e Aguardando Recepção, renomeação para Procedimentos na Clínica.

  • v3.10 (10/08/2026) — Modal de revisão para assinar e revisar prescrições.

  • v3.11 (10/08/2026) — Restauração completa de conteúdo truncado em 2.1, 2.2 e 2.5.

  • v3.12 (10/08/2026) — Mudança de conceito de assinatura e revisão de prescrições, modal para mensagens da Comunicação, Endpoints em inglês.

  • v4.0 (11/08/2026) — Retirada do nome do médico do cabeçalho, api de consulta e mapeamento do BD.

  • v4.1 (12/08/2026) — Ajustes de internacionalização.

  • v4.2 (12/08/2026) — Ajustes em Procedimentos na clínica e api de procedimentos e seu mapeamento do BD.

  • v4.3 (13/08/2026) — Ajustes de termos no Estado das agendas.

  • v4.4 (13/08/2026) — Backend da KPI Em tratamento e Alertas de Prescrição.

  • v4.5 (24/08/2026) — Correção de numeração duplicada na Seção 4; renumeração sequencial da Seção 2 (2.9→2.8, 2.10→2.9, 2.11→2.10); incorporação do protótipo HTML funcional v3.12 na Seção 3.

  • v4.6 (24/08/2026) — Correção conceitual: “a revisar” passa a ser gatilho automático do sistema (D-1 antes da aplicação agendada), não mais notificação manual de problema pela farmácia/enfermagem. RN-CM-011 atualizada e RN-CM-020 criada. Ajustes propagados em Personas, Workflow 3, Regras de Negócio, Fluxo Principal, Dependências, US-CM-006, KPI de Alertas, painel de Prescrições Pendentes, mensagens de sistema (SYS_PRESC_REVIEW) e dados mockados do protótipo.

  • v4.7 (24/08/2026) — Endpoints de Prescrições Pendentes finalizados na Seção 2.9 (kpis/prescription-alerts, prescriptions, GET/{id}, POST sign, novo PATCH review, PUT edição). Mapeamento de BD completo na Seção 4.6-4.8, incluindo tabela nova PrescricaoRevisao para suportar RN-CM-020 (consultado EsquemaGemed21.csv — não havia tabela equivalente no schema atual).

  • v4.8 (24/08/2026) — Correção de modelagem de BD: removida a tabela PrescricaoRevisao. Revisão e assinatura passam a ser tratadas com a tabela já existente PrescricaoAssinatura (TipoAssinatura = ‘M’ assinatura do médico, ‘R’ revisão), com janela de validade de 7 dias. Esclarecido que cada versão da prescrição é um novo registro em Prescricao (apenas 1 ativo por vez, Situacao <> ‘C’) e que editar uma prescrição já assinada gera nova versão que precisa ser assinada novamente — não é mais considerada “auto-revisada”. RN-CM-020, Seções 2.9, 2.4 e 4.6-4.8 atualizadas.

  • v4.9 (24/08/2026) — Adicionado toggle “Meus/Todos” ao painel de Prescrições Pendentes (2.4): afeta somente as prescrições a revisar (as a assinar já são sempre do médico logado). Default “Todos”. Novo parâmetro reviewScope no endpoint GET prescriptions (2.9) e no mapeamento de BD (4.7). Protótipo HTML atualizado com o toggle, novo item mockado (meu: false) para demonstrar o filtro, e estado do toggle preservado entre ações.

  • v4.10 (24/08/2026) — Correção no KPI de Alertas de Prescrição (4.6): removida cláusula especulativa de permissão RBAC “revisor geral” que inflaria a contagem pessoal com prescrições sem vínculo com o médico logado; explicitado que toSignNext7Days também é restrito ao médico prescritor (faltava esse filtro na descrição). O KPI está confirmado como estritamente pessoal (prescritor ou assistente do paciente), sem afetar nem ser afetado pelo toggle “Meus/Todos” do painel.

  • v4.11 (24/08/2026) — Default do toggle “Meus/Todos” (painel de Prescrições Pendentes) corrigido de “Todos” para “Meus” — e do parâmetro reviewScope de all para mine. Justificativa do touchpoint de workflow revisada: como as prescrições a assinar nunca são afetadas pelo toggle, o argumento anterior para default “Todos” não se sustentava. Corrigido em 2.4 (descrição funcional, critério de aceitação), 2.9, 4.7 e no protótipo HTML (botão ativo, variável de estado, chamada de inicialização).

  • v4.12 (24/08/2026) — Ajustes na API de Prescrições Pendentes (GET /prescriptions): campos protocol/cycle substituídos por prescriptionCode (= Prescricao.Identificacao, já formatado); prescriberName/attendingDoctorName removidos (basta isPreferredReviewer); checagem de assinatura agora considera também o campo legado Prescricao.IdChProfissionalAssinou (compatibilidade); type=to_sign passa a filtrar por até 7 dias de antecedência; patient passa a enviar preferredName/legalName (mesmo padrão de appointments/procedures). Removidos desta doc os endpoints de ação sobre prescrição individual (GET detalhe, sign, review, edição) e a Seção 4.8 correspondente — migram para um documento de processo de Prescrições à parte.

  • v4.13 (24/08/2026) — Painel de Notificações (2.5) reescrito para o modelo de Conversas e Alertas (IP.Mensageria): mensagem avulsa vira Conversa/Mensagem (thread); estado de leitura passa a ser por sequência (ConversaLeitura); “Descartado” deixa de excluir e passa a mudar Status (append-only); adicionada capacidade de responder direto do Cockpit (US-CM-010, RN-CM-015 revisada); ícone de origem resolvido por correspondência de nome de Equipe (camada de compatibilidade assumida, sinalizada como tal — revertida na v4.14). Endpoint renomeado de /messages para /conversations; DELETE /messages/{id} virou PATCH /conversations/{id}/discard; novos endpoints /read e /reply. Novo mapeamento de BD nas Seções 4.8 e 4.9. Protótipo HTML atualizado com thread completa no modal e campo de resposta.

  • v4.14 (24/08/2026) — Correções no painel de Comunicação: (1) abolido o conceito de ícone por equipe da v4.13 — ícone passa a ser binário (sistema vs. pessoa), e o nome da equipe deixa de aparecer junto ao nome do autor nos cards/thread/i18n; Seção 4.8 simplificada, removida a tabela Equipe do mapeamento de exibição. (2) Corrigida a resolução do nome do autor: a triangulação correta é AutorUsuarioId/RemetenteUsuarioId (GUID) → SegUsuario.SegurancaUsuarioClienteIdIPSeguranca.dbo.Usuario.Apelido — substituindo a suposição incorreta da v4.13 (join direto com Profissional.IdUsuario). Campo authorName substitui sourceName/sourceIcon/lastMessageAuthor na API. Protótipo HTML e mocks atualizados.

  • v4.15 (24/08/2026) — Painel de Comunicação restrito a conversas não lidas — conversas lidas ou já respondidas (sem atividade nova) passam a sair do painel imediatamente, mesmo em aberto; só reaparecem com nova atividade. RN-CM-015, 2.5 (descrição funcional, critérios de aceitação), endpoint GET /conversations (2.9, removido campo unread redundante) e mapeamento de BD (4.8, novo filtro de não lida) atualizados. Deixada nota explícita de que a lista completa de conversas (lidas ou não) será uma tela dedicada, fora do escopo desta versão. Protótipo HTML atualizado (painel filtra por unread, estado vazio adicionado). Revertida na v4.17.

  • v4.16 (24/08/2026) — Cabeçalho: removida a unidade/local de atendimento (fica só o nome da clínica) e removidos nome do médico/CRM/especialidade — fecha uma decisão registrada desde a v4.0 que nunca tinha sido aplicada ao protótipo. Avatar ganhou ícone de dropdown, abrindo um menu do usuário (por enquanto só “Sair”; autocadastro, dados de faturamento/pagamento e de especialidade ficam para versões futuras, ainda não especificadas). Seção 2.1, protótipo HTML (CSS + markup + JS) e Design System (2.8) atualizados. Revisão geral do documento encontrou e corrigiu 4 inconsistências remanescentes de versões anteriores: US-CM-010 duplicado (renumerada a história de resposta para US-CM-012); RN-CM-002 e a tabela de componentes (2.8) ainda descreviam mensagens por equipe/ícones abolidos na v4.14; nome do endpoint de busca de pacientes divergente entre a Seção 2.9 (/patients/search) e a Conformidade SBIS (/pacientes/busca); ECF.17.01 ainda referenciava “origem + nome do profissional” para mensagens, desatualizado desde a reformulação de Conversas e Alertas.

  • v4.17 (27/08/2026) — Painel de Notificações (2.5): revertida a regra da v4.15 — conversas lidas deixam de sair do painel. O painel passa a mostrar todas as conversas não finalizadas (Status diferente de Resolvida/Descartada) do médico logado, lidas ou não; conversas lidas permanecem visíveis, mas com prioridade reduzida na ordenação (sempre depois das não lidas). Dentro de cada camada de leitura, o critério de desempate (prazo, depois atividade mais recente) segue o mesmo de antes — extensão proposta pelo Designer para a camada de lidas, a confirmar com o solicitante. Destaque visual (ícone e borda coloridos) passa a distinguir apenas as não lidas dentro da lista mista. Estado vazio agora só dispara quando não há nenhuma conversa não finalizada (lida ou não), não mais apenas quando não há não lidas. RN-CM-015, 2.5 (descrição funcional, ordenação, critérios de aceitação, estado vazio), endpoint GET /conversations (2.9, campo unread reintroduzido) e mapeamento de BD (4.8, filtro de leitura vira critério de ordenação) atualizados. Protótipo HTML atualizado (painel deixa de filtrar por unread; classe msg-unread aplicada condicionalmente; ordenação e estado vazio ajustados).

  • v4.18 (27/08/2026) — Reestruturação do documento (não altera regras de negócio): o Histórico de Revisões saiu do topo do documento e virou a última seção, “Histórico de Versões”, em formato de lista (não mais tabela) — segue o padrão já adotado no documento guarda-chuva de Check-in de Usuários (CIU), e passa a ser o padrão do projeto (Skill Designer, seção “Estrutura Padrão: Histórico ao Final”). Removidas do corpo do texto (Seções 1 a 4 e Protótipo HTML) as referências a números de versão e a comparações com “a versão anterior” — o corpo passa a descrever só o estado atual, para não exigir do leitor (analistas, QAs, devs) o acompanhamento do histórico completo do documento para entender uma regra vigente. Racionais de decisão que valem a pena preservar (o “porquê” por trás de uma regra atual) permanecem no corpo como “Motivador”/“Nota”, sem referência a versão; racionais puramente históricos (o que mudou em relação a uma versão anterior) migraram para as entradas correspondentes deste Histórico. Removida do checklist de pendências do protótipo (Seção 3) a entrada já corrigida referente ao nome do médico no cabeçalho (resolvida na v4.16, permanece registrada aqui).

  • v4.19 (27/08/2026) — Seção 4.8 (nota “Resolução do nome do autor”): corrigido o nome do campo SegUsuario.SegurancaUsuarioClienteId para SegUsuario.SegurancaUsuarioId, refletindo o rename aplicado ao banco (registrado no “Log de Mudanças Estruturais do Banco de Dados”). A referência do campo também mudou — antes apontava para UsuarioCliente.Id, agora para Usuario.Id — mas a lógica de join descrita nesta seção já tratava o valor como o GUID de Usuario.Id desde a v4.14; não houve mudança de lógica, só de nome do campo citado.

  • v4.20 (28/08/2026) — Revisão de ponta a ponta do painel de Comunicação (regra de negócio, endpoint, mapeamento de BD, protótipo HTML/JS) em busca de resíduos do conceito de ícone/nome por equipe abolido na v4.14: RN-CM-015, Seção 2.9, Seção 4.8 e a lógica do protótipo (iconForOrigem, openMsgModal) já estavam consistentes — nenhuma mudança de regra foi necessária. Encontrado um resíduo isolado nos dados mockados do protótipo (Seção 3): a conversa de exemplo id 4 usava “Recepção” (nome de equipe) como origemNome/autor da thread, contradizendo a própria regra de que authorName é sempre o apelido de uma pessoa, nunca o nome de uma equipe. Corrigido para um nome de pessoa fictício (“Juliana”), mantendo o mesmo papel funcional (recepcionista repassando resultado de exame, conforme persona Recepcionista da Seção 1). Nenhuma outra ocorrência de nome de equipe como autor foi encontrada.

  • v4.21 (28/08/2026) — Revisão completa a partir de duas críticas independentes: uma do ponto de vista de um analista de negócio/médico não técnico (Seção 1), outra do ponto de vista técnico de QA/Dev/Designer (Seções 2 a 4). Seção 1 (regras de negócio): RN-CM-001, 002, 003, 011, 012, 015, 016, 017, 019 e 020 reescritas para remover jargão técnico de implementação (nomes de token de cor, font-weight, “polling/websocket”, nomes de campo de BD) do texto voltado a stakeholders não técnicos — o detalhamento técnico correspondente permanece por referência na Seção 2.8/2/4. RN-CM-017 (atualização frequente) marcada explicitamente como [A DEFINIR] quanto ao mecanismo/intervalo técnico exato, em vez de sugerir uma decisão já tomada. Nota adicionada à tabela de personas esclarecendo que Enfermagem e Farmácia entram nela como stakeholders impactados, não como usuárias do Cockpit. RN-CM-004 corrigida: deixa de descrever um algoritmo de busca aproximada a construir e passa a creditar o motor existente (já em produção, calcula proximidade fonológica e semântica), removendo a contradição com o protótipo — confirmado com o solicitante que esse algoritmo é fora do escopo deste documento. Decisões de negócio confirmadas com o solicitante e incorporadas: (1) a busca aproximada usa o motor já existente, não precisa de nova especificação; (2) a origem das tags do paciente é Paciente.PacienteVip (bit, tag “vip”) e as tabelas ProgramaApoioPaciente/ProgramaApoio (tags inseridas pelo corpo clínico/administração, ex. “Quimioterapia”) — PEPProgramaApoioPaciente existe no schema mas não é usada aqui por ser vinculada ao atendimento (PEP), não ao paciente; (3) os limiares que disparam badges de atraso/prioridade são parâmetros configuráveis por clínica, cujos valores padrão vêm da experiência do sistema legado; (4) a aderência ao protocolo (Seção 4.5) passa a usar condição “OU” entre desvio de data do C1D1 e desvio de intervalo entre sessões — basta uma delas estar fora do parâmetro para o tratamento ser considerado não aderente — com os valores de tolerância vindos do protocolo, em uma coluna nova [PROPOSTA] Protocolo.ToleranciaDiasAdesao (não existe hoje no EsquemaGemed21.csv); (5) quando o paciente tem mais de um diagnóstico oncológico ativo, todos são exibidos, não apenas o primeiro — patient.diagnosis passa de string única para array em todos os endpoints (2.3, 4.1) e o protótipo passa a renderizar a lista completa. Seções 2 a 4 (técnico): estados vazios de 2.3/2.4/2.5 alinhados ao texto real das chaves de i18n (MSG_appointmentNone, MSG_prescriptionNone, MSG_notificationNone); adicionadas linhas de i18n que faltavam (MSG_prescriptionSigned, MSG_prescriptionSignError, label-prazo-responder, MSG_conversationResolved, MSG_conversationDiscarded, MSG_replySent); exemplo de status do procedimento em 2.6 deixa de usar snake_case e passa a referenciar o domínio canônico definido em ProcedureStatus() (4.3/4.4); botão “Descartado” incluído no mapeamento de componentes (2.8); corrigidos dois erros de sintaxe JSON nos exemplos de 2.9 e 4.3 (aspas faltando após legalName); unificado o status in_progress/administering para o valor canônico administering no exemplo de 4.3. Corrigido bug de sinal na fórmula de delayMinutes (4.1) e do KPI delayed (4.2) — a fórmula anterior (horário previsto - agora) dava negativo quando a consulta estava atrasada; invertida para agora - horário previsto, positiva só quando o horário já passou. Corrigido o bucketing dos KPIs inProgress/waiting de procedimentos (4.4) para usar o domínio real de ProcedureStatus(). Unificadas para crase as aspas mistas (`/') nos valores retornados por ProcedureStatus() (4.3). Adicionada linha tags ao mapeamento de campos de appointments (4.1) e procedures (4.3), com a mesma origem (Paciente.PacienteVip + ProgramaApoioPaciente/ProgramaApoio) confirmada acima. Nova Seção 4.10, mapeando o endpoint /patients/search ao banco (não existia mapeamento de BD para esse endpoint). No protótipo (Seção 3): campo de data de nascimento incluído nos critérios de busca; mocks de consultas e da tela de clínica deixam de usar cor ad-hoc por item e passam a usar statusColor(); diagnóstico passa a array em ambos os mocks; renderConsultas/renderClinica atualizados para exibir múltiplos diagnósticos e tags, e para tratar estado vazio; botão “Resolvido” do modal de conversa trocado de btn-primary para btn-tertiary, consistente com o card e com o mapeamento de 2.8. Protótipo funcional avança de v3.12 para v3.13 refletindo essas mudanças de JS/mock.

Documento elaborado em 11 de agosto de 2026. As informações contidas são de responsabilidade do solicitante.