Pular para o conteúdo principal

Padrão Técnico para Cadastro Mestre (MDM)

Código: HORUS-ARQ-CM-001 Versão: 3.0 — 2026-10-05 Escopo: Regras para criar, manter, integrar e consumir cadastros mestres (master data) em bancos PostgreSQL. Aplica-se a: qualquer sistema, banco, módulo ou schema que use uma entidade mestre, atual ou futuro. Os nomes de schema, tabela e domínio dos exemplos são ilustrativos. Documentos relacionados: Doc 04 define auditoria, roles e governança. Doc 05 define a nomenclatura de colunas e o rito de modelagem. O modelo de dados de cada sistema (ex.: Doc 03) lista as entidades mestras, seus donos e domínios. Este documento não repete a nomenclatura do Doc 05.


1. Objetivo e Escopo​

O objetivo é acabar com a replicação desordenada de dados herdada dos sistemas legados. O padrão garante:

  1. Golden Record: uma fonte única, autoritativa e canônica para cada entidade estruturante.
  2. Desacoplamento de papéis: a identidade do objeto fica separada dos papéis que ele assume em cada processo (Party Model).
  3. Integridade referencial: chaves técnicas universais e índices únicos parciais que funcionam com soft delete.
  4. Conformidade legal: classificação de dados, ciclo de vida governado e salvaguardas da LGPD.

2. Princípio Fundamental​

Identidade única × contexto. O cadastro mestre define quem ou o que é o objeto (identidade permanente). O módulo de negócio define o que esse objeto faz ou significa naquele contexto (papel). Um dado mestre nunca conhece regras de processo, etapas de workflow ou atributos transitórios de contratos, vistorias e expedientes.

CADASTRO MESTRE (schema mdm)
mdm.pessoas (id_pessoa = UUID, nr_cpf)
mdm.empresas (id_empresa = UUID, nr_cnpj)
│ referência por FK técnica (UUID)
┌──────────┼───────────────┬──────────────────┐
▼ ▼ ▼ ▼
{dominio_a} {dominio_b} {dominio_c}
papel: beneficiário papel: mutuário papel: interessado
ocupante representante procurador

2.1 Estilo de implementação e responsabilidade​

O padrão adota o estilo centralizado (transactional hub, na classificação de Dreibelbis et al. e do DAMA-DMBOK): para cada entidade mestre, o schema mdm do sistema dono é o sistema de registro único e o único lugar onde o dado é criado e alterado. O dado mestre não se restringe a um banco: cada entidade tem um único sistema dono (Doc 03, Seção 2), o schema mdm de um sistema contém apenas as entidades que ele origina, e os demais sistemas consomem pela API do dono (Seção 3.7). Não é registry (índice que aponta para cópias em outros sistemas) nem coexistência (cópias sincronizadas entre sistemas). O responsável pelo dado mestre (data steward) é o Arquiteto de Dados, que revisa candidatos a duplicidade, aprova fusões e resolve conflitos de fonte.


3. Regras Normativas​

Cada regra tem a ação obrigatória e a proibição correspondente.

3.1 Identificação e chaves​

#RegraObrigatórioProibido
REG-CM-01Chave primáriaUUID v7: DEFAULT uuidv7()SERIAL, BIGSERIAL, INT IDENTITY ou outro inteiro auto-incremental; gen_random_uuid() como DEFAULT de PK
REG-CM-02Chave estrangeira entre tabelas e domíniosPK UUID da entidade (id_{entidade}). Entre bancos distintos, FK lógica (REG-CM-18)Chave de negócio (nr_cpf, nr_cnpj, cd_ibge, nr_processo) como FK. Município é referenciado sempre por id_municipio
REG-CM-03Nome da coluna de identificaçãoid_{entidade} (id_pessoa, id_empresa, id_municipio). A tabela consumidora usa o mesmo nome. Com papel: id_{entidade}_{papel}id, codigo, fk_pessoa ou variações

3.2 Schema e nomenclatura​

#RegraObrigatórioProibido
REG-CM-04SchemaToda tabela mestre compartilhada vive no schema mdm do banco do sistema dono da entidadeTabela mestre em public ou em schema de domínio; prefixo de utilidade (cad_, tb_, tbl_)
REG-CM-05Nome da tabelaPlural em português (mdm.pessoas, mdm.empresas, mdm.municipios, mdm.programas_habitacionais, mdm.arquivos). Tabela satélite: mdm.{tabela_mestre}_{atributo}, também no pluralTabela no singular
REG-CM-06Nome da colunaPrefixo de 2 letras do catálogo fechado do Doc 05, Seção 2.1.1, quando a coluna se enquadra em um termo do catálogoColuna que se enquadra em um termo do catálogo sem o prefixo; prefixo fora do catálogo

3.3 Ciclo de vida​

#RegraObrigatórioProibido
REG-CM-07Situação cadastralcd_situacao com domínio controlado: ATIVO, INATIVO, BLOQUEADO, PENDENTE_VALIDACAO. É o estado de negócio da entidade, que continua existindo e referenciávelstatus, fl_ativo, is_active; usar cd_situacao para indicar remoção do registro
REG-CM-08Exclusão lógicafl_excluido, dh_exclusao e id_usuario_exclusao, com CHECK (fl_excluido = (dh_exclusao IS NOT NULL)). Indica que o registro não deve mais existir para o negócio (cadastro indevido ou duplicado). A role da aplicação não tem DELETEDELETE físico em registro com vínculo a processo, vistoria ou contrato; usar fl_excluido como situação cadastral
REG-CM-09Unicidade de chave de negócioÍndice único parcial com WHERE fl_excluido = FALSEUNIQUE clássico em tabela com soft delete
REG-CM-10Formato e domínioCHECK na tabela: CPF com 11 dígitos, CNPJ com 14 caracteres [0-9A-Z] (CNPJ alfanumérico), código IBGE com 7 dígitos, cd_situacao dentro do domínio. O dígito verificador é validado na camada de aplicação ou de ETLAceitar valor fora do formato e corrigir depois

3.4 Papéis e histórico​

#RegraObrigatórioProibido
REG-CM-11Papel contextual (Party Role)Tabela associativa no módulo consumidor ligando a entidade ao papel naquele processoPapel transitório como coluna da tabela mestre (eh_fornecedor, eh_interessado)
REG-CM-12Foto histórica em ato jurídicoSnapshot imutável (coluna js_ auditada ou tabela satélite de fatos) quando o processo exigir o dado na data do atoCadastro paralelo de pessoa ou empresa no módulo para guardar o nome da época

3.5 Fonte e linhagem​

#RegraObrigatórioProibido
REG-CM-13Precedência de fontesAplicar a matriz de sobrevivência (Seção 4) em sincronizações e atualizações concorrentesSobrescrever dado de Nível 1 ou 2 por entrada de Nível 3
REG-CM-14Linhagemtp_origem_cadastro, dh_sincronizacao_fonte e id_origem_externa em todo registro mestreRegistro mestre sem fonte rastreada

3.6 LGPD e auditoria​

#RegraObrigatórioProibido
REG-CM-15LGPDCOMMENT ON COLUMN com LGPD:RESTRITO em identificadores pessoais (nr_cpf, nr_cnpj, nr_rg, nm_completo, nm_social, nm_mae, dt_nascimento, endereço, dados bancários). Mascaramento em DEV e HOMExportar ou clonar produção com PII sem proteção para DEV, HOM ou estação local
REG-CM-16AuditoriaTrigger auditoria.fn_registrar_operacao em toda tabela do mdm, com a PK como argumento; as colunas LGPD:RESTRITO e LGPD:SIGILOSO são mascaradas a partir do COMMENT ON COLUMN (Doc 04, Seção 7.2). O log registra [RESTRITO] no lugar do valorDesativar trigger de auditoria em produção (session_replication_role = replica); gravar dado restrito em claro no log

3.7 Acesso entre bancos (API de Cadastro Mestre)​

Vale quando o módulo consumidor está em um banco diferente do banco do sistema dono da entidade mestre. A "API de Cadastro Mestre" é o papel exercido pela API que cada sistema dono expõe para as entidades que origina; o contrato é o mesmo para todos (REG-CM-21). Em PostgreSQL não há chave estrangeira entre bancos; a integridade é garantida pela API e pela conciliação abaixo. Os nomes de serviço e de role (mdm-api, corporativo_app) e os caminhos de API deste documento são exemplos e não representam um serviço real.

#RegraObrigatórioProibido
REG-CM-17Acesso ao mestreTodo acesso a uma entidade mestre por módulo em outro banco passa pela API de Cadastro Mestre do sistema dono: consulta por id, em lote e por chave de negócio, e escrita por upsert idempotente (desduplicação por CPF, CNPJ ou código IBGE e matriz de sobrevivência, REG-CM-13, feitas pela API). A API do dono é o único serviço com role de escrita nas tabelas mdm do sistemaSQL direto no mdm de outro sistema; réplica local do dado mestre; postgres_fdw em aplicação (restrito a ETL e Arquiteto de Dados, Doc 04, Seção 3.4)
REG-CM-18Referência (FK lógica)Coluna id_{entidade} no consumidor, sem constraint e com índice. A API valida que o id existe e não está excluído antes de o módulo gravar o vínculoGravar vínculo sem validação; usar chave de negócio como referência (REG-CM-02)
REG-CM-19ConciliaçãoJob periódico que detecta id_{entidade} sem correspondente no mdm (órfãos) e gera alerta (Doc 04, Seção 5.1)Ignorar ou apagar órfão sem corrigir a origem
REG-CM-20Exclusão e vínculoO vínculo existente permanece válido para histórico; a API devolve o registro com fl_excluido e cd_situacao. Novo vínculo com registro excluído é rejeitado (HTTP 409 ou 422). Eliminação por LGPD anonimiza as colunas de PII e mantém o id. Duplicidade é fundida pelo dono do dado, e a API devolve o id sobreviventeDELETE físico no mdm; apagar o vínculo do módulo por causa da exclusão do mestre
REG-CM-21Contrato e segurançaContrato OpenAPI versionado (/v1), ingress interno, autenticação entre serviços por identidade de serviço do GCP (OIDC), usuário da aplicação propagado para a auditoria (Doc 04, Seção 7.2), resposta limitada aos campos necessários e timeout, retry e circuit breaker nos consumidoresEndpoint público; chave estática entre serviços; retornar PII além do necessário
REG-CM-22Resiliência e degradaçãoSLO da API documentado e monitorado (Doc 01, Seção 6). Em indisponibilidade da API: leitura de dados de exibição pode usar cache em memória do processo, com TTL curto; gravação de vínculo com o mestre falha de forma explícita (HTTP 503) e é repetida depois com a mesma chave de idempotência. Retry só em operação idempotente. Teste de contrato da API no CI de consumidor e produtorCache persistente do dado mestre; usar cache para decidir validação de vínculo; gravar vínculo sem a validação da API; retry em operação não idempotente; quebrar o contrato /v1 sem versão nova

3.8 Duplicidade, fusão e sobrevivência​

#RegraObrigatórioProibido
REG-CM-23Correspondência (matching)A identidade é decidida pela chave de negócio normalizada: CPF e CNPJ só com dígitos e dígito verificador validado pela API; código IBGE. Correspondência aproximada (nome normalizado, data de nascimento e filiação; razão social) só gera candidato a duplicidade, registrado para revisão do responsável pelo dadoFundir automaticamente por correspondência aproximada; gravar o dado normalizado no lugar do original
REG-CM-24Fusão e sobrevivênciaA fusão é aprovada pelo responsável pelo dado. Cada atributo sobrevive conforme a matriz de sobrevivência (REG-CM-13); em empate, vale a sincronização mais recente. O registro absorvido recebe soft delete (REG-CM-08) e a fusão é registrada em mdm.{tabela_mestre}_fusoes, com motivo e vínculos repontadosFundir sem aprovação e sem registro; DELETE do registro absorvido
REG-CM-25Propagação da fusãoA API resolve o id absorvido para o sobrevivente, inclusive em cadeia, e publica o evento de fusão no Pub/Sub. Os módulos repontam seus id_{entidade} para o sobreviventeManter vínculo apontando para o absorvido após o repontamento; módulo decidir sozinho qual registro sobrevive
REG-CM-26ReversãoA fusão é reversível enquanto o registro absorvido existir: reativa-se o absorvido e registra-se o evento REVERSAO; os vínculos repontados voltam conforme o registro da fusãoReverter sem registrar o evento

4. Matriz de Sobrevivência de Dados (REG-CM-13)​

NívelFonteExemplos
1Base oficial externa com fé públicaReceita Federal (CNPJ, razão social); IBGE (cd_ibge, municípios)
2Validação operacional formal do órgão responsávelDado conferido documentalmente por analista de instrução processual
3AutodeclaraçãoFormulário de rascunho de prefeitura ou munícipe
4Legado migrado sem batimentoDado importado de base antiga sem conciliação

O nível de numeração menor (mais confiável) prevalece sobre o de numeração maior. tp_origem_cadastro registra a fonte e dh_sincronizacao_fonte a última sincronização com ela.


5. DDL Canônico do Schema mdm​

DDL de referência com as entidades mestras típicas (município, pessoa e empresa); cada sistema adota as que usa. Colunas de auditoria e de soft delete seguem o Doc 05, Seção 3. As colunas id_usuario_* não têm FK.

CREATE SCHEMA IF NOT EXISTS mdm;

-- Município (fonte oficial: IBGE)
CREATE TABLE mdm.municipios (
id_municipio UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY,
cd_ibge VARCHAR(7) NOT NULL CHECK (cd_ibge ~ '^[0-9]{7}$'),
nm_municipio VARCHAR(150) NOT NULL,
sg_uf CHAR(2) NOT NULL,
cd_situacao VARCHAR(30) NOT NULL DEFAULT 'ATIVO'
CHECK (cd_situacao IN ('ATIVO','INATIVO','BLOQUEADO','PENDENTE_VALIDACAO')),
tp_origem_cadastro VARCHAR(50) NOT NULL DEFAULT 'IBGE',
dh_sincronizacao_fonte TIMESTAMPTZ,
id_origem_externa VARCHAR(100),
dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_cadastro UUID NOT NULL,
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_atualizacao UUID NOT NULL,
fl_excluido BOOLEAN NOT NULL DEFAULT FALSE,
dh_exclusao TIMESTAMPTZ,
id_usuario_exclusao UUID,
CONSTRAINT ck_municipio_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL))
);
CREATE UNIQUE INDEX uq_municipio_cd_ibge ON mdm.municipios (cd_ibge) WHERE fl_excluido = FALSE;
CREATE INDEX idx_municipio_nm_municipio ON mdm.municipios (nm_municipio) WHERE fl_excluido = FALSE;

-- Pessoa física
CREATE TABLE mdm.pessoas (
id_pessoa UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY,
nr_cpf VARCHAR(11) NOT NULL CHECK (nr_cpf ~ '^[0-9]{11}$'),
nm_completo VARCHAR(200) NOT NULL,
nm_social VARCHAR(200),
nm_mae VARCHAR(200),
dt_nascimento DATE,
cd_situacao VARCHAR(30) NOT NULL DEFAULT 'ATIVO'
CHECK (cd_situacao IN ('ATIVO','INATIVO','BLOQUEADO','PENDENTE_VALIDACAO')),
tp_origem_cadastro VARCHAR(50) NOT NULL DEFAULT 'MANUAL',
dh_sincronizacao_fonte TIMESTAMPTZ,
id_origem_externa VARCHAR(100),
dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_cadastro UUID NOT NULL,
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_atualizacao UUID NOT NULL,
fl_excluido BOOLEAN NOT NULL DEFAULT FALSE,
dh_exclusao TIMESTAMPTZ,
id_usuario_exclusao UUID,
CONSTRAINT ck_pessoa_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL))
);
CREATE UNIQUE INDEX uq_pessoa_nr_cpf ON mdm.pessoas (nr_cpf) WHERE fl_excluido = FALSE;
CREATE INDEX idx_pessoa_nm_completo ON mdm.pessoas (nm_completo) WHERE fl_excluido = FALSE;
COMMENT ON COLUMN mdm.pessoas.nr_cpf IS 'LGPD:RESTRITO — chave de deduplicação de pessoa física. Mascarar em HOM.';
COMMENT ON COLUMN mdm.pessoas.nm_completo IS 'LGPD:RESTRITO — nome do titular. Mascarar em HOM.';
COMMENT ON COLUMN mdm.pessoas.nm_social IS 'LGPD:RESTRITO — nome social do titular. Mascarar em HOM.';
COMMENT ON COLUMN mdm.pessoas.nm_mae IS 'LGPD:RESTRITO — filiação do titular. Mascarar em HOM.';
COMMENT ON COLUMN mdm.pessoas.dt_nascimento IS 'LGPD:RESTRITO — data de nascimento do titular. Mascarar em HOM.';

-- Empresa / pessoa jurídica (fonte oficial: Receita Federal)
CREATE TABLE mdm.empresas (
id_empresa UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY,
nr_cnpj VARCHAR(14) NOT NULL CHECK (nr_cnpj ~ '^[0-9A-Z]{14}$'),
nm_razao_social VARCHAR(250) NOT NULL,
nm_fantasia VARCHAR(250),
cd_cnae_principal VARCHAR(10),
cd_natureza_juridica VARCHAR(10),
cd_situacao VARCHAR(30) NOT NULL DEFAULT 'ATIVO'
CHECK (cd_situacao IN ('ATIVO','INATIVO','BLOQUEADO','PENDENTE_VALIDACAO')),
tp_origem_cadastro VARCHAR(50) NOT NULL DEFAULT 'RECEITA_FEDERAL',
dh_sincronizacao_fonte TIMESTAMPTZ,
id_origem_externa VARCHAR(100),
dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_cadastro UUID NOT NULL,
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_atualizacao UUID NOT NULL,
fl_excluido BOOLEAN NOT NULL DEFAULT FALSE,
dh_exclusao TIMESTAMPTZ,
id_usuario_exclusao UUID,
CONSTRAINT ck_empresa_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL))
);
CREATE UNIQUE INDEX uq_empresa_nr_cnpj ON mdm.empresas (nr_cnpj) WHERE fl_excluido = FALSE;
CREATE INDEX idx_empresa_nm_razao_social ON mdm.empresas (nm_razao_social) WHERE fl_excluido = FALSE;
COMMENT ON COLUMN mdm.empresas.nr_cnpj IS 'LGPD:RESTRITO — chave de deduplicação de empresa (pode identificar empresário individual). Mascarar em HOM.';

-- Triggers obrigatórios: dh_atualizacao (Doc 05, Seção 6.1) e auditoria
-- (argumentos da auditoria: PK + colunas LGPD:RESTRITO, mascaradas no log)
CREATE TRIGGER tg_before_municipios_update BEFORE UPDATE ON mdm.municipios
FOR EACH ROW EXECUTE FUNCTION fn_atualizar_dh_atualizacao();
CREATE TRIGGER tg_before_pessoas_update BEFORE UPDATE ON mdm.pessoas
FOR EACH ROW EXECUTE FUNCTION fn_atualizar_dh_atualizacao();
CREATE TRIGGER tg_before_empresas_update BEFORE UPDATE ON mdm.empresas
FOR EACH ROW EXECUTE FUNCTION fn_atualizar_dh_atualizacao();

CREATE TRIGGER tg_after_municipios_audit AFTER INSERT OR UPDATE OR DELETE ON mdm.municipios
FOR EACH ROW EXECUTE FUNCTION auditoria.fn_registrar_operacao('id_municipio');
CREATE TRIGGER tg_after_pessoas_audit AFTER INSERT OR UPDATE OR DELETE ON mdm.pessoas
FOR EACH ROW EXECUTE FUNCTION auditoria.fn_registrar_operacao('id_pessoa');
CREATE TRIGGER tg_after_empresas_audit AFTER INSERT OR UPDATE OR DELETE ON mdm.empresas
FOR EACH ROW EXECUTE FUNCTION auditoria.fn_registrar_operacao('id_empresa');

Atributo de município que não é identidade (região administrativa, geometria, indicadores) vive em tabela satélite no plural (mdm.municipios_regioes, mdm.municipios_geometrias), nunca como coluna extra de mdm.municipios nem em uma segunda tabela de município (Doc 03, Seção 2.0).

As entidades mdm.programas_habitacionais e mdm.arquivos (Doc 03, Seções 2.4 e 2.6) seguem o mesmo molde: PK UUIDv7, colunas de linhagem, auditoria e soft delete, índice único parcial na chave de negócio e os triggers acima. O histórico de exclusões de pessoas é a satélite mdm.pessoas_exclusoes (Doc 05, Seção 3.3). O registro de fusões de duplicidade (Seção 3.8) segue o padrão mdm.{tabela_mestre}_fusoes; abaixo, o caso de pessoa. A tabela é imutável (sem UPDATE nem DELETE para as roles de aplicação) e por isso dispensa as colunas de auditoria e de soft delete.

CREATE TABLE mdm.pessoas_fusoes (
id_pessoa_absorvida UUID NOT NULL REFERENCES mdm.pessoas (id_pessoa) ON DELETE RESTRICT,
id_pessoa_sobrevivente UUID NOT NULL REFERENCES mdm.pessoas (id_pessoa) ON DELETE RESTRICT,
dh_evento TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tp_evento VARCHAR(20) NOT NULL CHECK (tp_evento IN ('FUSAO','REVERSAO')),
id_usuario_evento UUID NOT NULL,
ds_motivo TEXT NOT NULL,
js_vinculos_repontados JSONB,
PRIMARY KEY (id_pessoa_absorvida, dh_evento),
CONSTRAINT ck_pessoa_fusao_distintas CHECK (id_pessoa_absorvida <> id_pessoa_sobrevivente)
);
CREATE INDEX idx_pessoa_fusao_sobrevivente ON mdm.pessoas_fusoes (id_pessoa_sobrevivente);

6. Exemplos de Consumo por Módulo​

Os exemplos 6.1 e 6.2 valem quando o módulo está no mesmo banco da entidade mestre consumida. Entre bancos distintos, as colunas id_empresa e id_pessoa não levam REFERENCES e seguem a Seção 6.3.

6.1 Consórcio de empresas em contrato (REG-CM-02, 11 e 12)​

Tabela associativa com FK por UUID e snapshot da razão social na data do ato. Nunca uma coluna de texto com vários CNPJs separados por vírgula.

CREATE TABLE {dominio}.contratos_empresas (
id_contrato_empresa UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY,
id_contrato UUID NOT NULL REFERENCES {dominio}.contratos (id_contrato) ON DELETE RESTRICT,
id_empresa UUID NOT NULL REFERENCES mdm.empresas (id_empresa) ON DELETE RESTRICT,
tp_participacao VARCHAR(30) NOT NULL, -- LIDER, CONSORCIADA, SUBCONTRATADA
vl_percentual_cotas NUMERIC(5,2),
dt_inicio_vinculo DATE NOT NULL,
dt_fim_vinculo DATE,
nm_razao_social_snapshot VARCHAR(250) NOT NULL, -- imutável: razão social na data do ato
dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_cadastro UUID NOT NULL,
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_atualizacao UUID NOT NULL,
fl_excluido BOOLEAN NOT NULL DEFAULT FALSE,
dh_exclusao TIMESTAMPTZ,
id_usuario_exclusao UUID,
CONSTRAINT ck_contrato_empresa_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL))
);
CREATE INDEX idx_contrato_empresa_id_contrato ON {dominio}.contratos_empresas (id_contrato);
CREATE INDEX idx_contrato_empresa_id_empresa ON {dominio}.contratos_empresas (id_empresa);
CREATE TRIGGER tg_before_contratos_empresas_update BEFORE UPDATE ON {dominio}.contratos_empresas
FOR EACH ROW EXECUTE FUNCTION fn_atualizar_dh_atualizacao();

6.2 Múltiplos papéis em um processo (REG-CM-11)​

Vínculo de pessoa física ou jurídica ao processo, sem recadastrar os dados da entidade no módulo.

CREATE TABLE {dominio}.processos_participantes (
id_processo_participante UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY,
id_processo UUID NOT NULL REFERENCES {dominio}.processos (id_processo) ON DELETE RESTRICT,
id_pessoa UUID REFERENCES mdm.pessoas (id_pessoa) ON DELETE RESTRICT,
id_empresa UUID REFERENCES mdm.empresas (id_empresa) ON DELETE RESTRICT,
tp_papel VARCHAR(30) NOT NULL, -- PROPRIETARIO, EMPREENDEDOR, RESPONSAVEL_TECNICO
nr_registro_crea_cau VARCHAR(30),
dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_cadastro UUID NOT NULL,
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_atualizacao UUID NOT NULL,
fl_excluido BOOLEAN NOT NULL DEFAULT FALSE,
dh_exclusao TIMESTAMPTZ,
id_usuario_exclusao UUID,
CONSTRAINT ck_participante_origem CHECK (
(id_pessoa IS NOT NULL AND id_empresa IS NULL) OR
(id_pessoa IS NULL AND id_empresa IS NOT NULL)),
CONSTRAINT ck_processo_participante_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL))
);
CREATE UNIQUE INDEX uq_processo_participante_vinculo
ON {dominio}.processos_participantes (id_processo, id_pessoa, id_empresa, tp_papel) NULLS NOT DISTINCT
WHERE fl_excluido = FALSE;
CREATE INDEX idx_processo_participante_id_pessoa ON {dominio}.processos_participantes (id_pessoa);
CREATE INDEX idx_processo_participante_id_empresa ON {dominio}.processos_participantes (id_empresa);
CREATE TRIGGER tg_before_processos_participantes_update BEFORE UPDATE ON {dominio}.processos_participantes
FOR EACH ROW EXECUTE FUNCTION fn_atualizar_dh_atualizacao();

O índice único com id_processo à esquerda já cobre a busca por id_processo.

6.3 Mestre em outro banco (REG-CM-17 a 22)​

Vínculo de empresa a contrato quando a entidade mestre está em outro banco: sem REFERENCES para o mestre, com índice, e a validação feita pela API antes do INSERT.

CREATE TABLE {dominio}.contratos_empresas (
id_contrato_empresa UUID NOT NULL DEFAULT uuidv7() PRIMARY KEY,
id_contrato UUID NOT NULL REFERENCES {dominio}.contratos (id_contrato) ON DELETE RESTRICT,
id_empresa UUID NOT NULL, -- FK lógica para mdm.empresas (REG-CM-18)
tp_participacao VARCHAR(30) NOT NULL,
nm_razao_social_snapshot VARCHAR(250) NOT NULL,
dh_cadastro TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_cadastro UUID NOT NULL,
dh_atualizacao TIMESTAMPTZ NOT NULL DEFAULT NOW(),
id_usuario_atualizacao UUID NOT NULL,
fl_excluido BOOLEAN NOT NULL DEFAULT FALSE,
dh_exclusao TIMESTAMPTZ,
id_usuario_exclusao UUID,
CONSTRAINT ck_contrato_empresa_exclusao CHECK (fl_excluido = (dh_exclusao IS NOT NULL))
);
CREATE INDEX idx_contrato_empresa_id_empresa ON {dominio}.contratos_empresas (id_empresa);
COMMENT ON COLUMN {dominio}.contratos_empresas.id_empresa IS 'FK lógica: mdm.empresas.id_empresa (API de Cadastro Mestre).';

Operações mínimas da API (nomes ilustrativos):

GET /v1/empresas/{id_empresa} -- consulta por id (404 se não existir; id absorvido por fusão devolve o sobrevivente, REG-CM-25)
POST /v1/empresas/consultas -- consulta em lote: { "ids": [ ... ] }
GET /v1/empresas?nr_cnpj={cnpj} -- consulta por chave de negócio
PUT /v1/empresas -- upsert por nr_cnpj; cabeçalho Idempotency-Key

Fluxo de gravação: (1) a API devolve o id_empresa, cadastrando ou localizando pelo CNPJ; (2) o módulo grava o vínculo com o id_empresa e o snapshot; (3) se a API responde que o registro está excluído, o módulo não grava (REG-CM-20); (4) se a API está indisponível, o módulo não grava o vínculo e informa o erro ao usuário, e a operação é repetida depois com a mesma chave de idempotência (REG-CM-22).


7. Fluxo de Decisão do Desenvolvedor​


8. Ficha Arquitetural de Módulo​

Todo módulo novo, ou módulo que passa a usar uma entidade mestre, anexa esta ficha à proposta de aprovação (Doc 05, Seção 1.2).

### Ficha Arquitetural de Módulo: [Nome do Módulo]
* **Domínio:** [ex.: Regularização Fundiária / Contratos / Aprovação]
* **Objetivo de negócio:** [descrição concisa]
* **Cadastros mestres consumidos:**
- [ ] `mdm.pessoas` (papéis: beneficiário, ocupante, ...)
- [ ] `mdm.empresas` (papéis: fornecedor, construtora, ...)
- [ ] `mdm.municipios` (vínculo geográfico oficial)
- [ ] `mdm.programas_habitacionais`
- [ ] `mdm.arquivos`
* **Verificações obrigatórias:**
- [ ] O módulo não cria tabela própria ou paralela de CPF ou CNPJ (REG-CM-11 e 12).
- [ ] O módulo não altera atributo do MDM sem passar pelo fluxo de governança.
- [ ] Nenhuma chave de negócio (CPF, CNPJ, IBGE) é usada como FK (REG-CM-02).
- [ ] FKs para o MDM têm `ON DELETE RESTRICT` e índice na coluna (entre bancos, FK lógica com índice, REG-CM-18).
- [ ] Módulo em banco diferente do da entidade mestre: consome só pela API do sistema dono, trata exclusão e órfãos (REG-CM-17 a 22).
- [ ] O módulo trata fusão de duplicidade: reponta o id absorvido para o sobrevivente e não decide a sobrevivência (REG-CM-23 a 26).
- [ ] Snapshots históricos estruturados para atos jurídicos imutáveis (REG-CM-12).
- [ ] Colunas `LGPD:RESTRITO` identificadas (REG-CM-15).
- [ ] Nomes de coluna dentro do catálogo do Doc 05, Seção 2.1.1.