Skip to content

Repository files navigation

LoanFlow

LoanFlow é um MVP full stack de gestão de empréstimos criado para demonstrar uma jornada de crédito completa: cadastro, autenticação, simulação, aprovação automática ou manual, cronograma de parcelas, pagamento demonstrativo e operação administrativa.

Este projeto usa dados e pagamentos fictícios. Ele é adequado para estudo, demonstração e portfólio; não deve processar crédito real sem as adaptações descritas em Limites do projeto.

Tela de login do LoanFlow

Outras telas: cadastro, painel do cliente e fila administrativa.

Funcionalidades

  • cadastro público exclusivo para clientes;
  • login com JWT e navegação por perfil;
  • papel do usuário revalidado no banco em toda requisição protegida;
  • simulação com acréscimo demonstrativo fixo de 5%;
  • aprovação automática ou encaminhamento para análise manual;
  • solicitação idempotente, segura contra reenvios após timeout;
  • dashboards de cliente e administrador;
  • listagem, detalhe e cronograma de empréstimos;
  • pagamento fictício de parcelas;
  • aprovação e rejeição administrativa com confirmação;
  • separação entre cliente e administrador, inclusive bloqueio de autorrevisão;
  • contas e dados opcionais para demonstração.

Regra de aprovação

Uma proposta é aprovada automaticamente quando atende simultaneamente a estes critérios:

  1. valor solicitado de até R$ 5.000;
  2. score simulado de pelo menos 650;
  3. parcela de até 30% da renda mensal.

Quando algum critério não é atendido, a proposta recebe o status pending_review e entra na fila administrativa.

Tecnologias

Backend

  • Node.js, TypeScript e Fastify;
  • OpenAPI 3.0 e Swagger UI;
  • PostgreSQL com postgres.js e migrations versionadas pelo Prisma;
  • JWT e bcrypt;
  • testes com node:test.

Frontend

  • Vue 3 e Vite;
  • Vue Router e Pinia;
  • Axios;
  • Lucide;
  • Cypress.

Estrutura

.
├── .github/workflows/     # integração contínua
├── src/
│   ├── controllers/       # tradução entre HTTP e serviços
│   ├── services/          # regras de negócio
│   ├── repositories/      # acesso ao PostgreSQL
│   ├── routes/            # rotas e schemas HTTP
│   ├── middlewares/       # autenticação e autorização
│   └── scripts/           # reset e seed do banco
├── prisma/                # schema declarativo e histórico de migrations
├── web/                   # aplicação Vue
├── cypress/               # testes de interface com API simulada
├── docs/                  # decisões e referências visuais
└── compose.yaml           # PostgreSQL local isolado

Pré-requisitos

  • Node.js 24 LTS recomendado;
  • npm 10 ou superior;
  • Docker com Docker Compose.

O arquivo .nvmrc fixa a versão usada no CI. Com nvm, execute nvm use na raiz.

Confira as versões instaladas:

node --version
npm --version
docker --version
docker compose version

Início rápido

1. Backend e banco

Na raiz do repositório:

cp .env.example .env
openssl rand -hex 32

Copie a saída do openssl para JWT_SECRET no arquivo .env. O valor de exemplo é recusado intencionalmente. Em seguida:

npm install
npm run db:up
npm run db:deploy
npm run dev

O PostgreSQL local fica disponível apenas em 127.0.0.1:5433, e a API inicia em:

http://localhost:3333

Valide a API:

curl http://localhost:3333

Resposta esperada:

{ "status": "ok", "name": "LoanFlow API" }

2. Frontend

Em outro terminal, a partir da raiz:

cp web/.env.example web/.env
cd web
npm install
npm run dev

Abra:

http://localhost:5173

Swagger e OpenAPI

Com a API em execução, a documentação interativa fica disponível em:

http://localhost:3333/docs

O contrato também pode ser consumido diretamente:

http://localhost:3333/docs/json
http://localhost:3333/docs/yaml

Para testar rotas protegidas:

  1. execute POST /auth/login;
  2. copie o valor de token da resposta;
  3. clique em Authorize no Swagger UI;
  4. informe somente o token JWT, sem adicionar o prefixo Bearer.

O documento descreve bodies, parâmetros, filtros, respostas, erros e autenticação de todas as rotas. Os endpoints de documentação são públicos neste MVP; restrinja-os no proxy ou por autenticação antes de expor uma implantação real.

Migrations com Prisma

O estado esperado do banco está descrito em prisma/schema.prisma. O histórico SQL fica em prisma/migrations e inclui as constraints que o Prisma Schema não representa diretamente.

Para criar uma migration durante o desenvolvimento:

npm run db:migrate -- --name descricao_da_alteracao

Para aplicar migrations pendentes sem apagar dados:

npm run db:deploy

Em CI, staging e produção, use somente db:deploy. O comando db:create foi mantido como alias compatível de db:deploy e deixou de resetar tabelas.

Confira o estado do histórico com:

npm run db:status
npm run db:validate

Adotar as migrations em um banco criado pela versão anterior

Se o banco já foi criado pelo antigo db:create, ainda não possui histórico em _prisma_migrations e não recebeu alterações manuais de estrutura, faça backup e execute:

npm run db:adopt

O wrapper também detecta o estado intermediário em que somente a baseline está registrada. db:deploy bloqueia esse caminho porque o hardening histórico publicado não é transacional; db:adopt compara colunas, tipos, defaults, chaves, índices únicos e enums com um fingerprint estrutural publicado — incluindo o estado válido/pronto dos índices — antes de registrar qualquer histórico. Em seguida, registra deliberadamente o hardening histórico e deixa as migrations compensatórias executarem todas as mudanças dentro de transações.

As migrations de reconciliação bloqueiam escritas e executam preflight antes de alterar dados ou estrutura. Se houver colisões de CPF/e-mail após normalização, valores incompatíveis ou pagamentos duplicados, a migration aborta sem mesclar, excluir ou ajustar registros silenciosamente. Consulte o preflight correspondente para localizar a regra, corrija os dados deliberadamente e confirme que a transação reverteu. Quando o Prisma registrar a tentativa como falha, libere uma nova tentativa antes de executar novamente:

npm exec prisma -- migrate resolve --rolled-back NOME_DA_MIGRATION
npm run db:deploy

Não use migrate resolve para esconder divergências. O wrapper compara o checksum de toda migration aplicada com seu arquivo local, recusa migrations desconhecidas, schemas parciais e históricos duplicados, incompletos ou fora de ordem, e exige db:adopt para qualquer adoção de histórico.

Reset de desenvolvimento

npm run db:reset apaga o schema configurado, reaplica todas as migrations e perde os dados. O wrapper exige confirmação explícita para toda execução e informa banco, usuário, host e porta antes de recusar. Mantenha a confirmação restrita ao comando:

ALLOW_DATABASE_RESET=true npm run db:reset

Mesmo a configuração Compose só é reconhecida como padrão quando usa loanflow_user, localhost:5433 e o banco loanflow; outra porta ou usuário é tratada como alvo excepcional. Nunca habilite essa variável globalmente ou em produção.

Variáveis de ambiente

Backend — .env

Variável Obrigatória Uso
DATABASE_URL sim conexão PostgreSQL
DATABASE_SSL não use true para TLS com certificado e hostname verificados
JWT_SECRET sim assinatura dos tokens; troque o valor de exemplo
PORT não porta da API, padrão 3333
CORS_ORIGINS não origens permitidas, separadas por vírgula
TRUST_PROXY não IPs/CIDRs explícitos de proxies confiáveis
ALLOW_DATABASE_RESET no reset confirmação explícita para toda execução de db:reset
ALLOW_INTEGRATION_DATABASE_RESET só em teste confirma o truncamento do banco local terminado em _test
ALLOW_DEMO_SEED só no demo confirmação explícita para toda execução de db:seed
DEMO_MODE não habilita o seed demonstrativo
DEMO_USER_EMAIL no demo e-mail do cliente demo
DEMO_USER_PASSWORD no demo senha do cliente demo
DEMO_ADMIN_EMAIL no demo e-mail do administrador demo
DEMO_ADMIN_PASSWORD no demo senha do administrador demo

Para gerar um segredo JWT de desenvolvimento:

openssl rand -hex 32

Copie o resultado para JWT_SECRET. O placeholder de .env.example nunca é aceito pela API.

Quando DATABASE_SSL=true ou a URL contém sslmode=verify-full, tanto a aplicação quanto o Prisma verificam certificado e hostname. Configurações conflitantes ou modos que não validam o hostname são recusados. Em desenvolvimento local, omita sslmode; não use sslmode=require, que cifra sem validar a identidade do servidor. Parâmetros TLS alternativos como ssl e sslrootcert também são recusados para impedir divergência entre o runtime e o Prisma.

Sem CORS_ORIGINS, a API aceita os frontends locais em localhost:5173 e 127.0.0.1:5173. Em uma implantação, informe somente origens HTTPS conhecidas. A API limita cada IP a 120 requisições por minuto; login aceita até 10 tentativas por minuto, cadastro até 5 por hora e pagamentos até 20 por minuto.

O Fastify ignora headers de IP encaminhado por padrão. Defina TRUST_PROXY apenas quando houver um proxy conhecido, usando IPs, CIDRs ou aliases locais separados por vírgula, por exemplo TRUST_PROXY="loopback,10.0.0.0/8". Valores universais são recusados. Ambientes com múltiplas instâncias ainda precisam de armazenamento compartilhado para os contadores.

Frontend — web/.env

Variável Uso
VITE_API_URL URL da API, padrão http://localhost:3333
VITE_DEMO_MODE exibe os atalhos de demonstração
VITE_DEMO_USER_EMAIL e-mail público preenchido pelo atalho
VITE_DEMO_USER_PASSWORD senha pública preenchida pelo atalho
VITE_DEMO_ADMIN_EMAIL e-mail público preenchido pelo atalho
VITE_DEMO_ADMIN_PASSWORD senha pública preenchida pelo atalho

Variáveis VITE_* fazem parte do bundle do navegador e nunca devem conter segredos reais.

Modo demonstrativo

O seed cria:

  • Cliente Demo;
  • Administrador Demo;
  • uma proposta aguardando revisão;
  • um empréstimo ativo;
  • seis parcelas pendentes.

No .env do backend:

DEMO_MODE=true
DEMO_USER_EMAIL=cliente@loanflow.demo
DEMO_USER_PASSWORD=ClienteDemo123
DEMO_ADMIN_EMAIL=admin@loanflow.demo
DEMO_ADMIN_PASSWORD=AdminDemo123

No web/.env:

VITE_DEMO_MODE=true
VITE_DEMO_USER_EMAIL=cliente@loanflow.demo
VITE_DEMO_USER_PASSWORD=ClienteDemo123
VITE_DEMO_ADMIN_EMAIL=admin@loanflow.demo
VITE_DEMO_ADMIN_PASSWORD=AdminDemo123

Depois que o schema existir, mantenha a confirmação restrita ao comando:

ALLOW_DEMO_SEED=true npm run db:seed

O seed inteiro roda em uma única transação e sempre requer ALLOW_DEMO_SEED=true. Fora do alvo Compose exato, ele também recusa as senhas públicas deste exemplo; use credenciais únicas e um banco dedicado. Uma falha reverte contas, propostas e parcelas criadas pela tentativa.

Credenciais:

Cliente
cliente@loanflow.demo
ClienteDemo123

Administrador
admin@loanflow.demo
AdminDemo123

O seed é idempotente: pode ser repetido sem duplicar as contas, empréstimos ou parcelas fixas. Decisões e pagamentos já realizados na demonstração são preservados. Os CPFs fixos ficam vinculados à identidade demo original; se nome ou e-mail não coincidirem, o seed aborta toda a transação em vez de sobrescrever a conta existente.

Não habilite o modo demo em ambientes com dados reais.

Scripts

Raiz

Comando Descrição
npm run dev inicia a API com reload
npm run build compila o backend
npm start executa o backend compilado
npm test executa os testes automatizados do backend
npm run db:up inicia o PostgreSQL local
npm run db:down encerra o Compose local
npm run db:migrate -- --name nome cria e aplica uma migration de desenvolvimento
npm run db:deploy aplica migrations pendentes sem apagar dados
npm run db:adopt adota deliberadamente um schema legado ou baseline-only
npm run db:create alias compatível de db:deploy
npm run db:status mostra o estado das migrations
npm run db:validate valida o Prisma Schema
npm run db:reset reset destrutivo protegido, somente para desenvolvimento
npm run db:seed provisiona os dados demonstrativos
npm run lint executa o ESLint no backend, frontend e Cypress
npm run format formata os arquivos suportados com Prettier
npm run format:check verifica a formatação sem alterar arquivos
npm run test:integration testa o ciclo completo em um PostgreSQL descartável
npm run test:migrations testa upgrade, rollback e papel restrito em banco _test
npm run test:e2e executa o Cypress headless
npm run cy:open abre o Cypress interativo

Frontend

cd web
npm run dev
npm run build
npm run preview

Testes

Backend:

npm test
npm run build
npm run db:validate
npm run lint
npm run format:check

Frontend:

npm --prefix web run build

Interface completa:

env -u ELECTRON_RUN_AS_NODE npm run test:e2e

O script E2E reserva 127.0.0.1:5173 com porta estrita. Se ela estiver ocupada, a execução falha claramente em vez de testar outro servidor.

Os testes Cypress interceptam as respostas da API. Eles validam cadastro, autenticação, guards, responsividade, simulação, solicitação, pagamento e revisão administrativa de maneira determinística, mas não substituem testes de integração entre Fastify e PostgreSQL.

Integração com PostgreSQL

test:integration executa cadastro, login, simulação, solicitação idempotente, vencimento, pagamento idempotente, separação de papéis, listagem administrativa, aprovação, rejeição e bloqueio de autorrevisão contra um banco real.

O conteúdo do banco é truncado no início da execução. Por segurança, isso só é permitido quando o DATABASE_URL usa PostgreSQL, aponta para um host local conhecido, identifica um banco terminado em _test e recebe a confirmação exata ALLOW_INTEGRATION_DATABASE_RESET=true.

Exemplo com um banco descartável já criado:

DATABASE_URL=postgresql://user:password@localhost:5432/loanflow_test npm run db:deploy
DATABASE_URL=postgresql://user:password@localhost:5432/loanflow_test \
JWT_SECRET=loanflow-integration-only-secret-at-least-32-bytes \
ALLOW_INTEGRATION_DATABASE_RESET=true \
npm run test:integration

Mantenha essa confirmação restrita ao comando do teste; não a defina globalmente em ambientes compartilhados.

Integração contínua

O workflow .github/workflows/ci.yml é executado em pushes para main, pull requests direcionados a main e manualmente por workflow_dispatch.

Os jobs são independentes e executam em paralelo:

Job Verificações
Backend instalação limpa, Prisma Schema, ESLint, Prettier, TypeScript e testes
Frontend instalação limpa, build Vue e auditoria
PostgreSQL integration deploy, upgrade legado, rollback, drift e ciclo real da API
Cypress E2E aplicação Vite em porta estrita e cenários de interface

O workflow usa Node.js 24, permissões somente de leitura, actions fixadas por commit SHA e cancelamento automático de execuções antigas da mesma branch ou pull request.

Garantias implementadas no backend

  • SQL parametrizado;
  • senhas armazenadas com bcrypt;
  • limite global e limites específicos de login, cadastro, empréstimos e pagamentos por IP;
  • CORS restrito a uma allowlist configurável e headers HTTP de segurança;
  • JWT de uma hora com algoritmo, emissor, audiência e finalidade validados;
  • papel e existência da conta revalidados no banco em cada requisição;
  • cadastro público sempre persistido como user;
  • separação explícita entre rotas de cliente e administrador;
  • administradores impedidos de revisar propostas da própria conta;
  • verificação de propriedade antes de expor empréstimos e parcelas;
  • validação runtime de bodies, parâmetros e filtros;
  • contrato OpenAPI gerado a partir dos schemas usados pelo Fastify;
  • limites de valor e quantidade de parcelas;
  • decisões administrativas atômicas;
  • solicitações de empréstimo idempotentes por usuário e chave;
  • pagamentos idempotentes por parcela;
  • transações para operações financeiras compostas;
  • distribuição de centavos para que parcelas somem exatamente o total;
  • datas mensais civis ajustadas ao último dia válido de cada mês;
  • parcelas vencidas derivadas como overdue nas consultas.

Rotas da API

Método Rota Descrição
GET / saúde da API
POST /users cadastro de cliente
POST /auth/login autenticação
GET /me perfil autenticado
POST /loans/simulate simulação e decisão
POST /loans solicitação; exige Idempotency-Key UUID
GET /loans/my empréstimos do cliente
GET /loans/:id detalhe e parcelas
POST /installments/:id/pay pagamento demonstrativo
GET /admin/loans fila administrativa
GET /admin/loans/:id detalhe administrativo
PATCH /admin/loans/:id/approve aprovação manual
PATCH /admin/loans/:id/reject rejeição manual

Rotas do frontend

Rota Acesso
/login público
/register público
/app cliente autenticado
/app/simulate cliente autenticado
/app/loans cliente autenticado
/app/loans/:id cliente autenticado
/admin administrador
/admin/loans administrador
/admin/loans/:id administrador

Limites do projeto

  • score e renda são valores simulados informados no cadastro;
  • pagamentos não movimentam dinheiro;
  • não há integração com bureaus de crédito, antifraude ou instituição financeira;
  • JWT não possui refresh token ou blacklist central, embora conta e papel sejam revalidados;
  • o frontend guarda a sessão no armazenamento local;
  • o Cypress usa contratos HTTP simulados;
  • migrations de produção são aplicadas de forma incremental com prisma migrate deploy;
  • o reset do banco continua sendo uma ferramenta exclusiva de desenvolvimento.

Antes de uso real seriam necessários, entre outros pontos, gestão de segredos, cookies seguros ou estratégia equivalente, refresh/revogação central de sessão, armazenamento distribuído para rate limiting, auditoria, observabilidade, ensaio de migrations com snapshots reais, LGPD, integração financeira e testes de carga e integração ponta a ponta.

Solução de problemas

EADDRINUSE na porta 3333

Já existe um processo usando a porta da API:

lsof -nP -iTCP:3333 -sTCP:LISTEN

Encerre a instância anterior ou execute:

PORT=3334 npm run dev

Nesse caso, atualize VITE_API_URL no web/.env.

Cypress em terminal Electron

Se o terminal herdou ELECTRON_RUN_AS_NODE=1:

env -u ELECTRON_RUN_AS_NODE npm run test:e2e

Erros EBADF ou EPERM no Windows

Evite diretórios sincronizados com nuvem. Use, por exemplo, C:\Dev\LoanFlow.

Documentação adicional

As decisões de interface, responsividade e acessibilidade estão em docs/frontend-redesign.md.

Status

MVP educacional em desenvolvimento, voltado à demonstração de arquitetura backend, regras de negócio, autenticação e experiência Vue.

About

Mini fintech full stack for loan simulation, hybrid approval, installment tracking, and admin review built with Fastify, PostgreSQL, Vue 3, and TypeScript.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages