Files
comparador-notas/IMPLEMENTACAO.md
T

14 KiB
Raw Blame History

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 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

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

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

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 14 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.