# Logs e Auditoria — Mapeamento de Banco de Dados (v1.0)

**Documentos desta funcionalidade:** [01 - Definição](./01%20-%20Defini%C3%A7%C3%A3o%20v1.0.md) (v1.0) · [02 - Especificação](./02%20-%20Especifica%C3%A7%C3%A3o%20v1.0.md) (v1.0) · [03 - Protótipo](./03%20-%20Prot%C3%B3tipo%20v1.0.md) (v1.0, código em `03 - Protótipo v1.0.html`) · 04 - Mapeamento de Banco de Dados (v1.0) · [Cenários de Teste](./Cen%C3%A1rios%20de%20Teste%20v1.0.md) (v1.0) · [05 - Registro de Decisões](./05%20-%20Registro%20de%20Decis%C3%B5es%20v1.0.md) (v1.0)

Fonte primária da estrutura: Biblioteca de Schema (DDL) (`IpSeguranca.sql`, `Gescom.sql`, `catalog.json`, `framework_pattern.md`). O log de sistema **não usa banco relacional** (vai para Loki e Prometheus); este mapeamento trata da trilha de auditoria. Tudo que é tabela ou coluna nova está marcado **[PROPOSTA]**.

Convenções: instantes em UTC (`datetime2(3)` no SQL Server, `timestamptz` no PostgreSQL). O banco final pode mudar de SQL Server para PostgreSQL (decisão em aberto), por isso há modelo lógico neutro e um mapeamento para cada motor (4.4).

---

## 4.1 Tabelas existentes utilizadas

| Tabela | Base | Uso |
|---|---|---|
| `Paciente` (PK `IdPaciente` int) | GescomClienteAlfa | Identificador opaco do paciente na trilha (RN-LOG-015). `NumProntuario`, `CPF` e nome **não** entram. |
| `Clinica` (`CNES`, `IdChClinica`) | GescomClienteAlfa | CNES na identificação da exportação (NGS1.07.08) |
| `SegEmpresa` (`SegurancaClienteEmpresaId`) | GescomClienteAlfa | Liga a clínica local ao `ClienteEmpresa` global |
| `ClienteEmpresa` (`Id`, `Nome`, `CNPJ`, `ClienteBDId`) | IPSeguranca | Nome e CNPJ da instituição na exportação; clínica na trilha |
| `ClienteBD` (`Id`, `NomeDatabase`) | IPSeguranca | Tenant (`ClienteBDId`) |
| `Usuario` (`Id` guid, `TentativasLogin`, `DHBloqueio`) | IPSeguranca | Usuário (GUID global) |
| `UsuarioCliente` (`UsuarioId`, `ClienteBDId`, `Situacao`) | IPSeguranca | Usuário por cliente |
| `ClientePerfilAcesso` (`Id`, `Nome`, `ClienteBDId`) | IPSeguranca | Perfil "Auditor" por cliente (seed 4.6) |
| `Processo`, `DireitoProcesso`, `ProcessoDireito` | IPSeguranca | Processo "Auditoria" e direito "Visualizar auditoria" (seed 4.6) |
| `Log`, `LogComplemento` | IPSeguranca | **Não reutilizadas.** Finalidade não confirmada, campos insuficientes (sem categoria, registro afetado, hash) e sem proteção de somente inclusão. Destino a decidir em P-LOG-07. |

## 4.2 Outras bases

`IpTerminologia`: sem uso. `GescomZeradoPadronizacao` (legado): a trilha não é criada no legado; o legado tem `PEPLog` (com `NovoValor`), que não é migrado para a trilha nova (ver P-LOG-01).

## 4.3 Estados e domínios

| Domínio | Valores | Origem |
|---|---|---|
| `Categoria` | 1 = Clínica, 2 = Operacional | RN-LOG-011 [PROPOSTA] |
| `TipoEvento` | smallint, numeração do catálogo `AuditEventType` (AUD-RES-001 = 1001, AUD-USR-001 = 2001, AUD-OPE-001 = 3001; sequência dentro da faixa) | Especificação 2.3.3 [PROPOSTA] |
| `Resultado` | 1 = sucesso, 2 = negado, 3 = falha | [PROPOSTA] |

A numeração do tipo é estável e nunca reaproveitada; o nome vem do catálogo em código e do dicionário i18n, não do banco.

## 4.4 Modelo lógico e mapeamento por banco

### 4.4.1 Modelo lógico (neutro) — entidade `auditoria.Evento` [PROPOSTA]

Existe em **dois lugares com a mesma estrutura**: `IpSeguranca` (repositório global) e `GescomClienteAlfa` (repositório do cliente). A coluna `PacienteId` só é usada no repositório do cliente.

| Campo lógico | Tipo lógico | Obrig. | Descrição | Requisito |
|---|---|---|---|---|
| Id | inteiro 64 bits, sequencial | sim | Identificador único | NGS1.07.05 |
| OcorridoEm | instante UTC | sim | Data e hora | NGS1.07.05 |
| TipoEvento | inteiro 16 bits | sim | Código do catálogo | NGS1.07.05 |
| Categoria | inteiro 8 bits | sim | Clínica ou operacional | RN-LOG-011 |
| UsuarioId | GUID | sim (nulo em login de usuário desconhecido: grava o login informado em `Detalhe`, sem senha) | Usuário global | NGS1.07.05 |
| ClienteBDId | GUID | sim | Tenant | RN-LOG-017 |
| ClinicaId | GUID | não | `ClienteEmpresa.Id` | RN-LOG-002 |
| PacienteId | inteiro 32 bits | não | `Paciente.IdPaciente`, identificador do registro afetado clínico | NGS1.07.07c |
| Recurso | texto 60 | não | Tipo do registro afetado (ex.: Prontuario, Prescricao, Usuario) | NGS1.07.05 |
| RecursoId | texto 64 | não | Id do recurso afetado | NGS1.07.05 |
| Componente | texto 60 | sim | Serviço/componente | NGS1.07.05 |
| Ip | texto 45 | não | IP de origem | NGS1.07.05 |
| TraceId | texto 32 | não | Liga ao log de sistema | RN-LOG-002 |
| Resultado | inteiro 8 bits | sim | Sucesso, negado, falha | [PROPOSTA] |
| Detalhe | JSON | não | Informação complementar: nomes de campos alterados; **sem valores clínicos** | NGS1.07.06 |
| HashLinha | 32 bytes | sim | SHA-256 dos campos canônicos | RN-LOG-025 |

Hash canônico: SHA-256 sobre a concatenação UTF-8 de `OcorridoEm` (RFC 3339), `TipoEvento`, `Categoria`, `UsuarioId`, `ClienteBDId`, `ClinicaId`, `PacienteId`, `Recurso`, `RecursoId`, `Componente`, `Ip`, `TraceId`, `Resultado` e `Detalhe`, na ordem fixa, separados por `|`, nulo como texto vazio. Calculado pela aplicação, igual nos dois bancos.

**Exceção ao padrão de 16 campos:** a tabela não leva o carimbo de última ação (`CriadoEm`, `AtualizadoEm`, `RemovidoEm`, etc.), porque o registro nunca é alterado nem removido; o próprio registro é o carimbo. A detecção de "tabelas sem o padrão" da Biblioteca de Schema deve tratar `auditoria.Evento` como exceção declarada.

### 4.4.2 SQL Server [PROPOSTA]

```sql
CREATE PARTITION FUNCTION pf_auditoria_mes (datetime2(3))
  AS RANGE RIGHT FOR VALUES ('2026-12-01','2027-01-01' /* ... criadas por rotina */);
CREATE PARTITION SCHEME ps_auditoria_mes
  AS PARTITION pf_auditoria_mes ALL TO ([PRIMARY]);

CREATE TABLE auditoria.Evento (
  Id          bigint IDENTITY(1,1) NOT NULL,
  OcorridoEm  datetime2(3)     NOT NULL,
  TipoEvento  smallint         NOT NULL,
  Categoria   tinyint          NOT NULL,
  UsuarioId   uniqueidentifier NULL,
  ClienteBDId uniqueidentifier NOT NULL,
  ClinicaId   uniqueidentifier NULL,
  PacienteId  int              NULL,
  Recurso     varchar(60)      NULL,
  RecursoId   varchar(64)      NULL,
  Componente  varchar(60)      NOT NULL,
  Ip          varchar(45)      NULL,
  TraceId     char(32)         NULL,
  Resultado   tinyint          NOT NULL,
  Detalhe     nvarchar(max)    NULL,
  HashLinha   binary(32)       NOT NULL,
  CONSTRAINT Evento_PK PRIMARY KEY CLUSTERED (OcorridoEm, Id)
    WITH (DATA_COMPRESSION = PAGE) ON ps_auditoria_mes (OcorridoEm),
  CONSTRAINT Evento_Detalhe_CK CHECK (Detalhe IS NULL OR ISJSON(Detalhe) = 1)
);
CREATE INDEX Evento_Usuario_IX  ON auditoria.Evento (UsuarioId,  OcorridoEm) ON ps_auditoria_mes (OcorridoEm);
CREATE INDEX Evento_Paciente_IX ON auditoria.Evento (PacienteId, OcorridoEm) WHERE PacienteId IS NOT NULL ON ps_auditoria_mes (OcorridoEm);
CREATE INDEX Evento_Tipo_IX     ON auditoria.Evento (TipoEvento, OcorridoEm) ON ps_auditoria_mes (OcorridoEm);
```

Compressão PAGE por partição (partições antigas podem ser comprimidas mais tarde). Tabela ledger só existe no SQL Server 2022 ou superior e **não** é adotada (RN-LOG-025). Permissões: o papel da aplicação recebe `INSERT` e `SELECT`; `DENY UPDATE, DELETE` sobre o schema `auditoria`; o dono do schema é outro papel.

### 4.4.3 PostgreSQL [PROPOSTA]

```sql
CREATE TABLE auditoria.evento (
  id           bigint GENERATED ALWAYS AS IDENTITY,
  ocorrido_em  timestamptz NOT NULL,
  tipo_evento  smallint    NOT NULL,
  categoria    smallint    NOT NULL,
  usuario_id   uuid,
  cliente_bd_id uuid       NOT NULL,
  clinica_id   uuid,
  paciente_id  int,
  recurso      varchar(60),
  recurso_id   varchar(64),
  componente   varchar(60) NOT NULL,
  ip           varchar(45),
  trace_id     char(32),
  resultado    smallint    NOT NULL,
  detalhe      jsonb,
  hash_linha   bytea       NOT NULL,
  PRIMARY KEY (ocorrido_em, id)
) PARTITION BY RANGE (ocorrido_em);
CREATE INDEX ON auditoria.evento (usuario_id, ocorrido_em);
CREATE INDEX ON auditoria.evento (paciente_id, ocorrido_em) WHERE paciente_id IS NOT NULL;
CREATE INDEX ON auditoria.evento USING brin (ocorrido_em);
REVOKE UPDATE, DELETE, TRUNCATE ON auditoria.evento FROM gemed_app;
```

Partições mensais criadas e rotacionadas por `pg_partman` (ou rotina própria). A chave primária precisa conter a chave de partição, por isso `(ocorrido_em, id)`. Sem compressão de página nativa: as opções são sistema de arquivos com compressão (ZFS), TimescaleDB ou Citus colunar; decidir após medir o volume (P-LOG-03). Partição antiga pode sair com `DETACH PARTITION [CONCURRENTLY]` para arquivamento, desde que continue visível na tela (NGS1.07.07b). Gatilho opcional que recusa UPDATE e DELETE como segunda barreira.

### 4.4.4 Equivalência de pacientes (cliente) [PROPOSTA]

`auditoria.PacienteEquivalencia (PacienteIdOrigem int, PacienteIdDestino int, UnificadoEm datetime2(3), UsuarioId uniqueidentifier)`. Usada só se houver unificação de pacientes (EX-LOG-09); a consulta por `IdPaciente` inclui os de origem. Criar quando a unificação existir (P-LOG-08).

### 4.4.5 Log de sistema

Sem tabelas. Loki (rótulos `service`, `environment`, `level`; campos estruturados para o resto) e Prometheus. Retenção e armazenamento de objetos (MinIO/S3) a definir após medir o volume (P-LOG-02).

## 4.5 Lacunas e propostas

| ID | Lacuna | Proposta |
|---|---|---|
| L-01 | Não há tabela de trilha real em nenhuma das 4 bases | Criar `auditoria.Evento` em `IpSeguranca` e em cada banco de cliente |
| L-02 | Não há perfil "Auditor" nem direito de visualização de auditoria em `IpSeguranca` | Seeds em 4.6 |
| L-03 | `Log`/`LogComplemento` sem finalidade confirmada | Manter como estão; decidir em P-LOG-07 |
| L-04 | Sem equivalência de paciente | 4.4.4, quando necessário |
| L-05 | Sem rotina de criação de partições | Rotina agendada que cria as partições dos próximos 3 meses [PROPOSTA] |
| L-06 | Sem papel de banco só de inserção para a aplicação | Criar papéis aplicação e dono, separados |

## 4.6 Seeds da Funcionalidade

| Seed | Tabela(s) | Por cliente ou global? | Status | Detalhe |
|---|---|---|---|---|
| Processo "Auditoria" (rota da tela, contexto Segurança) | `IpSeguranca.Processo` | Global | [PROPOSTA] | `Processo`, `Nome`, `Prefixo`, `Sistema`, `Contexto`, `Rota` a seguir o padrão dos processos existentes (conferir na Fase 4) |
| Direito "Visualizar auditoria" | `IpSeguranca.DireitoProcesso` | Global | [PROPOSTA] | `Direito`, `Nome`, `Tipo`, domínio conforme os direitos existentes |
| Ligação processo–direito | `IpSeguranca.ProcessoDireito` | Global | [PROPOSTA] | `Acao` conforme o padrão existente |
| Perfil "Auditor" | `IpSeguranca.ClientePerfilAcesso` | Por cliente (`ClienteBDId`) | [PROPOSTA] | Criado por cliente na implantação; sem usuários por padrão |
| Permissão do perfil "Auditor" ao processo | tabela de ligação perfil–processo (`ClientePerfilAcessoProcesso`, conferir nome exato no DDL) | Por cliente | [PROPOSTA] | Concede "Visualizar auditoria" |
| Papéis de banco da aplicação (inserção e consulta) e do dono | `auditoria` nos dois bancos | Por banco | [PROPOSTA] | Scripts de implantação |
| Partições iniciais | `auditoria.Evento` | Por banco | [PROPOSTA] | Mês corrente e 3 seguintes |

O administrador do cliente já poderá ser vinculado ao direito pela gestão de perfis existente.

## 4.7 Migração

Nada é migrado para a trilha nova. Os bancos de clientes em produção no legado não ganham a trilha; ela nasce com o Gemed 2.1 (`GescomClienteAlfa`). A troca de SQL Server para PostgreSQL, se ocorrer, copia a trilha por partição (mantendo `Id` e `HashLinha`, que independem do motor).
