# nfe_flow — Documentação do Código

**Projeto:** nfe_flow  
**Stack:** MariaDB 10.4+ / MySQL 8+  
**Fonte normativa:** [Portal NF-e — Documentos Diversos](https://www.nfe.fazenda.gov.br/portal/listaConteudo.aspx?tipoConteudo=/NJarYc9nus=)

---

## Índice de Migrations

| Arquivo | Tipo | Conteúdo |
|---|---|---|
| `001_tabelas_base.sql` | DDL | Objetos utilitários + tabelas geográficas |
| `002_tabelas_fiscais.sql` | DDL | Tabelas fiscais operacionais |
| `003_tabelas_classificacao.sql` | DDL | Tabelas IBS/CBS |
| `004_tabelas_servicos.sql` | DDL | Catálogo de serviços |
| `005_tabelas_produtos.sql` | DDL | NCM e produtos ANP |
| `006_indices_e_constraints.sql` | DDL | Views + chaves estrangeiras |
| `007_carga_estados.sql` | DML | 27 UFs + FCP por UF (84 linhas) |
| `008_carga_municipios.sql` | DML | 5.571 municípios |
| `009_carga_paises.sql` | DML | 264 países |
| `010_carga_ncm.sql` | DML | 13 unidades tributáveis + 10.520 NCMs |
| `011_carga_ncm_detalhes.sql` | DML | 7.485 nós hierárquicos NCM |
| `012_carga_cest.sql` | DML | 39 produtos ANP + 202 correlações |
| `013_carga_cfop.sql` | DML | 619 CFOPs + 552 detalhamentos |
| `014_carga_cst.sql` | DML | 18 CSTs IBS/CBS |
| `015_carga_classificacoes_tributarias.sql` | DML | 110 classificações cClassTrib |
| `016_carga_servicos.sql` | DML | 108 serviços raiz |
| `017_carga_servicos_detalhes.sql` | DML | 892 subitens de serviço |
| `018_carga_meios_pagamento.sql` | DML | 23 meios pagamento + 28 bandeiras + 39 veículos |
| `001_create_schemas_documentado.sql` | DDL (ref) | Cópia documentada do schema completo |
| `002_add_data_source_documentado.sql` | DML (ref) | Cópia documentada de todas as cargas |

---

## 001_tabelas_base.sql — Objetos Utilitários e Base Geográfica

**Finalidade:** Primeiro migration a ser executado. Cria a infraestrutura de normalização textual e as três tabelas geográficas fundamentais do projeto.

**Dependências de execução:** Nenhuma. Deve rodar antes de todos os demais.

---

### Função: `fn_normalizar_texto(p_texto TEXT)`

```sql
CREATE FUNCTION fn_normalizar_texto(p_texto TEXT)
RETURNS TEXT CHARSET utf8mb4 COLLATE utf8mb4_general_ci
DETERMINISTIC
```

**O que faz:**  
Sanitiza um texto para comparação case-insensitive e accent-insensitive, sem depender de collation específica. Usada em triggers e views para popular colunas `*_normalizada`.

**Pipeline interno:**
1. Retorna `NULL` imediatamente se a entrada for `NULL`
2. `UPPER(TRIM(p_texto))` — maiúsculas e remove espaços externos
3. 20 chamadas `REPLACE()` — remove diacríticos: Á→A, À→A, Ã→A, Â→A, Ä→A, É→E, È→E, Ê→E, Ë→E, Í→I, Ì→I, Î→I, Ï→I, Ó→O, Ò→O, Õ→O, Ô→O, Ö→O, Ú→U, Ù→U, Û→U, Ü→U, Ç→C
4. `REGEXP_REPLACE(..., '[[:space:]]+', ' ')` — compacta espaços múltiplos
5. Remove caracteres espúrios: `~`, `]`, `[`, `'`, `` ` ``, `´`, `"`
6. `REGEXP_REPLACE(..., '[^A-Z0-9 ]', '')` — elimina qualquer caractere fora de letras, números e espaço
7. Segunda passagem `REGEXP_REPLACE` para compactar espaços gerados nas etapas anteriores
8. `TRIM()` final

**Resultado:** texto em maiúsculas, sem acentos, sem pontuação, sem espaços duplos.

**Marcada como `DETERMINISTIC`** — o otimizador pode cachear chamadas com o mesmo argumento.

---

### Procedure: `sp_recarregar_normalizacao_servicos()`

```sql
CREATE PROCEDURE sp_recarregar_normalizacao_servicos()
```

**O que faz:**  
Procedure de manutenção idempotente. Recalcula `descricao_normalizada` nas tabelas `nfe_flow_servicos` e `nfe_flow_servicos_detalhes` somente para linhas onde o valor está vazio ou diverge do recálculo atual.

**Quando usar:**  
- Após cargas em massa via `LOAD DATA` ou `INSERT` com triggers desativadas
- Após modificação na lógica de `fn_normalizar_texto`
- Como passo de validação pós-deploy

**Retorno:** `SELECT` com três colunas:

| Coluna | Tipo | Descrição |
|---|---|---|
| `servicos_atualizados` | INT | Linhas atualizadas em `nfe_flow_servicos` |
| `detalhes_atualizados` | INT | Linhas atualizadas em `nfe_flow_servicos_detalhes` |
| `executado_em` | DATETIME | Timestamp de execução (`NOW()`) |

**Critério de atualização (ambas as tabelas):**
```sql
WHERE descricao_normalizada = ''
   OR descricao_normalizada <> fn_normalizar_texto(descricao)
```
Linhas já corretas não são tocadas — seguro executar múltiplas vezes.

---

### Tabela: `nfe_flow_estados`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_estados (
  id            INT UNSIGNED    PK AUTO_INCREMENT,
  codigo_ibge   CHAR(2)         NOT NULL,   -- cUF do XML
  sigla         CHAR(2)         UNIQUE NOT NULL,
  nome          VARCHAR(60)     NOT NULL,
  nome_normalizado VARCHAR(200) NULL        -- mantido por trigger
)
```

**Triggers:**

`trg_bi_nfe_flow_estados_nome_normalizado` — `BEFORE INSERT`
```sql
SET NEW.nome_normalizado = fn_normalizar_texto(NEW.nome);
```
Preenche o normalizado em qualquer novo registro.

`trg_bu_nfe_flow_estados_nome_normalizado` — `BEFORE UPDATE`
```sql
IF NEW.nome <> OLD.nome
   OR NEW.nome_normalizado IS NULL
   OR NEW.nome_normalizado = ''
THEN
    SET NEW.nome_normalizado = fn_normalizar_texto(NEW.nome);
END IF;
```
Recalcula somente quando `nome` muda ou o normalizado está inconsistente. Evita custo da função quando o UPDATE toca outras colunas.

---

### Tabela: `nfe_flow_municipios`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_municipios (
  id             INT UNSIGNED   PK AUTO_INCREMENT,
  estado_id      INT UNSIGNED   NOT NULL,   -- FK → nfe_flow_estados
  codigo_ibge    CHAR(7)        NOT NULL,   -- cMun do XML
  nome           VARCHAR(60)    NOT NULL,
  nome_normalizado VARCHAR(200) NULL
)
```

**Triggers:** Mesmo padrão de `nfe_flow_estados` — `trg_bi_*` e `trg_bu_*` com verificação condicional no UPDATE.

---

### Tabela: `nfe_flow_paises`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_paises (
  id                   INT UNSIGNED PK AUTO_INCREMENT,
  codigo_pais          CHAR(4)      NOT NULL,   -- cPais BACEN
  nome                 VARCHAR(100) NOT NULL,
  nome_normalizado     TEXT         NULL,
  situacao             VARCHAR(30)  NULL,
  data_inicio_vigencia DATE         NULL,
  data_fim_vigencia    DATE         NULL
)
```

**Sem triggers neste arquivo.** A normalização é carregada diretamente via INSERT no migration `009_carga_paises.sql`.

---

## 002_tabelas_fiscais.sql — Cadastros Fiscais Operacionais

**Finalidade:** Cria as tabelas que compõem os campos normativos do XML NF-e/NFC-e.

**Dependências de execução:** `001_tabelas_base.sql` (FK `estado_id` em `nfe_flow_fcp_uf`).

---

### Tabela: `nfe_flow_bandeiras_cartao`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_bandeiras_cartao (
  id                   INT UNSIGNED PK AUTO_INCREMENT,
  codigo_bandeira      CHAR(2)      NOT NULL,   -- tBand do XML
  nome                 VARCHAR(80)  NOT NULL,
  data_inicio_vigencia DATE         NOT NULL,
  data_fim_vigencia    DATE         NULL
)
```
Sem triggers — campos puramente descritivos sem coluna normalizada.

---

### Tabela: `nfe_flow_cfop`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_cfop (
  id                   INT UNSIGNED      PK AUTO_INCREMENT,
  codigo_cfop          CHAR(4)           NOT NULL,
  data_inicio_vigencia DATE              NOT NULL,
  data_fim_vigencia    DATE              NULL,
  ind_nfe              TINYINT UNSIGNED  NOT NULL,
  ind_comunicacao      TINYINT UNSIGNED  NOT NULL,
  ind_transporte       TINYINT UNSIGNED  NOT NULL,
  ind_devolucao        TINYINT UNSIGNED  NOT NULL,
  ind_retorno          TINYINT UNSIGNED  NOT NULL,
  ind_anulacao         TINYINT UNSIGNED  NOT NULL,
  ind_remessa          TINYINT UNSIGNED  NOT NULL,
  ind_combustivel      TINYINT UNSIGNED  NOT NULL   -- 0/1/2
)
```
Sem triggers — dados puramente numéricos/codificados.

---

### Tabela: `nfe_flow_cfop_detalhes`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_cfop_detalhes (
  id                      INT UNSIGNED   PK AUTO_INCREMENT,
  cfop_id                 INT UNSIGNED   UNIQUE NOT NULL,  -- FK → nfe_flow_cfop
  descricao               VARCHAR(500)   NOT NULL,
  descricao_normalizada   VARCHAR(500)   NULL,
  aplicacao               TEXT           NULL,
  aplicacao_normalizada   VARCHAR(1000)  NULL,
  fonte_nome              VARCHAR(120)   NOT NULL,
  fonte_url               VARCHAR(500)   NOT NULL,
  data_referencia         DATE           NULL
)
```

**Triggers:**

`trg_bi_nfe_flow_cfop_detalhes_normalizado` — `BEFORE INSERT`
```sql
SET NEW.descricao_normalizada = fn_normalizar_texto(NEW.descricao);
SET NEW.aplicacao_normalizada = fn_normalizar_texto(NEW.aplicacao);
```

`trg_bu_nfe_flow_cfop_detalhes_normalizado` — `BEFORE UPDATE`
```sql
IF NEW.descricao <> OLD.descricao OR normalizado IS NULL OR = ''
    SET NEW.descricao_normalizada = fn_normalizar_texto(NEW.descricao);

IF NEW.aplicacao <> OLD.aplicacao OR normalizado IS NULL OR = ''
    SET NEW.aplicacao_normalizada = fn_normalizar_texto(NEW.aplicacao);
```
Dois campos normalizados independentes — cada um só é recalculado se sua coluna-fonte mudar.

---

### Tabela: `nfe_flow_fcp_uf`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_fcp_uf (
  id         INT UNSIGNED   PK AUTO_INCREMENT,
  estado_id  INT UNSIGNED   UNIQUE NOT NULL,   -- FK → nfe_flow_estados
  observacao VARCHAR(255)   NULL
)
ENGINE=InnoDB
```
Sem triggers — `observacao` é texto livre, não buscável por normalização.

---

### Tabela: `nfe_flow_fcp_uf_aliquotas`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_fcp_uf_aliquotas (
  id              INT UNSIGNED    PK AUTO_INCREMENT,
  fcp_uf_id       INT UNSIGNED    NOT NULL,            -- FK → nfe_flow_fcp_uf
  ordem_aliquota  TINYINT UNSIGNED NOT NULL,           -- 1, 2 ou 3
  texto_aliquota  VARCHAR(20)     NOT NULL,
  UNIQUE (fcp_uf_id, ordem_aliquota)
)
```
Sem triggers.

---

### Tabela: `nfe_flow_meios_pagamento`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_meios_pagamento (
  id                       INT UNSIGNED  PK AUTO_INCREMENT,
  codigo_meio_pagamento    CHAR(2)       NOT NULL,   -- tPag do XML
  descricao                VARCHAR(120)  NOT NULL,
  descricao_normalizada    TEXT          NULL,
  data_inicio_vigencia     DATE          NOT NULL,
  data_fim_vigencia        DATE          NULL,
  observacoes              TEXT          NULL,
  observacoes_normalizadas TEXT          NULL
)
```

**Triggers:**

`trg_bi_nfe_flow_meios_pagamento_normalizado` — `BEFORE INSERT`
```sql
SET NEW.descricao_normalizada    = fn_normalizar_texto(NEW.descricao);
SET NEW.observacoes_normalizadas = fn_normalizar_texto(NEW.observacoes);
```

`trg_bu_nfe_flow_meios_pagamento_normalizado` — `BEFORE UPDATE`  
Mesma lógica de verificação condicional independente para cada campo.

---

### Tabela: `nfe_flow_unidades_tributaveis_exportacao`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_unidades_tributaveis_exportacao (
  id        INT UNSIGNED  PK AUTO_INCREMENT,
  sigla     VARCHAR(6)    NOT NULL,   -- uTrib
  descricao VARCHAR(60)   NOT NULL
)
```
Sem triggers — domínio pequeno e estático.

---

### Tabela: `nfe_flow_veiculo`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_veiculo (
  id                                    INT UNSIGNED  PK AUTO_INCREMENT,
  codigo_tipo_veiculo                   CHAR(2)       NOT NULL,   -- tpVeic
  descricao_tipo_veiculo                VARCHAR(60)   NULL,
  descricao_tipo_veiculo_normalizada    TEXT          NULL,
  codigo_especie_veiculo                CHAR(1)       NOT NULL,   -- EspVeic
  descricao_especie_veiculo             VARCHAR(60)   NULL,
  descricao_especie_veiculo_normalizada TEXT          NULL,
  data_inicio_vigencia                  DATE          NOT NULL,
  data_fim_vigencia                     DATE          NULL
)
```

**Triggers:**

`trg_bi_nfe_flow_veiculo_descricao_normalizada` — `BEFORE INSERT`
```sql
SET NEW.descricao_tipo_veiculo_normalizada    = fn_normalizar_texto(NEW.descricao_tipo_veiculo);
SET NEW.descricao_especie_veiculo_normalizada = fn_normalizar_texto(NEW.descricao_especie_veiculo);
```

`trg_bu_nfe_flow_veiculo_descricao_normalizada` — `BEFORE UPDATE`  
Verifica tipo e espécie independentemente antes de recalcular.

---

## 003_tabelas_classificacao.sql — Classificação IBS/CBS

**Finalidade:** Cria as tabelas da reforma tributária (LC 214/2025).

**Dependências de execução:** `001_tabelas_base.sql`.

---

### Tabela: `nfe_flow_cst_ibs_cbs`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_cst_ibs_cbs (
  cst_ibs_cbs                     CHAR(3)           PK,   -- código literal
  descricao_cst_ibs_cbs           VARCHAR(255)      NOT NULL,
  descricao_cst_ibs_cbs_normalizada TEXT            NULL,
  ind_gibscbs          TINYINT UNSIGNED NOT NULL,
  ind_gibscbsmono      TINYINT UNSIGNED NOT NULL,
  ind_gred             TINYINT UNSIGNED NOT NULL,
  ind_gdif             TINYINT UNSIGNED NOT NULL,
  ind_gtransfcred      TINYINT UNSIGNED NOT NULL,
  ind_gcredpresibszfm  TINYINT UNSIGNED NOT NULL,
  ind_gajustecompet    TINYINT UNSIGNED NOT NULL,
  ind_redutorbc        TINYINT UNSIGNED NOT NULL
)
```
PK é o próprio código CST (não há surrogate key).

**Triggers:**

`trg_bi_nfe_flow_cst_ibs_cbs_normalizado` — `BEFORE INSERT`
```sql
SET NEW.descricao_cst_ibs_cbs_normalizada = fn_normalizar_texto(NEW.descricao_cst_ibs_cbs);
```

`trg_bu_nfe_flow_cst_ibs_cbs_normalizado` — `BEFORE UPDATE`  
Verificação condicional padrão.

---

### Tabela: `nfe_flow_classificacao_ibs_cbs`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_classificacao_ibs_cbs (
  cclasstrib                      CHAR(6)   PK,   -- código literal
  cst_ibs_cbs                     CHAR(3)   NOT NULL,
  descricao_cst_ibs_cbs           VARCHAR(255) NOT NULL,
  nome_cclasstrib                 VARCHAR(255) NOT NULL,
  descricao_cclasstrib            TEXT      NOT NULL,
  descricao_cclasstrib_normalizada TEXT     NULL,
  lc                              LONGTEXT  NULL,
  redacao_lc_214_25               VARCHAR(255) NULL,
  tipo_aliquota                   VARCHAR(80)  NOT NULL,
  predibs                         VARCHAR(10)  NOT NULL,
  predcbs                         VARCHAR(10)  NOT NULL,
  -- 7 indicadores ind_g* TINYINT UNSIGNED NOT NULL
  dinivig                         CHAR(10)  NOT NULL,   -- dd/mm/yyyy literal
  dfimvig                         CHAR(10)  NULL,
  dataatualizacao                 CHAR(10)  NOT NULL,
  -- 15 indicadores ind* por tipo de documento
  anexo                           VARCHAR(20) NULL,
  link                            VARCHAR(500) NULL
)
ENGINE=InnoDB
COMMENT='Tabela literal de cClassTrib reconstruída do zero.'
```

**Decisão de design:** Datas de vigência e atualização são armazenadas como `CHAR(10)` no formato `dd/mm/yyyy` preservando o literal da tabela oficial da SEFAZ. A conversão é responsabilidade da camada de aplicação.

**Triggers:**

`trg_bi_nfe_flow_classificacao_ibs_cbs_normalizado` — `BEFORE INSERT`
```sql
SET NEW.descricao_cclasstrib_normalizada = fn_normalizar_texto(NEW.descricao_cclasstrib);
```

`trg_bu_nfe_flow_classificacao_ibs_cbs_normalizado` — `BEFORE UPDATE`  
Verificação condicional padrão.

---

## 004_tabelas_servicos.sql — Catálogo de Serviços

**Finalidade:** Cria o catálogo de serviços LC 116/2003 em dois níveis.

**Dependências de execução:** `001_tabelas_base.sql`.

---

### Tabela: `nfe_flow_servicos`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_servicos (
  id                    INT UNSIGNED   PK AUTO_INCREMENT,
  codigo_servico        CHAR(5)        NOT NULL,   -- cListServ formato NN.NN
  descricao             VARCHAR(600)   NOT NULL,
  descricao_normalizada VARCHAR(600)   NOT NULL DEFAULT ''
)
```

**Atenção — triggers duplicados:** Esta tabela possui **dois pares de triggers** para o mesmo evento, o que é válido em MariaDB/MySQL mas gera execução sequencial. Ambos fazem a mesma operação; o par canônico é o mais recente:

| Trigger | Evento | Comportamento |
|---|---|---|
| `trg_bi_nfe_flow_servicos_descricao_normalizada` | BEFORE INSERT | Normaliza sempre (legado) |
| `trg_nfe_flow_servicos_bi` | BEFORE INSERT | Normaliza sempre (canônico) |
| `trg_bu_nfe_flow_servicos_descricao_normalizada` | BEFORE UPDATE | Verifica `IS NULL OR = ''` (legado) |
| `trg_nfe_flow_servicos_bu` | BEFORE UPDATE | Verifica apenas `NEW.descricao <> OLD.descricao` (canônico, mais eficiente) |

> **Recomendação:** Remover os triggers legados (`trg_bi_*` e `trg_bu_nfe_flow_servicos_descricao_normalizada`) em refatoração futura para evitar dupla execução de `fn_normalizar_texto`.

---

### Tabela: `nfe_flow_servicos_detalhes`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_servicos_detalhes (
  id                      INT UNSIGNED  PK AUTO_INCREMENT,
  servico_id              INT UNSIGNED  NOT NULL,   -- FK → nfe_flow_servicos
  codigo_servico_detalhe  CHAR(8)       NOT NULL,   -- formato NN.NN.NN
  descricao               TEXT          NOT NULL,
  descricao_normalizada   TEXT          NOT NULL DEFAULT '',
  fonte_arquivo           VARCHAR(120)  NOT NULL
)
```

**Triggers:** Mesmo padrão duplo de `nfe_flow_servicos` — dois pares de triggers com a mesma observação de limpeza futura.

---

## 005_tabelas_produtos.sql — Classificação de Produtos

**Finalidade:** Cria as tabelas NCM e produtos ANP.

**Dependências de execução:** `002_tabelas_fiscais.sql` (FK `unidade_tributavel_exportacao_id` em `nfe_flow_ncm`).

---

### Tabela: `nfe_flow_ncm`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_ncm (
  id                               INT UNSIGNED  PK AUTO_INCREMENT,
  unidade_tributavel_exportacao_id INT UNSIGNED  NOT NULL,   -- FK
  codigo_ncm                       CHAR(8)       NOT NULL,
  data_inicio_vigencia             DATE          NOT NULL,
  data_fim_vigencia                DATE          NULL
)
```
Sem triggers.

---

### Tabela: `nfe_flow_ncm_detalhes`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_ncm_detalhes (
  id                  INT UNSIGNED   PK AUTO_INCREMENT,
  codigo_pai_formatado VARCHAR(10)   NULL,
  codigo_formatado    VARCHAR(10)    UNIQUE NOT NULL,
  codigo_limpo        VARCHAR(8)     NOT NULL,
  codigo_ncm          CHAR(8)        NULL,
  descricao           VARCHAR(1000)  NOT NULL,
  descricao_normalizada VARCHAR(1000) NULL,
  ipi                 VARCHAR(10)    NULL,
  nivel_codigo        TINYINT UNSIGNED NOT NULL,
  eh_item_final       TINYINT(1) UNSIGNED NOT NULL,
  ordem               INT UNSIGNED   UNIQUE NOT NULL,
  fonte_arquivo       VARCHAR(120)   NOT NULL
)
ENGINE=InnoDB
COMMENT='Detalhamento hierárquico do anexo de NCM com descrição e IPI.'
```

**Triggers:**

`trg_bi_nfe_flow_ncm_detalhes_normalizada` — `BEFORE INSERT`
```sql
SET NEW.descricao_normalizada = fn_normalizar_texto(NEW.descricao);
```

`trg_bu_nfe_flow_ncm_detalhes_normalizada` — `BEFORE UPDATE`  
Verificação condicional: recalcula se `descricao` mudou ou normalizado está vazio/NULL.

---

### Tabela: `nfe_flow_produtos_anp`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_produtos_anp (
  id                   INT UNSIGNED  PK AUTO_INCREMENT,
  codigo_anp           CHAR(9)       NOT NULL,   -- cProdANP
  nome                 VARCHAR(120)  NOT NULL,
  nome_normalizado     TEXT          NULL,
  descricao            VARCHAR(500)  NOT NULL,
  descricao_normalizada TEXT         NULL
)
```

**Triggers:**

`trg_bi_nfe_flow_produtos_anp_normalizado` — `BEFORE INSERT`
```sql
SET NEW.nome_normalizado      = fn_normalizar_texto(NEW.nome);
SET NEW.descricao_normalizada = fn_normalizar_texto(NEW.descricao);
```

`trg_bu_nfe_flow_produtos_anp_normalizado` — `BEFORE UPDATE`  
Dois campos normalizados com verificação condicional independente.

---

### Tabela: `nfe_flow_produtos_anp_correlacoes`

**DDL resumido:**
```sql
CREATE TABLE nfe_flow_produtos_anp_correlacoes (
  id                INT UNSIGNED  PK AUTO_INCREMENT,
  produto_anp_id    INT UNSIGNED  NOT NULL,   -- FK → nfe_flow_produtos_anp
  codigo_anterior   CHAR(9)       NOT NULL,
  observacao_origem VARCHAR(100)  NULL,
  UNIQUE (produto_anp_id, codigo_anterior)
)
```
Sem triggers.

---

## 006_indices_e_constraints.sql — Views e Chaves Estrangeiras

**Finalidade:** Finaliza o schema criando todas as views consumíveis pela API e adicionando as chaves estrangeiras.

**Dependências de execução:** Todas as migrations 001–005.

**Estratégia de criação das views:** O arquivo usa o padrão de "stand-in" gerado por ferramentas como phpMyAdmin:
1. `DROP VIEW IF EXISTS <view>`
2. `CREATE TABLE IF NOT EXISTS <view> (...)` — cria uma tabela temporária com a estrutura da view
3. `DROP TABLE IF EXISTS <view>` — remove a tabela temporária
4. `CREATE OR REPLACE VIEW <view> AS SELECT ...` — cria a view real

Isso evita erros de restauração quando referências ainda não estão disponíveis.

---

### Views

#### `vw_nfe_flow_estados`

```sql
SELECT e.codigo_ibge, e.sigla AS uf, e.nome AS nome_uf, e.nome_normalizado AS nome_uf_normalizado
FROM nfe_flow_estados AS e
```
**Propósito:** Projeção simplificada da tabela de estados, renomeando colunas para o padrão de resposta da API.

---

#### `vw_nfe_flow_municipios`

```sql
SELECT m.nome           AS nome_cidade,
       fn_normalizar_texto(m.nome) AS nome_cidade_normalizado,
       m.codigo_ibge    AS ibge_cidade,
       e.sigla          AS uf,
       e.nome           AS nome_uf,
       fn_normalizar_texto(e.nome) AS nome_uf_normalizado,
       e.codigo_ibge    AS ibge_uf,
       'BRASIL'         AS pais,
       '1058'           AS ibge_pais
FROM nfe_flow_municipios m
JOIN nfe_flow_estados e ON e.id = m.estado_id
```
**Propósito:** Entrega município já enriquecido com dados da UF e com país/código IBGE do Brasil fixos. Chama `fn_normalizar_texto()` diretamente em vez de usar colunas pré-computadas — compatível com linhas inseridas fora dos triggers.

---

#### `vw_nfe_flow_paises`

```sql
SELECT p.codigo_pais AS pais_ibge, p.nome AS pais, p.nome_normalizado AS pais_normalizado,
       p.situacao, p.data_inicio_vigencia AS inicio_vigencia, p.data_fim_vigencia AS fim_vigencia
FROM nfe_flow_paises AS p
```
**Propósito:** Projeção direta de países com aliases de API.

---

#### `vw_nfe_flow_cfop`

```sql
SELECT c.codigo_cfop AS cfop,
       d.descricao AS `descrição`, d.descricao_normalizada,
       d.aplicacao AS `aplicação`, d.aplicacao_normalizada,
       c.data_inicio_vigencia AS inicio_vigencia, c.data_fim_vigencia AS fim_vigencia,
       c.ind_nfe AS indnfe, c.ind_comunicacao AS indcomunica, ...
FROM nfe_flow_cfop c
LEFT JOIN nfe_flow_cfop_detalhes d ON d.cfop_id = c.id
```
**Propósito:** Une código CFOP e seu detalhamento textual em uma linha. Usa `LEFT JOIN` — CFOPs sem detalhamento ainda aparecem na view.

> **Atenção:** As colunas `descrição` e `aplicação` contêm acento no alias. Dependendo do cliente de banco de dados, pode ser necessário referenciar com backtick ou mudar o alias.

---

#### `vw_nfe_flow_meios_pagamento`

```sql
SELECT m.codigo_meio_pagamento AS codigo, m.descricao, m.descricao_normalizada,
       m.observacoes AS observacao, m.observacoes_normalizadas AS observacao_normalizada,
       m.data_inicio_vigencia AS inicio_vigencia, m.data_fim_vigencia AS fim_vigencia
FROM nfe_flow_meios_pagamento AS m
```
**Propósito:** Projeção direta com aliases padronizados.

---

#### `vw_nfe_flow_bandeiras_cartao`

```sql
SELECT b.codigo_bandeira AS codigo, b.nome AS operadora,
       b.data_inicio_vigencia AS inicio_vigencia, b.data_fim_vigencia AS fim_vigencia
FROM nfe_flow_bandeiras_cartao AS b
```

---

#### `vw_nfe_flow_alicota_fcp`

```sql
SELECT e.nome AS estado, e.nome_normalizado AS estado_normalizado,
       e.codigo_ibge, e.sigla AS uf,
       MAX(CASE WHEN a.ordem_aliquota = 1 THEN a.texto_aliquota END) AS aliquota_1,
       MAX(CASE WHEN a.ordem_aliquota = 2 THEN a.texto_aliquota END) AS aliquota_2,
       MAX(CASE WHEN a.ordem_aliquota = 3 THEN a.texto_aliquota END) AS aliquota_3,
       MAX(f.observacao) AS obs
FROM nfe_flow_fcp_uf f
JOIN nfe_flow_estados e ON e.id = f.estado_id
LEFT JOIN nfe_flow_fcp_uf_aliquotas a ON a.fcp_uf_id = f.id
GROUP BY e.nome_normalizado, e.nome, e.sigla, e.codigo_ibge
```
**Propósito:** Pivoteia as alíquotas FCP (que estão em linhas na tabela `nfe_flow_fcp_uf_aliquotas`) em colunas `aliquota_1`, `aliquota_2`, `aliquota_3`. A técnica de `MAX(CASE WHEN)` é o padrão de pivot condicional em MySQL/MariaDB — funciona porque cada UF tem no máximo uma alíquota por posição.

---

#### `vw_nfe_flow_ncm`

```sql
SELECT n.codigo_ncm AS ncm,
       u.descricao AS unidade_tributavel_exportacao_descricao,
       u.sigla     AS unidade_tributavel_exportacao_sigla,
       n.data_inicio_vigencia AS inicio_vigencia,
       n.data_fim_vigencia    AS fim_vigencia
FROM nfe_flow_ncm n
JOIN nfe_flow_unidades_tributaveis_exportacao u ON u.id = n.unidade_tributavel_exportacao_id
```
**Propósito:** NCM com sua unidade tributável resolvida.

---

#### `vw_nfe_flow_codigo_produto_anp`

```sql
SELECT p.codigo_anp AS codigo, p.nome AS produto, p.nome_normalizado AS produto_normalizado,
       p.descricao, p.descricao_normalizada,
       IFNULL(GROUP_CONCAT(c.codigo_anterior ORDER BY c.codigo_anterior SEPARATOR ', '),
              'Sem correlacao') AS correlacao_anterior,
       CASE WHEN COUNT(c.codigo_anterior) = 0
            THEN 'SEM CORRELACAO'
            ELSE GROUP_CONCAT(c.codigo_anterior ORDER BY c.codigo_anterior SEPARATOR ' ')
       END AS correlacao_anterior_normalizada
FROM nfe_flow_produtos_anp p
LEFT JOIN nfe_flow_produtos_anp_correlacoes c ON c.produto_anp_id = p.id
GROUP BY p.id, p.codigo_anp, p.nome, p.nome_normalizado, p.descricao, p.descricao_normalizada
```
**Propósito:** Produto ANP com todos os códigos anteriores correlacionados agregados em uma string CSV. Se não houver correlações, retorna `'Sem correlacao'` em `correlacao_anterior` e `'SEM CORRELACAO'` na versão normalizada.

---

#### `vw_nfe_flow_veiculo`

```sql
SELECT v.codigo_tipo_veiculo AS tipo,
       v.descricao_tipo_veiculo AS tipo_descricao,
       v.descricao_tipo_veiculo_normalizada AS tipo_descricao_normalizada,
       v.codigo_especie_veiculo AS especie,
       v.descricao_especie_veiculo AS especie_descricao,
       v.descricao_especie_veiculo_normalizada AS especie_descricao_normalizada,
       v.data_inicio_vigencia AS inicio_vigencia, v.data_fim_vigencia AS fim_vigencia
FROM nfe_flow_veiculo AS v
```

---

#### `vw_nfe_flow_ibs_cbs`

```sql
SELECT c.cst_ibs_cbs, t.descricao_cst_ibs_cbs AS descricao_cst, ...,
       c.cclasstrib AS codigo_classificacao, c.nome_cclasstrib AS nome_classificacao,
       c.descricao_cclasstrib AS descricao_classificacao, ...,
       c.lc AS texto_lc, c.redacao_lc_214_25 AS redacao_lc,
       c.tipo_aliquota, c.predibs AS perc_reducao_ibs, c.predcbs AS perc_reducao_cbs,
       -- indicadores renomeados para nomes funcionais
       c.ind_gtribregular   AS exige_grupo_trib_regular,
       c.ind_gcredpresoper  AS permite_grupo_cred_pres_oper,
       ...
       -- indicadores do CST macro
       t.ind_gibscbs        AS exige_grupo_padrao,
       t.ind_gibscbsmono    AS exige_grupo_monofasico,
       ...
FROM nfe_flow_classificacao_ibs_cbs c
LEFT JOIN nfe_flow_cst_ibs_cbs t ON t.cst_ibs_cbs = c.cst_ibs_cbs
```
**Propósito:** View principal de IBS/CBS. Une `cClassTrib` com seu CST macro e renomeia todos os indicadores `ind_g*` para nomes descritivos em português. Entrega uma linha por classificação tributária com toda a informação necessária para validação de documento fiscal.

---

#### `vw_nfe_flow_captu_servico`

```sql
SELECT c.codigo_servico AS codigo_raiz, c.descricao, c.descricao_normalizada
FROM nfe_flow_servicos AS c
```
**Propósito:** Exposição do catálogo raiz de serviços para lookup/autocomplete.

---

#### `vw_nfe_flow_detalhe_servico`

```sql
SELECT d.codigo_servico_detalhe AS codigo_servico, d.descricao, d.descricao_normalizada
FROM nfe_flow_servicos_detalhes AS d
```
**Propósito:** Detalhes de serviço isolados para lookup direto por código detalhado.

---

#### `vw_nfe_flow_servico`

```sql
SELECT c.codigo_servico AS codigo_raiz, c.descricao AS descricao_raiz,
       fn_normalizar_texto(c.descricao) AS descricao_raiz_normalizada,
       d.codigo_servico_detalhe AS codigo_servico,
       d.descricao AS descricao_servico,
       fn_normalizar_texto(d.descricao) AS descricao_servico_normalizada
FROM nfe_flow_servicos c
LEFT JOIN nfe_flow_servicos_detalhes d ON d.servico_id = c.id
```
**Propósito:** Serviço raiz + detalhes em uma linha por subitem. Chama `fn_normalizar_texto()` diretamente nas colunas — não usa as colunas pré-computadas das tabelas.

---

#### `vw_nfe_flow_servico_detalhe`

```sql
SELECT c.codigo_servico AS codigo_raiz, c.descricao AS descricao_raiz,
       c.descricao_normalizada AS descricao_raiz_normalizada,
       d.codigo_servico_detalhe AS codigo_servico,
       d.descricao AS descricao_servico,
       d.descricao_normalizada AS descricao_servico_normalizada
FROM nfe_flow_servicos c
LEFT JOIN nfe_flow_servicos_detalhes d ON d.servico_id = c.id
```
**Propósito:** Idêntica em estrutura a `vw_nfe_flow_servico`, mas usa as colunas pré-computadas `descricao_normalizada` das tabelas em vez de recalcular com a função. Mais eficiente para leituras frequentes.

> **Atenção — duplicidade:** `vw_nfe_flow_servico` e `vw_nfe_flow_servico_detalhe` retornam o mesmo conjunto de dados com a única diferença sendo a origem do campo normalizado (função × coluna). Avaliar consolidação futura.

---

#### `vw_nfe_flow_servico_detalhes`

```sql
SELECT c.codigo_servico AS codigo,
       CONCAT('[',
         COALESCE(
           GROUP_CONCAT(
             CASE WHEN d.servico_id IS NOT NULL
               THEN JSON_OBJECT('codigo', d.codigo_servico_detalhe,
                                'descricao', d.descricao,
                                'normalizado', d.descricao_normalizada)
             END
             ORDER BY d.codigo_servico_detalhe SEPARATOR ','
           ), ''),
       ']') AS detalhes
FROM nfe_flow_servicos c
LEFT JOIN nfe_flow_servicos_detalhes d ON d.servico_id = c.id
GROUP BY c.id, c.codigo_servico, c.descricao
```
**Propósito:** Retorna uma linha por serviço raiz com o campo `detalhes` contendo um array JSON com todos os subitens. O JSON é construído manualmente via `CONCAT + GROUP_CONCAT + JSON_OBJECT` — compatível com MariaDB 10.4 que não possui `JSON_ARRAYAGG`.

**Formato do campo `detalhes`:**
```json
[
  {"codigo": "01.01.01", "descricao": "análise de sistemas,", "normalizado": "ANALISE DE SISTEMAS"},
  {"codigo": "01.01.02", "descricao": "desenvolvimento de sistemas", "normalizado": "DESENVOLVIMENTO DE SISTEMAS"}
]
```
Se o serviço não tiver subitens, retorna `[]`.

---

### Chaves Estrangeiras

Adicionadas via `ALTER TABLE` após a criação de todas as tabelas:

| Constraint | Tabela Filha | Coluna | Tabela Pai | Coluna |
|---|---|---|---|---|
| `fk_nfe_flow_cfop_detalhes_cfop` | `nfe_flow_cfop_detalhes` | `cfop_id` | `nfe_flow_cfop` | `id` |
| `fk_nfe_flow_fcp_uf_estado` | `nfe_flow_fcp_uf` | `estado_id` | `nfe_flow_estados` | `id` |
| `fk_nfe_flow_fcp_uf_aliquotas_fcp_uf` | `nfe_flow_fcp_uf_aliquotas` | `fcp_uf_id` | `nfe_flow_fcp_uf` | `id` |
| `fk_nfe_flow_municipios_estado` | `nfe_flow_municipios` | `estado_id` | `nfe_flow_estados` | `id` |
| `fk_nfe_flow_ncm_unidade_tributavel_exportacao` | `nfe_flow_ncm` | `unidade_tributavel_exportacao_id` | `nfe_flow_unidades_tributaveis_exportacao` | `id` |
| `fk_nfe_flow_produtos_anp_correlacoes_produto` | `nfe_flow_produtos_anp_correlacoes` | `produto_anp_id` | `nfe_flow_produtos_anp` | `id` |
| `fk_nfe_flow_servicos_detalhes_servico` | `nfe_flow_servicos_detalhes` | `servico_id` | `nfe_flow_servicos` | `id` |

> **Nota:** As FKs são declaradas em arquivo separado (006) e não inline nas `CREATE TABLE` dos migrations 001–005. Isso permite restaurar tabelas filhas antes das pais sem erro de referência, seguindo o padrão de dumps phpMyAdmin com `FOREIGN_KEY_CHECKS = 0` durante a criação das tabelas.

---

## Migrations de Carga (007–018)

Padrão comum a todos os arquivos de carga:

```sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
START TRANSACTION;
-- INSERTs...
COMMIT;
```

`FOREIGN_KEY_CHECKS = 1` está ativo — a ordem de execução importa.

---

### 007 — `007_carga_estados.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_estados` | 27 | 1 |
| `nfe_flow_fcp_uf` | 27 | 1 |
| `nfe_flow_fcp_uf_aliquotas` | 30 | 1 |

Carrega as 27 UFs (26 estados + DF) com código IBGE e sigla. Em seguida, popula o FCP com uma linha por UF e as alíquotas correspondentes. Deve ser o primeiro arquivo de carga por ser dependência de `nfe_flow_municipios`.

---

### 008 — `008_carga_municipios.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_municipios` | 5.571 | 60 |

Carga completa da malha municipal brasileira. Dividida em 60 `INSERT` para respeitar limites de pacote de rede/memória. Depende de `007` (FK `estado_id`).

---

### 009 — `009_carga_paises.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_paises` | 264 | 5 |

Tabela `cPais_2` do BACEN. Inclui países com situação `NULL` (carga original sem status) e países com `situacao = 'INCLUÍDO'` (adicionados em versões posteriores da tabela BACEN). `data_fim_vigencia = NULL` indica vigência contínua.

---

### 010 — `010_carga_ncm.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_unidades_tributaveis_exportacao` | 13 | 1 |
| `nfe_flow_ncm` | 10.520 | 90 |

A unidade tributável é carregada **antes** do NCM neste mesmo arquivo porque o NCM depende dela por FK. Inclui 13 unidades (KG, G, L, UN, etc.) e 10.520 códigos NCM com vigências.

---

### 011 — `011_carga_ncm_detalhes.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_ncm_detalhes` | 7.485 | 2.558 |

Maior arquivo de carga em número de `INSERT`s (2.558). Carrega a hierarquia completa do anexo NCM/TIPI com capítulos, posições, subposições e itens finais. O alto número de INSERTs individuais reflete a granularidade dos dados da árvore.

---

### 012 — `012_carga_cest.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_produtos_anp` | 39 | 3 |
| `nfe_flow_produtos_anp_correlacoes` | 202 | 2 |

> **Nota de design:** O slot `012` foi originalmente reservado para CEST. Como o dump não inclui tabela própria de CEST, o slot foi reaproveitado para a carga de ANP e correlações ANP, preservando a sequência numérica dos arquivos.

---

### 013 — `013_carga_cfop.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_cfop` | 619 | 8 |
| `nfe_flow_cfop_detalhes` | 552 | 100 |

CFOPs com vigência a partir de `2006-01-01` (implantação da NF-e). Detalhamentos em arquivo separado por causa da FK `cfop_id` — a tabela principal é carregada primeiro dentro do mesmo arquivo.

---

### 014 — `014_carga_cst.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_cst_ibs_cbs` | 18 | 1 |

18 CSTs da reforma tributária IBS/CBS (LC 214/2025). Carregados antes das classificações (`015`) por serem referenciados na coluna `cst_ibs_cbs` de `nfe_flow_classificacao_ibs_cbs`.

---

### 015 — `015_carga_classificacoes_tributarias.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_classificacao_ibs_cbs` | 110 | 46 |

110 classificações `cClassTrib` com todos os indicadores, percentuais de redução, datas em formato literal e links para a LC 214/2025 no Planalto. Depende de `014`.

---

### 016 — `016_carga_servicos.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_servicos` | 108 | 10 |

108 itens raiz da Lista de Serviços LC 116/2003, no formato `NN.NN`. Separado dos detalhes (`017`) para respeitar a FK `servico_id`.

---

### 017 — `017_carga_servicos_detalhes.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_servicos_detalhes` | 892 | 32 |

892 subitens de serviço no formato `NN.NN.NN`. A coluna `fonte_arquivo` referencia `Codigo_Servico.pdf`, indicando que os dados foram extraídos do PDF oficial de código de serviço disponível no portal da NF-e.

---

### 018 — `018_carga_meios_pagamento.sql`

| Tabela | Linhas | INSERTs |
|---|---|---|
| `nfe_flow_meios_pagamento` | 23 | 2 |
| `nfe_flow_bandeiras_cartao` | 28 | 1 |
| `nfe_flow_veiculo` | 39 | 1 |

> **Nota de design:** Bandeiras de cartão e veículo não possuem slot sequencial próprio. Foram agrupados neste arquivo por serem domínios auxiliares de lookup sem dependência de FK entre si.

Inclui os novos meios de pagamento com `data_inicio_vigencia = '2026-05-04'` (PIX Automático, TEF Book Transfer), ainda não vigentes na data de geração do dump mas já presentes na tabela oficial de março/2026.

---

## Arquivos de Referência Documentada

### `001_create_schemas_documentado.sql`

Cópia integral do schema completo (DDL) com comentários expandidos por tabela. Estruturalmente idêntico ao conjunto de migrations 001–006 executados em sequência. Serve como documentação legível e referência para revisão sem necessidade de unir múltiplos arquivos.

### `002_add_data_source_documentado.sql`

Cópia integral das cargas de dados (DML) com cabeçalhos documentados por bloco. Estruturalmente idêntico ao conjunto de migrations 007–018. Serve como dump único para restore em ambientes que preferem um único arquivo de carga.

---

## Ordem de Execução Recomendada

```
001_tabelas_base.sql
002_tabelas_fiscais.sql
003_tabelas_classificacao.sql
004_tabelas_servicos.sql
005_tabelas_produtos.sql
006_indices_e_constraints.sql   ← views + FKs

007_carga_estados.sql           ← UFs + FCP (dependência base)
008_carga_municipios.sql        ← depende de 007
009_carga_paises.sql
010_carga_ncm.sql               ← uTrib + NCM
011_carga_ncm_detalhes.sql      ← depende de 010
012_carga_cest.sql              ← ANP + correlações
013_carga_cfop.sql              ← CFOP + detalhes
014_carga_cst.sql               ← CST IBS/CBS (dependência de 015)
015_carga_classificacoes_tributarias.sql
016_carga_servicos.sql          ← serviços raiz (dependência de 017)
017_carga_servicos_detalhes.sql
018_carga_meios_pagamento.sql
```

---

*Documentação gerada em 28/03/2026 — G4D Soluções e Desenvolvimento*