14 KiB
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:
[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
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 LayoutCsvcom todos os campos da seção 10.4 do PRDstruct LayoutXlsxcom todos os campos da seção 10.4 do PRDenum Layout { Csv(LayoutCsv), Xlsx(LayoutXlsx) }
1.5 domain/entities/resultado_analise.rs
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
numerocrescente - Percorre incrementalmente (sem lista intermediária)
- Retorna
Vec<u64>de faltantes - Respeitar o limite de 10.000 registros faltantes (RFC04): retornar
ResultadoPreAnaliseantes 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
- Linux:
- 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_versionpara controle de versão do schema - Migration v1: criar tabela
layoutscom todos os campos da seção 10.4 do PRD - Aplicar migrations automaticamente na inicialização
2.3 infrastructure/sqlite/layout_repository.rs
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
PdfGeneratordefinida emapplication/ - Gerar PDF com
genpdfcontendo:- 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
- Validar tamanho do arquivo (recusar > 50 MB)
- Detectar tipo (CSV ou XLSX)
- Para XLSX: chamar
listar_abas()e retornar lista para UI fazer a seleção - Chamar leitor adequado com configurações do layout
- Mapear colunas e construir
Vec<Nota> - Aplicar validações: regex de série, numero zero, valores inválidos
- Retornar notas válidas + relatório de avisos consolidado (RF06/RNF06)
3.2 application/usecases/executar_analise.rs
- Receber
Vec<Nota> - Chamar
parser_monetariopara cada valor - Agrupar por série
- Chamar
detector_sequenciapor série → gerarResultadoPreAnalise - Se algum intervalo > 10.000: retornar
ResultadoPreAnalisepara UI solicitar confirmação - Após confirmação: expandir faltantes e montar
ResultadoAnalise - Chamar
detector_duplicidade - Calcular somas com
Decimal(nuncaf64) - Retornar
ResultadoAnalise
3.3 application/usecases/exportar_pdf.rs
- Depende da trait
PdfGenerator(não degenpdfdiretamente) - 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 persistirlistar_layouts— lista separada por tipo (CSV / XLSX)carregar_layout— busca por idexcluir_layout— remove do bancoexportar_layout_json— serializa comserde_json, sugere nome do arquivoimportar_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
enum EstadoApp {
Importando,
ConfigurandoColunas,
SelecionandoAba(Vec<String>), // lista de abas XLSX
Analisando,
ConfirmandoIntervalo(ResultadoPreAnalise),
ExibindoResultado(ResultadoAnalise),
GerenciandoLayouts,
}
- Gerenciamento de modais/popups bloqueantes (RNF06)
- Struct
Appcom 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
LetraLinhade 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:
0001exibido como1) - 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 extremosdetector_sequencia: sequência completa, com faltantes, série com um único registro, intervalos > 10.000detector_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_monetarioe odetector_sequenciasã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.