e-li-nps/README.md
2025-12-31 11:56:10 -03:00

358 lines
10 KiB
Markdown

# e-li.nps (Go + HTMX)
Widget NPS embutível via **1 arquivo JS** + API em Go.
## Requisitos
- Go 1.22+
- PostgreSQL 14+
## Variáveis de ambiente
- `DATABASE_URL` (obrigatória)
- Ex: `postgres://postgres:postgres@localhost:5432/gonps?sslmode=disable`
- `ADDR` (opcional, default `:8080`)
- `SENHA_PAINEL` (opcional)
- Se definida, habilita o painel em `/painel`.
- Se vazia, o painel fica desabilitado.
### Cache do widget (e-li.nps.js)
O servidor controla o cache de `/static/e-li.nps.js` via **ETag**.
- A versão (ETag) é **gerada automaticamente a cada inicialização do servidor**.
- O browser é instruído a **revalidar** (`Cache-Control: no-cache, must-revalidate`), então:
- se o ETag não mudou: o servidor responde `304` (rápido)
- se o ETag mudou: o browser baixa o JS novo automaticamente
Isso evita problemas de clientes com JS antigo em cache após mudanças.
### Arquivo `.env`
O servidor carrega automaticamente um arquivo `.env` na raiz do projeto (se existir) usando `godotenv`.
Isso facilita rodar localmente sem exportar variáveis manualmente.
Exemplo de `.env`:
```env
DATABASE_URL='postgres://postgres:postgres@localhost:5432/gonps?sslmode=disable'
ADDR=':8080'
```
## Como rodar
1. Suba um Postgres (exemplo via Docker):
```bash
docker run --rm -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=gonps -p 5432:5432 postgres:16
```
2. Rode o server:
```bash
go run ./cmd/server
```
## Rodar com Docker
Este repositório inclui:
- `Dockerfile` (build multi-stage do binário Go)
- `docker-compose.yml` (apenas o app; Postgres é externo)
Para subir tudo:
```bash
docker compose up --build
```
> Dica: esse comando roda em **foreground**. Ao pressionar `Ctrl+C`, o Docker
> encerra os containers.
>
> Para manter rodando em background (recomendado em servidor):
>
> ```bash
> docker compose up -d --build
> ```
Para forçar rebuild da imagem (mesmo sem mudanças detectadas):
```bash
docker compose build --no-cache && docker compose up
```
Para parar a aplicação:
```bash
docker compose down
```
Para apenas parar sem remover (mantém o container):
```bash
docker compose stop
```
Para iniciar novamente após stop:
```bash
docker compose start
```
> Importante:
> - O Postgres é **externo**.
> - O arquivo `.env` é **obrigatório** e deve ser passado como **volume** para `/app/.env`.
> - O servidor carrega esse arquivo automaticamente via `godotenv` ao iniciar.
>
> Exemplo (compose): `./.env:/app/.env:ro`
### Postgres no host (host.docker.internal)
Se o seu Postgres estiver rodando **no host** (fora do container) e você quiser
que o container acesse via `host.docker.internal`, use no `.env`:
```env
DATABASE_URL='postgres://usuario:senha@host.docker.internal:5432/seu_banco?sslmode=disable'
```
No Linux, o `docker-compose.yml` já inclui `extra_hosts` com `host-gateway` para
esse hostname funcionar.
## Publicar com Caddy (reverse proxy)
Este repositório inclui um exemplo de `Caddyfile` para publicar o serviço em:
- `https://nps.idz.one``{ip-app}:8080`
### Pré-requisitos
- O DNS de `nps.idz.one` deve apontar para o **IP público** do servidor onde o Caddy roda.
- Portas **80/443** liberadas para o Caddy (TLS automático).
### IP real do usuário
O Caddy repassa o IP do cliente via `X-Forwarded-For` e `X-Real-IP`.
O servidor Go já usa `middleware.RealIP` (chi), então o IP real chega corretamente
e é gravado em `ip_real`.
### Check do IP real (direto / Docker / Caddy)
O painel tem um endpoint de debug que mostra o IP que a aplicação está enxergando
e os headers recebidos:
- `GET /painel/debug/ip`
Passo a passo:
1) Faça login no painel em `/painel`.
2) Acesse `/painel/debug/ip`.
O JSON retornado inclui:
- `remote_addr` (já após o `middleware.RealIP`)
- `x_forwarded_for`
- `x_real_ip`
Interpretação esperada:
- Rodando **direto** (sem proxy): `remote_addr` deve ser o IP do cliente (ou do seu balanceador).
- Rodando via **Docker**: se você acessar diretamente a porta publicada, o `remote_addr` tende a ser o IP do host/bridge; atrás de proxy (Caddy), o `remote_addr` deve refletir o IP real.
- Rodando via **Caddy**: `x_forwarded_for` deve conter o IP real do cliente e o `remote_addr` deve refletir esse IP após `RealIP`.
Depois acesse:
- Home/README: `http://localhost:8080/`
- Teste do widget: `http://localhost:8080/teste.html`
- Painel: `http://localhost:8080/painel` (senha em `SENHA_PAINEL`)
Painel:
- Acesse `http://localhost:8080/painel`
- Você será redirecionado para `/painel/login`
Healthcheck:
```bash
curl -i http://localhost:8080/healthz
```
## Incluir o widget em outra aplicação
### Tipagem TypeScript (opcional)
Se você quiser ter autocomplete e validação de tipos no seu projeto (TS), pode
declarar a interface abaixo:
```ts
declare global {
interface Window {
ELiNPS: {
init: (opts: ELiNPSInitOptions) => Promise<void> | void;
};
}
}
export type ELiNPSInitOptions = {
// apiBase (opcional)
// Base da API do e-li.nps.
// Se o widget estiver sendo servido pelo mesmo host, pode deixar vazio.
apiBase?: string;
// cooldownHours (opcional)
// Tempo (em horas) de cooldown visual no navegador.
cooldownHours?: number;
// data_minima_abertura (opcional)
// Bloqueia a abertura do modal antes de uma data.
// Formato ISO (data): YYYY-MM-DD (ex.: "2026-01-01").
data_minima_abertura?: string;
// produto_nome (obrigatório)
produto_nome: string;
// inquilino_codigo (obrigatório)
inquilino_codigo: string;
// inquilino_nome (obrigatório)
inquilino_nome: string;
// usuario_codigo (obrigatório)
usuario_codigo: string;
// usuario_nome (obrigatório)
usuario_nome: string;
// usuario_telefone (opcional)
usuario_telefone?: string;
// usuario_email (opcional)
usuario_email?: string;
};
```
```html
<!-- Carrega o widget (arquivo único) -->
<script src="http://localhost:8080/static/e-li.nps.js"></script>
<script>
window.ELiNPS.init({
// apiBase (opcional)
// Base da API do e-li.nps.
// - Se o widget estiver sendo servido pelo mesmo host, pode deixar vazio.
// - Se a API estiver em outro host, informe a URL completa.
// Ex.: "https://sua-api.exemplo.com".
apiBase: 'http://localhost:8080',
// cooldownHours (opcional)
// Tempo (em horas) de cooldown visual no navegador para evitar o modal
// reaparecer em sequência.
// Default: 24.
cooldownHours: 24,
// data_minima_abertura (opcional)
// Bloqueia a abertura do modal antes de uma data.
// Formato ISO (data): YYYY-MM-DD (ex.: "2026-01-01").
// Ex.: data_minima_abertura: '2026-01-01',
data_minima_abertura: '',
// produto_nome (obrigatório)
// Nome livre do produto (é exibido ao usuário exatamente como informado).
// Exemplos: "e-licencie.gov", "Cachaça & Churras".
// Importante: o backend normaliza apenas para montar nome de tabela/rotas.
produto_nome: 'e-licencie.gov',
// inquilino_codigo (obrigatório)
// Código do cliente/tenant (usado nas regras de exibição e no banco).
inquilino_codigo: 'acme',
// inquilino_nome (obrigatório)
// Nome do cliente/tenant (exibição / auditoria).
inquilino_nome: 'ACME LTDA',
// usuario_codigo (obrigatório)
// Identificador do usuário.
// Importante: é a chave principal para as regras de exibição.
usuario_codigo: 'u-123',
// usuario_nome (obrigatório)
// Nome do usuário (exibição / auditoria).
usuario_nome: 'Maria',
// usuario_telefone (opcional)
// Telefone do usuário (auditoria). Pode ser vazio.
usuario_telefone: '+55 11 99999-9999',
// usuario_email (opcional)
// Email do usuário. É opcional: o controle de exibição é por
// (produto + inquilino_codigo + usuario_codigo).
usuario_email: 'maria@acme.com',
});
</script>
```
## Endpoints
### `POST /api/e-li.nps/pedido`
```bash
curl -sS -X POST http://localhost:8080/api/e-li.nps/pedido \
-H 'Content-Type: application/json' \
-d '{
"produto_nome":"e-licencie.gov",
"inquilino_codigo":"acme",
"inquilino_nome":"ACME",
"usuario_codigo":"u-123",
"usuario_nome":"Maria",
"usuario_telefone":"+55...",
"usuario_email":"maria@acme.com"
}'
```
### `GET /e-li.nps/{produto}/{id}/form`
Abre o formulário (HTML) para responder/editar.
### `PATCH /api/e-li.nps/{produto}/{id}`
```bash
curl -sS -X PATCH http://localhost:8080/api/e-li.nps/elicencie_gov/<id> \
-H 'Content-Type: application/json' \
-d '{"nota":10}'
```
Finalizar:
```bash
curl -sS -X PATCH http://localhost:8080/api/e-li.nps/elicencie_gov/<id> \
-H 'Content-Type: application/json' \
-d '{"justificativa":"muito bom", "finalizar":true}'
```
## Observações importantes
- **Fail-closed**: se a API falhar, o widget não abre o modal.
- **CORS**: liberado com `Access-Control-Allow-Origin: *`.
- **IP real do usuário**: o sistema grava `ip_real` no banco (IPv4/IPv6).
- Para funcionar corretamente atrás de proxy/Docker, garanta que o proxy repasse
`X-Forwarded-For` / `X-Real-IP`.
- O servidor usa `middleware.RealIP` (chi) para resolver o IP antes de gravar.
- **Tabelas por produto**: `nps_{produto}` é criada automaticamente ao ver um `produto_nome` novo.
- O backend **normaliza** `produto_nome` apenas para uso técnico (nome da tabela e rota):
- minúsculo + trim
- remove diacríticos
- converte caracteres fora de `[a-z0-9_]` para `_`
- valida por regex: `^[a-z_][a-z0-9_]*$`
- O nome **exibido ao usuário** é o original informado e fica salvo em `produto_nome` na tabela do produto.
- O controle de exibição (regras 45 dias / 10 dias) é baseado em: **produto + inquilino_codigo + usuario_codigo**.
## Recomendações (para prompts / manutenção)
Alguns cuidados:
- Nomes de variáveis ou arquivos preferencialmente em português
- Sempre adicionar comentários em português que ajudem humanos e IAs na manutenção
- Se a mudança for importante, atualizar `README.md`
---
## Créditos e suporte
Desenvolvido por **Azteca Software (e-licencie)** para pesquisa de NPS.
Suporte: **ti@e-licencie.com.br** ou WhatsApp **(48) 9 9948 2983**.