atualiza versão do PRD para 1.6 e detalha arquitetura e estrutura de pastas do projeto
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user