# Central do Médico (Cockpit) — Mapeamento de Banco de Dados (v4.28)

**Documentos desta funcionalidade:** [01 - Definição](./01%20-%20Defini%C3%A7%C3%A3o%20v4.26.md) (v4.26) · [02 - Especificação](./02%20-%20Especifica%C3%A7%C3%A3o%20v4.25.md) (v4.25) · [03 - Protótipo](./03%20-%20Prot%C3%B3tipo%20v3.13.md) (v3.13, código executável em `03 - Protótipo v3.13.html`) · [04 - Mapeamento de Banco de Dados](./04%20-%20Mapeamento%20de%20Banco%20de%20Dados%20v4.28.md) (v4.28)

---

## 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|ModoAtendimento", Idioma=ClienteEmpresa.IdiomaOficial |
| `firstTime`             | char    | Agenda.PrimeiraConsulta | domínio = 'S'=sim, 'N'=não |
| `delayMinutes`          | int     | Now(no fuso da clínica)-Agenda.DhAgendaIniUTC(no fuso da clínica) | Se o valor for negativo (horário previsto ainda não chegou), retorna 0 — só é positivo quando o horário previsto já passou e a consulta não foi iniciada |
| `patient.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`     | array de string | PEPDiagnostico.Diagnostico para Tipo = 'O' e PEP.IdPaciente | retorna TODOS os diagnósticos oncológicos ativos do paciente (não só um) — o card exibe a lista completa |
| `tags`                  | array de string | `Paciente.PacienteVip` (bit=1 → tag `vip`) UNION `ProgramaApoioPaciente` filtrado por `IdPaciente` (ativo quando `DataInicio <= hoje` e `DataFim IS NULL` ou `DataFim >= hoje`) join `ProgramaApoio` (`Nome`/`Sigla`/`Cor`) | Cor de cada tag vem de `ProgramaApoio.Cor` (exceto `vip`, que usa o token de destaque do Design System, Seção 2.8). Consultado no `EsquemaGemed21.csv`: `ProgramaApoioPaciente`/`ProgramaApoio` existem; `PEPProgramaApoioPaciente` (vínculo por PEP, em vez de por paciente) existe mas não é usado aqui — as tags do Cockpit são no nível do paciente, não do atendimento. |

#### Funções de cálculo

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

| Status 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 campo `CriadoEm` da infraestrutura)

Internacionalização (i18n):

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

#### Filtros do endpoint

- `Agenda.IdChRecurso = {IdChProfissional do médico logado}` — apenas consultas do médico
- `Agenda.TipoAgenda = 'C'` - Agendas de consulta
- `Agenda.DhAgendaIniUTC(no fuso da clínica) = Hoje(no fuso da clinica)` — apenas consultas do dia atual
- Toggle "Pendentes": `Agenda.Situacao IN ('A','V')`AND`DhAtendimentoFimUTC 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, Now(no fuso da clínica)-Agenda.DhAgendaIniUTC(no fuso da clinica)>0) |

#### Domínios

** os mesmos do endpoint acima

#### Filtros do endpoint

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

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

Retorna a lista de pacientes em procedimentos no dia atual.

#### Tabelas envolvidas

| 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                                  |     |
| `tags`                  | array de string | mesma origem de `appointments.tags`, ver Seção 4.1 (`Paciente.PacienteVip` + `ProgramaApoioPaciente`/`ProgramaApoio`) |                                                        |     |

#### Funções de cálculo

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

| Status 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 campo `CriadoEm` da infraestrutura)

Internacionalização (i18n):

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

#### Filtros do endpoint

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

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

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

#### Tabelas envolvidas

| 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()=`administering`) |
| `waiting`    | int  | count(ProcedureStatus()=`waiting` OR `checked` OR `preparation`) |
| `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    |     |
| `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

Internacionalização (i18n):

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

#### Filtros do endpoint

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

### 4.5 Endpoint [GET] /api/v1/cockpit/doctor/kpis/in-treatment
    /api/v1/cockpit/doctor/kpis/in-treatment
    `{  "inTreatment": 43, 
	    "nonAdherent": 2 }`
Função: Retornar o indicador de Em Tratamento
#### Tabelas envolvidas

| 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 meses`
Então conta a quantidade de pacientes do profissional logado.

TreatmentPlanAdherenceCount() — calcula os pacientes com plano terapêutico (`PEPProjetoTerapeutico`) que estão em descompasso com o planejado; quem não tem plano terapêutico não entra na contagem. Um paciente conta como não aderente quando QUALQUER UMA das três condições abaixo é verdadeira (lógica OU — confirmado com o solicitante: basta uma delas estar fora do parametrizado) para algum `PEPProjetoTerapeuticoProtocolo` do seu plano que tenha `IdPrescricao`:
- A data do C1D1 realizado difere da data prevista em mais de `Protocolo.ToleranciaDiasAdesao` dias **[PROPOSTA]**
- A prescrição passada não foi realizada (`Prescricao.Situacao` IN ('P','G','A','C'))
- O intervalo entre sessões da prescrição diverge do intervalo programado no protocolo (`Prescricao.Sessao = Prescricao.Data - Prescricao.Data_da_prescrição_anterior`, comparado a `Protocolo.IntervaloCiclosEmDias` — já existe no schema — com a mesma tolerância `Protocolo.ToleranciaDiasAdesao` acima)

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

#### Filtros do endpoint

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

---

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

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

#### Tabelas envolvidas

| 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 = 'A'` é 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.

A janela que dispara "a revisar" (RN-CM-020) — descrita na Seção 1 como "1 dia (24h) antes da aplicação" — não é um valor fixo no código: é um parâmetro configurável por clínica, mesmo padrão já usado em 4.5 (`TempoPacienteAtivoAgenda`/`TempoPacienteAtivoPrescricao`): `IpParametro.PrescricaoTempoRevisao` → `IpParametroChave.Valor` (horas). Ver "Parâmetro de configuração — PrescricaoTempoRevisao" logo abaixo para a definição completa das duas linhas de banco necessárias.

```sql
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 = 'A')
        OR p.IdChProfissionalAssinou IS NOT NULL                 -- compatibilidade com legado
      )                                                          -- já assinada, por um dos dois caminhos
  AND a.DhAgendaIniUTC <= DATEADD(HOUR,
        COALESCE(
          (SELECT ipc.Valor FROM IpParametroChave ipc
           WHERE ipc.IdParametro = 'PrescricaoTempoRevisao' AND ipc.IdChave = {IdClinica}),
          24),                                                   -- default 24h se IpParametro/IpParametroChave não existir (confirmado pelo solicitante)
        SYSDATETIMEOFFSET())                                     -- a X horas ou menos da aplicação, X = parâmetro da clínica
  AND NOT EXISTS (
        SELECT 1 FROM PrescricaoAssinatura pa2
        WHERE pa2.IdPrescricao = p.IdPrescricao
          AND pa2.TipoAssinatura IN ('A','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.

#### Parâmetro de configuração — PrescricaoTempoRevisao

**[PROPOSTA — pedido pelo solicitante nesta versão]** A janela de disparo de "a revisar" precisa existir como parâmetro real de banco, não só como texto de regra de negócio. Não é uma tabela nova — `IpParametro`/`IpParametroChave` já existem e já hospedam parâmetros do mesmo tipo (ver 4.5). São 2 linhas novas de dado (seed), não uma mudança de schema:

`IpParametro` (linha nova):

| Campo | Valor |
| --- | --- |
| `IdParametro` | `PrescricaoTempoRevisao` |
| `Descricao` | `Define o tempo em horas antes da aplicação para uma prescrição ficar em estado de "pendente de revisão"` |
| `Dominio` | `NULL` |
| `UsuarioControla` | `S` |
| `Tipo` | `N` |

`IpParametroChave` (linha nova, valor por clínica — exemplo com o padrão-sugerido de 24h, correspondente à regra "1 dia" da Seção 1):

| Campo | Valor |
| --- | --- |
| `IdParametro` | `PrescricaoTempoRevisao` |
| `IdChave` | `{Id da Clínica}` |
| `Valor` | `24` |
| `IdChave2` | `NULL` |

**Comportamento se o parâmetro não existir:** se a linha em `IpParametro` ou em `IpParametroChave` não existir para a clínica, o sistema assume 24h como default (confirmado pelo solicitante) — ver `COALESCE` na query acima. O seed abaixo (Seção "Seeds da Funcionalidade") deve ainda assim ser aplicado em todo cliente, para que o valor fique explícito e configurável desde o início, em vez de depender do default implícito do código.

**Atenção Dev/DevOps:** este parâmetro é um dado novo de configuração, não uma migration de schema — precisa ser adicionado à documentação de deploy/seed de cada cliente (mesma lista onde já constam `TempoPacienteAtivoAgenda`/`TempoPacienteAtivoPrescricao`). Ver a subseção "Seeds da Funcionalidade", ao final desta Seção 4, para a lista consolidada de todos os seeds necessários para este documento.

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 = 'A'` 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 = 'A'` E sem `Prescricao.IdChProfissionalAssinou` preenchido (compatibilidade com legado), e `Agenda.DhAgendaIniUTC` entre agora e +7 dias.

#### Filtros do endpoint

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

---

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

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

#### Tabelas envolvidas

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

---

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

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

Retorna as conversas não finalizadas do médico logado — lidas e não lidas (painel compacto do Cockpit — ver escopo em RN-CM-015 e Seção 2.5), com base no modelo de Conversas e Alertas (`IP.Mensageria`). O schema desse domínio (`GescomClienteAlfa.mensageria`) passou a ter DDL real na Biblioteca de Schema (DDL) do projeto (`GescomMensageria.sql`) a partir de 28/08/2026 — antes disso, o mapeamento abaixo era baseado só na descrição do documento externo "Conversas-e-Alertas", sem o DDL para conferir nomes e tipos de campo exatos. Esta versão corrige os campos que o DDL real revelou estarem descritos incorretamente (ver "Resolução do nome do autor" abaixo e o changelog desta versão). 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 schema separado (`mensageria`), não do schema `dbo` 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)                     |
| Equipe                 | GescomClienteAlfa | Nome de exibição da equipe (`Nome`) — usado no sufixo "(Equipe)" do autor quando aplicável (ver "Resolução do nome da equipe" abaixo) |
| EquipeMembro            | GescomClienteAlfa | Determina se o autor da mensagem é membro da equipe destino da conversa (`Conversa.EquipeId`) — decide se o sufixo de equipe aparece |
| SegUsuario             | GescomClienteAlfa | Ponte legada entre o identificador local (int) e o GUID do IPSeguranca (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 nesta versão — DDL real de `mensageria` disponibilizado em 28/08/2026):** `Mensagem.IdUsuarioAutor`, `Conversa.IdUsuarioRemetente`, `Conversa.IdUsuarioDestino`, `ConversaDestinatario.IdUsuario` e `EquipeMembro.IdUsuario` são todos `int`, não GUID — o mesmo tipo de identificador legado local já usado em `Profissional.IdUsuario`/`Prescricao.IdUsuarioAssinatura`. Versões anteriores deste documento (v4.14 a v4.23) assumiam, sem o DDL real da `mensageria` disponível, que esses campos já armazenavam o GUID de `IPSeguranca.dbo.Usuario.Id` — suposição que o DDL real desmente. A triangulação correta é: `IdUsuarioAutor` (int) = `SegUsuario.IdUsuario` (smallint, PK — join dentro do próprio `GescomClienteAlfa`) → `SegUsuario.SegurancaUsuarioId` (GUID; nome de campo conforme "Log de Mudanças Estruturais do Banco de Dados", que prevalece sobre a Biblioteca de Schema (DDL) para este campo específico) → nome de exibição em `IPSeguranca.dbo.Usuario.Apelido` (mesmo GUID). A mesma triangulação vale para `IdUsuarioRemetente`, `IdUsuarioDestino` e `ConversaDestinatario.IdUsuario`.

> **Resolução do nome da equipe (nova nesta versão):** o autor de uma mensagem ganha o sufixo da equipe quando `Conversa.DestinoTipo = 'Team'` E existe uma linha em `EquipeMembro` com `EquipeId = Conversa.EquipeId` e `IdUsuario` igual ao `SegUsuario.IdUsuario` do autor resolvido acima. Quando as duas condições são verdadeiras, o nome de exibição final é `Usuario.Apelido` + " (" + `Equipe.Nome` + ")" (ex.: "Sandra (Farmácia)"); caso contrário, é só `Usuario.Apelido`. **[PROPOSTA — a confirmar com o solicitante]:** esta verificação usa o vínculo *atual* de `EquipeMembro` (`RemovidoEmUtc IS NULL`) no momento em que o card/thread é exibido, não o vínculo no momento em que a mensagem foi enviada — `EquipeMembro` não guarda um snapshot por mensagem, então alguém que já saiu da equipe deixaria de ter o sufixo em mensagens antigas dele. Alternativa não implementada aqui: checar `AdicionadoEmUtc <= Mensagem.CriadoEmUtc AND (RemovidoEmUtc IS NULL OR RemovidoEmUtc > Mensagem.CriadoEmUtc)` para refletir o vínculo histórico da época do envio. A escolha entre as duas abordagens fica pendente de confirmação.

O ícone continua binário: bell (Lucide) para `OrigemTipo = 'System'`, ícone genérico de pessoa para `OrigemTipo = 'User'`, sem variação por equipe (recepção/farmácia/enfermagem) — a identificação da equipe, quando aplicável, é resolvida como texto (acima), não como ícone. Diferente do que versões anteriores deste documento afirmavam, a tabela `Equipe` (e `EquipeMembro`) **é** necessária neste endpoint — não apenas no backend de roteamento — porque a exibição do sufixo "(Equipe)" no `authorName` depende dela (ver "Resolução do nome da equipe" acima).

#### 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` do autor da mensagem de maior `Sequencia`, via `SegUsuario` (ver "Resolução do nome do autor" acima); com sufixo `" (" + Equipe.Nome + ")"` quando `Conversa.DestinoTipo = 'Team'` e o autor é membro dessa `Equipe` (ver "Resolução do nome da equipe" acima); ou "Sistema" quando `sourceType = system` | |
| `lastMessage`               | string  | Mensagem.Conteudo (maior Sequencia da Conversa) | |
| `lastMessageAt`             | datetime| Mensagem.CriadoEmUtc (mesma linha) | |
| `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`) | usado para destaque visual e camada de prioridade (RN-CM-015); não filtra o resultado |
| `patient`                   | object|null | Paciente, via Conversa.PacienteId | mesmo padrão `preferredName`/`legalName` dos demais endpoints; `null` quando `PacienteId` é nulo |

#### Filtros do endpoint

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

---

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

#### Tabelas envolvidas

| 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 `Mensagem` da conversa ordenadas por `Sequencia`, mais os dados de `Conversa` (status, prazo, paciente). Para cada mensagem, resolve o sufixo de equipe do autor do mesmo jeito que o endpoint de lista (Seção 4.8, "Resolução do nome da equipe") — a thread completa também mostra "Sandra (Farmácia)" quando aplicável, não só o card compacto.
- **POST /read:** insere ou atualiza `ConversaLeitura` (chave `ConversaId` + `IdUsuario`) com `UltimaSequenciaLida = Conversa.UltimaSequencia`; insere uma linha em `ConversaAcao` (`Tipo` = `Read`); se `Conversa.Status = 'Sent'`, atualiza para `'Read'` — nunca regride um status já mais avançado (`'Responded'` permanece `'Responded'`).
- **POST /reply:** insere uma nova linha em `Mensagem` (`Sequencia = Conversa.UltimaSequencia + 1`, `IdUsuarioAutor` = médico logado, `Conteudo` do request, `Tipo` = `'Response'`); atualiza `Conversa.UltimaSequencia`; atualiza `Conversa.Status` para `'Responded'`; insere uma linha em `ConversaAcao` (`Tipo` = `'Response'`; `MensagemId` da nova mensagem); grava `OutboxEvento` (TipoEvento=`ConversaRespondida`). O valor `'Reply'`, antes um segundo valor válido para `Mensagem.Tipo`/`ConversaAcao.Tipo` no mesmo `CHECK`, foi abolido pelo solicitante — `'Response'` é o único valor usado para a ação de responder, resolvendo a pendência **[A DEFINIR]** da v4.24 (ver Log de Mudanças Estruturais do Banco de Dados).
- **PATCH /resolve:** atualiza `Conversa.Status` para `'Resolved'`; insere uma linha em `ConversaAcao` (`Tipo` = `'Resolve'`); grava `OutboxEvento` (TipoEvento=`ConversaResolvida`). Não altera nem exclui `Mensagem`.
- **PATCH /discard:** atualiza `Conversa.Status` para `'Dismissed'`; insere uma linha em `ConversaAcao` (`Tipo` = `'Dismiss'`); grava `OutboxEvento` (TipoEvento=`ConversaDescartada`). Mesma observação — nada é excluído fisicamente.

#### Fora do escopo deste documento

A criação de conversas (envio inicial por um usuário) e a publicação de alertas automáticos (`AlertaPublicacao`, com chave de idempotência) partem de outros módulos ou telas — o Cockpit é consumidor/participante das conversas, não o ponto de origem. O cadastro e a gestão de equipes (criar/renomear/desativar uma `Equipe`, adicionar ou remover membros em `EquipeMembro`) também partem de outro módulo — o Cockpit só *lê* `Equipe`/`EquipeMembro` para resolver o sufixo do nome do autor (Seção 4.8), nunca escreve nelas. O processamento assíncrono via `OutboxEvento` pertence ao domínio do `IP.Mensageria` e também não é especificado aqui.

---

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

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

#### Tabelas envolvidas

| Tabela   | Banco             | Função no endpoint                                          |
| -------- | ------------------ | ------------------------------------------------------------ |
| Paciente | GescomClienteAlfa | Tabela única consultada — todos os campos de busca e resposta vêm dela |

#### Mapa de campos

| Campo                  | Tipo   | Banco                 | Observação                                                                                          |
| ----------------------- | ------ | ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `id`                    | int    | Paciente.IdPaciente   |                                                                                                        |
| `name`                  | string | Paciente.Nome          | segue o mesmo padrão de `preferredName`/`legalName` dos demais endpoints quando `Paciente.NomeSocial` existir |
| `medicalRecordNumber`   | string | Paciente.NumProntuario |                                                                                                        |
| `gender`                | char   | Paciente.Sexo          | domínio 'M'/'F'                                                                                       |
| `birthDate`             | data   | Paciente.Nascimento    | campo de busca quando a entrada começa com dígito (RN-CM-004)                                        |
| `motherName`            | string | Paciente.NomeMae       | campo de busca quando a entrada começa com letra (RN-CM-004)                                         |
| `cpf`                   | string | Paciente.CPF           | campo de busca quando a entrada começa com dígito (RN-CM-004)                                        |

#### Filtros do endpoint

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

#### Nota sobre o motor de correspondência aproximada

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

---

### Seeds da Funcionalidade

Lista consolidada de todo dado (seed) que precisa existir em produção — não schema novo, mas linhas de dado que este documento pressupõe — para que a funcionalidade se comporte como especificado. Pensada como checklist de deploy para Dev/DevOps: cada linha aponta para a seção com a definição completa dos campos, não a repete (evita duplicar a mesma informação em dois lugares — Lição #10 da Skill Designer).

| Seed | Tabela(s) | Por cliente ou global? | Status | Detalhe completo |
| --- | --- | --- | --- | --- |
| `PrescricaoTempoRevisao` — janela (em horas) antes da aplicação em que uma prescrição assinada vira "a revisar" (RN-CM-020) | `IpParametro` (1 linha) + `IpParametroChave` (1 linha por clínica) | Por cliente (`IpParametroChave`); a linha de `IpParametro` é única, compartilhada entre clientes | **[PROPOSTA]** — pedido pelo solicitante nesta versão, ainda não aplicado em produção. Default de 24h no código se a linha de `IpParametroChave` (ou a de `IpParametro`) não existir para a clínica — confirmado pelo solicitante — mas o seed deve ser aplicado mesmo assim, para que o valor fique explícito | Seção 4.6, "Parâmetro de configuração — PrescricaoTempoRevisao" |

Nenhum outro seed novo é necessário por este documento — os demais parâmetros citados (`TempoPacienteAtivoAgenda`, `TempoPacienteAtivoPrescricao`, Seção 4.5) já existem em produção, não são propostos aqui.

---

### METADADOS FINAIS

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

---

## Histórico de Versões

- **v4.25** (29/08/2026) — Arquivo criado pela divisão do documento único `CM_CockPit_do_medico_v4_25.md` em 4 arquivos por seção (ver Skill Designer, "Organização Física: Pasta por Funcionalidade", Lição #18). Conteúdo desta seção sem alteração de substância em relação à v4.25 do documento original — apenas reorganização física. O histórico de revisões anterior a esta divisão (v4.0 a v4.25, incluindo o racional de cada mudança) está preservado integralmente no documento original, arquivado em `Histórico/CM_CockPit_do_medico_v4_25 (documento único, antes da divisão em 4 arquivos).md`.
- **v4.26** (30/08/2026) — Pedido do solicitante: a janela de "1 dia (24h)" que dispara o estado "a revisar" de uma prescrição (RN-CM-020) precisa existir como parâmetro real de banco, não só como texto de regra de negócio — mesmo padrão já usado para outros parâmetros por clínica neste documento (`TempoPacienteAtivoAgenda`/`TempoPacienteAtivoPrescricao`, Seção 4.5). Adicionado, na Seção 4.6: (1) referência ao parâmetro `IpParametro.PrescricaoTempoRevisao` → `IpParametroChave.Valor` na query de `PrescriptionToReviewCount()`, substituindo o `DATEADD(HOUR, 24, ...)` fixo; (2) nova subseção "Parâmetro de configuração — PrescricaoTempoRevisao" **[PROPOSTA]**, com a definição completa das 2 linhas de dado necessárias (`IpParametro`: `IdParametro='PrescricaoTempoRevisao'`, `Descricao`, `Dominio=NULL`, `UsuarioControla='S'`, `Tipo='N'`; `IpParametroChave`: `IdParametro='PrescricaoTempoRevisao'`, `IdChave={Id da Clínica}`, `Valor=24`, `IdChave2=NULL`) e um alerta explícito para Dev/DevOps sobre a necessidade de incluir esse seed na documentação de deploy de cada cliente. Não é uma tabela nova — `IpParametro`/`IpParametroChave` já existem. Filtro `type = to_review` do endpoint 4.7 atualizado para referenciar o mesmo parâmetro em vez de "D-1" fixo.
- **v4.27** (30/08/2026) — Pedido do solicitante, resolvendo o `[A DEFINIR]` da v4.26: (1) comportamento definido para quando `IpParametro`/`IpParametroChave` de `PrescricaoTempoRevisao` não existir para a clínica — default de 24h, confirmado pelo solicitante — implementado na query de `PrescriptionToReviewCount()` (Seção 4.6) via `COALESCE(..., 24)` em vez do `SELECT` direto; parágrafo "Atenção Dev/DevOps" reescrito para descrever esse comportamento e remover a pendência; (2) nova subseção "Seeds da Funcionalidade" ao final da Seção 4, consolidando em uma tabela todo dado (não schema) que precisa existir em produção para a funcionalidade funcionar como especificado — pedido explícito do solicitante ("seria bom termos uma subseção com os seeds da funcionalidade"). Por ora contém 1 linha (`PrescricaoTempoRevisao`, **[PROPOSTA]**), apontando para a Seção 4.6 em vez de repetir a definição (Lição #10).
- **v4.28** (11/09/2026) — Correção pontual do solicitante: o valor de `PrescricaoAssinatura.TipoAssinatura` para a assinatura do médico está errado em todo o documento — era `'M'`, o correto é `'A'`. Corrigido em todas as ocorrências da Seção 4.6 (prosa e SQL de `PrescriptionToReviewCount()`, `PrescriptionUrgentCount()`, `PrescriptionToSignNext7DaysCount()`) e da Seção 4.7 (tabela de domínio, mapa de campos, filtros do endpoint, nota final). Não é o mesmo valor de `PrescricaoAssinaturaEnfermagem.TipoAssinatura = 'E'` (tabela distinta, confirmada na Biblioteca de Schema/DDL) — apenas o `'M'`→`'A'` de `PrescricaoAssinatura` estava incorreto. Nenhuma outra seção deste conjunto de documentos (01, 02, 03) referenciava esse campo — correção isolada à Seção 4.
