# Painel Web NF Nacional

## Objetivo

Painel administrativo mínimo para operar o motor NF Nacional.

Funcionalidades atuais:

- login administrativo;
- listagem de municípios;
- cadastro e edição de município usando o mesmo formulário;
- seleção de UF por combo com `RS` como padrão local;
- upload de certificado A1 `.pfx/.p12`;
- validação antecipada de autenticidade do certificado e senha;
- validação da senha do certificado;
- criptografia do PFX e da senha no banco;
- criação automática das tabelas físicas do município;
- criação do estado inicial de sincronização;
- sincronização completa manual em background por município, incluindo documentos e eventos;
- acompanhamento do status `idle`, `pending`, `running`, `ready`, `error` ou `disabled`.
- troca de certificado por município, mantendo histórico e apenas um ativo;
- alteração da senha criptografada do certificado ativo;
- bloqueio de cadastro, troca, validação e sincronização quando o certificado estiver expirado.

## Como funciona o cadastro

Ao adicionar um município:

```text
1. operador informa código IBGE, nome e UF;
2. operador envia o PFX e a senha;
3. painel valida se o PFX abre;
4. painel extrai metadados do certificado;
5. painel cria as tabelas do município se ainda não existirem;
6. painel criptografa PFX e senha com NFSE_MASTER_KEY;
7. painel grava em nfse_certificados;
8. painel cria/atualiza nfse_sync_state;
9. worker passa a enxergar o município ativo.
```

## Edição

O botão `Editar` abre o mesmo formulário operacional do cadastro, reaproveitando os campos de município e certificado.

Na edição:

```text
1. nome e UF podem ser alterados;
2. se um novo PFX for anexado, o painel troca o certificado;
3. se apenas a senha for informada, o painel atualiza a senha do certificado atual;
4. se certificado e senha ficarem vazios, o painel salva apenas os dados do município.
```

## Execução Manual

Na tela de municípios, o botão `Sincronizar tudo` marca o município como `pending` e inicia uma thread em background no processo do painel.

O processamento é incremental e completo: começa no próximo NSU efetivo, calculado por `max(nfse_sync_state.last_nsu, MAX(nsu) da tabela física do município) + 1`, consulta o ADN com `tipoNSU=DISTRIBUICAO&lote=true`, grava o lote recebido e continua chamando o próximo NSU até o ADN não retornar mais documentos.

No mesmo fluxo, documentos são gravados em `nfse_documentos_{codigo_ibge}` e eventos são gravados em `nfse_eventos_{codigo_ibge}`. Quando um evento puder ser vinculado à chave de uma nota já importada, o `documento_id` fica preenchido; eventos de cancelamento também marcam a nota vinculada como `status_documento = 'cancelada'`.

Fluxo de status:

```text
idle -> pending -> running -> ready
```

Se a chamada ao ADN, o certificado ou o banco falhar, o status vai para `error` e a mensagem fica em `nfse_sync_state.last_error_message`.

Também é possível executar pelo terminal:

```sh
NFSE_MASTER_KEY='base64-32-bytes' \
NFSE_DB_DOCKER_CONTAINER=maspernf-mysql \
./scripts/sync_full.py --codigo-ibge 4314464
```

Para rodar todos os municípios ativos com certificado ativo:

```sh
NFSE_MASTER_KEY='base64-32-bytes' \
NFSE_DB_DOCKER_CONTAINER=maspernf-mysql \
./scripts/sync_full_all.py --concurrency 3
```

Paralelismo recomendado:

```text
- entre municípios: sim, cada município tem certificado, tabelas e cursor próprios;
- dentro do mesmo município: manter sequencial pelo NSU para preservar o cursor incremental;
- ajuste fino: NFSE_SYNC_MAX_LOOPS limita quantos lotes uma execução pode consumir.
- retomada: NFSE_SYNC_PROGRESS_EVERY define a cada quantos itens o last_nsu é salvo, padrão 100;
- proteção extra: se `nfse_sync_state.last_nsu` estiver atrasado, a rotina usa o maior NSU já gravado na tabela do município;
- lock antigo: NFSE_SYNC_STALE_LOCK_MINUTES libera município preso em running/pending antigo, padrão 120 minutos.
```

## Manutenção do Certificado

Na listagem de municípios, o botão `Editar` abre a manutenção do município e do certificado.

Fluxos disponíveis:

```text
Trocar certificado:
1. operador envia novo PFX e senha;
2. painel valida o novo PFX;
3. certificados anteriores do município ficam ativo = 0;
4. novo certificado e senha entram criptografados no banco;
5. evento certificado.trocado é registrado em nfse_sync_logs.

Alterar senha:
1. operador informa a nova senha;
2. painel descriptografa o PFX ativo do banco;
3. painel valida se a nova senha abre esse PFX;
4. senha nova e criptografada e salva;
5. evento certificado.senha_alterada é registrado em nfse_sync_logs.
```

Se o certificado estiver expirado, a listagem mostra `Expirado`, o botão de execução fica desabilitado e a tela de edição orienta a trocar o certificado. Cadastro e troca também rejeitam PFX expirado.

## Rodar local

Gerar chave mestra:

```sh
python3 - <<'PY'
import base64, os
print(base64.b64encode(os.urandom(32)).decode())
PY
```

Gerar hash da senha admin:

```sh
./scripts/hash_admin_password.py
```

Subir MySQL local:

```sh
./up.sh
```

Rodar painel:

```sh
NFSE_MASTER_KEY='base64-32-bytes' \
NFSE_ADMIN_PASSWORD_HASH='hash-gerado' \
./scripts/run_web.sh
```

Acesso local:

```text
http://127.0.0.1:8080
```

Usuário padrão:

```text
admin
```

A senha é a que foi usada para gerar `NFSE_ADMIN_PASSWORD_HASH`.

## Apache

Em produção, o painel roda em Python na porta local `8080`, e o Apache publica via proxy.

Módulo necessário:

```sh
a2enmod proxy proxy_http headers
systemctl reload apache2
```

Exemplo de VirtualHost:

```apache
<VirtualHost *:80>
    ServerName nf-nacional.exemplo.com

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    Header always set X-Frame-Options "DENY"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "same-origin"
</VirtualHost>
```

Recomendação: publicar com HTTPS e, se possível, restringir por IP/VPN.

## Systemd

Exemplo:

```ini
[Unit]
Description=NF Nacional Painel Web
After=network.target mysql.service

[Service]
WorkingDirectory=/opt/nf-nacional
Environment=PYTHONPATH=/opt/nf-nacional/src
Environment=NFSE_WEB_HOST=127.0.0.1
Environment=NFSE_WEB_PORT=8080
Environment=NFSE_DB_HOST=127.0.0.1
Environment=NFSE_DB_PORT=3306
Environment=NFSE_DB_NAME=maspernf
Environment=NFSE_DB_USER=root
Environment=NFSE_DB_PASSWORD=trocar
Environment=NFSE_ADMIN_USER=admin
Environment=NFSE_ADMIN_PASSWORD_HASH=trocar
Environment=NFSE_MASTER_KEY=trocar
ExecStart=/usr/bin/python3 -m nf_nacional_web.app
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
```

## Segurança atual

- Sem cadastro público de usuário.
- Senha admin com PBKDF2-SHA256.
- Sessão assinada.
- CSRF nos formulários.
- Upload limitado a 2 MB.
- Aceita apenas `.pfx` e `.p12`.
- PFX e senha ficam criptografados no banco.
- `NFSE_MASTER_KEY` fica fora do banco.
- Nomes de tabela são gerados apenas por código IBGE validado.

## Limites do MVP

- Ainda não há gestão de vários usuários.
- Ainda não há auditoria detalhada por usuário.
- Ainda não há tela de logs detalhados.
- Ainda não há HTTPS embutido, isso fica no Apache.
