# Migração efetiva legado → BT (Tarefa 4)

## Objetivo

Persistir dados do banco **legado** (SIATI / BT antigo) no banco **alvo** (novo BT) de forma **idempotente**: re-execuções não duplicam registros, apenas atualizam os existentes ou inserem os ausentes.

Domínios migrados nesta ordem:

| Ordem | Domínio | Tabela legada | Tabela alvo | Chave de idempotência |
|-------|---------|---------------|-------------|----------------------|
| 1 | Candidatos | `candidates` | `candidates` | `cpf` (11 dígitos) |
| 2 | Empresas | `companies` | `companies` | `cnpj` (14 dígitos) |
| 3 | Vagas | `jobs` | `job_listings` | `legacy_id` (ID do legado) |
| 4 | Candidaturas | `job_applications` | `job_listing_candidate` | `(job_listing_id, candidate_id)` |

> A ordem importa: vagas dependem de empresas migradas; candidaturas dependem de vagas e candidatos.

---

## Pré-requisitos

1. Dump `.sql` em `storage/app/siati_app.sql` (ou `TALENT_BANK_LEGACY_SQL_DUMP`) **ou** banco legado via `mysql_legacy`.
2. `php artisan migrate` executado no banco alvo (inclui `legacy_id` em `job_listings`).
3. Cliente `mysql` no PATH (modo dump).

---

## Uso

### Setup completo homolog (recomendado — 1 comando)

```bash
php artisan talent-bank:homolog-setup --force
```

### Migração via dump SQL

```bash
php artisan talent-bank:legacy-migrate --force --sql-dump=storage/app/siati_app.sql
```

### Migração completa (conexão direta — dev/local)

```bash
php artisan talent-bank:legacy-migrate --force
```

### Migração por domínio

```bash
php artisan talent-bank:legacy-migrate --domain=candidates
php artisan talent-bank:legacy-migrate --domain=companies
php artisan talent-bank:legacy-migrate --domain=jobs
php artisan talent-bank:legacy-migrate --domain=applications
```

### Filtro por data (migração incremental)

```bash
php artisan talent-bank:legacy-migrate --domain=candidates --from-date=2025-01-01
```

### Opções completas

| Opção | Descrição |
|-------|-----------|
| `--sql-dump=` | Caminho do dump `.sql` (modo homolog; cria banco temp) |
| `--domain=` | Domínio: `candidates\|companies\|jobs\|applications\|all` (padrão: `all`) |
| `--from-date=` | Filtra por `updated_at >= YYYY-MM-DD` (não se aplica a candidaturas, sem timestamps) |
| `--legacy-connection=` | Conexão Laravel ao banco legado |
| `--target-connection=` | Conexão Laravel ao banco alvo |
| `--batch-size=` | Registros por chunk (padrão: 500) |
| `--report=` | Caminho do arquivo JSON de relatório |
| `--stdout` | Imprime o JSON completo no terminal |
| `--force` | Executa sem confirmação interativa |

---

## Saída

O comando imprime uma tabela de resumo e grava um JSON em `storage/app/`:

```
| Domínio      | Inseridos | Atualizados | Ignorados | Erros |
|--------------|-----------|-------------|-----------|-------|
| Candidatos   | 1200      | 80          | 5         | 0     |
| Empresas     | 150       | 10          | 2         | 0     |
| Vagas        | 300       | 20          | 8         | 0     |
| Candidaturas | 2100      | 150         | 12        | 0     |
```

**Inseridos:** registros novos no alvo.
**Atualizados:** registros já existentes (atualiza campos, preserva `created_at`).
**Ignorados:** CPF/CNPJ inválido, FK obrigatória ausente ou mapeamento não encontrado.
**Erros:** exceções de banco (FK constraint, tipo de dado etc.) — detalhes no JSON.

---

## Comportamento de idempotência

- **Candidatos/Empresas:** upsert por CPF/CNPJ. Re-execução atualiza campos sem alterar `created_at`.
- **Vagas:** upsert por `legacy_id` (coluna adicionada via migration). Na re-execução, `company_id` e `legacy_id` não são sobrescritos.
- **Candidaturas:** `EXISTS` antes do INSERT; duplicata é contada como `updated` (sem alteração). Unique constraint `(job_listing_id, candidate_id)` garante consistência.

---

## Mapeamento de campos

### Candidatos (`candidates`)

| Campo legado | Campo novo | Observação |
|-------------|------------|------------|
| `cpf` | `cpf` | bigInteger → zero-pad 11 dígitos |
| `name` | `name` | — |
| `email` | `email` | lowercase + trim |
| `telephone` | `telephone` | bigInteger → string de dígitos |
| `cellphone` | `cellphone` | bigInteger → string de dígitos |
| `special_needs` | `special_needs` | `0/1/S/N` → bool |
| `has_experience` | `has_experience` | `0/1/S/N` → bool |
| `active` | `active` | `0/1/S/N` → bool |
| `min_payment` | `min_payment` | cap em 99999.99 |
| `desired_payment` | `desired_payment` | cap em 99999.99 |
| `neighbourhood_id` | `neighbourhood_id` | nullable |
| `ethnicity_id` | `ethnicity_id` | nullable (coluna adicionada 2024) |

### Empresas (`companies`)

| Campo legado | Campo novo | Observação |
|-------------|------------|------------|
| `cnpj` | `cnpj` | bigInteger → zero-pad 14 dígitos |
| `corporate_name` | `corporate_name` | — |
| `telephone` | `telephone` | bigInteger → string de dígitos |
| `cellphone` | `cellphone` | bigInteger → string de dígitos |
| `active` | `active` | `0/1/S/N` → bool |

Campos `occupation_area`, `county`, `cnae`, `module_id`, `module_name` do legado são ignorados (sem equivalente no novo sistema).

### Vagas (`jobs` → `job_listings`)

| Campo legado | Campo novo | Observação |
|-------------|------------|------------|
| `id` | `legacy_id` | Para rastreabilidade e idempotência |
| `company_id` | `company_id` | Traduzido via CNPJ após empresas migradas |
| `descripton` *(typo)* | `description` | Typo histórico no legado |
| `is_hiring` | `is_hiring` | `0/1/A` → bool ("A" = Aberta) |
| `hide_company` | `hide_company` | `0/1/S` → bool |
| `occupation_id`, `salary_range_id`, `contract_type_id` | idem | NOT NULL — vaga ignorada se ausentes |

### Candidaturas (`job_applications` → `job_listing_candidate`)

| Campo legado | Campo novo | Observação |
|-------------|------------|------------|
| `candidate_id` | `candidate_id` | Via CPF: legado→CPF→novo ID |
| `job_id` | `job_listing_id` | Via `job_listings.legacy_id` |

A tabela legada não tem timestamps.

---

## Decisões técnicas

- **Sem transação global:** cada registro é operação independente. Falhas individuais são coletadas sem abortar o lote. Re-execução idempotente recupera o que foi ignorado.
- **Per-chunk bulk check:** 1 SELECT `WHERE IN` por chunk para identificar existência antes do upsert (evita N queries independentes).
- **`legacy_id` em `job_listings`:** único campo adicionado ao schema novo — necessário porque vagas não têm chave natural. Não exposto na API pública.
- **Timestamps:** `created_at` preservado na inserção original; `updated_at` atualizado em cada re-execução que modifica o registro.
- **Acesso cross-DB:** permitido exclusivamente para comandos de migração (conexão `mysql_legacy` isolada). Não é padrão de runtime (ver `btrules`, regra 3).

---

## Código

- Comando: `App\Console\Commands\TalentBankLegacyMigrateCommand`
- Importer: `App\Support\TalentBank\LegacyMigrationImporter`
- Normalizadores: `App\Support\TalentBank\LegacyDocumentNormalizer` (reutilizado da T3)
- Migration: `database/migrations/2026_05_04_000001_add_legacy_id_to_job_listings.php`

---

## Workflow recomendado

```bash
# Homolog (dump) — um comando
php artisan talent-bank:homolog-setup --force

# Ou passo a passo manual
php artisan talent-bank:legacy-dry-run
php artisan talent-bank:legacy-migrate --force --sql-dump=storage/app/siati_app.sql
php artisan talent-bank:legacy-dry-run
php artisan talent-bank:validate-migration
```
