cmd/nanci: Ponto de entrada (main) do CLI.cmd/mockcert: Gera com o OpenSSL o certificado A1 de teste eminternal/foundation/cert/testdata/(make mockcert).cmd/seeddev: Cria um banco de desenvolvimento emdevdata/com uma empresa, o certificado de teste e algumas NFS-e de exemplo (make seeddev).internal/cli: Adaptadores e configuração dos comandos Cobra (ex:nanci pull,nanci export,nanci nfe,nanci cte).internal/desktop: O aplicativo Wails completo. Contém o código Go que faz ponte com o frontend, e dentro dele,frontend/com o código Vue 3 / TypeScript.internal/app: Casos de uso centrais. Responsável por inicializar as dependências e amarrar repositórios com serviços. Os casos de uso de NF-e ficam emNFeService(internal/app/nfe*.go) e os de CT-e emCTeService(internal/app/cte*.go).internal/sync: Loop de sincronização por NSU, genérico por origem (NFS-e, NF-e e CT-e), com orçamento de consultas, bloqueios e carregamento do certificado (CertificateLoader).internal/syncstate: Estado da sincronização por empresa e origem, sem o loop:SyncSource, os registrosSyncRuneSyncState, os enums de status, modo, motivo de parada e política inicial (SyncStartPolicy), o evento de progresso e os parâmetros que o store recebe para gravá-los. Pacote folha, só importainternal/dfe. Não fica eminternal/syncporquesyncimportastoreestoreprecisa desses tipos.internal/company: Cadastro das empresas (o tipoCompany,StoreeManager): validação de CNPJ e UF, política inicial de sincronização e vínculo com a credencial, que precisa ter a mesma raiz de CNPJ.internal/credential: Cadastro das credenciais (certificados A1 reutilizáveis entre empresas): o tipoCredential, caminho do arquivo e rótulo. A senha fica no keyring do sistema, a cargo deinternal/app.internal/store: Camada de persistência. Contém as queries (frequentemente geradas via sqlc), conexões SQLite e a pastamigrations_v2/com o schema do banco.internal/store/sqlgen: Código gerado pelo sqlc a partir deinternal/store/queries, com o schema lido das migrations emmigrations_v2/(configuração emsqlc.yaml). Não editar à mão.internal/files: Armazenamento dos XMLs brutos emblobs/, um arquivo por hash SHA-256 (BlobStore).internal/nfseeinternal/adn: Modelos da Nota Fiscal de Serviço Eletrônica (documento, eventos, chave de 50 caracteres, papel da empresa e parsers; só NFS-e, comonfeecte) e cliente da API do Ambiente de Dados Nacional.internal/danfse: InterfaceRenderer(XML da NFS-e entra, PDF do DANFSe sai) e a implementação emgodanfsev2/, com a bibliotecago-danfse-v2. O DANFSe é gerado localmente, sem chamar a API do ADN.internal/danfeeinternal/dacte: InterfacesRenderer(XML da NF-e ou do CT-e entra, PDF do DANFE ou do DACTE sai, com a marca d'água de cancelada opcional) e as implementações emfiscalrenderer/, com a biblioteca fiscal-renderer (LGPL-3.0). O DACTE só tem o leiaute do CT-e modelo 57: CT-e OS, GTV-e e CT-e Simplificado ficam sem PDF (cte.Document.HasDACTE).internal/dfe: Vocabulário comum dos documentos fiscais eletrônicos da SEFAZ (NF-e, CT-e): chave de acesso de 44 caracteres, valores monetários (Money) eErrInvalidEnum, mais o que todas as origens usam, NFS-e inclusive:CompanyID,CredentialID,GenerateIDeEnvironment(producaoouproducao_restrita). Sem código de rede ou banco.internal/nfe: Modelos da NF-e (modelo 55): leitura deresNFe,procNFee eventos, papel da empresa na nota, regras de mesclagem resumo/completa, estado e prazos da manifestação.internal/cte: Modelos do CT-e (modelos 57 e 67, GTV-e modelo 64 e CT-e Simplificado): leitura deprocCTe,procCTeOS,procGTVe,procCTeSimpeprocEventoCTe, resolução do tomador, papéis da empresa no documento e regras de mesclagem e situação.internal/sefaz: Cliente SOAP dos webservices do Ambiente Nacional da NF-e e do CT-e (NFeDistribuicaoDFe,CTeDistribuicaoDFeeNFeRecepcaoEvento4) e assinatura XMLDSig dos eventos. Só fala o protocolo; regras de armazenamento e sincronização ficam com quem chama.internal/report: Escritores das exportações compartilhados pelas origens:WriteCSVeWriteXLSXrecebem umaSpreadsheet(colunas, abas e linhas) que cada origem monta com seu próprio tipo de linha (NFSeRow,NFeRow,CTeRow),GenerateZIPgrava os XMLs no.zipconforme as entradas de cada origem eGeneratePDFZIPgrava no.zipo PDF que o renderizador da origem gera de cada XML.internal/foundation: Utilitários gerais do projeto (parsers de CNPJ, códigos de UF emuf, handlers de build, criptografia).internal/foundation/httpclient: Cliente HTTP mTLS neutro compartilhado porinternal/adneinternal/sefaz: transporte TLS, retry, leitura limitada do corpo e registros de log. Quem chama define cabeçalhos, formato do payload, o significado de cada status e como mascarar identificadores (CNPJ, chave de acesso) antes de irem para o log.internal/foundation/redact: Mascaramento de identificadores fiscais (CNPJ, CPF, chave de acesso, nomes) em XML de NF-e, CT-e e NFS-e.internal/sefazeinternal/adnmascaram os corpos de resposta que vão para o log e para as mensagens de erro;internal/adnmascara também ocnpjConsultae a chave deNFSe/{chave}/...nas URLs.internal/syncmascara a prévia do XML que não pôde ser lido. Nas respostas JSON do ADN a máscara não alcança o XML, que vem em base64 com gzip (ArquivoXml).internal/foundation/xmlwalk: Leitura de XML em fluxo que entrega cada elemento pelo caminho a partir da raiz (Walk,HasAnySuffix,AttrValue), para os parsers de documentos fiscais casarem campos por sufixo de caminho. Usado porinternal/nfeeinternal/cte.third_party/gonfe: Licença do gonfe (MIT), do qual partes deinternal/sefazforam adaptadas.
- Entrada de Dados: O usuário interage pelo Frontend Wails ou pelo Terminal CLI.
- Orquestração: A requisição bate no
internal/appque delega para ointernal/sync. - Comunicação Segura: O
synccarrega o certificado da empresa e consulta a origem escolhida por mTLS:internal/adnpara NFS-e,internal/sefazpara NF-e e CT-e. - Persistência Bruta: Os XMLs recebidos são validados no
internal/nfse,internal/nfeouinternal/ctee salvos de forma intacta no armazenamento de blobs, com os metadados no SQLite viainternal/store. - Processamento: Partes vitais do XML (Emitente, Tomador/Destinatário, Valor, Descrição) são cacheadas em colunas específicas no banco para pesquisa rápida e exportação.
Cada serviço de distribuição por NSU é uma implementação da interface Source em internal/sync/source.go:
Kind()identifica a origem (nfse,nfe,cte).Policy()informa o intervalo entre requisições e o limite de consultas por hora (0 = sem limite). A NFS-e não tem limite; a NF-e e o CT-e têm 20 por hora cada, contados separadamente. Na NF-e, o limite de 20 por hora da NT 2014.002 vale paraconsNSUeconsChNFesomados, e odistNSUtem a espera de 1 hora depois docStat137; contar também odistNSUnas 20 é escolha conservadora do Nanci (ver NFE_SEFAZ.md).Fetch()busca a página seguinte ao cursor e traduz a resposta em umBatch: itens, próximo cursor, se deve parar e até quando esperar (WaitUntil).ProcessItem()decodifica e grava um item e chamacommitexatamente uma vez, para que o checkpoint avance na mesma transação.
O loop (internal/sync/loop.go) é o mesmo para todas as origens. Ele desconta o orçamento antes de cada requisição, grava o bloqueio pedido pela origem e pula, como não suportado, um item que falha ao ser interpretado três vezes seguidas no mesmo NSU. O Manager recusa o pull de uma origem bloqueada antes de pedir a senha, e não deixa dois pulls da mesma empresa e origem rodarem ao mesmo tempo.
O estado é separado por empresa, origem e ambiente:
sync_stateesync_runs: cursor, checkpoint e histórico de execuções, com as colunassourceeenvironment.company_sync_sources: data em que a sincronização inicial terminou eblocked_until/blocked_reason, por empresa, origem e ambiente. Zerar o estado local limpa a sincronização inicial em todos os ambientes, mas mantém o bloqueio.sync_requests: uma linha por requisição enviada, com a origem e o ambiente, para a janela móvel de 1 hora do orçamento.
Uma nova origem precisa de um Source, tabelas e tela próprias; o loop, o orçamento, o httpclient e o CertificateLoader servem sem mudança, como aconteceu com o CT-e. Os detalhes da NF-e estão em NFE_SEFAZ.md e os do CT-e em CTE_SEFAZ.md.
internal/app usa internal/store e internal/sync direto, de propósito: a camada de casos de uso é fina e os repositórios são testados com SQLite real, então uma interface entre os dois só repetiria as assinaturas.
Os pacotes de domínio internal/nfse, internal/nfe, internal/cte, internal/dfe e internal/syncstate não importam internal/app, internal/store nem database/sql. A regra domain do depguard em .golangci.yml cobra isso, mas só fora do Windows. No Windows os caminhos usam barra invertida, os globs de files não casam e a regra não dispara.