diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6a43c80 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Dependencies +node_modules + +# Data files (mounted as volume) +data/ + +# Environment files +.env +.env.local + +# Git +.git +.gitignore + +# Docs +docs/ +README.md +*.md + +# IDE +.vscode +.idea + +# BMAD +_bmad/ +_bmad-output/ + +# Logs +*.log +npm-debug.log* + +# OS +.DS_Store +Thumbs.db diff --git a/.env.example b/.env.example index f590bbc..43470a3 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,11 @@ -# RDS Read-Only +# Netbird VPN (required for RDS access) +NETBIRD_SETUP_KEY=14A782C8-24D2-46A9-B427-A422854E9B50 + +# RDS Read-Only (accessible via Netbird) MYSQL_URL=seu-host-rds.region.rds.amazonaws.com USER_MYSQL=usuario_readonly PW_MYSQL=senha_aqui -# Sessão +# Sessao SESSION_SECRET=trocar-por-chave-secreta-aleatoria PORT=3080 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2c94890 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# BI Agentes - Dockerfile +FROM node:20-alpine + +# Install netbird client +RUN apk add --no-cache curl bash iptables ip6tables \ + && curl -fsSL https://pkgs.netbird.io/install.sh | sh + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy application files +COPY . . + +# Create data directory for SQLite +RUN mkdir -p /app/data + +# Expose port +EXPOSE 3080 + +# Start script will handle netbird connection +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/README.md b/README.md index ce305cd..328aa3c 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,243 @@ -# BI Agentes — CambioReal +# BI Agentes - CambioReal -Dashboard de transações para agentes CambioReal com autenticação local e dados ao vivo do RDS. +Dashboard de Business Intelligence para agentes de cambio CambioReal. Monitoramento em tempo real de transacoes BRL-USD com dados do AWS RDS. + +## Visao Geral + +Sistema web que permite aos agentes de cambio visualizar suas transacoes, KPIs de performance e graficos analiticos. Cada agente ve apenas suas proprias transacoes, filtradas pelo `agente_id`. + +**Principais recursos:** +- Autenticacao segura (bcrypt + sessoes) +- KPIs em tempo real (volume, spread, IOF, ticket medio) +- Graficos interativos (Chart.js) +- Filtros por periodo, fluxo e cliente +- Exportacao de dados (em breve) ## Arquitetura ``` -┌─────────────────────────────────┐ -│ BI Agentes (Express) │ -│ │ -│ SQLite (auth) Express/HTTP │ -│ agentes.db :3080 │ -│ email/senha /login │ -│ agente_id /dashboard │ -└───────────────────────┬─────────┘ - │ READ-ONLY - ┌──────▼──────┐ - │ cambio_db │ - │ (AWS RDS) │ - └─────────────┘ +┌─────────────────────────────────────────────────────────────┐ +│ BI Agentes (Express) │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Login │───▶│ Session │───▶│ Dashboard │ │ +│ │ (HTML) │ │ (Memory) │ │ (HTML) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Express Server │ │ +│ │ PORT 3080 │ │ +│ └────────────────┬─────────────────────┬───────────────┘ │ +│ │ │ │ +└────────────────────┼─────────────────────┼───────────────────┘ + │ │ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ SQLite │ │ AWS RDS │ + │ (agentes) │ │ (cambio_db) │ + │ AUTH │ │ READ-ONLY │ + └──────────────┘ └──────────────┘ ``` -- **SQLite local** — controle de agentes (email, senha bcrypt, agente_id). Sem dependência do RDS para autenticação. -- **MySQL RDS** — somente leitura. Consulta `br_transaction_to_usa` (BRL→USD) e `pagamento_br` (USD→BRL) filtradas pelo `agente_id` da sessão. +### Bancos de Dados -## Estrutura +| Banco | Tipo | Proposito | +|-------|------|-----------| +| SQLite (`agentes.db`) | Local | Autenticacao - email, senha hash, agente_id | +| MySQL (`cambio_db`) | AWS RDS | Transacoes - somente leitura | + +### Fluxos de Transacao + +| Fluxo | Tabela RDS | Descricao | +|-------|------------|-----------| +| BRL → USD | `br_transaction_to_usa` | Envio de reais para dolares | +| USD → BRL | `pagamento_br` | Recebimento de dolares em reais | + +## Estrutura do Projeto ``` bi-agentes/ -├── server.js ← Entry point (Express + sessions) +├── server.js # Entry point Express +├── package.json # Dependencias +├── .env.example # Template de configuracao +├── .gitignore +│ ├── src/ -│ ├── auth.js ← Login/logout, bcrypt, middleware requireAuth -│ ├── db-local.js ← SQLite (better-sqlite3) — tabela agentes -│ ├── db-rds.js ← MySQL pool read-only (mysql2) -│ ├── queries.js ← SQL parametrizado por agente_id -│ └── dashboard.js ← Gerador HTML (KPIs, Chart.js, tabela) +│ ├── auth.js # Login, logout, bcrypt, middleware +│ ├── db-local.js # SQLite - tabela agentes +│ ├── db-rds.js # MySQL pool read-only +│ ├── queries.js # SQL parametrizado + serializacao +│ └── dashboard.js # Geracao HTML (KPIs, graficos, tabela) +│ ├── public/ -│ └── login.html ← Tela de login +│ └── login.html # Tela de login +│ ├── scripts/ -│ └── seed-agente.js ← CLI para cadastrar/listar agentes +│ └── seed-agente.js # CLI gerenciamento de agentes +│ ├── data/ -│ └── agentes.db ← SQLite database (gerado automaticamente) -├── .env ← Credenciais RDS + SESSION_SECRET -└── .gitignore +│ └── agentes.db # SQLite (criado automaticamente) +│ +└── docs/ # Documentacao adicional + ├── ARCHITECTURE.md # Detalhes tecnicos + ├── API.md # Endpoints + └── GUIA-USUARIO.md # Manual do agente ``` -## Setup +## Instalacao + +### Pre-requisitos + +- Node.js 18+ +- Acesso ao AWS RDS (credenciais read-only) + +### Setup ```bash +# Clonar e instalar +git clone +cd bi-agentes npm install -cp .env.example .env # editar com credenciais do RDS + +# Configurar ambiente +cp .env.example .env +# Editar .env com credenciais ``` -## Configuração (.env) +### Configuracao (.env) -``` -MYSQL_URL= -USER_MYSQL= -PW_MYSQL= -SESSION_SECRET= +```env +# Conexao AWS RDS +MYSQL_URL=cambio-db.xxx.us-east-1.rds.amazonaws.com +USER_MYSQL=bi_readonly +PW_MYSQL=senha_segura + +# Aplicacao +SESSION_SECRET=chave_secreta_aleatoria_32_chars PORT=3080 ``` -## Gerenciar Agentes +## Uso -Cadastrar: +### Iniciar o Servidor ```bash -node scripts/seed-agente.js --email agente@email.com --senha 123456 --agente 76 --nome "ValorFx" +# Desenvolvimento +node server.js + +# Producao (PM2) +pm2 start server.js --name bi-agentes +pm2 save ``` -Listar: +Acesse: `http://localhost:3080` +### Gerenciar Agentes + +**Cadastrar novo agente:** +```bash +node scripts/seed-agente.js \ + --email agente@cambioreal.com \ + --senha senha123 \ + --agente 76 \ + --nome "ValorFx" +``` + +**Listar agentes:** ```bash node scripts/seed-agente.js --list ``` -## Rodar - -```bash -node server.js -# BI Agentes rodando: http://localhost:3080 +**Saida:** +``` +ID | Agente | Nome | Email | Ativo | Criado +1 | 76 | ValorFx | agente@cambioreal.com | Sim | 2024-01-15 ``` -Para produção (intranet): +## Funcionalidades -```bash -pm2 start server.js --name bi-agentes +### Dashboard + +#### KPIs Exibidos +| KPI | Descricao | +|-----|-----------| +| Transacoes | Quantidade total no periodo | +| Volume BRL | Soma em reais | +| Volume USD | Soma em dolares | +| Taxa Media | Media ponderada da taxa cobrada | +| Spread Medio | % medio sobre PTAX | +| IOF Total | Soma do IOF cobrado | +| Ticket Medio | USD medio por transacao | +| Clientes Ativos | Quantidade de clientes unicos | + +#### Graficos (Chart.js) +- **Volume por Periodo** - Barras duplas BRL/USD com eixo dual +- **Top 10 Clientes** - Barras horizontais por volume +- **Taxa vs PTAX** - Linha comparando taxa cobrada com PTAX + +#### Filtros Disponiveis +- Data inicio / Data fim +- Granulacao: Dia, Mes, Ano +- Fluxo: BRL→USD, USD→BRL, Ambos +- Cliente especifico + +### Tabela de Transacoes + +Colunas: Data, Cliente, Valor BRL, Valor USD, IOF %, IOF R$, PTAX, Taxa Cobrada, Spread, Spread %, Status + +## Seguranca + +- **Senhas**: Hash bcrypt com 10 salt rounds +- **SQL**: Queries parametrizadas (previne SQL injection) +- **Sessoes**: 8 horas de timeout, armazenamento em memoria +- **RDS**: Acesso somente leitura (sem permissao de escrita) +- **Isolamento**: Cada agente ve apenas seus dados (`WHERE agente_id = ?`) + +## Tech Stack + +| Camada | Tecnologia | +|--------|------------| +| Runtime | Node.js 18+ | +| Framework | Express.js | +| Auth DB | better-sqlite3 (WAL mode) | +| Data DB | mysql2/promise (pool) | +| Seguranca | bcrypt, express-session | +| Frontend | HTML5, CSS3, Vanilla JS | +| Graficos | Chart.js 4.x (CDN) | +| Tipografia | Google Fonts Inter | + +## Troubleshooting + +### Erro de conexao RDS ``` +Error: connect ETIMEDOUT +``` +- Verificar se IP esta liberado no Security Group +- Confirmar credenciais no .env -## Funcionalidades do Dashboard +### Agente nao consegue logar +``` +Credenciais invalidas +``` +- Verificar se agente esta ativo: `node scripts/seed-agente.js --list` +- Recriar senha se necessario -- **Autenticação** — cada agente loga com email/senha e vê apenas suas transações -- **KPIs** — transações, volume BRL/USD, taxa média, spread, IOF, ticket médio, clientes ativos -- **Gráficos** — volume por período (BRL+USD dual axis), volume por cliente (top 10), taxa cobrada vs PTAX -- **Tabela** — todas as transações com data/hora, cliente, valores, IOF, taxas, spread, status -- **Filtros** — período (de/até), granulação (dia/mês/ano), fluxo (BRL→USD / USD→BRL), cliente -- **Dados ao vivo** — cada refresh consulta o RDS em tempo real -- **Dois fluxos** — BRL→USD (`br_transaction_to_usa`) e USD→BRL (`pagamento_br`) +### Graficos nao aparecem +- Verificar conexao com internet (Chart.js via CDN) +- Abrir DevTools e verificar erros no console -## Stack +## Roadmap -- Node.js + Express -- express-session (sessões em memória) -- better-sqlite3 (auth local) -- mysql2 (RDS read-only) -- bcrypt (hash de senhas) -- Chart.js 4.x (gráficos client-side via CDN) -- Google Fonts Inter (tipografia via CDN) +- [x] Autenticacao local +- [x] Dashboard com KPIs +- [x] Graficos interativos +- [x] Filtros de periodo +- [ ] Exportacao Excel/CSV +- [ ] Comparativo periodo anterior +- [ ] Dashboard administrativo +- [ ] Alertas de spread + +## Licenca + +Uso interno CambioReal. diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..6f7a90b --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e + +echo "=== BI Agentes Startup ===" + +# Check for Netbird setup key +if [ -n "$NETBIRD_SETUP_KEY" ]; then + echo "Connecting to Netbird network..." + echo "Management URL: ${NETBIRD_MANAGEMENT_URL:-https://netbird.cambioreal.com}" + + # Start netbird daemon directly (not via systemd - doesn't work in containers) + MGMT_URL="${NETBIRD_MANAGEMENT_URL:-https://netbird.cambioreal.com}" + + # Run netbird daemon in background + netbird service run & + sleep 3 + + # Connect using setup key and management URL + netbird up --setup-key "$NETBIRD_SETUP_KEY" --management-url "$MGMT_URL" & + + # Wait for connection + echo "Waiting for Netbird connection..." + sleep 10 + + # Check connection status + netbird status || echo "Netbird connection pending..." +else + echo "WARNING: NETBIRD_SETUP_KEY not set. Database connection may fail." +fi + +echo "Starting BI Agentes server..." +exec node server.js diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..cc1d9d4 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,350 @@ +# API Reference - BI Agentes + +Documentacao dos endpoints HTTP do sistema BI Agentes. + +## Base URL + +``` +http://localhost:3080 +``` + +## Autenticacao + +O sistema usa autenticacao baseada em sessao. Apos login bem-sucedido, um cookie de sessao e enviado automaticamente pelo browser. + +**Cookie:** `connect.sid` +**Duracao:** 8 horas + +--- + +## Endpoints + +### GET /login + +Exibe a pagina de login. + +**Autenticacao:** Nao requerida + +**Comportamento:** +- Se usuario ja autenticado: redireciona para `/dashboard` +- Se nao autenticado: retorna `login.html` + +**Response:** + +| Status | Descricao | +|--------|-----------| +| 200 | Pagina HTML de login | +| 302 | Redirect para /dashboard (se ja logado) | + +**Query Parameters:** + +| Param | Tipo | Descricao | +|-------|------|-----------| +| error | number | Se `1`, exibe mensagem de erro | + +**Exemplo:** +```http +GET /login HTTP/1.1 +Host: localhost:3080 +``` + +```http +HTTP/1.1 200 OK +Content-Type: text/html + + +...login form... +``` + +--- + +### POST /login + +Processa tentativa de login. + +**Autenticacao:** Nao requerida + +**Content-Type:** `application/x-www-form-urlencoded` + +**Request Body:** + +| Campo | Tipo | Obrigatorio | Descricao | +|-------|------|-------------|-----------| +| email | string | Sim | Email do agente | +| senha | string | Sim | Senha em texto plano | + +**Response:** + +| Status | Descricao | Redirect | +|--------|-----------|----------| +| 302 | Login bem-sucedido | /dashboard | +| 302 | Login falhou | /login?error=1 | + +**Comportamento:** +1. Busca agente por email no SQLite +2. Compara senha com hash (bcrypt) +3. Se valido, cria sessao com dados do agente +4. Redireciona para dashboard + +**Exemplo:** +```http +POST /login HTTP/1.1 +Host: localhost:3080 +Content-Type: application/x-www-form-urlencoded + +email=agente@cambioreal.com&senha=minhasenha123 +``` + +```http +HTTP/1.1 302 Found +Location: /dashboard +Set-Cookie: connect.sid=s%3A...; Path=/; HttpOnly +``` + +**Dados da Sessao (interno):** +```javascript +req.session.agente = { + id: 1, // ID interno SQLite + email: "agente@email.com", + agente_id: 76, // ID do agente no RDS + nome: "ValorFx" +} +``` + +--- + +### GET /logout + +Encerra a sessao do usuario. + +**Autenticacao:** Nao requerida (mas so faz sentido se logado) + +**Response:** + +| Status | Descricao | Redirect | +|--------|-----------|----------| +| 302 | Sessao destruida | /login | + +**Comportamento:** +1. Destroi sessao server-side +2. Invalida cookie +3. Redireciona para login + +**Exemplo:** +```http +GET /logout HTTP/1.1 +Host: localhost:3080 +Cookie: connect.sid=s%3A... +``` + +```http +HTTP/1.1 302 Found +Location: /login +Set-Cookie: connect.sid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT +``` + +--- + +### GET /dashboard + +Exibe o dashboard de transacoes do agente. + +**Autenticacao:** Requerida (cookie de sessao) + +**Response:** + +| Status | Descricao | +|--------|-----------| +| 200 | Pagina HTML do dashboard | +| 302 | Redirect para /login (nao autenticado) | +| 500 | Erro ao carregar dados | + +**Comportamento:** +1. Middleware `requireAuth` verifica sessao +2. Busca `agente_id` da sessao +3. Executa queries no RDS (BRL→USD e USD→BRL) +4. Serializa e ordena dados +5. Gera HTML com KPIs, graficos e tabela +6. Retorna HTML completo + +**Dados Retornados no HTML:** + +O dashboard inclui dados embedded como JavaScript: + +```javascript +window.TRANSACOES = [ + { + fluxo: "BRL → USD", + cliente: "Cliente A", + data_operacao: "2024-01-15 14:30", + valor_reais: 10000.00, + valor_dolar: 2000.00, + iof_pct: 1.10, + iof_valor_rs: 110.00, + taxa_ptax: 4.95, + taxa_cobrada: 5.00, + spread_bruto: 0.05, + spread_pct: 1.00, + status: "completed" + }, + // ... mais transacoes +]; +``` + +**Exemplo:** +```http +GET /dashboard HTTP/1.1 +Host: localhost:3080 +Cookie: connect.sid=s%3A... +``` + +```http +HTTP/1.1 200 OK +Content-Type: text/html + + + +BI Agentes - Dashboard + + + + +``` + +--- + +### GET / + +Redireciona para o dashboard. + +**Autenticacao:** Nao requerida (redirect acontece primeiro) + +**Response:** + +| Status | Descricao | Redirect | +|--------|-----------|----------| +| 302 | Redirect | /dashboard | + +**Exemplo:** +```http +GET / HTTP/1.1 +Host: localhost:3080 +``` + +```http +HTTP/1.1 302 Found +Location: /dashboard +``` + +--- + +## Arquivos Estaticos + +### GET /public/* + +Serve arquivos estaticos da pasta `public/`. + +**Autenticacao:** Nao requerida + +**Arquivos Disponiveis:** + +| Path | Arquivo | +|------|---------| +| /public/login.html | Pagina de login | + +**Exemplo:** +```http +GET /public/login.html HTTP/1.1 +Host: localhost:3080 +``` + +--- + +## Codigos de Erro + +| Codigo | Significado | Causa Comum | +|--------|-------------|-------------| +| 302 | Redirect | Login/logout, acesso nao autorizado | +| 500 | Erro interno | Falha conexao RDS, erro de query | + +--- + +## Middleware + +### requireAuth + +Middleware que protege rotas que exigem autenticacao. + +**Comportamento:** +```javascript +function requireAuth(req, res, next) { + if (req.session && req.session.agente) { + return next(); // Continua para rota + } + res.redirect('/login'); // Redireciona +} +``` + +**Rotas Protegidas:** +- GET /dashboard + +--- + +## Modelo de Dados (Sessao) + +### Agente (sessao) + +```typescript +interface AgenteSession { + id: number; // ID interno (SQLite) + email: string; // Email do agente + agente_id: number; // ID no sistema RDS + nome: string; // Nome de exibicao +} +``` + +--- + +## Exemplos de Uso + +### Fluxo Completo de Login + +```bash +# 1. Tentar acessar dashboard (sera redirecionado) +curl -v http://localhost:3080/dashboard +# < HTTP/1.1 302 Found +# < Location: /login + +# 2. Fazer login +curl -v -X POST http://localhost:3080/login \ + -d "email=agente@cambioreal.com" \ + -d "senha=minhasenha" \ + -c cookies.txt +# < HTTP/1.1 302 Found +# < Location: /dashboard +# < Set-Cookie: connect.sid=... + +# 3. Acessar dashboard com cookie +curl -v http://localhost:3080/dashboard \ + -b cookies.txt +# < HTTP/1.1 200 OK +# < Content-Type: text/html + +# 4. Fazer logout +curl -v http://localhost:3080/logout \ + -b cookies.txt +# < HTTP/1.1 302 Found +# < Location: /login +``` + +--- + +## Futuras Extensoes + +Endpoints planejados para proximas versoes: + +| Endpoint | Metodo | Descricao | +|----------|--------|-----------| +| /api/export | GET | Exportar transacoes (CSV/Excel) | +| /api/kpis | GET | KPIs em formato JSON | +| /api/transacoes | GET | Transacoes paginadas (JSON) | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..f049b8f --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,349 @@ +# Arquitetura - BI Agentes + +Documentacao tecnica da arquitetura do sistema BI Agentes. + +## Visao Geral + +O BI Agentes e uma aplicacao web monolitica construida com Node.js/Express que serve dashboards de BI para agentes de cambio. A arquitetura prioriza simplicidade, seguranca e isolamento de dados. + +## Diagrama de Arquitetura + +``` + INTERNET + │ + ▼ +┌───────────────────────────────────────────────────────────────────────────┐ +│ SERVIDOR EXPRESS │ +│ (PORT 3080) │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ +│ │ Static │ │ Session │ │ Auth │ │ Dashboard │ │ +│ │ Files │ │ Middleware │ │ Middleware │ │ Generator │ │ +│ │ /public │ │ (in-memory) │ │ requireAuth │ │ (HTML) │ │ +│ └─────────────┘ └──────┬──────┘ └──────┬──────┘ └─────┬──────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌────────────────────────────────────────────────────────────────────┐ │ +│ │ REQUEST ROUTER │ │ +│ │ │ │ +│ │ GET /login ──────▶ login.html │ │ +│ │ POST /login ─────▶ authenticate() ──▶ session.create() │ │ +│ │ GET /logout ─────▶ session.destroy() │ │ +│ │ GET /dashboard ──▶ requireAuth ──▶ fetchTransacoes() ──▶ HTML │ │ +│ │ GET / ───────────▶ redirect /dashboard │ │ +│ └────────────────────────────────────────────────────────────────────┘ │ +│ │ │ │ +└────────────────────────────┼───────────────────────────┼──────────────────┘ + │ │ + ┌──────────────┘ └──────────────┐ + ▼ ▼ + ┌───────────────────┐ ┌───────────────────┐ + │ SQLite │ │ AWS RDS │ + │ (agentes.db) │ │ (cambio_db) │ + │ │ │ │ + │ ┌─────────────┐ │ │ ┌─────────────┐ │ + │ │ agentes │ │ │ │br_transac...│ │ + │ │ - id │ │ │ │- id │ │ + │ │ - email │ │ │ │- id_conta │ │ + │ │ - senha_hash│ │ │ │- amount_brl │ │ + │ │ - agente_id │ │ │ │- amount_usd │ │ + │ │ - nome │ │ │ │- exchange...| │ + │ │ - ativo │ │ │ │- status │ │ + │ └─────────────┘ │ │ └─────────────┘ │ + │ │ │ │ + │ LOCAL (WAL) │ │ ┌─────────────┐ │ + │ WRITE + READ │ │ │pagamento_br │ │ + └───────────────────┘ │ │- id │ │ + │ │- id_conta │ │ + │ │- valor │ │ + │ │- valor_sol │ │ + │ │- cotacao │ │ + │ └─────────────┘ │ + │ │ + │ REMOTE (MySQL) │ + │ READ-ONLY │ + └───────────────────┘ +``` + +## Componentes + +### 1. Camada de Apresentacao + +**Tecnologias:** HTML5, CSS3, Vanilla JavaScript, Chart.js + +| Arquivo | Responsabilidade | +|---------|------------------| +| `public/login.html` | Formulario de login estilizado | +| `src/dashboard.js` | Geracao dinamica do HTML do dashboard | + +**Caracteristicas:** +- Server-Side Rendering (SSR) - HTML gerado no backend +- Chart.js carregado via CDN (sem build step) +- CSS inline no dashboard para simplicidade +- Google Fonts Inter para tipografia + +### 2. Camada de Aplicacao + +**Tecnologias:** Node.js, Express.js + +| Modulo | Responsabilidade | +|--------|------------------| +| `server.js` | Entry point, rotas, middleware | +| `src/auth.js` | Autenticacao, bcrypt, sessoes | +| `src/queries.js` | Queries SQL, serializacao de dados | + +**Middleware Stack:** +``` +Request + │ + ▼ +express.urlencoded() ← Parse form data + │ + ▼ +express.json() ← Parse JSON + │ + ▼ +express-session() ← Gerenciar sessao + │ + ▼ +requireAuth() ← Verificar autenticacao (rotas protegidas) + │ + ▼ +Route Handler +``` + +### 3. Camada de Dados + +#### SQLite (Local) + +**Biblioteca:** better-sqlite3 (sincrono, WAL mode) + +```sql +CREATE TABLE agentes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + senha_hash TEXT NOT NULL, + agente_id INTEGER NOT NULL, + nome TEXT NOT NULL, + ativo INTEGER DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); +``` + +**Caracteristicas:** +- WAL mode para melhor concorrencia +- Arquivo em `data/agentes.db` +- Criado automaticamente no primeiro start + +#### MySQL (RDS) + +**Biblioteca:** mysql2/promise (pool de conexoes) + +**Configuracao do Pool:** +```javascript +{ + host: process.env.MYSQL_URL, + user: process.env.USER_MYSQL, + password: process.env.PW_MYSQL, + database: 'cambio_db', + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0 +} +``` + +**Tabelas Consultadas:** + +| Tabela | Fluxo | Campos Principais | +|--------|-------|-------------------| +| `br_transaction_to_usa` | BRL→USD | amount_brl, amount_usd, exchange_rate, iof, ptax | +| `pagamento_br` | USD→BRL | valor, valor_sol, cotacao, ptax | +| `ag_contas` | Join | agente_id, conta_id | +| `conta` | Join | id_conta, nome | + +## Fluxo de Dados + +### Autenticacao + +``` +┌──────────┐ POST /login ┌──────────┐ +│ Browser │ ─────────────────▶ │ Express │ +│ │ email, senha │ │ +└──────────┘ └────┬─────┘ + │ + ┌────────────────┘ + ▼ + ┌──────────────┐ + │ SQLite │ + │ SELECT WHERE │ + │ email = ? │ + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ bcrypt │ + │ compare │ + └──────┬───────┘ + │ + ┌───────┴───────┐ + ▼ ▼ + [MATCH] [NO MATCH] + │ │ + ▼ ▼ + session.agente redirect + = {...} /login?error=1 + │ + ▼ + redirect + /dashboard +``` + +### Carregamento do Dashboard + +``` +┌──────────┐ GET /dashboard ┌──────────┐ +│ Browser │ ─────────────────▶ │ Express │ +│ (cookie) │ │ │ +└──────────┘ └────┬─────┘ + │ + requireAuth()────┘ + │ + ┌─────────────┴─────────────┐ + ▼ ▼ + [NO SESSION] [HAS SESSION] + │ │ + ▼ ▼ + redirect ┌─────────────────┐ + /login │ fetchTransacoes │ + │ (agente_id) │ + └────────┬────────┘ + │ + ┌─────────────────┘ + ▼ + ┌──────────────┐ + │ AWS RDS │ + │ 2 queries: │ + │ BRL→USD │ + │ USD→BRL │ + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ serialize() │ + │ merge + │ + │ sort │ + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ + │ buildHTML() │ + │ KPIs + │ + │ Charts + │ + │ Table │ + └──────┬───────┘ + │ + ▼ + res.send(html) +``` + +## Seguranca + +### Autenticacao + +| Aspecto | Implementacao | +|---------|---------------| +| Hash de senha | bcrypt, 10 salt rounds | +| Sessao | express-session, in-memory | +| Timeout | 8 horas (cookie maxAge) | +| Middleware | requireAuth em rotas protegidas | + +### Protecao de Dados + +| Aspecto | Implementacao | +|---------|---------------| +| SQL Injection | Queries parametrizadas (?) | +| Isolamento | WHERE agente_id = ? em todas queries | +| RDS Access | Usuario read-only (sem INSERT/UPDATE/DELETE) | + +### Recomendacoes para Producao + +- [ ] Usar Redis para sessoes (em vez de in-memory) +- [ ] HTTPS obrigatorio (TLS) +- [ ] Helmet.js para headers de seguranca +- [ ] Rate limiting no login +- [ ] Logs de auditoria + +## Performance + +### Estrategias Atuais + +- **Connection Pool**: 10 conexoes MySQL reutilizaveis +- **WAL Mode**: SQLite com Write-Ahead Logging +- **CDN**: Chart.js e fonts via CDN (cache do browser) +- **SSR**: HTML pre-renderizado (sem SPA overhead) + +### Gargalos Potenciais + +| Componente | Risco | Mitigacao | +|------------|-------|-----------| +| Sessoes in-memory | Perda em restart | Migrar para Redis | +| Queries RDS | Tabelas grandes | Adicionar indices, paginacao | +| HTML generation | Muitas transacoes | Paginacao server-side | + +## Estrutura de Modulos + +``` +src/ +├── auth.js # Autenticacao +│ ├── authenticate(email, senha) +│ └── requireAuth(req, res, next) +│ +├── db-local.js # SQLite setup +│ └── initDB() +│ +├── db-rds.js # MySQL pool +│ └── pool (export) +│ +├── queries.js # Data access +│ ├── fetchTransacoes(agenteId) +│ └── serialize(rowsBrlUsd, rowsUsdBrl) +│ +└── dashboard.js # View generation + └── buildHTML(data, agente) +``` + +## Dependencias + +| Pacote | Versao | Proposito | +|--------|--------|-----------| +| express | ^4.x | Framework web | +| express-session | ^1.x | Gerenciamento de sessao | +| better-sqlite3 | ^9.x | SQLite driver (sync) | +| mysql2 | ^3.x | MySQL driver (async) | +| bcrypt | ^5.x | Hash de senhas | +| dotenv | ^16.x | Variaveis de ambiente | + +## Extensibilidade + +### Adicionar Nova Feature + +1. **Novo endpoint**: Adicionar rota em `server.js` +2. **Nova query**: Adicionar funcao em `src/queries.js` +3. **Nova UI**: Modificar `src/dashboard.js` ou criar novo modulo + +### Adicionar Novo Fluxo de Transacao + +1. Criar query em `src/queries.js` +2. Atualizar `serialize()` para incluir novo fluxo +3. Atualizar `buildHTML()` para exibir dados + +## Decisoes de Arquitetura + +| Decisao | Justificativa | +|---------|---------------| +| Monolito | Simplicidade para equipe pequena | +| SSR | Sem necessidade de SPA, SEO nao relevante | +| SQLite para auth | Independencia do RDS, portabilidade | +| Vanilla JS | Sem build step, menor complexidade | +| better-sqlite3 | Sync API mais simples para auth | +| mysql2 | Pool de conexoes async para RDS | diff --git a/docs/CambioReal_original.png b/docs/CambioReal_original.png new file mode 100644 index 0000000..134ec93 Binary files /dev/null and b/docs/CambioReal_original.png differ diff --git a/docs/GUIA-USUARIO.md b/docs/GUIA-USUARIO.md new file mode 100644 index 0000000..c989e09 --- /dev/null +++ b/docs/GUIA-USUARIO.md @@ -0,0 +1,272 @@ +# Guia do Usuario - BI Agentes + +Manual de uso do dashboard de transacoes para agentes CambioReal. + +--- + +## Indice + +1. [Acessando o Sistema](#acessando-o-sistema) +2. [Visao Geral do Dashboard](#visao-geral-do-dashboard) +3. [Usando os Filtros](#usando-os-filtros) +4. [Entendendo os KPIs](#entendendo-os-kpis) +5. [Interpretando os Graficos](#interpretando-os-graficos) +6. [Tabela de Transacoes](#tabela-de-transacoes) +7. [Perguntas Frequentes](#perguntas-frequentes) + +--- + +## Acessando o Sistema + +### Login + +1. Abra o navegador e acesse o endereco fornecido pelo administrador +2. Na tela de login, digite: + - **E-mail**: Seu email cadastrado + - **Senha**: Sua senha pessoal +3. Clique em **Entrar** + +``` +┌─────────────────────────────────┐ +│ BI Agentes │ +│ CambioReal - Dashboard │ +│ │ +│ E-mail: [_________________] │ +│ │ +│ Senha: [_________________] │ +│ │ +│ [ Entrar ] │ +└─────────────────────────────────┘ +``` + +**Dica:** Se aparecer a mensagem "E-mail ou senha incorretos", verifique se digitou corretamente. Caso persista, contate o administrador. + +### Logout + +Para sair do sistema, clique no botao **Sair** no canto superior direito do dashboard. + +--- + +## Visao Geral do Dashboard + +Apos o login, voce vera o dashboard com suas transacoes: + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ [Nome do Agente] - Agente [ID] [Ao vivo] [Sair] │ +├────────────────────────────────────────────────────────────────────┤ +│ De: [____] Ate: [____] Granulacao: [___] Fluxo: [___] [Aplicar] │ +├────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ KPI 1 │ │ KPI 2 │ │ KPI 3 │ │ KPI 4 │ <- Indicadores │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ Grafico 1 │ │ Grafico 2 │ <- Graficos │ +│ └─────────────────┘ └─────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Tabela de Transacoes │ <- Detalhes │ +│ └─────────────────────────────────────────┘ │ +│ │ +└────────────────────────────────────────────────────────────────────┘ +``` + +**Importante:** Voce ve apenas suas proprias transacoes. Outros agentes nao tem acesso aos seus dados. + +--- + +## Usando os Filtros + +Os filtros ficam na barra abaixo do cabecalho. Use-os para refinar a visualizacao: + +### Periodo (De / Ate) + +Selecione a data inicial e final para ver transacoes em um periodo especifico. + +**Exemplo:** Para ver transacoes de janeiro: +- De: `2024-01-01` +- Ate: `2024-01-31` + +### Granulacao + +Escolha como agrupar os dados nos graficos: + +| Opcao | Uso | +|-------|-----| +| **Dia** | Ver detalhes diarios (periodos curtos) | +| **Mes** | Visao mensal (padrao, recomendado) | +| **Ano** | Comparar anos inteiros | + +### Fluxo + +Filtre por tipo de operacao: + +| Opcao | Significado | +|-------|-------------| +| **Todos** | BRL→USD e USD→BRL juntos | +| **BRL → USD** | Apenas envios (reais para dolares) | +| **USD → BRL** | Apenas recebimentos (dolares para reais) | + +### Cliente + +Selecione um cliente especifico ou deixe em "Todos" para ver todas as transacoes. + +### Aplicar + +**Importante:** Apos alterar os filtros, clique em **Aplicar** para atualizar a tela. + +--- + +## Entendendo os KPIs + +Os KPIs (Indicadores-Chave) mostram um resumo do periodo selecionado: + +### Transacoes +Quantidade total de operacoes realizadas. + +### Volume BRL +Soma de todos os valores em reais movimentados. + +**Exemplo:** Se teve 3 transacoes de R$ 10.000, R$ 20.000 e R$ 15.000, o volume e R$ 45.000. + +### Volume USD +Soma de todos os valores em dolares movimentados. + +### Taxa Media +Media ponderada da taxa de cambio cobrada (BRL/USD). + +**Ponderada significa:** Transacoes maiores tem mais peso no calculo. + +### Spread Medio +Porcentagem media de lucro sobre a taxa PTAX. + +**O que e PTAX?** Taxa de cambio oficial do Banco Central. + +**Exemplo:** Se a PTAX e 5,00 e voce cobra 5,05, o spread e 1% (0,05 / 5,00). + +### IOF Total +Soma do IOF (Imposto sobre Operacoes Financeiras) recolhido. + +### Ticket Medio +Valor medio em USD por operacao. + +**Calculo:** Volume USD / Numero de Transacoes + +### Clientes Ativos +Quantos clientes diferentes fizeram transacoes no periodo. + +--- + +## Interpretando os Graficos + +### Volume BRL / USD por Periodo + +Grafico de barras mostrando a evolucao do volume ao longo do tempo. + +- **Barras verdes** = Volume em BRL (eixo esquerdo) +- **Barras azuis** = Volume em USD (eixo direito) + +**Como ler:** +- Barras mais altas = mais volume naquele periodo +- Compare meses para identificar sazonalidade + +### Volume por Cliente (Top 10) + +Grafico horizontal mostrando os 10 maiores clientes por volume USD. + +**Como usar:** +- Identifique seus clientes mais importantes +- Foque atencao nos maiores volumes + +### Taxa Cobrada vs PTAX + +Grafico de linha comparando sua taxa com a taxa oficial. + +- **Linha roxa** = Sua taxa cobrada (media ponderada) +- **Linha azul** = PTAX oficial + +**O que observar:** +- A distancia entre as linhas e seu spread +- Quanto maior a distancia, maior sua margem + +--- + +## Tabela de Transacoes + +A tabela mostra todas as transacoes individuais do periodo. + +### Colunas + +| Coluna | Descricao | +|--------|-----------| +| **Data/Hora** | Quando a transacao foi realizada | +| **Cliente** | Nome do cliente | +| **Valor BRL** | Valor em reais | +| **Valor USD** | Valor em dolares | +| **IOF %** | Aliquota de IOF aplicada | +| **IOF R$** | Valor do IOF em reais | +| **PTAX** | Taxa oficial do BC no momento | +| **Taxa Cobrada** | Sua taxa de cambio | +| **Spread** | Diferenca entre taxa cobrada e PTAX | +| **Spread %** | Spread em porcentagem | +| **Status** | Situacao da operacao | + +### Organizacao + +As transacoes sao separadas por fluxo: +- **BRL → USD** (fundo roxo claro) - Envios +- **USD → BRL** (fundo azul claro) - Recebimentos + +### Navegando + +- Use a barra de rolagem horizontal para ver todas as colunas +- A tabela ordena por data (mais antigas primeiro) + +--- + +## Perguntas Frequentes + +### Posso ver transacoes de outros agentes? + +**Nao.** Cada agente ve apenas suas proprias transacoes. O sistema garante isolamento total dos dados. + +### Os dados sao atualizados em tempo real? + +**Sim.** A cada vez que voce carrega o dashboard ou aplica filtros, os dados sao buscados diretamente do sistema. + +### O que fazer se os graficos nao aparecerem? + +1. Verifique sua conexao com a internet (os graficos usam biblioteca externa) +2. Tente recarregar a pagina (F5) +3. Se persistir, contate o suporte tecnico + +### Como exportar os dados? + +*Em breve!* Estamos desenvolvendo funcionalidade de exportacao para Excel/CSV. + +### A sessao expira? + +**Sim.** Apos 8 horas de inatividade, voce precisara fazer login novamente. + +### Posso acessar de celular? + +**Sim.** O dashboard e responsivo e funciona em dispositivos moveis, porem a experiencia e melhor em telas maiores. + +### O que significa PTAX? + +PTAX e a taxa de cambio oficial divulgada pelo Banco Central do Brasil. E usada como referencia para calcular o spread das operacoes. + +--- + +## Suporte + +Em caso de problemas: +1. Verifique usuario e senha +2. Tente recarregar a pagina +3. Contate o administrador do sistema + +--- + +*CambioReal - BI Agentes v1.0* diff --git a/docs/logo-small.png b/docs/logo-small.png new file mode 100644 index 0000000..79f6b47 Binary files /dev/null and b/docs/logo-small.png differ diff --git a/scripts/seed-admin.js b/scripts/seed-admin.js new file mode 100644 index 0000000..b430c06 --- /dev/null +++ b/scripts/seed-admin.js @@ -0,0 +1,70 @@ +/** + * CLI para cadastrar administradores no SQLite + * + * Uso: + * node scripts/seed-admin.js --email admin@cambioreal.com --senha 123456 --nome "Admin" + * + * Listar admins: + * node scripts/seed-admin.js --list + */ +require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') }); + +const db = require('../src/db-local'); +const { createAdmin } = require('../src/admin-auth'); + +const args = process.argv.slice(2); + +function getArg(name) { + const idx = args.indexOf('--' + name); + return idx !== -1 && args[idx + 1] ? args[idx + 1] : null; +} + +async function main() { + // List mode + if (args.includes('--list')) { + const rows = db.prepare('SELECT id, email, nome, created_at FROM admins').all(); + if (!rows.length) { + console.log('Nenhum administrador cadastrado.'); + } else { + console.log('\n ID | Nome | Email | Criado em'); + console.log(' ' + '-'.repeat(75)); + rows.forEach(r => { + console.log(` ${String(r.id).padEnd(3)}| ${r.nome.padEnd(19)}| ${r.email.padEnd(29)}| ${r.created_at}`); + }); + } + console.log(); + process.exit(0); + } + + // Create mode + const email = getArg('email'); + const senha = getArg('senha'); + const nome = getArg('nome'); + + if (!email || !senha || !nome) { + console.log(` + Uso: node scripts/seed-admin.js --email --senha --nome "" + + Exemplos: + node scripts/seed-admin.js --email admin@cambioreal.com --senha 123456 --nome "Admin" + node scripts/seed-admin.js --list + `); + process.exit(1); + } + + try { + await createAdmin(email, senha, nome); + console.log(`\n Administrador cadastrado com sucesso!`); + console.log(` Nome: ${nome}`); + console.log(` Email: ${email}\n`); + } catch (err) { + if (err.message.includes('UNIQUE')) { + console.error(`\n Erro: email "${email}" ja esta cadastrado.\n`); + } else { + console.error('Erro:', err.message); + } + process.exit(1); + } +} + +main(); diff --git a/src/queries.js b/src/queries.js index 6d37011..198a360 100644 --- a/src/queries.js +++ b/src/queries.js @@ -94,4 +94,133 @@ function serialize(rowsBrlUsd, rowsUsdBrl) { return [...dataBrlUsd, ...dataUsdBrl].sort((a, b) => a.data_sort.localeCompare(b.data_sort)); } -module.exports = { fetchTransacoes, serialize }; +// Fetch ALL transactions (for admin) - with date filter for performance +async function fetchAllTransacoes(diasAtras = 90) { + const conn = await pool.getConnection(); + try { + const [rowsBrlUsd] = await conn.execute(` + SELECT + c.nome AS cliente, + t.created_at AS data_operacao, + t.amount_brl AS valor_reais, + t.amount_usd AS valor_dolar, + t.iof AS iof_pct, + ROUND(t.iof / 100 * t.amount_usd * t.exchange_rate, 2) AS iof_valor_rs, + ROUND(t.ptax, 4) AS taxa_ptax, + ROUND(t.exchange_rate, 4) AS taxa_cobrada, + ROUND(t.exchange_rate - t.ptax, 4) AS spread_bruto, + ROUND((t.exchange_rate - t.ptax) / t.exchange_rate * 100, 2) AS spread_pct, + t.status + FROM br_transaction_to_usa t + INNER JOIN conta c ON c.id_conta = t.id_conta + WHERE t.created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) + ORDER BY t.created_at DESC + `, [diasAtras]); + + const [rowsUsdBrl] = await conn.execute(` + SELECT + c.nome AS cliente, + p.created_at AS data_operacao, + p.valor_sol AS valor_reais, + p.valor AS valor_dolar, + 0 AS iof_pct, + 0 AS iof_valor_rs, + ROUND(p.ptax, 4) AS taxa_ptax, + ROUND(p.cotacao, 4) AS taxa_cobrada, + ROUND(p.ptax - p.cotacao, 4) AS spread_bruto, + CASE WHEN p.cotacao > 0 THEN ROUND((p.ptax - p.cotacao) / p.ptax * 100, 2) ELSE 0 END AS spread_pct, + p.pgto AS status + FROM pagamento_br p + INNER JOIN conta c ON c.id_conta = p.id_conta + WHERE p.created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) + ORDER BY p.created_at DESC + `, [diasAtras]); + + return { rowsBrlUsd, rowsUsdBrl }; + } finally { + conn.release(); + } +} + +// Fast daily stats for admin home (today and yesterday only) +// Separates USD->BRL (with cotacao) from USD->USD (balance/no cotacao) +async function fetchDailyStats() { + const conn = await pool.getConnection(); + try { + // BRL -> USD counts and volumes for today and yesterday + const [brlUsdStats] = await conn.execute(` + SELECT + DATE(created_at) as dia, + COUNT(*) as qtd, + ROUND(SUM(amount_brl), 2) as total_brl, + ROUND(SUM(amount_usd), 2) as total_usd + FROM br_transaction_to_usa + WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) + GROUP BY DATE(created_at) + ORDER BY dia DESC + `); + + // USD -> BRL (with cotacao - real currency exchange) + const [usdBrlStats] = await conn.execute(` + SELECT + DATE(created_at) as dia, + COUNT(*) as qtd, + ROUND(SUM(valor_sol), 2) as total_brl, + ROUND(SUM(valor), 2) as total_usd + FROM pagamento_br + WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) + AND cotacao IS NOT NULL AND cotacao > 0 + AND (pgto IS NULL OR pgto != 'balance') + GROUP BY DATE(created_at) + ORDER BY dia DESC + `); + + // USD -> USD (balance or no cotacao - dollar to dollar) + const [usdUsdStats] = await conn.execute(` + SELECT + DATE(created_at) as dia, + COUNT(*) as qtd, + ROUND(SUM(valor), 2) as total_usd + FROM pagamento_br + WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) + AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance') + GROUP BY DATE(created_at) + ORDER BY dia DESC + `); + + // Format results + const today = new Date().toISOString().slice(0, 10); + const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10); + + const formatDay = (stats, targetDate) => { + const row = stats.find(r => { + const d = r.dia instanceof Date ? r.dia.toISOString().slice(0, 10) : String(r.dia).slice(0, 10); + return d === targetDate; + }); + return row ? { + qtd: Number(row.qtd), + total_brl: Number(row.total_brl) || 0, + total_usd: Number(row.total_usd) || 0 + } : { qtd: 0, total_brl: 0, total_usd: 0 }; + }; + + return { + brlUsd: { + hoje: formatDay(brlUsdStats, today), + ontem: formatDay(brlUsdStats, yesterday) + }, + usdBrl: { + hoje: formatDay(usdBrlStats, today), + ontem: formatDay(usdBrlStats, yesterday) + }, + usdUsd: { + hoje: formatDay(usdUsdStats, today), + ontem: formatDay(usdUsdStats, yesterday) + } + }; + } finally { + conn.release(); + } +} + +module.exports = { fetchTransacoes, fetchAllTransacoes, serialize, fetchDailyStats };