# Domain — AGENTS.md Camada de domínio do projeto `comparador-notas`. Contém as entidades, serviços de negócio e erros tipados. Não possui dependências de infraestrutura — todo acesso a banco, arquivos ou UI é responsabilidade das camadas superiores. --- ## Estrutura dos arquivos ``` src/domain/ ├── mod.rs ├── errors.rs # Enums de erro tipados ├── entities/ │ ├── mod.rs │ ├── chave_serie.rs # Chave composta (serie + documento_tipo) │ ├── layout.rs # Entidades de configuração de layout CSV/XLSX │ ├── nota.rs # Entidade Nota Fiscal │ ├── resultado_analise.rs # Resultados de análise (pré e completo) │ └── serie.rs # Validação de série └── services/ ├── mod.rs ├── detector_duplicidade.rs # Detecção de notas duplicadas ├── detector_sequencia.rs # Detecção de notas faltantes na sequência └── parser_monetario.rs # Parsing e formatação de valores monetários ``` --- ## errors.rs Erros tipados com a crate `thiserror`. Cada domínio tem seu próprio enum. ### `ErroSerie` | Variante | Mensagem | |---|---| | `Invalida(String)` | Série inválida: deve conter de 1 a 3 dígitos numéricos | | `Vazia` | Série vazia | ### `ErroValor` | Variante | Mensagem | |---|---| | `Negativo(String)` | Valor negativo não é permitido | | `NaoNumerico(String)` | Valor não numérico | ### `ErroLayout` | Variante | Mensagem | |---|---| | `CampoObrigatorioAusente(String)` | Campo obrigatório ausente | | `JsonMalformado(String)` | JSON malformado | | `NomeConflitante(String)` | Layout com esse nome já existe | ### `ErroArquivo` | Variante | Mensagem | |---|---| | `TamanhoExcedido(u64)` | Arquivo > 50 MB | | `Corrompido(String)` | Arquivo corrompido ou ilegível | | `ErroLeitura(String)` | Erro genérico de leitura | ### `ResumoAvisos` Estrutura de avisos não-fatais acumulados durante a importação de um arquivo. ```rust pub struct ResumoAvisos { pub linhas_malformadas: usize, pub numeros_invalidos: usize, pub series_invalidas: usize, pub valores_invalidos: usize, pub detalhes: Vec, // mensagens individuais por linha } ``` - `tem_avisos()` → `true` se qualquer contador > 0 - `linhas_para_exibir()` → `Vec` com resumo para exibição em modal --- ## entities/ ### `chave_serie.rs` — `ChaveSerie` Chave composta que identifica um grupo de notas fiscais. Combina série com tipo de documento (`NFE`, `NFCE`, etc.). ```rust pub struct ChaveSerie { pub serie: String, pub documento_tipo: Option, } ``` - Deriva `Hash`, `Eq`, `Ord` — usada como chave em `HashMap` e para ordenação. - `new(serie, documento_tipo)` — construtor. - `label()` — formata para exibição: - `Some(tipo)` → `"001 / NFE"` - `None` → `"001"` ### `nota.rs` — `Nota` Entidade central. `(numero, serie, documento_tipo)` é o identificador único. ```rust pub struct Nota { pub numero: u64, pub serie: String, pub documento_tipo: Option, // None quando não mapeado pub valor: Option, pub data: Option, // usado no PDF, não em regras } ``` ### `serie.rs` — `validar_serie` ```rust pub fn validar_serie(s: &str) -> Result ``` Valida via regex `^[0-9]{1,3}$` (1 a 3 dígitos numéricos). Faz trim antes de validar. Usa `OnceLock` para compilar o regex uma única vez. ### `resultado_analise.rs` Dois tipos de resultado que modelam o fluxo de análise em duas etapas: #### `ResultadoPreAnalise` Resultado intermediário — calculado sem expandir a lista completa de faltantes. Usado para verificar se algum intervalo excede 10.000 registros (RF04) antes de pedir confirmação ao usuário. ```rust pub struct ResultadoPreAnalise { pub intervalos_por_serie: HashMap, pub duplicadas_por_serie: HashMap>, pub soma_total: Decimal, pub soma_por_serie: HashMap, pub total_por_serie: HashMap, } ``` #### `IntervaloSerie` ```rust pub struct IntervaloSerie { pub minimo: u64, pub maximo: u64, pub contagem_faltantes: u64, } ``` - `excede_limite(limite)` → `contagem_faltantes > limite` #### `ResultadoAnalise` Resultado completo com a lista materializada de faltantes. ```rust pub struct ResultadoAnalise { pub faltantes_por_serie: HashMap>, // ordenados crescentemente pub duplicadas_por_serie: HashMap>, pub soma_total: Decimal, pub soma_por_serie: HashMap, pub total_por_serie: HashMap, } ``` Métodos auxiliares: - `sem_inconsistencias()` → `true` se não há faltantes nem duplicatas - `total_faltantes()` → soma do tamanho de todas as listas de faltantes - `total_duplicatas()` → soma do número de grupos de duplicatas ### `layout.rs` Entidades de configuração de layout de arquivo. #### `TipoArquivo` ```rust pub enum TipoArquivo { Csv, Xlsx } ``` #### `LayoutCsv` | Campo | Tipo | Descrição | |---|---|---| | `delimitador` | `char` | `','`, `';'` ou `'\t'` | | `encoding` | `String` | `"utf-8"` ou `"windows-1252"` | | `linha_cabecalho` | `usize` | Linha do cabeçalho (base 1). `0` = sem cabeçalho | | `indice_numero` | `usize` | Índice da coluna Numero (base 0) | | `indice_serie` | `usize` | Índice da coluna Serie (base 0) | | `indice_valor` | `Option` | Índice da coluna Valor (base 0), opcional | | `indice_data` | `Option` | Índice da coluna Data (base 0), opcional | | `indice_documento_tipo` | `Option` | Índice da coluna Tipo Documento (base 0), opcional | #### `LayoutXlsx` | Campo | Tipo | Descrição | |---|---|---| | `aba` | `String` | Nome da aba a processar | | `pos_numero` | `String` | Posição LetraLinha (ex: `"D3"`) | | `pos_serie` | `String` | Posição LetraLinha (ex: `"B3"`) | | `pos_valor` | `Option` | Posição LetraLinha, opcional | | `pos_data` | `Option` | Posição LetraLinha, opcional | | `pos_documento_tipo` | `Option` | Posição LetraLinha, opcional | #### `Layout` (enum) ```rust pub enum Layout { Csv { id: Option, nome: String, config: LayoutCsv }, Xlsx { id: Option, nome: String, config: LayoutXlsx }, } ``` Métodos: `id()`, `nome()`, `tipo()`. #### `LayoutJson` (serialização) Representação `serde` com tag `"tipo"` para importação/exportação em JSON. Implementa `TryFrom for Layout` (valida campos obrigatórios) e `From<&Layout> for LayoutJson`. --- ## services/ ### `detector_duplicidade.rs` #### `detectar_duplicidades(notas) -> HashMap<(u64, String, Option), usize>` Conta ocorrências de cada `(numero, serie, documento_tipo)`. Retém apenas grupos com mais de uma ocorrência. #### `duplicidades_por_serie(notas) -> HashMap>` Agrupa o resultado de `detectar_duplicidades` por `ChaveSerie`. Cada vetor é ordenado por `numero` crescente. Regras: - O mesmo número em séries diferentes **não** é duplicata. - O mesmo número com `documento_tipo` diferente **não** é duplicata. - O mesmo número com mesma série e mesmo `documento_tipo` **é** duplicata. --- ### `detector_sequencia.rs` #### Constante ```rust pub const LIMITE_FALTANTES: u64 = 10_000; ``` #### `calcular_intervalo(notas) -> Option` Calcula `minimo`, `maximo` e `contagem_faltantes` de forma incremental (usando `windows(2)`) **sem materializar** a lista de faltantes. Deduplica números antes do cálculo para não contar duplicatas como faltantes. #### `detectar_faltantes(notas) -> Vec` Materializa a lista completa de faltantes em ordem crescente. Deve ser chamado apenas após confirmação do usuário quando `calcular_intervalo` indica que o intervalo excede `LIMITE_FALTANTES`. #### `agrupar_contiguos(faltantes) -> Vec<(u64, u64)>` Recebe uma lista **já ordenada** de números faltantes e retorna intervalos contíguos como pares `(inicio, fim)`. Números isolados têm `inicio == fim`. Exemplo: `[1, 2, 3, 5, 8, 9]` → `[(1, 3), (5, 5), (8, 9)]` --- ### `parser_monetario.rs` #### `parse_valor(input) -> Result` Faz o parsing de uma string monetária suportando formatos brasileiro e americano. Rejeita valores negativos. Algoritmo (RF06): | Condição | Regra | |---|---| | Contém ponto **e** vírgula | Último separador é o decimal | | Apenas ponto, 3 dígitos após | Separador de milhar (`1.234` → `1234`) | | Apenas ponto, outros casos | Decimal (`1000.00`) | | Apenas vírgula, 3 dígitos após | Separador de milhar (`1,234` → `1234`) | | Apenas vírgula, outros casos | Decimal (`1000,00` → `1000.00`) | | Sem separador | Número inteiro | Aceita prefixo `R$` (case-insensitive). #### `formatar_valor_br(valor) -> String` Formata `Decimal` para exibição brasileira com 2 casas decimais e pontos de milhar. Ex: `1234567.89` → `"1.234.567,89"`.