430 lines
14 KiB
Markdown
430 lines
14 KiB
Markdown
# Roteiro de Implementação — Comparador de Notas
|
||
|
||
**Versão:** 1.0
|
||
**Data:** 02/03/2026
|
||
**Baseado no PRD:** v1.6
|
||
|
||
---
|
||
|
||
## Fase 0 — Fundação do Projeto
|
||
|
||
**Objetivo:** Estrutura compilável com todas as dependências.
|
||
|
||
### 0.1 Configurar `Cargo.toml`
|
||
|
||
Adicionar todas as dependências:
|
||
|
||
```toml
|
||
[dependencies]
|
||
eframe = "0.31"
|
||
egui = "0.31"
|
||
rusqlite = { version = "0.32", features = ["bundled"] }
|
||
csv = "1.3"
|
||
calamine = "0.26"
|
||
rust_decimal = { version = "1.36", features = ["serde"] }
|
||
rust_decimal_macros = "1.36"
|
||
serde = { version = "1", features = ["derive"] }
|
||
serde_json = "1"
|
||
genpdf = "0.2"
|
||
chrono = { version = "0.4", features = ["serde"] }
|
||
encoding_rs = "0.8"
|
||
dirs = "5"
|
||
thiserror = "2"
|
||
```
|
||
|
||
### 0.2 Criar estrutura de pastas completa
|
||
|
||
Conforme definido na seção 9.1 do PRD:
|
||
|
||
```
|
||
src/
|
||
main.rs
|
||
ui/
|
||
mod.rs
|
||
app.rs
|
||
screens/
|
||
import.rs
|
||
configuracao_colunas.rs
|
||
layouts.rs
|
||
resultado.rs
|
||
application/
|
||
mod.rs
|
||
usecases/
|
||
importar_arquivo.rs
|
||
executar_analise.rs
|
||
exportar_pdf.rs
|
||
domain/
|
||
mod.rs
|
||
errors.rs
|
||
entities/
|
||
nota.rs
|
||
serie.rs
|
||
layout.rs
|
||
resultado_analise.rs
|
||
services/
|
||
detector_sequencia.rs
|
||
detector_duplicidade.rs
|
||
parser_monetario.rs
|
||
infrastructure/
|
||
mod.rs
|
||
csv_reader.rs
|
||
xlsx_reader.rs
|
||
pdf_generator.rs
|
||
sqlite/
|
||
mod.rs
|
||
connection.rs
|
||
migrations.rs
|
||
layout_repository.rs
|
||
```
|
||
|
||
### 0.3 Criar todos os `mod.rs` com declarações vazias
|
||
|
||
Garantir que o projeto compila antes de começar a implementar.
|
||
|
||
---
|
||
|
||
## Fase 1 — Camada Domain (núcleo puro)
|
||
|
||
**Dependência:** Fase 0 concluída.
|
||
**Restrição:** Zero dependência de egui, rusqlite, calamine, csv ou genpdf. Apenas Rust puro + `rust_decimal` e `chrono`.
|
||
|
||
### 1.1 `domain/errors.rs`
|
||
|
||
Definir tipos de erro com `thiserror`:
|
||
|
||
- `ErroNumero` (Zero, NaoNumerico)
|
||
- `ErroSerie` (Invalida, Vazia)
|
||
- `ErroValor` (Negativo, NaoNumerico)
|
||
- `ErroLayout` (CampoObrigatorioAusente, JsonMalformado, NomeConflitante)
|
||
|
||
### 1.2 `domain/entities/nota.rs`
|
||
|
||
```rust
|
||
pub struct Nota {
|
||
pub numero: u64,
|
||
pub serie: String,
|
||
pub valor: Option<Decimal>,
|
||
pub data: Option<NaiveDate>,
|
||
}
|
||
```
|
||
|
||
### 1.3 `domain/entities/serie.rs`
|
||
|
||
- Validação da regex `[0-9]{1,3}`
|
||
- Função `validar_serie(s: &str) -> Result<String, ErroSerie>`
|
||
|
||
### 1.4 `domain/entities/layout.rs`
|
||
|
||
- `enum TipoArquivo { Csv, Xlsx }`
|
||
- `struct LayoutCsv` com todos os campos da seção 10.4 do PRD
|
||
- `struct LayoutXlsx` com todos os campos da seção 10.4 do PRD
|
||
- `enum Layout { Csv(LayoutCsv), Xlsx(LayoutXlsx) }`
|
||
|
||
### 1.5 `domain/entities/resultado_analise.rs`
|
||
|
||
```rust
|
||
pub struct ResultadoPreAnalise {
|
||
// min, max, contagem_faltantes por série
|
||
pub intervalos_por_serie: HashMap<String, (u64, u64, usize)>,
|
||
}
|
||
|
||
pub struct ResultadoAnalise {
|
||
pub faltantes_por_serie: HashMap<String, Vec<u64>>,
|
||
pub duplicadas_por_serie: HashMap<String, Vec<(u64, usize)>>,
|
||
pub soma_total: Decimal,
|
||
pub soma_por_serie: HashMap<String, Decimal>,
|
||
}
|
||
```
|
||
|
||
### 1.6 `domain/services/parser_monetario.rs`
|
||
|
||
Implementar exatamente o algoritmo da seção RF06:
|
||
|
||
| Regra | Condição | Comportamento |
|
||
|-------|----------|---------------|
|
||
| 1 | Contém ponto **e** vírgula | Último separador é o decimal |
|
||
| 2a | Apenas um separador + exatamente 2 dígitos após | Separador decimal |
|
||
| 2b | Apenas um separador + exatamente 3 dígitos após | Separador de milhar |
|
||
| 2c | Apenas um separador + outros casos | Separador decimal |
|
||
| 3 | Sem separador | Número inteiro |
|
||
|
||
- Rejeitar valores negativos (precedidos de `-`)
|
||
- Armazenar como `rust_decimal::Decimal`
|
||
|
||
> **Prioridade:** Testar exaustivamente com todos os exemplos da tabela do PRD antes de avançar.
|
||
|
||
### 1.7 `domain/services/detector_sequencia.rs`
|
||
|
||
- Recebe `Vec<&Nota>` de uma série
|
||
- Ordena por `numero` crescente
|
||
- Percorre **incrementalmente** (sem lista intermediária)
|
||
- Retorna `Vec<u64>` de faltantes
|
||
- Respeitar o limite de 10.000 registros faltantes (RFC04): retornar `ResultadoPreAnalise` antes de expandir
|
||
|
||
### 1.8 `domain/services/detector_duplicidade.rs`
|
||
|
||
- Recebe `Vec<&Nota>`
|
||
- Retorna `HashMap<(u64, String), usize>` com contagem por grupo
|
||
- Filtra apenas grupos com contagem > 1
|
||
|
||
---
|
||
|
||
## Fase 2 — Camada Infrastructure
|
||
|
||
**Dependência:** Fase 1 concluída.
|
||
|
||
### 2.1 `infrastructure/sqlite/connection.rs`
|
||
|
||
- Determinar caminho do banco conforme SO via `dirs::config_dir()`:
|
||
- Linux: `~/.config/comparador-notas/config.db`
|
||
- Windows: `%APPDATA%\comparador-notas\config.db`
|
||
- macOS: `~/Library/Application Support/comparador-notas/config.db`
|
||
- Criar diretório automaticamente se não existir
|
||
- Abrir conexão SQLite
|
||
- Tratamento de banco corrompido: renomear para `config.db.bak`, recriar banco vazio
|
||
|
||
### 2.2 `infrastructure/sqlite/migrations.rs`
|
||
|
||
- Tabela `schema_version` para controle de versão do schema
|
||
- Migration v1: criar tabela `layouts` com todos os campos da seção 10.4 do PRD
|
||
- Aplicar migrations automaticamente na inicialização
|
||
|
||
### 2.3 `infrastructure/sqlite/layout_repository.rs`
|
||
|
||
```rust
|
||
fn salvar(layout: &Layout) -> Result<i64>
|
||
fn listar() -> Result<Vec<Layout>>
|
||
fn buscar_por_id(id: i64) -> Result<Option<Layout>>
|
||
fn excluir(id: i64) -> Result<()>
|
||
fn existe_nome(nome: &str) -> Result<bool>
|
||
```
|
||
|
||
### 2.4 `infrastructure/csv_reader.rs`
|
||
|
||
- Suporte a delimitadores: `,` `;` `\t`
|
||
- Suporte a encoding: UTF-8 e Windows-1252 (via `encoding_rs`)
|
||
- Respeitar linha de cabeçalho configurável (base 1)
|
||
- Ignorar linhas em branco silenciosamente
|
||
- Coletar linhas malformadas para relatório consolidado
|
||
- Validar limite de 50 MB antes de ler
|
||
- Retornar `Vec<Vec<String>>` (linhas × colunas)
|
||
|
||
### 2.5 `infrastructure/xlsx_reader.rs`
|
||
|
||
- `listar_abas(path) -> Vec<String>` — chamado imediatamente após seleção do arquivo
|
||
- Ler dados da aba selecionada a partir da posição `LetraLinha`
|
||
- Converter notação `LetraLinha` (ex: `B3`) para `(col_idx, row_idx)`
|
||
- Ignorar linhas em branco silenciosamente
|
||
- Validar limite de 50 MB
|
||
- Tratar arquivo corrompido retornando erro tipado
|
||
|
||
### 2.6 `infrastructure/pdf_generator.rs`
|
||
|
||
- Implementar trait `PdfGenerator` definida em `application/`
|
||
- Gerar PDF com `genpdf` contendo:
|
||
- Metadados: nome do arquivo importado, data/hora de geração, nome do layout
|
||
- Faltantes agrupados por série
|
||
- Duplicatas agrupadas por série
|
||
- Totais por série e total geral
|
||
|
||
---
|
||
|
||
## Fase 3 — Camada Application
|
||
|
||
**Dependência:** Fases 1 e 2 concluídas.
|
||
|
||
### 3.1 `application/usecases/importar_arquivo.rs`
|
||
|
||
1. Validar tamanho do arquivo (recusar > 50 MB)
|
||
2. Detectar tipo (CSV ou XLSX)
|
||
3. Para XLSX: chamar `listar_abas()` e retornar lista para UI fazer a seleção
|
||
4. Chamar leitor adequado com configurações do layout
|
||
5. Mapear colunas e construir `Vec<Nota>`
|
||
6. Aplicar validações: regex de série, numero zero, valores inválidos
|
||
7. Retornar notas válidas + relatório de avisos consolidado (RF06/RNF06)
|
||
|
||
### 3.2 `application/usecases/executar_analise.rs`
|
||
|
||
1. Receber `Vec<Nota>`
|
||
2. Chamar `parser_monetario` para cada valor
|
||
3. Agrupar por série
|
||
4. Chamar `detector_sequencia` por série → gerar `ResultadoPreAnalise`
|
||
5. Se algum intervalo > 10.000: retornar `ResultadoPreAnalise` para UI solicitar confirmação
|
||
6. Após confirmação: expandir faltantes e montar `ResultadoAnalise`
|
||
7. Chamar `detector_duplicidade`
|
||
8. Calcular somas com `Decimal` (nunca `f64`)
|
||
9. Retornar `ResultadoAnalise`
|
||
|
||
### 3.3 `application/usecases/exportar_pdf.rs`
|
||
|
||
- Depende da trait `PdfGenerator` (não de `genpdf` diretamente)
|
||
- Recebe `ResultadoAnalise` + metadados do arquivo e layout
|
||
- Delega geração para a implementação concreta em infrastructure
|
||
|
||
### 3.4 Gerenciamento de layouts
|
||
|
||
- `salvar_layout` — valida campos obrigatórios antes de persistir
|
||
- `listar_layouts` — lista separada por tipo (CSV / XLSX)
|
||
- `carregar_layout` — busca por id
|
||
- `excluir_layout` — remove do banco
|
||
- `exportar_layout_json` — serializa com `serde_json`, sugere nome do arquivo
|
||
- `importar_layout_json` — desserializa + valida campos obrigatórios + verifica conflito de nome
|
||
|
||
---
|
||
|
||
## Fase 4 — Camada UI (egui/eframe)
|
||
|
||
**Dependência:** Fase 3 concluída.
|
||
**Regra:** Nenhuma regra de negócio, parsing ou cálculo dentro da UI.
|
||
|
||
### 4.1 `ui/app.rs` — Estrutura principal
|
||
|
||
```rust
|
||
enum EstadoApp {
|
||
Importando,
|
||
ConfigurandoColunas,
|
||
SelecionandoAba(Vec<String>), // lista de abas XLSX
|
||
Analisando,
|
||
ConfirmandoIntervalo(ResultadoPreAnalise),
|
||
ExibindoResultado(ResultadoAnalise),
|
||
GerenciandoLayouts,
|
||
}
|
||
```
|
||
|
||
- Gerenciamento de modais/popups bloqueantes (RNF06)
|
||
- Struct `App` com estado compartilhado e referência ao banco SQLite
|
||
|
||
### 4.2 `ui/screens/import.rs`
|
||
|
||
- Botão "Selecionar arquivo" com diálogo nativo
|
||
- Exibir nome do arquivo selecionado
|
||
- Para XLSX: exibir dropdown de seleção de aba **imediatamente** após seleção do arquivo
|
||
- Dropdown de layouts salvos para carregar configuração existente
|
||
- Botão "Gerenciar Layouts" → navegar para `layouts.rs`
|
||
|
||
### 4.3 `ui/screens/configuracao_colunas.rs`
|
||
|
||
- Para CSV: campos numéricos para índice de cada coluna (base 0) + delimitador + encoding + linha cabeçalho
|
||
- Para XLSX: campos de texto para posição `LetraLinha` de cada coluna
|
||
- Validação em tempo real: índices duplicados, campos obrigatórios ausentes
|
||
- Bloqueio do botão "Executar Análise" quando configuração inválida
|
||
- Botão "Executar Análise"
|
||
|
||
### 4.4 `ui/screens/layouts.rs`
|
||
|
||
- Listar layouts salvos separados por tipo (CSV / XLSX)
|
||
- Campo de nome + botão "Salvar layout atual"
|
||
- Botão "Excluir" com modal de confirmação
|
||
- Botão "Exportar para JSON" → diálogo de salvar com nome sugerido baseado no nome do layout
|
||
- Botão "Importar de JSON" → diálogo de abertura
|
||
- Modal de conflito de nome ao importar: "Sobrescrever" ou "Salvar com novo nome"
|
||
|
||
### 4.5 `ui/screens/resultado.rs`
|
||
|
||
- Seções: **Faltantes** | **Duplicatas** | **Totais**
|
||
- Agrupamento por série em todas as seções
|
||
- Paginação com dropdown: 50 / 100 / 200 / 1000 itens por página
|
||
- Valores monetários formatados em pt-BR (ex: `1.234,56`)
|
||
- Números de notas sem zeros à esquerda (ex: `0001` exibido como `1`)
|
||
- Botão "Exportar PDF" → chamar use case `exportar_pdf`
|
||
|
||
### 4.6 Modais globais (RNF06)
|
||
|
||
| Modal | Trigger |
|
||
|-------|---------|
|
||
| Erro genérico | Qualquer erro com mensagem + botão OK |
|
||
| Aviso consolidado | Final de importação com lista de categorias de problema |
|
||
| Confirmação de intervalo | Faltantes > 10.000 por série |
|
||
| Confirmação de exclusão | Excluir layout |
|
||
| Conflito de nome | Importar JSON com nome existente |
|
||
| Banco corrompido | Inicialização com `config.db` inválido |
|
||
|
||
---
|
||
|
||
## Fase 5 — Testes
|
||
|
||
**Dependência:** Fases 1–4 concluídas (testes unitários podem ser escritos junto com cada fase).
|
||
|
||
### 5.1 Testes unitários — Domain
|
||
|
||
- `parser_monetario`: todos os casos da tabela do PRD + casos extremos
|
||
- `detector_sequencia`: sequência completa, com faltantes, série com um único registro, intervalos > 10.000
|
||
- `detector_duplicidade`: sem duplicatas, com duplicatas, múltiplas séries
|
||
- Validação de série (regex `[0-9]{1,3}`): válidos e inválidos
|
||
- Validação de numero zero: deve ser descartado
|
||
|
||
### 5.2 Testes de integração — Infrastructure
|
||
|
||
- Leitura CSV: UTF-8, Windows-1252, diferentes delimitadores, linhas malformadas, arquivo > 50 MB
|
||
- Leitura XLSX: aba correta, posição `LetraLinha`, arquivo corrompido, limite de tamanho
|
||
- SQLite: migrations, CRUD de layouts, banco corrompido → recriação
|
||
|
||
### 5.3 Testes de integração — Application
|
||
|
||
- Fluxo completo CSV → análise → resultado
|
||
- Fluxo completo XLSX → análise → resultado
|
||
- Importação de layout JSON: válido, malformado, conflito de nome com sobrescrita, conflito com renomeação
|
||
- Intervalo > 10.000: verificar que `ResultadoPreAnalise` é retornado antes da expansão
|
||
|
||
### 5.4 Testes manuais de UI
|
||
|
||
- Todas as mensagens em PT-BR
|
||
- Formatação monetária correta (pt-BR)
|
||
- Paginação funcionando
|
||
- Geração do PDF com metadados corretos
|
||
- Fluxo XLSX: aba exibida imediatamente após seleção do arquivo
|
||
|
||
---
|
||
|
||
## Fase 6 — Polimento e Empacotamento
|
||
|
||
### 6.1 Strings e idioma
|
||
|
||
- Revisar todas as mensagens de erro/aviso para PT-BR (RNF05)
|
||
- Garantir que nenhuma string de erro interna vaze para a UI como texto bruto
|
||
|
||
### 6.2 Performance
|
||
|
||
- Validar com arquivo de 100.000 registros (RNF03)
|
||
- Confirmar que detecção de faltantes é incremental (sem lista intermediária antes da confirmação)
|
||
- Medir tempo de importação e análise
|
||
|
||
### 6.3 Build multiplataforma
|
||
|
||
- Linux: validar caminho `~/.config/comparador-notas/config.db`
|
||
- Windows: validar caminho `%APPDATA%\comparador-notas\config.db`
|
||
- macOS: validar caminho `~/Library/Application Support/comparador-notas/config.db`
|
||
|
||
### 6.4 Empacotamento
|
||
|
||
- Configurar ícone da aplicação
|
||
- Build de release otimizado: `cargo build --release`
|
||
- Testar executável standalone (sem instalação, sem dependências externas)
|
||
|
||
---
|
||
|
||
## Ordem de Execução Recomendada
|
||
|
||
```
|
||
Fase 0 → Fase 1 (com testes unitários) → Fase 2.1/2.2/2.3 → Fase 2.4/2.5
|
||
→ Fase 3.1/3.2 → Fase 4.1/4.2/4.3/4.5 → Fase 2.6/3.3 → Fase 3.4/4.4
|
||
→ Fase 5 → Fase 6
|
||
```
|
||
|
||
> O núcleo de domínio deve estar **totalmente testado** antes de qualquer integração. O `parser_monetario` e o `detector_sequencia` são os componentes mais críticos do sistema.
|
||
|
||
---
|
||
|
||
## Dependências entre Componentes
|
||
|
||
```
|
||
domain/errors.rs
|
||
└─ domain/entities/ (nota, serie, layout, resultado_analise)
|
||
└─ domain/services/ (parser_monetario, detector_sequencia, detector_duplicidade)
|
||
└─ application/usecases/ (importar_arquivo, executar_analise, exportar_pdf)
|
||
└─ infrastructure/ (csv_reader, xlsx_reader, sqlite/, pdf_generator)
|
||
└─ ui/ (app, screens/)
|
||
```
|
||
|
||
Nenhuma camada pode importar de uma camada acima dela na hierarquia.
|