DOCUMENTO DE ESPECIFICAÇÃO FUNCIONAL CONSOLIDADO
Central do Médico (Cockpit) — Versão 4.17
27 de agosto de 2026
1. Histórico de Revisões
| Versão | Data | Autor | Descrição |
|---|---|---|---|
| 1.0 | 21/07/2026 | Designer de Protótipos | Versão inicial da Definição. |
| 1.1 | 22/07/2026 | Designer de Protótipos | Atualização com busca, KPIs e grid 2×2. |
| 2.0 | 22/07/2026 | Designer de Protótipos | Especificação completa com histórias e regras. |
| 3.0 | 27/07/2026 | Designer de Protótipos | Consolidação narrativa e técnica; inclusão de SBIS. |
| 3.1 | 28/07/2026 | Analista de Sistemas | Atualização de regras de busca, assinatura e status. |
| 3.2 | 29/07/2026 | Designer de Protótipos | Reestruturação da Seção 2 com descrição funcional detalhada por painel. |
| 3.3 | 30/07/2026 | Designer de Protótipos | Correções de busca, KPIs, prescrições, comunicação, procedimentos e validações. |
| 3.4 | 30/07/2026 | Designer de Protótipos | Correções de dependências, avatar, KPI Em Tratamento e navegação. |
| 3.5 | 31/07/2026 | Designer de Protótipos | Protótipo HTML: títulos como links, sem negrito, KPIs com borda colorida, barra de progresso discreta, toggles pill, busca com hint. |
| 3.6 | 31/07/2026 | Designer de Protótipos | KPI Em Tratamento: número principal agora é “em tratamento” (removido conceito de “ativos”). |
| 3.7 | 09/08/2026 | Designer de Protótipos | Incorporação de Análise de Workflow com touchpoints detalhados por workflow. |
| 3.8 | 10/08/2026 | Designer de Protótipos | 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. |
| 3.9 | 10/08/2026 | Designer de Protótipos | 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. |
| 3.10 | 10/08/2026 | Designer de Protótipos | Modal de revisão para assinar e revisar prescrições. |
| 3.11 | 10/08/2026 | Designer de Protótipos | Restauração completa de conteúdo truncado em 2.1, 2.2 e 2.5. |
| 3.12 | 10/08/2026 | Designer de Protótipos | Mudança de conceito de assinatura e revisão de prescrições, modal para mensagens da Comunicação, Endpoints em inglês. |
| 4.0 | 11/08/2026 | Marcos Arao | Retirada do nome do médico do cabeçalho, api de consulta e mapeamento do BD |
| 4.1 | 12/08/2026 | Marcos Arao | Ajustes de internacionalização |
| 4.2 | 12/08/2026 | Marcos Arao | Ajustes em Procedimentos na clínica e api de procedimentos e seu mapeamento do BD |
| 4.3 | 13/08/2026 | Marcos Arao | Ajustes de termos no Estado das agendas |
| 4.4 | 13/08/2026 | Marcos Arao | Backend da KPI Em tratamento e Alertas de Prescrição |
| 4.5 | 24/08/2026 | Marcos Arao | 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 |
| 4.6 | 24/08/2026 | Marcos Arao | 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. |
| 4.7 | 24/08/2026 | Marcos Arao | 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). |
| 4.8 | 24/08/2026 | Marcos Arao | 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. |
| 4.9 | 24/08/2026 | Marcos Arao | 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. |
| 4.10 | 24/08/2026 | Marcos Arao | 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. |
| 4.11 | 24/08/2026 | Marcos Arao | 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). |
| 4.12 | 24/08/2026 | Marcos Arao | 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. |
| 4.13 | 24/08/2026 | Marcos Arao | Painel de Notificações (2.5) reescrito para o novo 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). 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. |
| 4.14 | 24/08/2026 | Marcos Arao | Correções no painel de Comunicação: (1) abolido o conceito de ícone por equipe da v4.13 — ícone agora é binário (sistema vs. pessoa), e o nome da equipe não aparece mais 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.SegurancaUsuarioClienteId → IPSeguranca.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. |
| 4.15 | 24/08/2026 | Marcos Arao | Painel de Comunicação restrito a conversas não lidas — conversas lidas ou já respondidas (sem atividade nova) saem 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). |
| 4.16 | 24/08/2026 | Marcos Arao | Cabeçalho: removida a unidade/local de atendimento (fica só o nome da clínica) e removidos nome do médico/CRM/especialidade — isso finalmente 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. |
| 4.17 | 27/08/2026 | Marcos Arao | 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). |
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
| Persona | Papel no Sistema | Necessidade Principal |
|---|---|---|
| Médico Oncologista | Usuário Principal | Visão 360º do dia, agilidade na assinatura e monitoramento de segurança. |
| Recepcionista | Stakeholder operacional | Manté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êutica | Stakeholders clínicos | 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). |
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 workflow | Necessidade do profissional | O que a Central oferece | Decisão de design justificada |
|---|---|---|---|
| Antes de chamar o próximo paciente | Saber rapidamente quem está recepcionado e pronto para atendimento | Painel 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 paciente | Identificar se o paciente está atrasado (horário passou e não iniciou) | Badge vermelho pulsante “Atrasado · Xmin” no card de consulta | O atraso é uma exceção do workflow que exige ação imediata — o pulsante chama atenção sem que o médico precise procurar. |
| Entre atendimentos | Acessar o prontuário do próximo paciente sem navegar por menus | Clique no card de consulta abre diretamente o PEP | No workflow, o médico precisa do prontuário imediatamente antes de chamar o paciente — um clique é o menor caminho possível. |
| Início do turno | Ter 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 atendimento | Não ser interrompido por informações irrelevantes | Consultas finalizadas aparecem esmaecidas na sua posição original | No 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 workflow | Necessidade do profissional | O que a Central oferece | Decisão de design justificada |
|---|---|---|---|
| Entre consultas / durante o turno | Saber quais pacientes estão em procedimento na clínica e em qual etapa | Painel de Procedimentos na Clínica com status (Em Atendimento / Aguardando) e local na clínica | No 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 monitoramento | Saber se uma infusão está dentro do tempo previsto ou atrasada | Barra de progresso inline (3px) na linha do nome, com porcentagem | No 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 consultas | Decidir se precisa intervir em um procedimento em andamento | Clique no card abre a prescrição do procedimento; botão separado “PEP” abre o prontuário | No 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 turno | Saber quantos pacientes estão em tratamento sob sua responsabilidade e quantos não estão aderentes | KPI “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 turno | Filtrar apenas seus pacientes vs. ver todos os da clínica | Toggle 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 workflow | Necessidade do profissional | O que a Central oferece | Decisão de design justificada |
|---|---|---|---|
| Início do turno / entre atendimentos | Assinar prescrições pendentes antes que a farmácia possa manipular | Painel de Prescrições Pendentes com botão “Assinar” no card que abre a tela de assinatura com a prescrição completa | No 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 turno | Priorizar quais prescrições assinar primeiro (as mais urgentes) | Chips de urgência: vermelho (<12h) e laranja (12-24h) com tempo restante | No 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 agendada | Confirmar que a prescrição foi revisada por um médico antes da administração ao paciente | Card 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 confirmar | No 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 turno | Saber quantas prescrições urgentes (menos de 12h) precisam de sua atenção | KPI “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ção | Confirmar que a assinatura foi processada e retornar à Central | Toast de sucesso “Prescrição assinada com sucesso” + retorno automático à Central, focada no painel de Prescrições Pendentes + atualização do KPI | No 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ções | Focar primeiro nas prescrições que é preferencialmente responsável por revisar (seus pacientes), mas poder ajudar a revisar as de colegas quando necessário | Toggle 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 logado | No 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 workflow | Necessidade do profissional | O que a Central oferece | Decisão de design justificada |
|---|---|---|---|
| Durante todo o turno | Receber conversas e alertas da equipe sem sair da Central | Painel 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 nova | Identificar rapidamente que há conversa não lida | Conversas não lidas com ícone colorido e borda lateral destacada | No 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 conversas | Priorizar conversas com prazo de resposta | Prazo exibido no card (“Responder até 14:00”) + ordenação por prazo primeiro | No 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 rapidamente | Responder sem sair do Cockpit, sem precisar telefonar ou ir até a equipe | Campo 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ção | Arquivar a conversa com um clique | Botão “Resolvido” no card que remove a conversa e atualiza o contador | No 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 turno | Distinguir 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
Layout e Visualização
- RN-CM-001 — Layout Full Viewport: A Central deve ocupar 100% da altura da tela (100vh), evitando barras de rolagem laterais na página principal. Cada painel interno possui sua própria rolagem.
- RN-CM-002 — Grid 2×2: 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 fundo cinza claro (color-base-dark) para diferenciar do corpo do painel (fundo branco), orientando o olhar do usuário entre a área estrutural e a área de conteúdo.
- 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 (color-secondary-dark) e uma seta (→) aparece discretamente ao lado do título, deslizando da esquerda. 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 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 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. 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.
- 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.
Comunicação Interna
- RN-CM-015 — Conversas, Alertas e Priorização: Toda comunicação no Cockpit (mensagens entre profissionais e alertas automáticos) é modelada como uma
Conversa(thread), que pode conter váriasMensagem(inicial, resposta, réplica ou atualização de alerta). Uma conversa é considerada não lida para o médico logado quandoConversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida(ou quando não existe registro de leitura para ele). O painel compacto do Cockpit (Seção 2.5) exibe todas as conversas não finalizadas (Statusdiferente deResolvida/Descartada) do médico logado, lidas ou não — a partir da v4.17, ler uma conversa não a remove mais do painel: ela permanece visível, mas perde prioridade na ordenação (reverte a regra da v4.15, ver Histórico de Revisões). Uma conversa só sai do painel quando é resolvida ou descartada. A lista completa de conversas do médico (histórico irrestrito, incluindo as já finalizadas) fica reservada a uma tela dedicada, fora do escopo desta versão do documento. Não lidas são destacadas visualmente através do ícone da origem na cor primary-pure e borda lateral esquerda na cor secondary-pure (sem uso de negrito no texto, conforme RN-CM-019); conversas lidas não recebem esse destaque. Ao abrir o modal da conversa, o sistema grava/atualizaConversaLeitura, o destaque visual é removido e a conversa migra para o grupo de prioridade reduzida na próxima atualização da lista, sem sair do painel. Quem inicia a conversa (usuário ou módulo automático) pode definir umPrazoEmUtcpara resposta ou resolução. A ordenação da lista tem duas camadas: primeiro, conversas não lidas sempre antes das lidas — uma conversa lida nunca aparece à frente de uma não lida, mesmo com prazo mais próximo ou atividade mais recente; dentro de cada camada, o prazo (quando definido, mais próximo primeiro) e, para as demais, a atividade mais recente da thread (LIFO) decidem a ordem — critério de desempate da camada de lidas proposto pelo Designer por consistência com a camada de não lidas, a confirmar com o solicitante. O médico pode responder diretamente pelo Cockpit — a resposta grava uma novaMensagem(Tipo=Resposta) e mudaConversa.StatusparaRespondida; como responder marca a conversa como lida para o médico no mesmo golpe, ela migra para a camada de prioridade reduzida após a resposta, permanecendo no painel — a menos que já exista nova atividade não vista, caso em que continua na camada de não lidas. Resolver ou descartar grava uma entrada emConversaAcao(append-only) e muda oStatusparaResolvida/Descartada, respectivamente — ação que nunca regride para um estado anterior e é a única forma de uma conversa sair do painel. Se um alerta automático recorrer depois de uma conversa já encerrada, uma novaConversaé criada apontando para a anterior viaConversaAnteriorId, preservando o histórico separado por ocorrência.
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.
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. O mecanismo técnico de atualização (polling, websocket ou outro) será definido pelo time técnico, mas o requisito funcional é que as informações exibidas estejam sempre atualizadas dentro de uma janela de tempo curta o suficiente para ser operacionalmente útil.
- RN-CM-019 — Hierarquia visual sem negrito: A Central não utiliza negrito (font-weight: 700) em nenhum elemento estrutural. A hierarquia visual é construída exclusivamente através de tamanho tipográfico, cor e espaçamento. Elementos estruturais (títulos de painel, labels, metadados) usam peso regular (400). Destaques importantes são feitos por contraste cromático (chips coloridos, borda lateral dos KPIs, número vermelho de alertas) e tamanho (22px nos valores de KPI, 14px nos nomes, 11px nos metadados). 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.
Fluxo Principal (O Ciclo do Dia)
- Início do Turno: O médico faz login e visualiza o panorama geral nos KPIs.
- 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.
- Atendimento: Ao identificar um paciente “Recepcionado” no painel de Consultas, o médico clica no nome para abrir o PEP e iniciar o atendimento.
- Monitoramento: Entre consultas, o médico observa o painel de Procedimentos na Clínica para verificar se algum tratamento está atrasado ou finalizado.
- 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ódulo | Dependência | Impacto |
|---|---|---|
| Módulo de Agenda | Status da agenda | Sem isso, o painel de consultas não atualiza. |
| Módulo de Agenda | Data/hora da aplicação da prescrição | A 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 Assinatura | Integração com Certificadora | Necessário para a validade jurídica da assinatura (quando aplicável). |
| Recepcionista | Atualização de status na agenda | Define os status “Agendado” e “Recepcionado” no painel de Consultas. |
Requisitos SBIS Aplicáveis
| ID SBIS | Descrição Acessível | Implementação no Cockpit |
|---|---|---|
| ECF.03.11 | Busca multifatorial de pacientes | Implementado na busca do Header (Nome, CPF, Mãe). |
| ECF.16.01 | Listagem de pendências do profissional | Implementado no Painel de Prescrições Pendentes. |
| ECF.17.04 | Registro de eventos em ordem cronológica | Aplicado 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:
- 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, v4.16).
- Campo de busca de pacientes: Campo de texto. A busca é inteligente e contextual:
- O placeholder do campo deve ser
MSG_searchPacpara 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).
- O placeholder do campo deve ser
- 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 mais nome, CRM ou especialidade do médico no cabeçalho (removido por decisão do responsável do projeto, v4.16) — essas informações passam a viver 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_searchMinno 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 a busca não retorna resultados, então o sistema deve exibir
- 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:
| Termo | pt-BR | en-US | es-419 | |
|---|---|---|---|---|
| MSG_searchPac | Procurar paciente… | Search Pacient… | Buscar paciente… | |
| MSG_searchFoot | Digite parte do nome, CPF, data de nascimento ou nome da mãe para filtrar | Enter parte of the name, ID, mother’s name or DOB to filter | Ingrese parte del busque por nombre, ID, nombre de la madre o fecha de nacimiento | |
| MSG_searchMin | Digite 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_searchNone | Nenhum 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.
- 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
- Definição dos termos:
- 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
- Definição dos termos:
- 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
- Definição dos termos:
- 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
- Definição dos termos:
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ção | pt-BR | en-US | es-419 |
|---|---|---|---|
| Card Consultas Hoje - título | CONSULTAS HOJE | TODAY’S APPOINTMENTS | CONSULTAS DE HOY |
| Card Consultas Hoje - pendentes | pendentes | pending | pendientes |
| Card Consultas Hoje - atrasadas | atrasadas | delayed | atrasada |
| Card Em tratamento - título | EM TRATAMENTO | IN TREATMENT | EN TRATAMIENTO |
| Card Em tratamento - não aderentes | não aderentes | non-adherent | no adherentes |
| Card Procedimentos hoje - título | PROCEDIMENTOS HOJE | TODAY’S PROCEDURES | PROCEDIMIENTOS HOY |
| Card Procedimentos hoje - em atendimento | em atendimento | in progress | en atención |
| Card Procedimentos hoje - aguardando | aguardando | waiting | en espera |
| Card Procedimentos hoje - previstos | previstos | scheduled | programados |
| Card Alertas de prescrição = título | ALERTAS DE PRESCRIÇÃO | PRESCRIPTION ALERTS | ALERTAS DE PRESCRIPCIÓN |
| Card Alertas de prescrição - revisar | p/revisar | to review | p/revisar |
| Card Alertas de prescrição - assinar | p/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 oncológico (se houver — ex: “Ca. Mama”)
- 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 consulta agendada para hoje”.
- 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ção | pt-BR | en-US | es-419 |
|---|---|---|---|
| painel-título | Consultas | Appointments | Consultas |
| toggle-todas | Todas | All | Todas |
| toggle-pendentes | Pendentes | Pending | Pendientes |
| chip-atrasado | Atrasado | Delayed | Atrasado |
| status-agendado | Agendado | Shceduled | Agendada |
| status-aguardando | Aguardando recepção | Waiting reception | Esperando recepción |
| status-recepcionado | Recepcionado | Checked-in | Recepcionado |
| status-atendimento | Em Atendimento | In consulation | En atención |
| status-finalizado | Finalizado | Completed | Finalizado |
| MSG_appointmentNone | Nenhuma agenda para este dia | No appointments for this day | No 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:
- Prescrições para assinar — aguardando assinatura digital do médico
- 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 “Nenhuma prescrição pendente”.
- 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ção | pt-BR | en-US | es-419 |
|---|---|---|---|
| painel-título | Prescrições Pendentes | Pending Prescriptions | Prescripciones Pendientes |
| botão-assinar | Assinar | Sign | Firmar |
| botão-revisar | Revisar | Review | Revisar |
| chip-T_restante | T RESTANTES | T REMAINING | T RESTANTES |
| MSG_prescriptionNone | Sem prescrições pendentes | No pending prescriptions | Sin prescripciones pendientes |
| toggle-meus | Meus | Mine | Míos |
| toggle-todos | Todos | All | Todos |
2.5 Painel de Notificações (Quadrante Inferior Esquerdo)
Atualizado na v4.14 para refletir o modelo de dados de Conversas e Alertas (
IP.Mensageria, ver documento “Conversas-e-Alertas”). A mudança central: o que antes era uma “mensagem” avulsa agora é umaConversa(thread), que pode acumular váriasMensagem— o médico pode responder, não só resolver/descartar. Escopo do painel (v4.17): 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). A partir da v4.17, ler uma conversa (abrir o modal, ou responder) não a remove mais 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. Isso substitui o comportamento da v4.15, em que uma conversa lida saía do painel imediatamente. Motivador da mudança: 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 fazia o médico perder o rastro dela até a próxima atividade, obrigando-o a lembrar de tudo que já leu mas não encerrou. 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 emEquipe, 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.PrazoEmUtcestá 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.PacienteIdestá 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 mais quais conversas aparecem no painel (a partir da v4.17, lidas também aparecem), mas continua determinando 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):
- Prazo (
PrazoEmUtc, quando definido) — conversas com prazo mais próximo aparecem primeiro - 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), atualizaConversa.UltimaSequenciae mudaStatusparaRespondida. 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.StatusparaDescartada. Diferente da versão anterior deste documento, isso não exclui a conversa (o modelo de dados é append-only para preservar 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 mensagem | pt-BR | en-US | es-419 |
|---|---|---|---|
| SYS_PRESC_URGENT | Prescriçã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_REVIEW | Prescriçã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_DELAY | Infusã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_REMINDER | Lembrete: {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
ConteudodaMensageminicial de umaConversacomOrigemTipo = 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 (v4.17), 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. Diferente da v4.15, a existência de conversas já lidas mas ainda em aberto não é mais motivo para o estado vazio — elas agora contam como conteúdo do painel.
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.UltimaSequenciapara 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 mensagens no momento”.
Internacionalização
| Termo/Localização | pt-BR | en-US | es-419 |
|---|---|---|---|
| painel-título | Notificações | Notifications | Notificaciones |
| botão-resolvido | Resolvido | Resolved | Resuelto |
| botão-descartado | Descartado | Discarded | Descartado |
| botão-responder | Responder | Reply | Responder |
| placeholder-resposta | Escreva uma resposta… | Write a reply… | Escriba una respuesta… |
| chip-respondida | Respondida | Replied | Respondida |
| label-há | Há T | T ago | Hace T |
| MSG_notificationNone | Sem notificações pendentes | No pending notifications | Sin notificaciones pendientes |
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 (ex: em_atendimento, aguardando)
- 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ção | pt-BR | en-US | es-419 |
|---|---|---|---|
| painel-título | Procedimentos na clínica | Procedures at the clinic | Procedimientos en la clínica |
| toggle-todos | Todos | All | Todos |
| toggle-meus | Meus | Mine | Mios |
| botão-PEP | PEP | EHR | HCe |
| MSG_proceduresNone | Sem pacientes para o dia | No patients for today | Sin pacientes para el día |
2.7 Fluxos de Exceção e Estados (Consolidado)
| Painel | Condição | Exibição |
|---|---|---|
| Qualquer painel | Falha de endpoint | MSG_endpointFail |
| Qualquer painel | Queda de conexão | Manter últimos dados na tela + ícone discreto de “Offline” no canto do painel |
| KPI Bar | Falha de endpoint | Exibir ”—” 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ção | pt-BR | en-US | es-419 |
|---|---|---|---|
| MSG_endpointFail | Dados temporariamente indisponíveis. Tente novamente | Data temporarily unavailable. Try again | Datos temporalmente no disponibles. Reintentar |
2.8 Mapeamento de Componentes do Design System
| Componente | Variante | Estados | Tokens aplicados |
|---|---|---|---|
| Button | Primary (Assinar) | default, hover, disabled, loading | bg: color-primary-pure; text: color-base-pure; height: btn-height-sm; radius: radius-m |
| Button | Secondary (Revisar) | default, hover, disabled | bg: transparent; border: color-primary-pure; text: color-primary-pure |
| Button | Tertiary (Resolvido) | default, hover | bg: transparent; text: color-primary-pure |
| Chip | Active (Crítico) | default | bg: color-error-light; text: color-error-dark |
| Chip | Active (Atenção) | default | bg: color-highlight-light; text: color-highlight-dark |
| Chip | Status (consulta) | default | Ver cores por status na RN-CM-005 |
| Badge | Error (KPI Alertas) | default | bg: color-error-pure; text: color-base-pure |
| Badge | Primary (contadores) | default | bg: color-primary-pure; text: color-base-pure |
| Input | Bordered (busca) | default, focus, disabled | border: color-neutral-light → focus: color-neutral-dark (2px); height: input-height |
| Avatar | SM/MD (paciente) | default | bg: color-primary-light; text: color-primary-dark |
| Progress | Bar (atendimento) | default, critical | bg track: color-neutral-lighter; bg bar: color-primary-pure → critical: color-error-pure |
| Toast | Success/Error | default | Ver Design System para tokens de toast |
| Tooltip | Default | hover | bg: color-neutral-dark; text: color-base-pure |
| KPI Card | Borda colorida lateral | default, hover | border-left: 4px solid; cores: secondary-pure (consultas), primary-pure (tratamento), highlight-pure (procedimentos), error-pure (alertas) |
| Panel Title Link | Link com seta no hover | default, hover | font-weight: 400; color: neutral-darker → hover: secondary-dark; arrow: opacity 0 → hover: opacity 1 |
| Toggle Pill | Filtro de painel | active, inactive | border: 1px solid neutral-lighter; active: bg secondary-light, text secondary-dark; inactive: bg transparent, text neutral-pure |
| Progress Bar Inline | Barra na linha do nome | default, danger | width: 50px; height: 3px; bg track: neutral-lighter; bg fill: primary-pure → danger: error-pure; pct font: 9px |
| Card Highlight (Consulta) | Borda colorida por status | recepcionado, aguardando, default | recepcionado: border-left 3px solid secondary-pure; aguardando: border-left 3px solid ff8a80; default: sem borda |
| Message Icon (Lucide) | Origem da conversa | unread, read | unread: color primary-pure; read: color neutral-pure. Apenas 2 ícones desde a v4.14: bell (sistema/alerta automático) e um ícone genérico de pessoa (conversa com usuário) — não há mais ícone por equipe |
| User Menu Dropdown | Avatar + caret | closed, open | avatar: 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) comhoursRemaining< 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 } ] }
- Response 200:
- [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 porhoursRemainingcrescente (RN-CM-011).reviewScope(opcional:mine|all, defaultmine) — controla o toggle “Meus/Todos”: afeta somente as prescriçõesto_review; asto_signnunca 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 deappointmentseprocedures— 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 camposprotocol/cyclepor um único campo já formatado (protocolo + ciclo/sessão, ex: “CARBO-TAXOL C3D1”), espelhandoPrescricao.Identificacaodiretamente — sem necessidade de montar a string no backend.- Campo exclusivo do tipo
to_review(RN-CM-020):isPreferredReviewer=truequando 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 camposprescriberName/attendingDoctorNamecogitados numa versão anterior foram removidos — o booleano já é suficiente para o front filtrar/destacar, sem expor nomes de terceiros nesta lista. type = to_signsó 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.
- Query params:
- [GET] /api/v1/cockpit/doctor/conversations
- Renomeado de
/messagespara/conversations(v4.13), alinhado à entidadeConversado novo modelo de mensageria (IP.Mensageria). v4.17: volta a retornar todas as conversas do médico logado comStatusnão-terminal (Enviada,LidaouRespondida), lidas ou não — reverte o filtro de “somente não lidas” da v4.15. 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:systemquandoConversa.OrigemTipo = sistema(alerta automático viaModuloOrigem);personquandoOrigemTipo = 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 (v4.14: conceito de ícone/nome por equipe abolido).authorName: nome de quem escreveu a última mensagem (Usuario.Apelido, ver 4.8), ou “Sistema” quandosourceType = system. Não inclui nome de equipe.status:sent=Enviada,read=Lida,replied=Respondida.Resolvida/Descartadanunca aparecem aqui (já saíram da lista). Um item comstatus = repliedpode estar lido ou não lido, dependendo se houve atividade nova desde a resposta do médico — ver campounread.unread(boolean, reintroduzido na v4.17):truequandoConversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida(ou não existeConversaLeiturapara 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.
- Renomeado de
- [GET] /api/v1/cockpit/doctor/procedures
- Response 200:
{ "procedures": [ { "id": "uuid", "prescriptionId": "AC-T C1D21", "patient": { "id": "uuid", "legalName": "Roberto Oliveira", "preferredName: "", "photo": "url|null" }, "location": "Sala de Infusão 2", "status": "in_progress", "startTime": "2026-07-22T09:30:00Z", "progress": 65, "mine": true } ] }
- Response 200:
- [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" } ] }
- Query params:
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" } ] }
- Response 200:
- [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á estiverRespondida, o status não regride pararead.
- Response 200:
- [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" } }
- Request:
- [PATCH] /api/v1/conversations/{id}/resolve — Muda o status da conversa para
Resolvida. Substitui o antigoPATCH /messages/{id}/resolve.- Response 200:
{ "id": "uuid", "status": "resolved" }
- Response 200:
- [PATCH] /api/v1/conversations/{id}/discard — Muda o status da conversa para
Descartada. Substitui o antigoDELETE /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" }
- Response 200:
2.10 Conformidade SBIS (Detalhamento)
| Requisito | Como a Central do Médico atende |
|---|---|
| ECF.03.11 | O 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.14 | O 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.01 | Todos 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.02 | As mensagens internas exibem timestamp de criação (criadaEm). As ações de assinatura e resolução registram data/hora automaticamente no backend. |
| ECF.17.04 | As 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.18 | Toda a interface, incluindo rótulos, mensagens, títulos de tela e descritivos, está em português do Brasil. |
| ECF.17.19 | Mensagens 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.12, 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).
Nome do médico ainda aparece no header— corrigido na v4.16: cabeçalho não exibe mais nome, CRM, especialidade nem unidade; avatar ganhou menu dropdown (ver Seção 2.1).
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-primary" 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", diag: "Ca. Mama", tipo: "Presencial", tags: ["vip"], status: "Finalizado", color: "#9679E1" },
{ id: 2, hora: "08:30 - 09:00", nome: "Roberto Oliveira", diag: "Ca. Prostata", tipo: "Presencial", tags: [], status: "Recepcionado", color: "#40C4FF" },
{ id: 3, hora: "09:00 - 09:30", nome: "Ana Paula Costa", diag: "Ca. Mama", tipo: "Teleatendimento", tags: ["1aconsulta"], status: "Agendado", atraso: "15min", color: "#FFC400" },
{ id: 4, hora: "09:30 - 10:00", nome: "Carlos Mendes", diag: "Ca. Colon", tipo: "Presencial", tags: [], status: "Aguardando Recepcao", color: "#FF8A80" },
{ id: 5, hora: "10:00 - 10:30", nome: "Joana Ferreira", diag: "Ca. Pulmao", tipo: "Presencial", tags: [], status: "Agendado", color: "#FFC400" },
{ id: 6, hora: "10:30 - 11:00", nome: "Pedro Santos", diag: "", tipo: "Presencial", tags: ["1aconsulta"], status: "Em Atendimento", color: "#00E676" }
];
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 a v4.17: 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: "Recepcao", texto: "Combinado, aguardo confirmacao apos a consulta.", meta: "Ha 1h - Recepcao", unread: false, status: "replied",
thread: [
{ autor: "Recepcao", 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: "Recepcao", 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 Atendimento", inicio: "09:30", progresso: 65, tags: ["Quimioterapia"], meu: true },
{ id: 2, nome: "Maria da Silva", local: "Sala de Infusao 1", status: "Em Atendimento", inicio: "09:00", progresso: 110, tags: ["vip"], meu: true },
{ id: 3, nome: "Ana Paula Costa", local: "Consultorio 3", status: "Aguardando", inicio: "-", progresso: 0, tags: [], meu: true },
{ id: 4, nome: "Pedro Santos", local: "Consultorio 4", status: "Em Atendimento", inicio: "10:30", progresso: 30, tags: [], meu: true },
{ id: 5, nome: "Joao Silva", local: "Sala de Infusao 3", status: "Em Atendimento", inicio: "08:00", progresso: 80, tags: [], meu: false },
{ id: 6, nome: "Fernanda Lima", local: "Sala de Repouso 1", status: "Aguardando", inicio: "-", progresso: 0, tags: [], meu: false },
{ id: 7, nome: "Jose Pereira", local: "Consultorio 2", status: "Em Atendimento", inicio: "09:45", progresso: 50, tags: [], meu: false },
{ id: 8, nome: "Lucia Santos", local: "Sala de Infusao 4", status: "Em Atendimento", inicio: "10:00", progresso: 95, tags: [], meu: false }
];
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;
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: ' + c.color + '">' + 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.diag ? '<span>' + c.diag + '</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>Nenhuma prescricao pendente</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 - conceito de icone por equipe abolido (v4.14)
}
function renderMensagens() {
var container = document.getElementById('list-mensagens');
var countEl = document.getElementById('count-mensagens');
// v4.17: o painel mostra TODAS as conversas nao finalizadas (lidas e nao lidas) - reverte o filtro da v4.15.
// Ler uma conversa nao a remove mais 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 mensagens no momento</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);">⏰ ' + 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;
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>';
}
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>' +
'</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() {
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.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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Agenda | GescomClienteAlfa | Tabela principal — dados da agenda |
| Paciente | GescomClienteAlfa | Dados do paciente (nome, nome social) |
| PacienteFoto | GescomClienteAlfa | Foto do paciente |
| PEPDiagnostico | GescomClienteAlfa | Diagnóstico oncológico do paciente |
| TermoTraducao | IpTerminologia | Tradução do modo de atendimento |
| ClienteEmpresa | IpSeguranca | Idioma e fuso horário da clínica |
Mapa de campos
| Campo | Tipo | Banco | Observação |
|---|---|---|---|
id | int | Agenda.IdAgenda | |
startTime | horário | Agenda.DhAgendaIniUTC | só horário, convertido para o fuso e idioma da clínica |
endTime | horário | Agenda.DhAgendaFimUTC | só horário, convertido para o fuso e idioma da clínica |
status | string | calculado | vide AppointmentStatus() |
type | string | Agenda.Atendimento -> TermoTraducao.TermoValor | Termo = Agenda.Atendimento, Origem=“Indicadores |
firstTime | char | Agenda.PrimeiraConsulta | domínio = ‘S’=sim, ‘N’=não |
delayMinutes | int | Agenda.DhAgendaIniUTC(no fuso da clínica)-Now(no fuso da clínica) | Se o valor for negativo, retorna 0 |
patient.id | int | Agenda.IdPaciente | |
patient.prefferedName | string | Paciente.NomeSocial | se existir, usar este como nome principal |
patient.legalName | string | Paciente.Nome | se NomeSocial não existir, usar este como nome |
patient.photo | url | PacienteFoto.NomeArquivo, busca IdPaciente e Padrao = ‘S’, se multiplos, retorna o mais recente | url gerada pelo MinIO |
patient.diagnosis | string | PEPDiagnostico.Diagnostico para Tipo = ‘O’ e PEP.IdPaciente | pode haver mais de um |
Funções de cálculo
AppointmentStatus() — calcula o status da consulta a partir dos campos da Agenda:
| Status retornado | Condiçã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:
| Valor | Significado |
|---|---|
| C | Consulta |
| Q | Quimioterapia |
| R | Radioterapia |
| S | Reserva de sala para assuntos não assistenciais |
ModoAtendimento (domínio de Agenda.Atendimento) na tabela Indicadores:
| Valor | Significado |
|---|---|
| P | Padrão (Presencial) |
| T | Teleatendimento |
| C | Customizado |
| R | Reservado |
SituacaoAgenda (domínio de Agenda.Situacao) na tabela Indicadores:
| Valor | Significado |
|---|---|
| A | Aberta |
| V | Confirmada (check-in realizado) |
| C | Cancelada pelo usuário |
| D | Cancelada pelo paciente |
| M | Cancelada pelo médico |
| F | Falta |
| I | Cancelado por internação |
| O | Cancelada por óbito |
| T | Transferido (cancelado) |
TipoDiagnostico (domínio de Diagnostico.Tipo):
| Valor | Significado |
|---|---|
| C | Comorbidade |
| O | Doenç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 campoCriadoEmda 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
- Origem: nome da tabela de origem ou
Filtros do endpoint
Agenda.IdChRecurso = {IdChProfissional do médico logado}— apenas consultas do médicoAgenda.TipoAgenda = 'C'- Agendas de consultaAgenda.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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Agenda | GescomClienteAlfa | Tabela principal — dados da agenda |
Mapa de campos
| Campo | Tipo | Banco |
|---|---|---|
total | int | count(Agenda.Situacao IN (‘A’, ‘V’)) |
finished | int | count(Agenda.Situacao=‘V’, Agenda.DhAtendimentoFimUTC<>NULL) |
pending | int | count(Agenda.Situacao IN (‘A’, ‘V’), Agenda.DhAtendimentoFimUTC=NULL) |
delayed | int | count(Agenda.Situacao IN (‘A’, ‘V’), Agenda.DhAtendimentoFimUTC=NULL, Agenda.DhAgendaIniUTC(no fuso da clínica)-Now(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édicoAgenda.TipoAgenda = 'C'- agendas de consultaAgenda.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": "Roberto Oliveira",
"legalName: "Alberto Roberto"
},
"location": "Sala de Infusão 2",
"status": "in_progress",
"startTime": "2026-07-22T09:30:00Z",
"progress": 65,
"mine": true
}
]
}`
Retorna a lista de pacientes em procedimentos no dia atual.
Tabelas envolvidas
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Agenda | GescomClienteAlfa | Tabela principal — dados da agenda |
| Paciente | GescomClienteAlfa | Dados do paciente (nome, nome social) |
| PacienteFoto | GescomClienteAlfa | Foto do paciente |
| Prescricao | GescomClienteAlfa | Diagnóstico oncológico do paciente |
| TermoTraducao | IpTerminologia | Tradução do modo de atendimento |
| ClienteEmpresa | IpSeguranca | Idioma e fuso horário da clínica |
Mapa de campos
| Campo | Tipo | Banco | Observação | |
|---|---|---|---|---|
id | int | Prescricao.IdPrescricao | ||
prescriptionCode | string | Prescricao.Identificacao | ||
location | string | Prescricao.IdAgenda->Agenda.IdUAtendimento->UnidadeAtendimento.Descricao + Agenda.IdChRecurso->Chave.Descricao | IdUAtendimento pode ser NULL | |
startTime | horário | se 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 | |
status | string | calculado | vide ProcedureStatus() | |
progress | int | (Now()-Agenda.DhAtendimentoIniUTC(no fuso do servidor))/Agenda.Tempo*100 | ||
mine | boolean | Prescricao.IdChProfissionalMed={IdChProfissional do médico logado} | ||
patient.id | int | Prescricao.IdPaciente | ||
patient.prefferedName | string | Paciente.NomeSocial | se existir, usar este como nome principal | |
patient.legalName | string | Paciente.Nome | se NomeSocial não existir, usar este como nome | |
patient.photo | url | PacienteFoto.NomeArquivo, busca IdPaciente e Padrao = ‘S’, se multiplos, retorna o mais recente | url gerada pelo MinIO |
Funções de cálculo
ProcedureStatus() — calcula o status do procedimento a partir dos campos da Agenda:
| Status retornado | Condiçã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:
| Valor | Significado |
|---|---|
| C | Consulta |
| Q | Quimioterapia |
| R | Radioterapia |
| S | Reserva de sala para assuntos não assistenciais |
SituacaoAgenda (domínio de Agenda.Situacao) na tabela Indicadores:
| Valor | Significado |
|---|---|
| A | Aberta |
| V | Confirmada (check-in realizado) |
| C | Cancelada pelo usuário |
| D | Cancelada pelo paciente |
| M | Cancelada pelo médico |
| F | Falta |
| I | Cancelado por internação |
| O | Cancelada por óbito |
| T | Transferido (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 campoCriadoEmda 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
- Origem: nome da tabela de origem ou
Filtros do endpoint
Prescricao.IdAgenda <> NULLAgenda.TipoAgenda IN 'Q', 'R'- Agendas de quimio, radioAgenda.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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Agenda | GescomClienteAlfa | Tabela principal — dados da agenda |
| Paciente | GescomClienteAlfa | Dados do paciente (nome, nome social) |
| PacienteFoto | GescomClienteAlfa | Foto do paciente |
| Prescricao | GescomClienteAlfa | Diagnóstico oncológico do paciente |
| TermoTraducao | IpTerminologia | Tradução do modo de atendimento |
| ClienteEmpresa | IpSeguranca | Idioma e fuso horário da clínica |
Mapa de campos
| Campo | Tipo | Banco |
|---|---|---|
total | int | count(Agenda.Situacao IN (‘A’, ‘V’)) |
inProgress | int | count(ProcedureStatus()=preparation OR administering)) |
waiting | int | count(ProcedureStatus()=waiting OR checked) |
scheduled | int | count(ProcedureStatus()=scheduled) |
Funções de cálculo
ProcedureStatus() — calcula o status do procedimento a partir dos campos da Agenda:
| Status retornado | Condiçã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:
| Valor | Significado |
|---|---|
| C | Consulta |
| Q | Quimioterapia |
| R | Radioterapia |
| S | Reserva de sala para assuntos não assistenciais |
SituacaoAgenda (domínio de Agenda.Situacao) na tabela Indicadores:
| Valor | Significado |
|---|---|
| A | Aberta |
| V | Confirmada (check-in realizado) |
| C | Cancelada pelo usuário |
| D | Cancelada pelo paciente |
| M | Cancelada pelo médico |
| F | Falta |
| I | Cancelado por internação |
| O | Cancelada por óbito |
| T | Transferido (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
- Origem: nome da tabela de origem ou
Filtros do endpoint
Agenda.TipoAgenda IN 'Q', 'R'- Agendas de quimio, radioAgenda.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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Agenda | GescomClienteAlfa | Tabela principal — dados da agenda |
| Paciente | GescomClienteAlfa | Dados do paciente (nome, nome social) |
| Prescricao | GescomClienteAlfa | Diagnóstico oncológico do paciente |
| TermoTraducao | IpTerminologia | Tradução do modo de atendimento |
Mapa de campos
| Campo | Tipo | Banco |
|---|---|---|
inTreatment | int | PatientInTreatmentCount() |
nonAdherent | int | TreatmentPlanAdherenceCount() |
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 mesesEntão conta a quantidade de pacientes do profissional logado.
TreatmentPlanAdherenceCount() - calcula os pacientes com plano terapêutico que estão em descompasso com o planejado
- refere-se aos pacientes que tem plano terapêutico (PEPProjetoTerapeutico)
- quem não tem, não é adicionado na contagem
- em cada PEPProjetoTerapeuticoProtocolo do plano terapêutico, calcular:
- Se tem IdPrescricao,
- verificar se a data de C1D1 está mais do que 5 dias diferente da prevista
- verificar se a prescricao passada não foi realizada (Prescricao.Situacao = P, G, A, C)
- verificar se os intervalos entre as sessões das prescrições estão de acordo com o programado (Prescricao.Sessao = Prescricao.Data - Prescricao.Data_da_prescrição_anterior)
- Se tem IdPrescricao,
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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Prescricao | GescomClienteAlfa | Tabela principal — versão ativa (Situacao <> 'C'), médico prescritor |
| PrescricaoAssinatura | GescomClienteAlfa | Histórico de assinaturas/revisões — usada para determinar se a prescrição está revisada |
| Agenda | GescomClienteAlfa | Data/hora agendada da aplicação (via Prescricao.IdAgenda) |
Mapa de campos
| Campo | Tipo | Banco | Observação |
|---|---|---|---|
urgent | int | PrescriptionUrgentCount() | |
toReview | int | PrescriptionToReviewCount() | |
toSignNext7Days | int | PrescriptionToSignNext7DaysCount() |
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) paratoReviewe a parcela de revisão deurgent; apenas prescritor paratoSignNext7Dayse a parcela de assinatura deurgent. 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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Prescricao | GescomClienteAlfa | Tabela principal — versão ativa (Situacao <> 'C'); Identificacao já traz protocolo + ciclo/sessão formatados |
| PrescricaoAssinatura | GescomClienteAlfa | Determina se a prescrição está assinada e/ou revisada (ver 4.6) |
| Paciente | GescomClienteAlfa | Nome social/civil do paciente e médico assistente |
| Agenda | GescomClienteAlfa | Data/hora agendada da aplicação |
Domínio — PrescricaoAssinatura.TipoAssinatura
| Valor | Significado |
|---|---|
M | Assinatura do médico (assinatura original da prescrição ou de uma nova versão gerada por edição) |
R | Confirmaçã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
| Campo | Tipo | Banco | Observação |
|---|---|---|---|
id | uuid | Prescricao.IdPrescricao | identifica diretamente a versão ativa — não há campo de número de versão a considerar à parte |
patient.id | uuid | Prescricao.IdPaciente | |
patient.preferredName | string | Paciente.NomeSocial | mesmo padrão de appointments (4.1) e procedures (4.3) — enviado sempre, mesmo vazio, para o front sinalizar nome social ao médico |
patient.legalName | string | Paciente.Nome | enviado sempre, junto com preferredName |
prescriptionCode | string | Prescricao.Identificacao | substitui os antigos protocol/cycle — já vem formatado do banco (ex: “CARBO-TAXOL C3D1”), sem necessidade de montar a string a partir de Protocolo/PrescricaoProtocolo |
scheduleDate | datetime | Agenda.DhAgendaIniUTC, via Prescricao.IdAgenda | convertida para o fuso da clínica na exibição |
hoursRemaining | int | calculado: Agenda.DhAgendaIniUTC - Now() em horas | |
type | string | calculado | to_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) |
isPreferredReviewer | bool | calculado | true 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 existePrescricaoAssinatura.TipoAssinatura = 'M'EPrescricao.IdChProfissionalAssinou IS NULL) ANDPrescricao.IdChProfissionalMed= médico logado ANDAgenda.DhAgendaIniUTCaté 7 dias a partir de agora — nunca afetado porreviewScope. 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 dePrescriptionToReviewCount()(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 peloreviewScopeabaixo (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 porhoursRemainingascendente (RN-CM-011);reviewScopeaplicado apenas à parteto_reviewda 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 histórica (v4.12): o mapeamento de BD dos endpoints de ação de prescrições (GET /prescriptions/{id}, POST /sign, PATCH /review, PUT /prescriptions/{id}) saiu deste documento e foi movido para um documento de processo de Prescrições à parte — 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 viaPrescricaoVersao, 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
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Conversa | GescomClienteAlfa | Tabela principal — thread, status, prazo, origem, paciente relacionado |
| ConversaDestinatario | GescomClienteAlfa | Snapshot de quem recebeu a conversa — usada para filtrar pelo médico logado |
| ConversaLeitura | GescomClienteAlfa | Determina se a conversa está lida para o médico logado |
| Mensagem | GescomClienteAlfa | Conteúdo e autor da última mensagem da thread (preview do card) |
| SegUsuario | GescomClienteAlfa | Ponte legada entre o GUID do IPSeguranca e o usuário local (ver resolução de nome abaixo) |
| Paciente | GescomClienteAlfa | Nome social/civil do paciente relacionado, quando Conversa.PacienteId existe |
Resolução do nome do autor (corrigida, v4.14):
Mensagem.AutorUsuarioIdeConversa.RemetenteUsuarioIdarmazenam o GUID =IPSeguranca.dbo.Usuario.Id. Como não há FK física entre bancos, a triangulação correta — via compatibilidade com o legado — é:AutorUsuarioId(GUID) =SegUsuario.SegurancaUsuarioClienteId(join dentro do próprioGescomClienteAlfa) → nome de exibição emIPSeguranca.dbo.Usuario.Apelido(mesmo GUID). Diferente do que este documento assumiu numa versão anterior (join direto comProfissional.IdUsuario), a fonte correta do nome é o apelido do usuário, não o nome do profissional — e a ponte éSegUsuario, nãoProfissional. Como efeito colateral,SegUsuario.IdUsuario(smallint) expõe o identificador legado local, o mesmo tipo de valor já usado emProfissional.IdUsuario/Prescricao.IdUsuarioAssinatura, útil se for necessário cruzar com tabelas assistenciais no futuro.
Conceito de ícone por equipe abolido (v4.14): a versão anterior deste documento calculava um ícone específico por equipe (recepção/farmácia/enfermagem) via correspondência de nome — decisão revertida por instrução direta do responsável do projeto. O ícone agora é binário: bell (Lucide) para OrigemTipo = sistema, ícone genérico de pessoa para OrigemTipo = usuario, sem depender do nome da equipe. A tabela Equipe deixou de ser necessária neste endpoint — ela permanece relevante apenas no backend de roteamento de conversas (quem recebe, via EquipeMembro), não na exibição.
Mapa de campos
| Campo | Tipo | Banco | Observação |
|---|---|---|---|
id | uuid | Conversa.Id | |
sourceType | string | calculado a partir de Conversa.OrigemTipo | system se sistema; person se usuario |
authorName | string | IPSeguranca.dbo.Usuario.Apelido, via SegUsuario (ver resolução acima), do autor da mensagem de maior Sequencia; ou “Sistema” | |
lastMessage | string | Mensagem.Conteudo (maior Sequencia da Conversa) | |
lastMessageAt | datetime | Mensagem.CriadoEm (mesma linha) | campo não listado explicitamente no documento de origem — assume-se equivalente a um campo de auditoria padrão do projeto |
deadline | datetime | Conversa.PrazoEmUtc | pode ser nulo |
status | string | Conversa.Status | mapeado para sent/read/replied (ver 2.9) |
unread | bool | calculado: Conversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida (ou sem ConversaLeitura) | reintroduzido na v4.17: usado para destaque visual e camada de prioridade (RN-CM-015); não filtra mais o resultado |
patient | object | null | Paciente, via Conversa.PacienteId |
Filtros do endpoint
ConversaDestinatario.UsuarioId = {médico logado}— recebida diretamente ou via snapshot de equipeConversa.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')— excluiResolvidaeDescartadaConversa.UltimaSequencia > ConversaLeitura.UltimaSequenciaLida, OU não existeConversaLeiturapara o médico logado — calcula o campounread(v4.17: deixou de ser filtro do WHERE, passa a ser critério de ordenação; reverte a v4.15).- Ordenação:
unreaddescendente (não lidas primeiro), depoisConversa.PrazoEmUtcascendente (nulos por último), depoislastMessageAtdescendente — RN-CM-015 (v4.17)
4.9 Endpoints de ação: GET /conversations/{id}, POST /read, POST /reply, PATCH /resolve, PATCH /discard
Tabelas envolvidas
| Tabela | Banco | Função no endpoint |
|---|---|---|
| Conversa | GescomClienteAlfa | Status atual, UltimaSequencia |
| Mensagem | GescomClienteAlfa | Thread completa (GET detalhe); nova linha inserida em POST /reply |
| ConversaLeitura | GescomClienteAlfa | Atualizada em POST /read |
| ConversaAcao | GescomClienteAlfa | Histórico append-only — nova linha em toda ação (leitura, resposta, resolução, descarte) |
| OutboxEvento | GescomClienteAlfa | Evento 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
Mensagemda conversa ordenadas porSequencia, mais os dados deConversa(status, prazo, paciente). - POST /read: insere ou atualiza
ConversaLeitura(chaveConversaId+UsuarioId) comUltimaSequenciaLida = Conversa.UltimaSequencia; insere uma linha emConversaAcao(Tipo=Leitura); seConversa.Status = 'Enviada', atualiza para'Lida'— nunca regride um status já mais avançado (RespondidapermaneceRespondida). - POST /reply: insere uma nova linha em
Mensagem(Tipo=Resposta,Sequencia = Conversa.UltimaSequencia + 1,AutorUsuarioId= médico logado,Conteudodo request); atualizaConversa.UltimaSequencia; atualizaConversa.Statuspara'Respondida'; insere uma linha emConversaAcao(Tipo=Resposta,MensagemIdda nova mensagem); gravaOutboxEvento(TipoEvento=ConversaRespondida). - PATCH /resolve: atualiza
Conversa.Statuspara'Resolvida'; insere uma linha emConversaAcao(Tipo=Resolução); gravaOutboxEvento(TipoEvento=ConversaResolvida). Não altera nem excluiMensagem. - PATCH /discard: atualiza
Conversa.Statuspara'Descartada'; insere uma linha emConversaAcao(Tipo=Descarte); gravaOutboxEvento(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.
METADADOS FINAIS
- Prioridade: Alta (Core do Sistema)
- Complexidade: Média/Alta (Devido às integrações em tempo real)
- Próximos Passos:
- Homologação do serviço de atualização automática.
- Teste de carga para múltiplos médicos acessando simultaneamente.
- Validação da assinatura digital em ambiente de sandbox.
- Definir ícones definitivos para os KPI cards (biblioteca Lucide Icons).
- Substituir ícones placeholder de comunicação por SVGs da biblioteca Lucide — apenas 2 ícones desde a v4.14 (bell para alertas de sistema, ícone de pessoa genérico para conversas com usuário; o conceito de ícone por equipe/farmácia/enfermagem/recepção foi abolido).
- Validar acessibilidade (WCAG, navegação por teclado) dos títulos como links e toggles pill.
- Testar usabilidade do título como link com seta no hover com médicos.
- Validar touchpoints de workflow com médicos em ambiente real (shadowing).
- Mapear touchpoints de workflows ainda não documentados (ex: workflow de alta, workflow de intercorrência).
- Especificar os demais itens do menu do usuário (v4.16): 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”.
Documento elaborado em 11 de agosto de 2026. As informações contidas são de responsabilidade do solicitante.