mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
O cabeçalho da migration 0169 já DESCREVIA esta poda quando ela não existia:
uma afirmação de estado falsa dentro do próprio artefato, que é o defeito que o
DoD 16 combate. Agora ela existe.
`lib/webhooks/retencao-da-captacao.ts`, chamada no MESMO tique do cron
`webhook-log-retention` — e não num cron novo, porque uma rota a mais seria
mais uma linha no `entrypoint.sh` do scheduler para alguém esquecer de agendar
(o defeito que já custou meses ao `risk-watcher` e ao `routing-worker`).
## Um horizonte só, e por que é diferente do arquivo forense
`webhook_events_log` é esvaziado em 7 dias e apagado em 90: lá o corpo é 97% do
peso e ninguém o lê depois de uma semana, então "esvaziar mantendo a linha" faz
sentido. Aqui a linha É o produto — é o que a aba "Leads recebidos" mostra
quando alguém pergunta de qual campanha vieram os clientes que fecharam. Ou o
registro serve inteiro, ou não serve. Default de 365 dias, ~1 kB por formulário:
300 leads/dia dão ~110 MB, e o ano fiscal cabe nos 500 MB do plano gratuito.
## Dois erros meus que o repo corrigiu, e um em que ele estava mais certo
1. Escrevi um `Math.max(dias, 30)` PRÓPRIO antes de descobrir
`lib/retencao/politica.ts` — o módulo canônico que a poda da fila e o
expurgo da auditoria já usam. Duplicação sem fonte declarada: duas cópias
do piso divergem no primeiro ajuste. Refatorado para o módulo, com as
constantes novas (`RETENCAO_CAPTACAO_DIAS_PADRAO/PISO`) morando lá.
2. Meu piso elevava o número em SILÊNCIO. `interpretarRetencao` devolve um
AVISO, que a versão caseira jogava fora — sem ele, quem escreveu
`LEAD_CAPTURE_RETENTION_DAYS=1` descobriria pela ausência de efeito. Falha
fechada na ação e fechada também na informação, que é o pior dos dois
mundos. A frase agora vai para o log.
3. Escrevi o caso do `0` esperando que caísse no piso de 30; o módulo trata
`<= 0` como LIXO e resolve para o PADRÃO de 365. Está certo — cair no piso
encurtaria a retenção de um ano para um mês por causa de um zero digitado.
O teste registra a correção em vez de escondê-la, e o caso do valor
NEGATIVO entrou junto: sem a guarda, `-30` daria um limite 30 dias no
FUTURO e o `lt(received_at, …)` apagaria o histórico INTEIRO, inclusive o
de hoje — o pior desfecho possível desta função.
⚠️ DIFERENÇA DE ALCANCE DECLARADA: os pisos da fila e da auditoria moram DENTRO
de uma função do banco (`greatest(..., piso)`), o que os faz valer até para um
`psql` na mão. Este mora no TypeScript, porque a poda da captação é um DELETE do
admin client e não uma `security definer` — não há função onde enfiá-lo. Está
escrito em `lib/retencao/politica.ts` em vez de presumido.
## Gates
typecheck 0 erros
lint 0 erros
lint:channels ok
test:unit 477 de 478 arquivos, 5343 testes
o teste novo 14 casos; sabotado (piso removido), 2 reprovam
O único arquivo falho é `lib/ai/dispatcher/rate-limit.test.ts`, com as MESMAS 5
falhas de 5 que dá na referência `e815b434` — estado idêntico.
E a atribuição anterior dele estava certa na conclusão e ERRADA no motivo: não é
"dependência de temporização". É o endereço do Redis no `.env.local` (que é
gitignored e não entra aqui). Isolado: `127.0.0.1:3998` — um
`serverless-redis-http` de outra sessão que morreu no meio do trabalho e deixou
um socket que ACEITA e não responde — dá 9 falhas de 15000ms; `localhost:8079`,
que RECUSA na hora, dá 9 passes com o mesmo código. Um socket que recusa faz o
`checkRateLimit` cair no contador em memória, que é o caminho que aqueles testes
declaram exercitar; um que aceita e cala faz esperar até o timeout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Rar8AjvB5rzqm8QKWeLMx
331 lines
18 KiB
Bash
331 lines
18 KiB
Bash
# =============================================================================
|
||
# DeskcommCRM — variáveis de ambiente
|
||
# =============================================================================
|
||
# Copie para .env.local e preencha. NUNCA commitar .env*.
|
||
# Variáveis NEXT_PUBLIC_* são expostas ao browser; o resto fica server-side.
|
||
# =============================================================================
|
||
|
||
# --- Supabase ----------------------------------------------------------------
|
||
# URL pública do projeto Supabase (ex: https://xxxx.supabase.co)
|
||
NEXT_PUBLIC_SUPABASE_URL=
|
||
# anon key (safe pra browser; RLS é o gatekeeper real)
|
||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||
# service role key — BYPASSA RLS. Apenas server. Nunca commitar. Rotação trimestral.
|
||
SUPABASE_SERVICE_ROLE_KEY=
|
||
|
||
# --- Cron / interno ----------------------------------------------------------
|
||
# Bearer secret pros endpoints /api/v1/cron/*. DIFERENTE do service role key.
|
||
INTERNAL_SECRET=
|
||
# Secret dedicado opcional pros endpoints de cron. Se vazio, cai pra INTERNAL_SECRET.
|
||
INTERNAL_CRON_SECRET=
|
||
|
||
# --- Janela do retorno agendado ----------------------------------------------
|
||
# Quão cedo e quão tarde o agente pode marcar um retorno ao cliente. Lidas pelo
|
||
# WORKER (agent-engine) e pelo APP (a capacidade que o dono liga na tela) — as
|
||
# duas leituras têm de bater, senão o mesmo horário seria aceito num caminho e
|
||
# recusado no outro. Defaults em lib/followup/janela.ts; deixe vazio para usá-los.
|
||
# FOLLOWUP_MIN_AHEAD_MS=300000 # 5 min
|
||
# FOLLOWUP_MAX_AHEAD_MS=15552000000 # 180 dias
|
||
# CRON_STAGGER_WINDOW_MS=60000 # espalha disparos do mesmo minuto (0 desliga)
|
||
|
||
# --- Encryption keys (pgcrypto) ----------------------------------------------
|
||
# Chave separada pra criptografar CPF em contacts (PII sensível LGPD).
|
||
CPF_ENCRYPTION_KEY=
|
||
# Chave pra criptografar tokens OAuth Nuvemshop em tenant_integrations.
|
||
NUVEMSHOP_OAUTH_ENCRYPTION_KEY=
|
||
# Chave pra criptografar credenciais BYO-WAHA (cliente que roda WAHA próprio).
|
||
WAHA_BYO_ENCRYPTION_KEY=
|
||
# Chave AES-256 (32 bytes em base64) pra criptografar API keys de provedores LLM
|
||
# (ai_provider_credentials). Gerar: openssl rand -base64 32
|
||
AI_CRED_AES_KEY=
|
||
|
||
# --- WAHA --------------------------------------------------------------------
|
||
# URL base da instância WAHA (ex: http://localhost:3000 em dev; https://waha.deskcomm.com em prod)
|
||
WAHA_API_BASE_URL=http://localhost:3000
|
||
# Plaintext da API key do WAHA. O ENV do container WAHA recebe o hash SHA512 hex desta.
|
||
# Gerar: echo -n "minha-chave-aqui" | shasum -a 512
|
||
WAHA_API_KEY=
|
||
# Segredo com que o WAHA assina os webhooks (header X-Webhook-Hmac). O app usa
|
||
# para CONFERIR a assinatura; o container do WAHA recebe o mesmo valor.
|
||
WAHA_HMAC_SECRET=
|
||
# "true" exige assinatura válida em TODO webhook do WAHA. Desligado por padrão
|
||
# porque o WAHA Core não assina — exigir derrubaria a ingestão de mensagens.
|
||
# Ligue se roda WAHA Plus (ou um proxy que assine).
|
||
WAHA_WEBHOOK_REQUIRE_SIGNATURE=false
|
||
# URL pública do app que o WAHA chama em webhooks (precisa ser HTTPS público; use ngrok em dev).
|
||
WAHA_WEBHOOK_BASE_URL=
|
||
# Retomar as sessões já pareadas quando o contêiner do WAHA reinicia. É lido
|
||
# pelo WAHA, não pelo app (por isso não está em lib/env.ts). O default do WAHA é
|
||
# False: o volume guarda a credencial, mas a sessão não sobe, e o número fica
|
||
# "conectado" no disco e mudo na prática até alguém abrir o front e clicar
|
||
# Reconectar. Só mexa aqui se quiser o comportamento antigo de propósito.
|
||
WHATSAPP_RESTART_ALL_SESSIONS=True
|
||
|
||
# --- OpenRouter: atribuição OPCIONAL ----------------------------------------
|
||
# `HTTP-Referer` e `X-Title` que a OpenRouter usa para atribuir o consumo e
|
||
# montar o ranking público do site dela. São OPCIONAIS — a doc deles diz
|
||
# "optional headers to identify your app", e a chamada funciona sem. Vazios,
|
||
# nenhum header é enviado. Preencha com o SEU domínio e o SEU nome se quiser
|
||
# que o consumo desta instalação apareça atribuído a você.
|
||
OPENROUTER_APP_URL=
|
||
OPENROUTER_APP_TITLE=
|
||
|
||
# --- Upstash Redis (rate limit, idempotency) ---------------------------------
|
||
UPSTASH_REDIS_REST_URL=
|
||
UPSTASH_REDIS_REST_TOKEN=
|
||
|
||
# --- AI providers ------------------------------------------------------------
|
||
# Vercel AI Gateway (preferencial — fallback de provedor + observability + ZDR)
|
||
# AI_GATEWAY_API_KEY: chave do gateway. Quando ausente, ai-response-worker
|
||
# pula com skip="ai_gateway_key_missing" (não bloqueia boot do app).
|
||
AI_GATEWAY_API_KEY=
|
||
AI_GATEWAY_BASE_URL=
|
||
VERCEL_AI_GATEWAY_URL=
|
||
# Fallback direto Anthropic (caso AI Gateway indisponível ou desabilitado pra um tenant)
|
||
ANTHROPIC_API_KEY=
|
||
|
||
# OpenRouter (OPCIONAL) — alternativa ao gateway da Vercel, compatível com a API
|
||
# da OpenAI, com catálogo grande de modelos num único faturamento.
|
||
# Ordem de resolução do chat: AI_GATEWAY_API_KEY > OPENROUTER_API_KEY > provider
|
||
# direto (ANTHROPIC_API_KEY / OPENAI_API_KEY). Vazio = nada muda.
|
||
#
|
||
# ALCANCE — leia antes de trocar. Esta chave alcança a classificação de
|
||
# sentimento, o bot de resposta antigo E, desde que a organização esteja em
|
||
# OpenRouter, o AGENTE do CRM: quando não há credencial cadastrada na tela, o
|
||
# agente cai nesta chave como credencial de plataforma. É o caso normal de quem
|
||
# instalou pelo kit escolhendo OpenRouter e nunca abriu a tela de Provedores.
|
||
#
|
||
# O que ela NÃO faz é ESCOLHER o provedor. Quem escolhe é a instalação
|
||
# (`AI_PROVIDER`, gravado em settings.llm.provider) ou a tela Agente de IA →
|
||
# Provedores. Preencher a chave aqui com a organização em Anthropic não migra
|
||
# nada; migrar é trocar o provedor, e aí esta chave passa a ser a que paga.
|
||
#
|
||
# ATENÇÃO ao escolher o modelo do agente na tela: ele opera por FERRAMENTAS
|
||
# (contatos, leads, funil, catálogo, transferência para humano). Modelo sem
|
||
# suporte a ferramentas NÃO dá erro — responde um texto plausível ao cliente e
|
||
# nunca cria o lead nem move o card. Por isso o painel RECUSA salvar um modelo
|
||
# sem ferramentas nos pontos "Responder o cliente" e "Trabalhar o funil", usando
|
||
# o que o próprio fabricante declara no catálogo.
|
||
# O invariante que segura estas afirmações: tests/unit/openrouter-alcance.test.ts
|
||
OPENROUTER_API_KEY=
|
||
OPENROUTER_BASE_URL= # vazio = https://openrouter.ai/api/v1
|
||
# Pra embeddings (text-embedding-3-small) usados no RAG por tenant
|
||
OPENAI_API_KEY=
|
||
|
||
# --- Teto de gasto de IA: a alavanca de emergência ----------------------------
|
||
# Quem escolhe o teto é cada organização, na tela Uso de IA › Orçamento (teto,
|
||
# aviso, e se a IA deve PARAR ao chegar nele). Esta chave não escolhe nada: ela
|
||
# só sabe AFROUXAR o que já foi escolhido.
|
||
# on (default) respeite o que cada organização escolheu
|
||
# avisar rebaixa qualquer "parar a IA" para apenas avisar
|
||
# off desliga a proteção nesta instalação inteira
|
||
# Também valem, como off: false, 0, no, nao, não, disabled. Valor não
|
||
# reconhecido cai em `on` — de propósito: a alavanca de emergência NUNCA pode
|
||
# derrubar o app por um typo. Mudou? reinicie o app e o worker.
|
||
AI_BUDGET_ENFORCEMENT=on
|
||
|
||
# --- Workers -----------------------------------------------------------------
|
||
# Opt-in pra rodar consumers de event_log. Default false em dev; true em prod cron.
|
||
EVENT_LOG_WORKER_ENABLED=false
|
||
# Stub do endpoint de teste do agente: 'true' devolve trace fabricado em vez de
|
||
# executar o agente. Deixe false — o runtime real já existe. Só ligue para
|
||
# exercitar o render da UI sem gastar token.
|
||
INTERNAL_AGENT_RUN_STUB=false
|
||
|
||
# --- Sentry ------------------------------------------------------------------
|
||
SENTRY_DSN=
|
||
|
||
# --- E-mail transacional (Resend) ---------------------------------------------
|
||
# Convite de time, entrega de export LGPD e alarme de SLA saem por aqui.
|
||
#
|
||
# RESEND_FROM_EMAIL precisa ser um endereço de um domínio VERIFICADO na SUA
|
||
# conta Resend. Vazio = e-mail desligado (o convite mostra o link de aceite na
|
||
# tela e o export de LGPD fica em `pending_review`), e isso é de propósito:
|
||
# herdar um domínio que não é seu faz todo envio falhar lá na Resend, com uma
|
||
# mensagem que não diz que o problema é configuração.
|
||
RESEND_API_KEY=
|
||
RESEND_FROM_EMAIL=
|
||
|
||
# --- Impersonate / suporte ----------------------------------------------------
|
||
# Segredo HMAC do cookie de impersonate. Mínimo 32 chars quando a feature é usada.
|
||
IMPERSONATE_COOKIE_SECRET=
|
||
# Endereço de suporte mostrado ao CLIENTE FINAL (conta suspensa, cobrança).
|
||
# Quem instala para terceiros põe o próprio endereço aqui. Vazio = a tela não
|
||
# mostra endereço nenhum, e nunca cai num endereço do produto.
|
||
SUPPORT_EMAIL=
|
||
|
||
# --- LGPD ---------------------------------------------------------------------
|
||
# Chave usada para assinar exports de dados LGPD.
|
||
LGPD_SIGNING_KEY=
|
||
# E-mail do Encarregado (DPO). Impresso no rodapé do relatório entregue ao
|
||
# titular (`lib/lgpd/pdf-renderer.tsx`) quando a organização não tem
|
||
# `dpo_email` própria, e é o destinatário do alarme de SLA.
|
||
LGPD_DPO_EMAIL=
|
||
# Prazo, em horas, antes de um export LGPD expirar.
|
||
LGPD_EXPORT_EXPIRES_HOURS=72
|
||
|
||
# --- Nuvemshop OAuth ---------------------------------------------------------
|
||
# Obtenha em https://partners.tiendanube.com/ (criar app → copiar credenciais).
|
||
# Deixe vazio em dev/staging se ainda não tiver app aprovado — a integração
|
||
# degrada graciosamente e a UI mostra "Integração não configurada".
|
||
# Liga a integração Nuvemshop. Quando true, exige as credenciais abaixo.
|
||
NUVEMSHOP_ENABLED=false
|
||
# APP_ID aparece na URL do painel do app no portal de parceiros.
|
||
NUVEMSHOP_APP_ID=
|
||
NUVEMSHOP_CLIENT_ID=
|
||
NUVEMSHOP_CLIENT_SECRET=
|
||
# Para testar webhooks em dev exponha o app via ngrok/cloudflared (HTTPS) e
|
||
# aponte NEXT_PUBLIC_APP_URL para o túnel antes de clicar "Conectar".
|
||
|
||
# --- App URLs ----------------------------------------------------------------
|
||
# URL canônica do app (tenant). Ex: https://app.deskcomm.com
|
||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||
# URL canônica do super-admin. Ex: https://admin.deskcomm.com
|
||
NEXT_PUBLIC_ADMIN_URL=http://localhost:3000
|
||
|
||
# =============================================================================
|
||
# AGENT ENGINE (fusão Vendaval) — worker 24/7 do agente SDR
|
||
# =============================================================================
|
||
# Connection string do Postgres do Supabase (Settings → Database). O worker usa
|
||
# Postgres direto (fila FOR UPDATE SKIP LOCKED, locks, FTS). Recomendado: role
|
||
# dedicada com login (ex.: agent_worker) — nunca a service_role key aqui.
|
||
SUPABASE_DB_URL=
|
||
|
||
# A conexão que MEXE NO SCHEMA — e SÓ o kit (install.sh/update.sh/backup.sh) a
|
||
# usa; nenhum código do app lê esta chave. OPCIONAL: vazia, tudo roda pela de
|
||
# cima, que é o caso da nuvem (a string do pooler já vem privilegiada).
|
||
# Preencha quando o banco for um Supabase PRÓPRIO e a de cima for a role menor:
|
||
# `create extension`, o `baseline.sql` e a promoção do dono exigem o dono do
|
||
# banco. Ver docs/deploy-selfhost/README.md §2.
|
||
SUPABASE_DB_ADMIN_URL=
|
||
|
||
# --- Ritmo da fila do worker (custo de banco) --------------------------------
|
||
# Os dois governam quanto o worker conversa com o Postgres quando NÃO há trabalho
|
||
# — a conta que estourou a cota de egress do plano free do Supabase na issue #258.
|
||
# Ambos são opcionais: sem eles valem os defaults abaixo, que são o comportamento
|
||
# recomendado. Como ver o que está em vigor: a linha `agent-engine pronto` no log
|
||
# do worker imprime os dois. Quando mexer e o que medir:
|
||
# docs/runbooks/custo-e-cota-do-supabase.md
|
||
#
|
||
# TETO de espera do laço da fila. Com a fila vazia, é quanto o worker dorme entre
|
||
# uma consulta e a próxima; com job agendado, ele acorda no vencimento e este
|
||
# valor só limita a soneca. Não passe de 10000: acima disso a conexão ociosa
|
||
# expira entre as rodadas e cada consulta volta a pagar TCP+TLS — gasta MAIS.
|
||
#QUEUE_POLL_INTERVAL_MS=2000
|
||
# Ritmo do "havia trabalho e eu não peguei" (todas as vagas de
|
||
# QUEUE_MAX_CONCURRENCY ocupadas, ou outro turno rodando para o mesmo contato).
|
||
# Aqui há job vencido esperando vaga, então este valor é curto de propósito:
|
||
# aumentá-lo não economiza nada no ocioso e custa throughput no pico.
|
||
#QUEUE_CLAIM_RETRY_INTERVAL_MS=250
|
||
|
||
# ── Retenção do histórico (poda diária, cron `data-retention`) ───────────────
|
||
# Espaço em disco é uma cota diferente da de tráfego: o plano free do Supabase
|
||
# limita 500 MB de BANCO, e `job_queue` + `api_audit_log` são as duas que crescem
|
||
# sozinhas. Os dois valores abaixo JÁ SÃO os defaults do código: apagar as linhas
|
||
# de um `.env` existente não muda nada, e quem atualiza sem tocar no arquivo
|
||
# recebe exatamente este comportamento. Quanto cada tabela ocupa hoje e o que
|
||
# fazer com o número: docs/runbooks/custo-e-cota-do-supabase.md
|
||
#
|
||
# Idade a partir da qual um job TERMINAL (done/failed/dead) é apagado. Job
|
||
# `pending`/`running` NUNCA é tocado — tem trabalho dentro. Apagar um job leva
|
||
# junto o `send_ledger` e o `before_send_traces` daquele run. Piso de 7 dias,
|
||
# aplicado dentro da função do banco (valor menor é elevado, com aviso no log).
|
||
JOB_QUEUE_RETENTION_DAYS=90
|
||
# Idade a partir da qual uma linha de auditoria é expurgada. O default é a
|
||
# retenção de 5 anos da regra L-10. Piso de 90 dias: nem com a chave de serviço
|
||
# esta poda alcança rastro recente. Diminuir aqui é a alavanca de quem está
|
||
# apertado de espaço e aceita guardar menos histórico.
|
||
AUDIT_LOG_RETENTION_DAYS=1825
|
||
|
||
# Chave LLM de plataforma (fallback quando a org não tem credencial BYOK
|
||
# cadastrada em /app/ai/credentials). Opcional com BYOK.
|
||
ANTHROPIC_API_KEY=
|
||
|
||
# Dono ÚNICO dos eventos de despacho do agente. 'engine' (default) = o worker
|
||
# consome; 'native' = o dispatcher interno do app consome (deploy sem worker).
|
||
# NUNCA os dois — dois consumidores = turno duplicado ou perdido.
|
||
AGENT_DISPATCH_CONSUMER=engine
|
||
|
||
# Watchdog de sessão: reconcilia channel_sessions × WAHA e reenvia mensagens
|
||
# presas em queued. Usa WAHA_API_BASE_URL/WAHA_API_KEY acima (no compose o
|
||
# worker já recebe http://waha:3000). Knobs opcionais:
|
||
#WATCHDOG_INTERVAL_MS=60000
|
||
#WATCHDOG_REDRIVE_BATCH_SIZE=10
|
||
|
||
# Flywheel agendado (judge → proposta de melhoria; promoção SEMPRE manual na
|
||
# tela de Agentes IA). 0 = desligado; default 6h.
|
||
#FLYWHEEL_INTERVAL_MS=21600000
|
||
#FLYWHEEL_BATCH_LIMIT=10
|
||
|
||
# ── Marca da instalação (white-label) ───────────────────────────────────────
|
||
# Quem instala o CRM para clientes (agência, revendedor) troca a marca aqui, sem
|
||
# editar código — patch em código se perde no próximo update. Vazio = padrão.
|
||
# APP_LOGO_URL aceita qualquer URL pública de imagem; sem ela, o nome aparece
|
||
# como texto. Ver lib/branding.ts.
|
||
APP_NAME=
|
||
APP_LOGO_URL=
|
||
# Cor da marca, em hex (ex.: #506d48). Dela sai a rampa de 11 tons que pinta o
|
||
# produto inteiro — botão, anel de foco, chip, hover — nos dois temas. Vazio = a
|
||
# cor do produto. Cor sem contraste suficiente é DESLOCADA em vez de recusada, e
|
||
# marca acromática (cinza/preto/branco) não vira accent: em ambos os casos o
|
||
# motivo fica registrado. Ver lib/branding/contraste.ts.
|
||
APP_ACCENT_HEX=
|
||
|
||
# --- WhatsApp Cloud API (Meta) — Fase 3a/3b do seam de canais ---
|
||
# Opcionais: sem elas o canal oficial simplesmente não é configurado, e o WAHA
|
||
# segue funcionando. Nada aqui adiciona container ao compose — o servidor é a
|
||
# nuvem da Meta.
|
||
META_APP_ID=
|
||
# App Secret: chave do HMAC SHA-256 que valida os webhooks (X-Hub-Signature-256).
|
||
META_APP_SECRET=
|
||
META_WABA_ID=
|
||
META_PHONE_NUMBER_ID=
|
||
META_SYSTEM_USER_TOKEN=
|
||
# Token que VOCÊ escolhe; a Meta o devolve no handshake (hub.verify_token) para
|
||
# provar que o endpoint é seu. Sem ele o GET de verificação responde 403.
|
||
META_WEBHOOK_VERIFY_TOKEN=
|
||
# Versão da Graph API. Explícita de propósito: bump é decisão, não deriva.
|
||
META_GRAPH_VERSION=v22.0
|
||
|
||
# --- Canal via intermediário (BSP) — opcional ---
|
||
# Deixe em branco se você não usa este canal; nada quebra sem estas três.
|
||
#
|
||
# As DUAS de baixo andam juntas: o adapter só se considera configurado com as
|
||
# duas preenchidas. Preencher só uma não é meio-caminho — é o estado que fazia a
|
||
# mensagem ser marcada como enviada sem sair, e por isso a checagem agora exige
|
||
# o par. A credencial também pode viver na SESSÃO (cifrada), e aí estas ficam
|
||
# vazias.
|
||
ZERNIO_ACCOUNT_ID=
|
||
ZERNIO_API_KEY=
|
||
# Só para apontar para homologação. Vazio usa a produção do provedor.
|
||
ZERNIO_API_BASE_URL=
|
||
|
||
# --- Teto de login por IP (SÓ para CI de e2e) ---
|
||
# NÃO defina isto em produção nem numa VPS. O default (60 tentativas por IP a
|
||
# cada 5 min) é o valor correto para uso real e já é folgado por causa de NAT.
|
||
#
|
||
# Existe porque no CI todos os testes saem de UM IP (o runner): 28 specs de e2e
|
||
# estouram o teto e o login passa a responder "Muitas tentativas", reprovando
|
||
# specs que não têm nada a ver com autenticação.
|
||
#
|
||
# O teto por CONTA (5 falhas na mesma conta em 5 min) NÃO é configurável de
|
||
# propósito — é ele que barra brute force, inclusive distribuído por vários IPs.
|
||
# Valor inválido ou ausente cai no default: a falha é fechada.
|
||
# AUTH_RATE_LIMIT_LOGIN_IP=1000
|
||
|
||
# Retenção do arquivo do corpo cru dos webhooks (webhook_events_log).
|
||
# 7 dias mantém o banco dentro dos 500 MB do plano gratuito do Supabase; o
|
||
# arquivo era 86% do banco numa instalação real, crescendo ~23 MB/dia.
|
||
WEBHOOK_LOG_BODY_RETENTION_DAYS=7
|
||
# Quando a LINHA some (não só o corpo). Até aqui ela custa ~200 B e ainda
|
||
# responde quantos eventos chegaram, de que tipo e se a assinatura conferia.
|
||
WEBHOOK_LOG_ROW_RETENTION_DAYS=90
|
||
|
||
# Retenção do HISTÓRICO de leads captados (webhook_lead_captures) — a aba
|
||
# "Leads recebidos". Bem mais longa que a do arquivo acima porque aqui a linha
|
||
# É o produto: é ela que responde de qual campanha veio o cliente que fechou.
|
||
# ~1 kB por formulário preenchido, então 300/dia por um ano dão ~110 MB.
|
||
# O código que apaga aplica um piso de 30 dias — número menor não desarma a
|
||
# evidência de origem de um contato.
|
||
LEAD_CAPTURE_RETENTION_DAYS=365
|