# Importação Prodata — Conta Angra

Documentação técnica da rotina que importa servidores municipais (CSV exportado da Prodata) como usuários do Conta Angra (`users`) e concede as permissões padrão `GERENCIADOR_DE_CONTA_ANGRA` (Conta Angra) e `GUEST` (Visitante).

- CSV de entrada: `temp/importacao-prodata-contaangra/dados.csv`
- Volume esperado: ~5.368 registros
- Seeder principal: `Database\Seeders\ImportacaoProdataContaAngraSeeder`
- Service de negócio: `App\Http\Services\Importacoes\ImportacaoProdataContaAngraService`
- Resource (DE-PARA): `App\Http\Resources\Importacoes\MapeamentoUnidadeResource`
- Canal de log: `importacao_prodata_contaangra` (rotação diária, 30 dias)

---

## 1. Estratégia utilizada

- **Seeder + Service + Resource**: o seeder orquestra; o Service contém a regra de persistência (create/update, dirty check, permissões); o Resource centraliza o DE-PARA das unidades.
- **Leitura via generator + chunking**: o CSV é lido com `fgetcsv()` por linha através de um `Generator`, agrupado em chunks de **500** registros. O processamento usa memória constante (não carrega o arquivo inteiro na RAM).
- **Transação por chunk**: cada chunk abre uma transação isolada (`DB::beginTransaction`). Erros pontuais por linha não interrompem o restante; um erro crítico (chunk inteiro) faz rollback apenas daquele chunk.
- **Idempotência por CPF**: o CPF é a chave primária da importação. Em cada execução o seeder localiza o usuário por CPF e aplica **dirty check** somente em `matriculation`, `structure_id` e `email` — campos divergentes do CSV. Demais atributos (senha, telefone, foto, permissões, configurações etc.) são **preservados**.
- **Sem updates redundantes**: se a 2ª execução não encontrar diferenças, o registro é classificado como `sincronizado` e nenhum `UPDATE` é executado.
- **DE-PARA em camadas** (ordem de precedência, a última sobrescreve):
  1. **`database/data/importacao-prodata-contaangra/de-para-unidades-predefinido.json`** — arquivo **versionado** com o texto **exato** da coluna UNIDADE do CSV em `mapa` → `structure_id`. Revise antes da importação em produção. Regere com o script abaixo quando o CSV ou o cadastro de `structures` mudar.
  2. **`storage/app/importacao-prodata-contaangra/de-para-unidades.json`** — ajustes locais / respostas de CLI anteriores (sobrescreve o predefinido para a mesma chave normalizada).
  3. **Match automático** no banco (`structures.name` / `abbreviation` normalizados).
  4. **Prompt CLI** somente na fase preparatória (`prepararParaUnidades`), se não for ambiente `testing`, não houver `--no-interaction` e ainda faltar unidade.
- **Processamento em lote**: durante a leitura dos chunks, **não** há prompt — só uso do mapa já montado + match automático (evita travar em milhares de linhas).

Regenerar o predefinido a partir do CSV atual e do banco:

```bash
php database/scripts/gerar-de-para-prodata-conta-angra.php
```

```mermaid
flowchart LR
    CSV[CSV em chunks de 500] --> Seeder[ImportacaoProdataContaAngraSeeder]
    Seeder --> DePara[MapeamentoUnidadeResource]
    DePara --> PreJSON[database/data/.../de-para-unidades-predefinido.json]
    DePara --> StorageJSON[storage/app/.../de-para-unidades.json]
    DePara --> Auto[Match automatico structures]
    Seeder --> Service[ImportacaoProdataContaAngraService.processarLinha]
    Service --> Banco[(users + permissoes)]
    Service --> Logs[storage/logs/importacao-prodata-contaangra-YYYY-MM-DD.log]
    Seeder --> Relatorio[storage/app/importacao-prodata-contaangra/relatorio-YYYYMMDD_HHMMSS.json/.md]
```

---

## 2. Estrutura dos seeders / componentes

| Componente | Responsabilidade |
| --- | --- |
| `ImportacaoProdataContaAngraSeeder` | Lê o CSV em chunks, abre/fecha transação por chunk, mantém contadores, gera relatório final em JSON + MD, integra prompt interativo. |
| `ImportacaoProdataContaAngraService` | Validação/normalização, `processarLinha` (create/update), dirty check, concede permissões padrão preservando as existentes, trata conflito de e-mail. |
| `MapeamentoUnidadeResource` | Normaliza unidades; carrega `de-para-unidades-predefinido.json` + `storage/.../de-para-unidades.json`; match automático; prompt CLI só na fase preparatória (se aplicável). |

Arquivos principais:

- `database/seeders/ImportacaoProdataContaAngraSeeder.php`
- `app/Http/Services/Importacoes/ImportacaoProdataContaAngraService.php`
- `app/Http/Resources/Importacoes/MapeamentoUnidadeResource.php`
- `database/data/importacao-prodata-contaangra/de-para-unidades-predefinido.json` (DE-PARA revisável, versionado)
- `database/scripts/gerar-de-para-prodata-conta-angra.php` (regenera o JSON a partir do CSV + `structures`)

---

## 3. Regras de negócio implementadas

### 3.1 Validação e normalização

| Campo | Regra |
| --- | --- |
| CPF | Remove não dígitos; valida 11 dígitos e dígitos verificadores oficiais. CPF inválido → registro **ignorado** e logado em `registro_invalido`. |
| E-mail | `trim` + lowercase; valida com `filter_var(FILTER_VALIDATE_EMAIL)`. Inválido → registro ignorado. |
| Matrícula | Mantém apenas dígitos; vazio é convertido em `null`; tamanho > 30 caracteres → inválido. |
| Nome | Colapsa espaços; `firstName` = primeira palavra, `lastName` = restante. Vazio → inválido. |
| Unidade | Resolvida pelo DE-PARA. Se não resolvida e operador escolher "Pular" no prompt → inválido. |

### 3.2 Criação (CPF novo)

Atributos atribuídos em `users`:

| Campo | Valor |
| --- | --- |
| `structure_id` | Resultado do DE-PARA |
| `firstName` / `lastName` | Quebra do nome do CSV |
| `cpf` | 11 dígitos do CSV |
| `matriculation` | Dígitos da matrícula do CSV |
| `phoneNumber` | String vazia `""` |
| `email` | Lowercased do CSV |
| `password` | `Hash::make(Str::random(12))` |
| `email_verified_at` | `null` |
| `photo` | `default/assets/img/icons/usuario.png` |
| `status` | `ATIVADO` |
| `isPasswordDefault` | `1` |
| `isServidorPublico` | `Sim` |

Permissões criadas (somente se ainda não existirem):

- `GERENCIADOR_DE_CONTA_ANGRA` (visualizar/adicionar/editar/excluir = 1)
- `GUEST` (visualizar/adicionar/editar/excluir = 1)

### 3.3 Atualização (CPF existente)

**Dirty check**: o Service compara somente `matriculation`, `structure_id` e `email`.

- Se nenhum diferente **e** as permissões padrão já estão concedidas → classificado como `sincronizado`, **nenhum UPDATE** é executado.
- Se algum campo diferente → executa `UPDATE` apenas dos campos divergentes.
- **Permissões padrão**: as permissões `GERENCIADOR_DE_CONTA_ANGRA` e `GUEST` são garantidas também em updates — concedidas apenas se ainda não existirem para aquele usuário (preservando todas as demais permissões pré-cadastradas).
- **Conflito de e-mail**: se o novo e-mail do CSV já estiver em uso por outro usuário (CPF diferente), o e-mail atual é preservado e o evento é registrado em `email_em_conflito` no log. O update prossegue para os outros campos divergentes.

Atributos **nunca alterados** em updates:

- `password`, `phoneNumber`, `photo`, `status`, `isPasswordDefault`, `isServidorPublico`, `email_verified_at`
- Permissões pré-existentes (apenas as 2 permissões padrão são adicionadas se faltarem)
- Unidades organizacionais vinculadas
- Demais relacionamentos do usuário

### 3.4 DE-PARA de unidades

Fluxo de resolução em ordem:

1. Consulta o cache em memória (normalizado em UPPERCASE sem acentos);
2. Tenta match automático contra `structures.name` e `structures.abbreviation` normalizados;
3. Abre prompt CLI com a lista numerada de todas as Structures ativas e a opção "Pular";
4. A resposta é salva no JSON imediatamente, permitindo recuperação caso a execução seja interrompida.

A normalização aplicada é: `Transliterator::Any-Latin; Latin-ASCII; Upper()` (fallback: `iconv` + `mb_strtoupper`), removendo acentuação e caracteres especiais, e colapsando espaços.

---

## 4. Logs

Canal `importacao_prodata_contaangra` configurado em `config/logging.php`:

- Driver: `daily`, retenção 30 dias
- Caminho: `storage/logs/importacao-prodata-contaangra-YYYY-MM-DD.log`

Eventos registrados:

| Nível | Evento | Quando |
| --- | --- | --- |
| `info` | `importacao_iniciada` | Início da execução |
| `info` | `usuario_criado` | User criado |
| `info` | `usuario_atualizado` | User atualizado, com lista de campos |
| `debug` | `usuario_sincronizado` | Dirty check sem diferenças |
| `warning` | `registro_invalido` | Validação falhou |
| `warning` | `email_em_conflito` | E-mail do CSV já pertence a outro CPF |
| `error` | `erro_linha` | Exceção em uma linha (não interrompe o chunk) |
| `critical` | `erro_chunk` | Exceção no chunk inteiro (rollback do chunk) |
| `info` | `importacao_concluida` | Final da execução com contadores e duração |

---

## 5. Relatório final

Após cada execução, dois arquivos são gerados em `storage/app/importacao-prodata-contaangra/`:

- `relatorio-YYYYMMDD_HHMMSS.json` (estruturado, consumível por scripts)
- `relatorio-YYYYMMDD_HHMMSS.md` (humano, com tabelas)

Conteúdo:

- Data de geração
- Duração em segundos
- Contadores: `processados`, `criados`, `atualizados`, `sincronizados`, `invalidos`, `erros`
- Lista detalhada de inválidos (`linha`, `cpf`, `motivo`)
- Lista detalhada de erros (`linha`, `cpf`, `erro`)

O mesmo resumo é exibido no console ao término da execução.

---

## 6. Comandos

```bash
# 0. Conferir / atualizar o DE-PARA versionado (recomendado antes de produção)
php database/scripts/gerar-de-para-prodata-conta-angra.php
# Edite database/data/importacao-prodata-contaangra/de-para-unidades-predefinido.json se necessário (objeto "detalhes" e campo confianca ajudam na revisao).

# 1) Executar importação (prompt só se faltar unidade no JSON e sem --no-interaction)
php artisan db:seed --class=ImportacaoProdataContaAngraSeeder

# 2) Reexecutar (idempotente)
php artisan db:seed --class=ImportacaoProdataContaAngraSeeder

# 3) Sem prompt (somente JSON predefinido + storage + match automatico)
php artisan db:seed --class=ImportacaoProdataContaAngraSeeder --no-interaction

# 4) Rodar somente os testes desta importação
php vendor/bin/phpunit --filter=ImportacaoProdataContaAngra

# 5) Inspecionar logs
tail -f storage/logs/importacao-prodata-contaangra-$(date +%Y-%m-%d).log
```

---

## 7. Estratégia de reprocessamento / reexecução

A rotina foi desenhada para ser **executada quantas vezes forem necessárias** sem efeitos colaterais:

1. **Registros já sincronizados são ignorados** pelo dirty check — nenhum `UPDATE` redundante.
2. **Erros específicos não interrompem a importação** — cada linha falha em isolamento e é registrada no log e no relatório.
3. Após corrigir o CSV (ou o banco) e reexecutar, somente os registros divergentes serão tocados.
4. O arquivo **`database/data/.../de-para-unidades-predefinido.json`** versionado cobre as 26 unidades do CSV atual; ajuste-o no Git quando o Prodata ou o organograma mudar. O JSON em `storage/app/...` continua opcional para overrides locais.
5. Conflitos de e-mail só podem ser resolvidos manualmente (intervenção do operador) — após o ajuste, basta reexecutar.

Em caso de necessidade de **rollback parcial** (por exemplo, reverter usuários criados em uma execução específica), inspecione o log do dia (`importacao-prodata-contaangra-YYYY-MM-DD.log`) filtrando por `usuario_criado` e remova manualmente os IDs envolvidos via SQL/Admin — não há rollback automático global, pois o seeder confirma cada chunk individualmente.

---

## 8. Limitações conhecidas

- **`phoneNumber`** é gravado como string vazia para usuários novos (o CSV não traz telefone). O usuário deve completar pela área de perfil.
- **`password`** é aleatória de 12 caracteres e não é exibida nem enviada por e-mail; usuários novos devem usar o fluxo "Esqueci minha senha".
- **Conflito de e-mail** (mesmo e-mail em CPF diferente) **não atualiza** o e-mail do registro existente — apenas registra em log para revisão manual.
- O DE-PARA depende de Structures **já cadastradas**; a rotina **não cria** novas Structures automaticamente. Se uma unidade não tem equivalente, o operador deve escolher "Pular" e os registros daquela unidade serão marcados como inválidos.
- A coluna `users.matriculation` é `nullable` e não tem mais constraint `unique` (migração `2025_04_10_162951`), portanto matrículas repetidas no CSV não geram conflito.
- A rotina **não envia e-mail** para usuários criados — comunicação posterior fica a cargo do canal apropriado.

---

## 9. Performance e segurança em produção

- **Memória constante**: leitura via `Generator`, chunks de 500.
- **Locks curtos**: transação por chunk (~500 linhas), não pela importação inteira.
- **Sem N+1**: queries diretas por CPF (índice unique em `users.cpf`) e por `permissoes.user_id`.
- **Idempotente**: dirty check evita `UPDATE`s redundantes; cache do DE-PARA evita prompts em produção (basta deployar com o JSON pré-preenchido).
- **Permissões duplicadas**: protegido por `Permissao::where(...)->exists()` antes de cada `create`.
- **Continuidade após falha**: erro em uma linha não derruba o chunk inteiro; erro em um chunk não derruba a importação.

---

## 10. Testes automatizados

Cobertura em:

- `tests/Unit/Importacoes/ImportacaoProdataContaAngraServiceTest.php` — validações, normalizações, dirty check, conflito de e-mail, preservação de senha/permissões.
- `tests/Feature/Importacoes/ImportacaoProdataContaAngraSeederTest.php` — execução end-to-end do seeder com CSV temporário, DE-PARA, registros inválidos, idempotência, lote de 50 registros.

Helpers em `tests/Support/Importacoes/ImportacaoProdataTestHelpers.php` (geração de CPFs válidos com prefixo `999`, fallback de Structures, limpeza ao final dos testes).

Relatório dos testes em `docs/tests/importacao-prodata-contaangra/relatorio-testes.md`.
