Ctrl K
ComunidadGuías…

docs/white-label.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
🇧🇷 Português · [🇺🇸 English](white-label.en.md) · [🇪🇸 Español](white-label.es.md)

# Instalar para clientes (agências e revendedores)

Guia para quem instala o DeskcommCRM **para outras empresas** — agência, consultoria, revendedor — e cobra por isso.

A licença é MIT: você pode modificar, hospedar para terceiros, revender e cobrar o que quiser. Não há royalty, não há cláusula proibindo hospedagem comercial e não existe versão paga que trave funcionalidade do seu cliente.

---

## Trocar a marca

**Pela tela, e sem reiniciar nada.** Em `/admin/marca` você troca o **nome do sistema** e a **cor da marca**. Salvou, recarregou, a interface inteira já está repintada — a barra lateral, os botões, o anel de foco, o título da aba e o ícone do navegador.

A cor é **derivada**, não aplicada crua: de um hex saem onze tons nos dois temas (claro e escuro), com um piso de contraste calculado por papel e por superfície. Se a cor que você escolheu ficaria ilegível como texto de botão no tema escuro, o sistema anda os degraus necessários e a tela **te mostra** em qual tom cada coisa vai pousar, antes de salvar. Nada de "escolhi amarelo e o botão ficou branco no branco".

**O logo também.** Na mesma tela você **sobe o arquivo** — PNG ou JPG, até 512 KB. Ele vai para o storage da sua própria instalação e passa a valer na hora, sem reiniciar nada e sem você hospedar imagem em lugar nenhum. Altura fixa e largura livre, para não distorcer arte de proporção qualquer; sem logo, o nome aparece como texto.

**E o ícone do aplicativo e do navegador.** Logo abaixo do logo, esse campo recebe uma imagem quadrada — PNG ou JPG, até 512 KB, de preferência 512×512 ou maior. A mesma imagem aparece na aba de todas as telas, inclusive o login, e no aplicativo instalado. O manifest anuncia PNGs de 192×192 e 512×512, renderizados no servidor a partir do arquivo limitado no Storage; nenhum endereço arbitrário é buscado. Sem arquivo válido, mantém o símbolo do produto ou a inicial sobre a cor da marca. Remover o arquivo volta a esse desenho. O ícone é da instalação, não de cada organização; trocar de organização não renomeia o aplicativo. Navegadores podem demorar para atualizar um app já instalado; remover o aplicativo e instalá-lo novamente permite conferir a nova marca.

### CSS personalizado

Na mesma página **Marca da instalação**, o administrador da instalação pode adicionar CSS para ajustes visuais finos no login e nas telas de todas as organizações. A folha vale globalmente, sem reiniciar o servidor, e pode ser removida apagando o conteúdo e salvando.

Por segurança, não é um editor de CSS irrestrito: aceita seletores de classes e propriedades visuais de cor, borda, sombra e tipografia. Recusa seletores globais ou por ID, regras `@`, URLs e carregamento remoto, funções fora de `rgb`/`rgba`/`hsl`/`hsla`/`var`/`calc`/`min`/`max`/`clamp`, comentários, escapes, scripts, `!important` e propriedades de layout e posicionamento. O limite é 16 KB. Se uma folha já salva deixar de passar na validação, ela não é aplicada e a tela Marca mostra o motivo. Como a configuração é da instalação, qualquer ajuste também afeta as organizações atendidas por ela; confira as telas depois de salvar.

Exemplo:

```css
.text-muted-foreground {
	color: #52645a;
}

.rounded-md {
	border-radius: 12px;
}
```

**Se o CSS deixar as telas ilegíveis.** Uma folha válida ainda pode esconder texto (cor transparente, fonte de tamanho zero) ou cobrir a tela com uma sombra — inclusive o login e a própria página Marca. Para sair disso, abra a página com `?sem_css=1` no endereço: ela vem sem o CSS personalizado, só para você. Entre por `/login?sem_css=1`, depois abra `/admin/marca?sem_css=1` digitando o endereço, apague o conteúdo do campo e salve. Sem acesso à tela, pelo servidor: `psql "$SUPABASE_DB_URL" -c "delete from public.platform_config where chave = 'APP_CUSTOM_CSS';"` — o CSS deixa de valer em até 30 segundos. Passo a passo em [`runbooks/css-personalizado.md`](runbooks/css-personalizado.md).

O arquivo é aceito **pelos bytes, não pela extensão**. Renomear um `.svg` para `.png` não engana: o sistema lê o conteúdo, recusa e diz por quê. Isso não é preciosismo — SVG é XML e pode carregar script, que executaria se alguém abrisse a imagem direto pelo endereço dela, num bucket que é público por necessidade.

Quem preferir hospedar por conta própria continua podendo, pelo `.env`:

```bash
APP_LOGO_URL=https://cdn.suaempresa.com.br/logo.svg
```

Entre os dois, **o arquivo subido pela tela vence a URL do `.env`** — quem subiu expressou a escolha mais recente. E remover o logo de uma organização **devolve o da instalação**, não "nenhum": as camadas caem uma na outra, em vez de apagar.

### As três variáveis do `.env`, e o papel real delas

```bash
APP_NAME=Vendas Turbo CRM
APP_LOGO_URL=https://cdn.suaempresa.com.br/logo.svg
APP_ACCENT_HEX=#7a5cd6
```

O `install.sh` pergunta **duas** delas e as grava: o `APP_NAME` (Enter mantém o padrão) e o `APP_ACCENT_HEX` (Enter usa a cor do sistema). `APP_LOGO_URL` ele não pergunta — o caminho normal do logo é subir o arquivo pela tela, e esta chave existe para quem prefere hospedar por conta própria.

> A cor é pedida com validador: só `#` + 6 dígitos passa. É mais estreito do que a tela aceita, e de propósito — os **e-mails de acesso** (confirmação de conta e recuperação de senha) leem essa chave do `.env`, e eles só reconhecem essa forma. Um `#abc` ou um `7a5cd6` pintaria a interface com a sua cor e deixaria o verde do produto no primeiro e-mail que o seu cliente abre.

> ⚠️ **Trocar a cor pela tela depois NÃO reescreve os e-mails de acesso.** O texto deles vive dentro do Supabase (GoTrue), não no CRM, e quem o empurra para lá é o `marca-emails.sh` — que lê o **`.env`**, não o banco. Para os e-mails acompanharem uma cor trocada em `/admin/marca`: ajuste também o `APP_ACCENT_HEX` no `.env` e rode `bash hostgator-setup-kit/marca-emails.sh`. É por isso que a entrevista do instalador importa: ela é o único momento em que as duas pontas nascem iguais sem ninguém precisar saber disso.

O que essas variáveis são, exatamente: **semente e piso de rollback.**

- **Semente** — na primeira leitura, o que estiver no `.env` é gravado no banco. É assim que uma instalação nova já nasce com o seu nome.
- **Piso de rollback** — se você voltar para uma versão anterior do sistema, o `agent.sh` reverte a **imagem**, não o **banco**. A marca que sobrevive a qualquer volta é a que está no `.env`.

Depois da primeira leitura, **o banco manda**. Trocar a cor pela tela não pede `docker compose up -d`, não pede reinício, não pede nada: a leitura é feita a cada carregamento, com um cache curto que a própria tela invalida ao salvar.

### Por que isso é configuração, e não uma edição de código

Trocar a marca editando os arquivos-fonte funciona **uma vez**. No próximo `bash update.sh`, a imagem nova sobrescreve o patch e a marca do seu cliente volta a ser a nossa — normalmente sem ninguém perceber, até o cliente ver.

Configuração sobrevive a toda atualização. É por isso que a marca é lida em tempo de execução e nunca embutida na compilação: **uma única imagem Docker serve qualquer marca**. Pela mesma razão não existe "imagem com a sua marca": o `update.sh` regrava a linha `APP_IMAGE` do `.env` em toda atualização, e a sua imagem seria substituída pela nossa em silêncio, numa atualização de rotina.

---

## Marca por organização

**Uma instalação atende várias organizações, e cada uma pode ter a própria marca.** O admin de cada organização abre `Configurações → Marca` (`/app/settings/marca`) e define **nome**, **cor** e **logo** dela — sem precisar de você, e sem enxergar as outras.

A fronteira, que é deliberada:

| Onde | Qual marca aparece |
|---|---|
| `/login`, cadastro, recuperação de senha, verificação em duas etapas | A da **instalação** (a sua) |
| Dentro do sistema (`/app/...`), depois de entrar | A da **organização**, se ela tiver; senão, a da instalação |
| E-mails de acesso (confirmação de conta, recuperação de senha) | A da **instalação** |
| Convite de time, e-mails de LGPD | A da **organização** que enviou |

O motivo de o login ficar de fora não é limitação: **antes de a pessoa entrar, o sistema não sabe de qual organização ela é.** Pintar o login com a cor de alguma delas seria escolher uma no escuro.

Isso não torna a instalação dedicada obsoleta — ver a comparação abaixo, que continua valendo por infra, isolamento e discurso de venda.

### O que ainda não é configurável

Sendo direto, para você não descobrir na frente do cliente. Cada linha traz a razão medida, não a desculpa:

- **Domínio por organização.** Uma instalação, um domínio. Não há coluna de domínio no schema, o desvio por host no `proxy.ts` é um NOOP declarado (existe só como documentação da topologia pretendida), e no Edge não há banco para consultar antes de decidir a quem aquele host pertence. Cliente que exige o próprio domínio pede **instalação dedicada**.
- **Fonte.** A tipografia é a mesma em toda instalação. `next/font` resolve em tempo de **build**, e a imagem que a sua VPS baixa já vem construída — um seletor de fonte no painel salvaria um valor que nada leria. (A fonte é a Atkinson Hyperlegible, escolhida pelo Braille Institute por legibilidade; trocá-la não muda percepção de marca e piora a leitura de quem passa o dia no sistema.)
- **Tema.** O par claro/escuro é do design system. A sua marca move o **accent** — o que é ação, destaque e foco —, e deliberadamente **não** move o fundo da página: o fundo é o mesmo em toda marca, e é por isso que a cor da barra do navegador também é.
- **O relatório de LGPD do titular não leva a sua marca — e isso é de propósito.** Ver a seção própria abaixo.
- **O alarme de orçamento de IA** ainda sai com a nossa marca. É o único vazamento conhecido, e ele fica: hoje esse alarme não tem agendamento nenhum ligado, então consertar a marca dele não mudaria nada que alguém veja. Sai quando o alarme ganhar cron de verdade.
- **Dois nomes técnicos não mudam**: o cabeçalho `X-Deskcomm-Signature` dos webhooks de saída e o cookie de sessão. O primeiro é contrato com sistemas de terceiros que já conferem esse nome; renomear derrubaria integração de cliente **em silêncio** — o receptor não dá erro, apenas deixa de reconhecer.

---

## Os e-mails

### Os e-mails de acesso — o primeiro artefato que o cliente do seu cliente recebe

Confirmação de conta e recuperação de senha **não são enviados pelo sistema**: quem os monta e dispara é o serviço de autenticação do Supabase. Nenhum código nosso roda ali dentro — é texto que precisa ser **empurrado** para lá por API.

Por isso o kit traz um script próprio:

```bash
bash hostgator-setup-kit/marca-emails.sh
```

Ele sobe o assunto e o corpo dos dois e-mails com o **seu** nome e a **sua** cor, e de quebra configura o endereço de retorno do link (que nenhum script configurava antes, e é pré-requisito do link funcionar). O `install.sh` o chama sozinho, logo depois de criar o projeto Supabase; o `update.sh` também o chama, para que uma instalação antiga receba isso na primeira atualização.

**Se ele não tiver a chave de acesso da API do Supabase** (`SUPABASE_ACCESS_TOKEN`), ele não falha e não derruba a instalação: imprime exatamente o que fazer à mão no painel do Supabase e sai com sucesso. Esse é o caso de quem criou o projeto pelo painel e colou as credenciais, em vez de deixar o instalador criá-lo.

> ⚠️ **Se for fazer à mão, atenção ao caractere.** O link do template precisa levar `&token_hash=`, com **`&`**, nunca `?`. Com `?` o endereço fica com dois pontos de interrogação, o sistema perde o token e o usuário cai numa tela dizendo que o link expirou — quando o problema é o template. Nesse caso o sistema agora **nomeia a causa** em vez de dizer só "link inválido".

### Convite de time e e-mails de LGPD

Saem com a marca da **organização** que os originou — porque quem processou a solicitação, no produto do seu cliente, é o sistema do seu cliente.

Para enviá-los, preencha as duas variáveis (o `install.sh` pergunta as duas):

```bash
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=nao-responda@suaempresa.com.br
```

**O endereço tem de ser de um domínio verificado na SUA conta Resend.** Esse é o único pedaço que a marca não resolve: o nome que aparece na caixa de entrada é a marca; o endereço é de quem hospeda.

**Deixar em branco é uma escolha suportada, não um defeito.** Sem elas, o sistema não tenta enviar e não falha calado: o convite mostra o link de aceite **na própria tela**, para você copiar e mandar por onde quiser, e o export de LGPD fica pendente de revisão em vez de sumir. Antes, um endereço em branco fazia todo envio falhar lá na Resend com uma mensagem opaca, e o operador ia caçar rede, contêiner e chave por causa de uma variável vazia.

### O endereço de suporte que os seus clientes veem

```bash
SUPPORT_EMAIL=suporte@suaempresa.com.br
```

Aparece nas telas de conta suspensa e de cobrança. **Vazio significa vazio:** a tela simplesmente não mostra endereço nenhum — nunca cai de volta no nosso. Numa tela de conta suspensa isso importa: quem suspendeu foi você, não nós.

---

## O relatório de LGPD é a única coisa que NÃO leva a sua marca

Quando um titular de dados exerce o direito do Art. 18 II, o sistema gera um relatório em PDF. Esse documento **não leva marca nenhuma** — nem a nossa, nem a sua, nem logo, nem cor. Ele nomeia o **controlador**: a razão social da organização, mais o encarregado (DPO).

Isso é decisão de produto, não item esquecido. Num documento que responde a um direito legal, quem é nomeado responde pelos dados. **Você é operador, não controlador.** Trocar o nome ali pela sua marca não seria "completar o whitelabel" — seria piorar: hoje o nome que aparecia era obviamente o do software, e depois pareceria a declaração de quem responde juridicamente pelos dados daquelas pessoas.

**O que isso te pede na instalação:** confira a **Razão social** de cada organização em `Configurações → Empresa`. Ela nasce igual ao nome fantasia (é o que o instalador tem para dar), e um nome fantasia impresso como razão social num documento jurídico é o tipo de erro que só aparece quando alguém reclama.

O e-mail que entrega o link do export é outra coisa e leva, sim, a marca da organização: ele diz quem **operou**. O PDF diz quem **responde**. São papéis diferentes, e é por isso que os dois carregam nomes diferentes.

---

## Um cliente por instalação, ou todos numa só?

O sistema é multi-tenant desde a primeira linha: uma instalação atende várias organizações, e o isolamento entre elas é verificado no CI a cada alteração — um usuário de uma organização não enxerga nenhuma linha de outra. Não é promessa de marketing: é o teste `tests/invariants/rls-isolation.test.ts`, que cria duas organizações e prova o não-vazamento pelo mesmo caminho de autenticação que a produção usa.

Mesmo assim, os dois modelos servem a propósitos diferentes:

| | Uma instalação por cliente | Uma instalação para todos |
|---|---|---|
| **Marca** | A de cada cliente, inclusive na tela de entrada | A sua no login; a de cada organização dentro do sistema |
| **Custo de infra** | Uma VPS por cliente | Uma VPS |
| **Falha** | Isolada | Atinge todos |
| **Atualização** | Uma por vez, pode escalonar | Todos de uma vez |
| **Dado do cliente** | Fisicamente separado | Separado por RLS |
| **Melhor para** | Revender com a marca do cliente | Sua própria operação atendendo várias contas |

Se o seu cliente pergunta "onde ficam meus dados?", a instalação dedicada tem a resposta mais simples de dar — e de defender.

---

## O argumento jurídico que fecha venda no Brasil

A **Resolução CD/ANPD nº 19/2024** tornou obrigatórias as cláusulas-padrão contratuais para **transferência internacional de dados pessoais**, com o prazo de adequação encerrado em **23 de agosto de 2025**.

Todo cliente seu que usa um CRM estrangeiro realiza essa transferência e precisa do artefato contratual. Hospedando em VPS no Brasil, **o CRM em si não transfere dados para fora do país** — e, para ele, a obrigação não se aplica.

⚠️ **A ressalva é a IA, e ela vale para quase toda instalação.** A frase acima só é inteira enquanto nenhum provedor de IA estrangeiro estiver ligado. O atendimento automático manda a conversa ao provedor que o cliente conectou (Anthropic, OpenAI, Google, DeepSeek ou OpenRouter, todos fora do Brasil). O Jev, quando o administrador o liga em IA › Provedores, manda cada mensagem dos clientes à TypeSafe AI, nos Estados Unidos, uma de cada vez e sem o resto da conversa, depois de apagar CPF, telefone e e-mail. Cada tarefa do Jev tem a sua finalidade — medir o clima da conversa, perceber tentativa de manipulação do atendimento automático, escolher qual agente atende, perceber pedido para falar com uma pessoa ou para parar de receber mensagens e ler a resposta do cliente a um follow-up — e todas usam só essa mensagem, sozinha. Para escolher o agente, vão junto as intenções que a própria empresa cadastrou no roteador; para ler a resposta ao follow-up, as saídas e a dica que ela criou no passo “Classificar (IA)” do fluxo (textos dela, não do cliente). Nesses fluxos há transferência internacional, e as cláusulas-padrão valem para eles. O Jev nasce desligado e pede o aceite de quem administra antes de enviar qualquer coisa; a IA de atendimento, não. Fora da IA há um caso só: quando o administrador cadastra uma chave de Mapas em IA › Provedores (desligado por padrão), as coordenadas do pino de localização que o cliente manda pelo WhatsApp vão ao Google, fora do Brasil, para voltar com a rua e a cidade aproximadas — e esse fluxo também é transferência internacional.

⚠️ **Não venda como "servidor no Brasil = conformidade com a LGPD".** Isso é falso e um advogado desmonta na primeira pergunta: conformidade depende de base legal, finalidade, segurança e direitos do titular. O argumento correto e defensável é o de cima: sem transferência internacional, não há exigência de cláusulas-padrão.

---

## Operação

Cada instalação traz os scripts em `hostgator-setup-kit/`:

| Comando | O que faz |
|---|---|
| `bash update.sh` | Atualiza. Faz backup do banco **antes**, reaplica o schema de forma idempotente e confere a saúde no fim |
| `bash backup.sh` / `restore.sh` | Backup e restauração |
| `bash reset-password.sh` | Redefine a senha de um usuário |
| `bash reset-mfa.sh` | Remove a verificação em duas etapas de quem perdeu o aparelho |
| `bash healthcheck.sh` | Diagnóstico da instalação |

O `reset-mfa.sh` é o que você mais vai usar: a verificação em duas etapas é obrigatória para administradores, e trocar de celular sem salvar os códigos de recuperação é a chamada de suporte mais comum.

---

## Requisitos por instalação

**4 GB de RAM recomendados** (a stack sobe com 2 GB, mas opera no limite — o WAHA usa ~150 MB por sessão de WhatsApp), portas 80 e 443, Docker Compose v2 e um domínio com registro A apontando para o IP. A VPS não compila nada — baixa uma imagem pronta. O certificado HTTPS é emitido automaticamente no primeiro acesso.

Guia completo de instalação: [`hostgator-setup-kit/README.md`](../hostgator-setup-kit/README.md).

---

*Última atualização: 14 de agosto de 2026 — revisão completa depois do épico de marca própria. As duas frases mais citadas deste documento ("cores, fontes e tema não são configuráveis" e "a marca é por instalação, não por organização") deixaram de ser verdadeiras e foram reescritas com o limite honesto de hoje.*

*Este documento existe em três idiomas, e o pré-requisito que a versão anterior deste rodapé nomeava foi pago: as traduções carregam na primeira linha um selo com o hash do original, e editar `docs/white-label.md` sem re-selar reprova `pnpm test:unit`. Depois de traduzir, re-sele com `pnpm exec tsx scripts/selar-traducao.ts --todas`.*

*Os três READMEs ficaram **fora** do selo de propósito. São o arquivo mais editado do repositório: com selo, cada conserto viraria um PR bloqueado até ~490 linhas serem re-traduzidas duas vezes — e o desfecho realista disso não é tradução em dia, é alguém re-selar sem traduzir, que é o único jeito de o selo morrer. Eles entram quando alguém quiser pagar esse custo de olhos abertos.*

## Logo para cada tema

Em **Marca**, o campo **Logo** continua sendo a imagem padrão (tema claro e mensagens por e-mail). O campo **Logo para o tema escuro (opcional)** aceita uma segunda imagem PNG ou JPG de até 512 KB, preparada para fundo escuro. Essa imagem aparece sem moldura branca no menu lateral, na tela de entrada da instalação e na prévia. Sem a segunda imagem, o logo padrão conserva a proteção branca no tema escuro. Cada arquivo tem sua própria remoção; remover o escuro restaura o comportamento anterior.

A organização que envia um logo padrão próprio deixa de herdar a arte escura da instalação: assim, os dois temas não mostram marcas diferentes por acidente. Sem logos próprios, herda o par da instalação. O login sempre usa a marca da instalação. [Compatibilidade e reversão](runbooks/logo-por-tema.md).