Files
comparador-notas/src/domain/AGENTS.md
T
2026-03-04 18:18:11 -03:00

8.9 KiB

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.

pub struct ResumoAvisos {
    pub linhas_malformadas: usize,
    pub numeros_invalidos: usize,
    pub series_invalidas: usize,
    pub valores_invalidos: usize,
    pub detalhes: Vec<String>,  // mensagens individuais por linha
}
  • tem_avisos()true se qualquer contador > 0
  • linhas_para_exibir()Vec<String> com resumo para exibição em modal

entities/

chave_serie.rsChaveSerie

Chave composta que identifica um grupo de notas fiscais. Combina série com tipo de documento (NFE, NFCE, etc.).

pub struct ChaveSerie {
    pub serie: String,
    pub documento_tipo: Option<String>,
}
  • 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.rsNota

Entidade central. (numero, serie, documento_tipo) é o identificador único.

pub struct Nota {
    pub numero: u64,
    pub serie: String,
    pub documento_tipo: Option<String>,  // None quando não mapeado
    pub valor: Option<Decimal>,
    pub data: Option<NaiveDate>,         // usado no PDF, não em regras
}

serie.rsvalidar_serie

pub fn validar_serie(s: &str) -> Result<String, ErroSerie>

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.

pub struct ResultadoPreAnalise {
    pub intervalos_por_serie: HashMap<ChaveSerie, IntervaloSerie>,
    pub duplicadas_por_serie: HashMap<ChaveSerie, Vec<(u64, usize)>>,
    pub soma_total: Decimal,
    pub soma_por_serie: HashMap<ChaveSerie, Decimal>,
    pub total_por_serie: HashMap<ChaveSerie, usize>,
}

IntervaloSerie

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.

pub struct ResultadoAnalise {
    pub faltantes_por_serie: HashMap<ChaveSerie, Vec<u64>>,  // ordenados crescentemente
    pub duplicadas_por_serie: HashMap<ChaveSerie, Vec<(u64, usize)>>,
    pub soma_total: Decimal,
    pub soma_por_serie: HashMap<ChaveSerie, Decimal>,
    pub total_por_serie: HashMap<ChaveSerie, usize>,
}

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

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<usize> Índice da coluna Valor (base 0), opcional
indice_data Option<usize> Índice da coluna Data (base 0), opcional
indice_documento_tipo Option<usize> Í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<String> Posição LetraLinha, opcional
pos_data Option<String> Posição LetraLinha, opcional
pos_documento_tipo Option<String> Posição LetraLinha, opcional

Layout (enum)

pub enum Layout {
    Csv  { id: Option<i64>, nome: String, config: LayoutCsv  },
    Xlsx { id: Option<i64>, 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<LayoutJson> for Layout (valida campos obrigatórios) e From<&Layout> for LayoutJson.


services/

detector_duplicidade.rs

detectar_duplicidades(notas) -> HashMap<(u64, String, Option<String>), 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<ChaveSerie, Vec<(u64, usize)>>

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

pub const LIMITE_FALTANTES: u64 = 10_000;

calcular_intervalo(notas) -> Option<IntervaloSerie>

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

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<Decimal, ErroValor>

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.2341234)
Apenas ponto, outros casos Decimal (1000.00)
Apenas vírgula, 3 dígitos após Separador de milhar (1,2341234)
Apenas vírgula, outros casos Decimal (1000,001000.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".