From 31cd94907aecb108866af99be649e6d379c155ea Mon Sep 17 00:00:00 2001 From: Felipe Canin Novaes Date: Mon, 2 Mar 2026 21:39:05 -0300 Subject: [PATCH] =?UTF-8?q?atualiza=20vers=C3=A3o=20do=20PRD=20para=201.6?= =?UTF-8?q?=20e=20detalha=20arquitetura=20e=20estrutura=20de=20pastas=20do?= =?UTF-8?q?=20projeto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IMPLEMENTACAO.md | 429 +++++++++++++++++++++++++++++++++++++++++++++++ PRD.md | 196 ++++++++++++++++++++-- 2 files changed, 613 insertions(+), 12 deletions(-) create mode 100644 IMPLEMENTACAO.md diff --git a/IMPLEMENTACAO.md b/IMPLEMENTACAO.md new file mode 100644 index 0000000..753df8f --- /dev/null +++ b/IMPLEMENTACAO.md @@ -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, + pub data: Option, +} +``` + +### 1.3 `domain/entities/serie.rs` + +- Validação da regex `[0-9]{1,3}` +- Função `validar_serie(s: &str) -> Result` + +### 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, +} + +pub struct ResultadoAnalise { + pub faltantes_por_serie: HashMap>, + pub duplicadas_por_serie: HashMap>, + pub soma_total: Decimal, + pub soma_por_serie: HashMap, +} +``` + +### 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` 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 +fn listar() -> Result> +fn buscar_por_id(id: i64) -> Result> +fn excluir(id: i64) -> Result<()> +fn existe_nome(nome: &str) -> Result +``` + +### 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>` (linhas × colunas) + +### 2.5 `infrastructure/xlsx_reader.rs` + +- `listar_abas(path) -> Vec` — 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` +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` +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), // 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. diff --git a/PRD.md b/PRD.md index dd03670..e7bbdb5 100644 --- a/PRD.md +++ b/PRD.md @@ -1,6 +1,6 @@ # PRD — Comparador de Notas -**Versão:** 1.5 +**Versão:** 1.6 **Data:** 02/03/2026 **Status:** Planejamento @@ -468,22 +468,194 @@ Fluxo principal: --- -# 9. Arquitetura Inicial Sugerida +# 9. Arquitetura -Arquitetura desktop local. +Arquitetura desktop local com separação em quatro camadas: `domain`, `application`, `infrastructure` e `ui`. -Componentes: - -* Interface gráfica -* Módulo de importação -* Módulo de processamento -* Módulo de configuração - -Sem dependências externas obrigatórias. +Não é Clean Architecture radical. É apenas separação suficiente para manter fronteiras claras, domínio isolado e infraestrutura concreta sem vazar para a lógica de negócio. --- -## 9.1 Stack Tecnológica +## 9.1 Estrutura de Pastas + +``` +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 +``` + +--- + +## 9.2 Papel de Cada Camada + +### Domain (núcleo puro) + +Contém toda a lógica de negócio real. + +**Não pode depender de:** + +* egui +* rusqlite +* calamine +* csv +* genpdf + +Apenas Rust puro + crates matemáticas (`rust_decimal`, `chrono`). + +#### Entidades + +**`Nota`** + +| Campo | Tipo | +| ------ | ----------------- | +| numero | `u64` | +| serie | `String` | +| valor | `Option` | +| data | `Option` | + +**`ResultadoAnalise`** + +| Campo | Tipo | +| -------------------- | -------------------------------------- | +| faltantes_por_serie | `HashMap>` | +| duplicadas_por_serie | `HashMap>` | +| soma_total | `Decimal` | +| soma_por_serie | `HashMap` | + +> **Importante:** a geração dos faltantes não deve ser eager. O use case `executar_analise` deve primeiro calcular os intervalos por série e retornar um resultado intermediário (`ResultadoPreAnalise`) contendo o intervalo calculado. Somente após confirmação do usuário — quando algum intervalo exceder 10.000 registros (RF04) — o sistema expande e materializa a lista completa de faltantes. Isso evita alocar memória para intervalos gerados por mapeamento incorreto de colunas. + +#### Services + +`detector_sequencia` — recebe `Vec` agrupadas por série, retorna faltantes. + +`detector_duplicidade` — retorna mapa de contagem por `(numero, serie)`. + +`parser_monetario` — implementa exatamente o algoritmo definido no RF06. + +#### Erros + +`domain/errors.rs` define os erros do domínio de forma tipada (ex: `ErroSerie::Invalida`, `ErroNumero::Zero`, `ErroValor::Negativo`). Nenhuma camada deve propagar `String` livre como erro de domínio. + +--- + +### Application (orquestração) + +Coordenam o fluxo entre domain e infrastructure. + +Conhece o domain. O domain não conhece o application. + +**`executar_analise.rs`** + +1. Recebe dados crus +2. Chama `parser_monetario` +3. Chama `detector_sequencia` +4. Chama `detector_duplicidade` +5. Monta `ResultadoAnalise` + +**`exportar_pdf.rs`** + +Depende de uma trait abstrata (`PdfGenerator`) definida no próprio módulo application. A implementação concreta fica em `infrastructure/pdf_generator.rs`. Isso evita que o application dependa diretamente de `genpdf`. + +--- + +### Infrastructure (implementações concretas) + +Implementa leitores, persistência e geração de arquivos. + +| Arquivo | Responsabilidade | +| -------------------------------- | ----------------------------------------- | +| `csv_reader.rs` | Leitura de arquivos CSV via `csv` | +| `xlsx_reader.rs` | Leitura de arquivos XLSX via `calamine` | +| `pdf_generator.rs` | Geração de PDF via `genpdf` | +| `sqlite/connection.rs` | Abertura e inicialização da conexão SQLite | +| `sqlite/migrations.rs` | Aplicação de migrations de schema | +| `sqlite/layout_repository.rs` | CRUD de layouts via `rusqlite` | + +Nada de infrastructure sobe para domain. + +#### Por que `layout_repository.rs` dentro de `sqlite/` + +Manter o repositório dentro de `sqlite/` concentra todos os artefatos SQLite em um único módulo. Se futuramente o sistema armazenar histórico de análises ou configurações do usuário (RF14/10.8), novos repositórios são adicionados no mesmo lugar sem dispersão. + +--- + +### UI (interface) + +Apenas coleta input, chama use cases e renderiza resultado. + +Nenhuma regra de sequência ou parsing monetário deve estar na camada de UI. + +#### Screens + +| Arquivo | Responsabilidade | +| ------------------------- | ----------------------------------------------------- | +| `import.rs` | Seleção de arquivo e configurações de importação | +| `configuracao_colunas.rs` | Mapeamento de colunas (RF02) | +| `layouts.rs` | Gerenciamento de layouts: salvar, carregar, excluir (RF08) | +| `resultado.rs` | Exibição de resultados com paginação (RF07) | + +> `configuracao_colunas.rs` e `layouts.rs` são mantidos separados porque tratam de responsabilidades distintas do RF02 e RF08, evitando que uma única screen acumule lógica de mapeamento de colunas e gerenciamento de persistência. + +--- + +## 9.3 Fluxo de Execução + +``` +UI → Application → Domain +Infrastructure entra apenas quando necessário. +``` + +Exemplo real: + +1. UI chama `importar_arquivo` +2. Infrastructure lê CSV/XLSX +3. Application transforma registros em entidades `Nota` +4. Domain executa análise +5. Application retorna `ResultadoAnalise` +6. UI renderiza + +--- + +## 9.4 Stack Tecnológica | Camada | Tecnologia | Status | | ---------------- | ------------------ | -------------- |