Aplicação para gerenciamento de NFSe com backend FastAPI, frontend web estático e empacotamento desktop com Tauri.
O projeto armazena clientes e notas em um banco SQLite local, sincroniza dados com o portal de NFSe, baixa XML/PDF, gera relatórios e exibe indicadores no dashboard.
- Web app servido pelo próprio FastAPI em
backend/main.py. - Frontend SPA em
frontend/, com páginas carregadas dinamicamente. - Desktop app em
ui/, usando Tauri como shell nativo. - Banco local:
nfse.db.
- Cadastro, edição e remoção de clientes.
- Sincronização de notas emitidas e recebidas por período.
- Download de XML e PDF.
- Recuperação de arquivos ausentes usando as URLs salvas no banco.
- Exportação de relatórios em JSON, CSV e XLSX.
- Dashboard com estatísticas, gráfico temporal e ranking.
- Verificação de atualização do sistema via GitHub Releases.
- Navegação de pastas para seleção de diretórios de saída.
- Python 3.12+
- FastAPI
- SQLAlchemy
- SQLite
- Pydantic
- Uvicorn
- OpenPyXL
- HTTPX
- Frontend em HTML/CSS/JavaScript puro
- Tauri para desktop
nfse_manager/
├── 📁 backend/ # API, modelos, schemas e rotas
├── 🎨 frontend/ # UI web servida pelo FastAPI
├── 🖥️ ui/ # Shell desktop com Tauri
├── 📚 docs/ # Documentação do projeto
├── 🗃️ nfse.db # Banco SQLite local
├── 🧩 pyproject.toml # Dependências e configuração do Poetry
├── ▶️ run_web.sh # Inicializa a versão web
└── 🚀 run_ui.sh # Inicializa a versão desktop
Leia também:
docs/arquitetura-e-uso.md
Os exemplos abaixo foram condensados do USAGE_EXAMPLES.md para facilitar o uso direto pelo README.
python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reloadimport uvicorn
from backend.main import app
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)python -c "from backend.main import app; import uvicorn; uvicorn.run(app, port=8000)"Use sempre os caminhos do pacote backend:
from backend.models import Client, Invoice
from backend.schemas import ClientResponse, SyncRequest, DashboardStats
from backend.database import get_db, SessionLocal
from backend.main import app
from backend.api.deps import get_nfse_client
from backend.api.state import tasks_statuscurl -X POST "http://localhost:8000/api/clients" \
-F "cnpj=19.495.981/0001-13" \
-F "razao_social=Minha Empresa" \
-F "username=usuario" \
-F "password=senha123" \
-F "description=Cliente de teste"Com certificado:
curl -X POST "http://localhost:8000/api/clients" \
-F "cnpj=19.495.981/0001-13" \
-F "razao_social=Minha Empresa" \
-F "username=usuario" \
-F "password=senha123" \
-F "cert_file=@certificado.pfx"curl "http://localhost:8000/api/clients"curl -X POST "http://localhost:8000/api/sync" \
-H "Content-Type: application/json" \
-d '{
"client_id": 1,
"data_inicio": "01/01/2026",
"data_fim": "31/05/2026",
"tipo": "emitidas"
}'curl "http://localhost:8000/api/tasks/{task_id}"curl "http://localhost:8000/api/analytics/stats?client_id=1"# JSON
curl "http://localhost:8000/api/reports/export?format=json&period=30d" > relatorio.json
# CSV
curl "http://localhost:8000/api/reports/export?format=csv&period=30d" > relatorio.csv
# XLSX
curl "http://localhost:8000/api/reports/export?format=xlsx&period=30d" > relatorio.xlsx- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
Para adicionar um novo domínio de negócio:
- criar o arquivo em
backend/api/routers/; - declarar um
APIRoutercom prefixo próprio; - importar e registrar o router em
backend/main.py.
Exemplo mínimo:
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from backend.database import get_db
router = APIRouter(prefix="/api/novo_dominio", tags=["novo_dominio"])
@router.get("/rota-1")
def exemplo(db: Session = Depends(get_db)):
return {"status": "ok"}python -c "from backend.main import app; print('✓ OK')"
curl "http://localhost:8000/openapi.json" | python -m json.tool
pytest tests/ -vpoetry install --with devO projeto não usa requirements.txt; a referência oficial de dependências é o pyproject.toml.
chmod +x run_web.sh
./run_web.shOu, manualmente:
poetry run python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reloadA aplicação ficará disponível em:
http://localhost:8000- documentação automática da API em
http://localhost:8000/docs - alternativa em
http://localhost:8000/redoc
chmod +x run_ui.sh
./run_ui.shO script entra na pasta ui/ e chama o fluxo de desenvolvimento do Tauri.
O backend principal está em backend/main.py e:
- inicializa o banco com
init_db(); - registra os routers da API;
- monta a pasta
frontend/na raiz da aplicação; - aplica CORS liberado para facilitar o uso local.
backend/api/routers/clients.py— CRUD de clientes.backend/api/routers/invoices.py— Sincronização e listagem de faturas (invoices).backend/api/routers/downloads.py— Download de arquivos e lote ZIP.backend/api/routers/analytics.py— Estatísticas e gráficos.backend/api/routers/reports.py— Exportação de relatórios.backend/api/routers/system.py— Suporte, update check e navegação de pastas.
GET /api/clientsPOST /api/clientsPUT /api/clients/{client_id}DELETE /api/clients/{client_id}
POST /api/syncGET /api/tasks/{task_id}GET /api/invoices
POST /api/download/sync-portalGET /api/download/file?path=...POST /api/download/batch
GET /api/analytics/statsGET /api/analytics/chartPOST /api/analytics/enrich-xmlGET /api/analytics/ranking
GET /api/reports/export?format=json|csv|xlsx
POST /api/support/messageGET /api/system/check-updateGET /api/system/list-folders
Guarda credenciais e configuração de cada cliente:
- CNPJ
- razão social
- usuário/senha do portal
- certificado A1 opcional
- caminho base de salvamento
- estrutura personalizada de diretórios
Guarda as notas sincronizadas:
- identificador interno
- cliente vinculado
- chave da nota
- número
- status
- tipo (
emitidas/recebidas) - valor
- caminhos do XML e PDF
- URLs de download do portal
- Abrir a aplicação web ou desktop.
- Cadastrar um cliente na área de clientes.
- Sincronizar notas por período.
- Baixar XML/PDF quando necessário.
- Gerar relatórios ou acompanhar o dashboard.
- Datas de filtro e sincronização usam o formato
DD/MM/AAAA. - O progresso das tarefas é guardado em memória; reiniciar o processo limpa o status.
- O sistema depende da estrutura atual do portal de NFSe; mudanças no HTML podem exigir ajustes.
- Os arquivos baixados seguem a estrutura configurada por cliente.
ModuleNotFoundError: No module named 'backend': execute os comandos a partir da raiz do projeto.- Porta já em uso: altere a porta no comando do Uvicorn.
- Banco não encontrado: o arquivo
nfse.dbé criado automaticamente ao inicializar a aplicação. - Se estiver importando módulos antigos, troque por
backend.models,backend.schemasebackend.database.
./run_web.sh # sobe a API + frontend
./run_ui.sh # inicia o desktop TauriEste projeto centraliza a versão no pyproject.toml (campo project.version). Para manter tudo sincronizado e facilitar releases, seguem ferramentas e recomendações:
- Fonte única de verdade: edite a versão em
pyproject.toml(ex.:version = "0.2.0"). - O backend expõe essa versão via
backend.version.VERSIONe existe um endpoint leve/api/system/versionque retorna{ "current_version": "X.Y.Z" }. - A página Sobre carrega a versão dinamicamente e o botão de update não abre mais o GitHub: ele baixa o ZIP da release em background, extrai em
release/staging/e só depois libera a aplicação. - Para atualizar automaticamente trechos estáticos (ex.:
frontend/views/about.html) execute:
python3 scripts/sync_version.py- Para criar a tag Git e a release no GitHub localmente execute (após merge e com a árvore limpa):
- Para criar a tag Git e a release no GitHub localmente execute (após merge e com a árvore limpa):
chmod +x scripts/release.sh
./scripts/release.sh- Critério de "estável": por convenção aqui consideramos estável uma versão sem sufixo de pre-release. Ex.:
1.2.3é estável,1.3.0-alphae2.0.0-rc.1são pré-releases e não gerarão automaticamente a release.
Existe um workflow (.github/workflows/auto-release.yml) que é disparado quando uma PR para main é encerrada com merge e recebe a label release. O comportamento padrão do workflow:
- Executa testes (se existirem) e tenta criar uma tag
vX.Y.Ze uma release no GitHub; - Só cria release automática se a versão não contiver sufixos
-ou+(pré-release); - Só roda quando a PR estiver mesclada e marcada com a label
release; - Se preferir releases manuais, desabilite o workflow ou ajuste as condições.
Recomendações de processo:
- Atualize a versão em
pyproject.toml(usepoetry version patch/minor/majorse utilizar Poetry). - Abra PR para
main. Aguarde a execução do CI (testes/lint). - Depois do merge, o workflow de
auto-releasepode criar a tag e a release automaticamente.
Se quiser que eu adapte o workflow para um fluxo diferente (por exemplo: apenas criar release quando uma PR tiver uma label release ou quando um arquivo RELEASE.md for modificado), posso ajustar isso.
backend/
├── 🚪 main.py # inicia a API e serve o frontend
├── 🗄️ database.py # engine, sessão e get_db()
├── 🧱 models.py # Client, Invoice e init_db()
├── 🧾 schemas.py # modelos Pydantic
└── api/
├── 🔌 deps.py # cliente NFSe a partir do banco
├── 🧠 state.py # status de tarefas em memória
└── routers/
├── 👥 clients.py
├── 📝 invoices.py
├── ⬇️ downloads.py
├── 📊 analytics.py
├── 📤 reports.py
└── ⚙️ system.py
| Área | Arquivo | Função |
|---|---|---|
| API principal | backend/main.py |
Sobe o FastAPI e monta o frontend |
| Banco | backend/database.py |
Conexão SQLite e sessão SQLAlchemy |
| Modelos | backend/models.py |
Tabelas Client e Invoice |
| Schemas | backend/schemas.py |
Validação de dados |
| Clientes | backend/api/routers/clients.py |
CRUD de clientes |
| Invoices | backend/api/routers/invoices.py |
Sincronização e listagem |
| Downloads | backend/api/routers/downloads.py |
Baixa arquivos e gera ZIP |
| Analytics | backend/api/routers/analytics.py |
Estatísticas e ranking |
| Relatórios | backend/api/routers/reports.py |
Exportação JSON/CSV/XLSX |
| Sistema | backend/api/routers/system.py |
Update, suporte e pastas |
Contribuições, melhorias e correções são bem-vindas.
Cleiton Leonel Creton — cleiton.leonel@gmail.com

