Arquitetura — 4 Camadas
Sistema SaaS multiempresa white-label. Cada barbearia é um tenant isolado com slug próprio.
Mapa de Rotas
/master → Painel master (protegido por MASTER_TOKEN) /empresa/{slug}/dashboard → Dashboard da barbearia /empresa/{slug}/agenda → Agenda visual por barbeiro /empresa/{slug}/agendamentos → Lista de agendamentos /empresa/{slug}/clientes → CRM de clientes /empresa/{slug}/barbeiros → Gestão de barbeiros e horários /empresa/{slug}/servicos → CRUD de serviços /empresa/{slug}/financeiro → Cobranças do tenant /empresa/{slug}/configuracoes → WhatsApp, dados da empresa /empresa/{slug}/bloqueada → Página de acesso bloqueado /agendar/{slug} → Link público (sem login, mobile-first) /confirmar/{token} → Confirmar/cancelar presença via token /api/webhooks/asaas → Recebe fan-out do Multi-IaSolux
Middleware de Acesso
Roda em TODAS as rotas /empresa/{slug}/* e /agendar/{slug}:
// Status → Ação trial → trial_expira_em < NOW? → SET bloqueada → bloquear → senão: permitir + injetar BANNER_TRIAL ativa → permitir acesso completo inadimplente → permitir + injetar BANNER_INADIMPLENTE bloqueada → redirect /empresa/{slug}/bloqueada cancelada → redirect /empresa/{slug}/bloqueada
Stack & Convenções
Framework: Next.js 14 (App Router)
Linguagem: TypeScript strict
Banco: PostgreSQL + Prisma ORM
Estilo: Tailwind CSS
Ícones: Lucide React
Toasts: Sonner
#0D0D0D fundo
#1A1A1A cards
#C9A84C accent dourado
#22C55E sucesso/ativa
#EF4444 erro/bloqueada
#F97316 alerta/inadimplente
Estrutura de Diretórios
barbearia-saas/ ├── prisma/ │ ├── schema.prisma ← todas as 13 tabelas │ └── seed.ts ← dados de teste ├── src/ │ ├── app/ │ │ ├── master/ ← painel master │ │ ├── empresa/[slug]/ ← painel tenant │ │ ├── agendar/[slug]/ ← link público │ │ ├── confirmar/[token]/← confirmação presença │ │ └── api/ │ │ ├── webhooks/asaas/ ← recebe fan-out │ │ └── agente/ ← chat AI │ ├── components/ │ ├── lib/ │ │ ├── prisma.ts ← client singleton │ │ ├── asaas.ts ← cliente thin para multi-iasolux │ │ ├── evolution.ts ← Evolution API client │ │ └── notificacoes.ts ← dispatch de notificações │ └── middleware.ts ← verificação de status ├── .env.example └── package.json
Plano de Construção
Construir um módulo por vez. Não avançar sem o anterior funcionando.
Criar todas as 13 tabelas + 7 índices. Popular com dados de teste. Middleware de controle de acesso por status.
- prisma/schema.prisma com todas as tabelas
- prisma migrate dev sem erros
- seed.ts: 3 planos, 1 empresa, 2 barbeiros, 5 serviços, 3 clientes, 8 agendamentos
- middleware.ts intercepta /empresa/[slug]/* e /agendar/[slug]
- Banner trial exibe (azul informativo)
- Banner inadimplente exibe (laranja)
- Página /bloqueada com botões Pagar e Suporte
- Link público bloqueado para empresa bloqueada
- Middleware não afeta rota /master
Registrar projeto barbearia no multi-iasolux. Criar cliente fino para criar customer e cobrança. Webhook receiver com fan-out. Job diário.
- Projeto "barbearia" registrado no PROJETOS do multi (servidor 209)
- Token gerado e salvo em BARBEARIA_WEBHOOK_TOKEN
- Multi reiniciado, /health mostra "barbearia" em projetos_registrados
- Migration: campos cpf_cnpj e tipo_pessoa em empresas
- lib/asaas.ts com criarCustomer() e criarCobranca()
- Modal de ativação no master chama e salva corretamente
- POST /api/webhooks/asaas valida token e registra log
- PAYMENT_RECEIVED: empresa ativa no banco
- PAYMENT_OVERDUE: inadimplente/bloqueada conforme dias
- SUBSCRIPTION_DELETED: empresa cancelada
- Job diário: trials + inadimplentes + recalcular dias_atraso
- Rota /master protegida por env MASTER_TOKEN
- Aba Visão Geral: MRR, métricas, gráfico 6 meses
- Aba Empresas: tabela com filtros e badges coloridos
- Ações por empresa: ver, editar, ativar, bloquear, reativar, cancelar
- Aba Cobranças: histórico unificado com filtros
- Aba Planos: CRUD completo
- Modal Ativar Assinatura: 2 passos + integração multi-asaas
- Menu lateral responsivo (colapsa em mobile)
- CRUD barbeiros com verificação de limite do plano
- Horários por dia da semana (7 dias) salvos corretamente
- CRUD serviços completo
- Agenda visual por barbeiro (dia e semana)
- Modal novo agendamento com slots calculados em tempo real
- Ações no agendamento: concluir/cancelar/no-show
- Lista clientes com busca por nome e telefone
- Perfil cliente: histórico + resumo (total gasto, barbeiro favorito)
- Dashboard com todos os dados vindos do banco real
- Nenhum card com dado mocado
Fluxo: Verificar empresa → Serviço → Barbeiro → Data/Hora (slots reais) → Dados → Resumo → Confirmação → Sucesso
- Barra de progresso visível em todos os steps
- Slots calculados corretamente (sem conflito, sem passado)
- Opção "Sem preferência" agrupa por barbeiro disponível
- Validações: nome 2 palavras, telefone 10-11 dígitos, email opcional
- Não cria cliente duplicado (empresa_id + telefone)
- Conflito final verificado antes do INSERT
- Token de confirmação gerado e salvo
- Tela de sucesso com número #8chars do agendamento
- Link bloqueado para empresa bloqueada
- Widget AI público: conduz agendamento completo por chat
- Sessão salva em conversa_agente a cada etapa
- Agente interno: 6 queries de gestão respondidas corretamente
- Instância WhatsApp criada pela tela de configurações
- QR Code exibido e polling a cada 5s funciona
- Status "conectado" salvo no banco após leitura
- Notificação 1: aviso ao barbeiro ao criar agendamento
- Notificação 2: job a cada 5min envia lembrete 1h antes
- Página /confirmar/{token}: confirmar e cancelar funcionam
- Ambas as ações notificam o barbeiro
- Financeiro tenant: histórico real de cobranças
- Com NOTIFICATION_CHANNEL=none: log gerado, sem envio
Modelo de Dados — 13 Tabelas
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | gen_random_uuid() |
| nome | VARCHAR(100) | Ex: Básico, Profissional, Premium |
| descricao | TEXT | opcional |
| preco_mensal | DECIMAL(10,2) | Valor cobrado mensalmente |
| limite_barbeiros | INTEGER | Default 3. NULL = ilimitado |
| limite_agendamentos_mes | INTEGER | NULL = ilimitado |
| funcionalidades_json | JSONB | {"agente_ai": true, "relatorios": true} |
| ativo | BOOLEAN | Default true |
| criado_em | TIMESTAMP | Default NOW() |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | gen_random_uuid() |
| nome | VARCHAR(200) | Nome da barbearia |
| slug | VARCHAR(100) UNIQUE | Ex: navalha-dourada — usado nas rotas |
| dono_nome | VARCHAR(200) | opcional |
| telefone | VARCHAR(20) | opcional |
| VARCHAR(200) | opcional | |
| cpf_cnpj | VARCHAR(20) | Obrigatório para Asaas |
| tipo_pessoa | VARCHAR(10) | fisica | juridica |
| cidade | VARCHAR(100) | opcional |
| endereco | TEXT | opcional |
| plano_id | UUID → planos | FK |
| status | VARCHAR(30) | trial | ativa | inadimplente | bloqueada | cancelada |
| trial_expira_em | TIMESTAMP | NOW() + 7 dias ao criar |
| asaas_customer_id | VARCHAR(100) | ID do customer no Asaas (cus_XXX) |
| asaas_subscription_id | VARCHAR(100) | ID da assinatura (sub_XXX) |
| proximo_vencimento | DATE | Próximo vencimento da cobrança |
| ultimo_pagamento_em | TIMESTAMP | opcional |
| dias_atraso | INTEGER | Default 0. Calculado pelo webhook |
| ativo | BOOLEAN | Default true |
| criado_em / atualizado_em | TIMESTAMP | Default NOW() |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| empresa_id | UUID → empresas | ON DELETE CASCADE |
| asaas_payment_id | VARCHAR(100) UNIQUE | ID do pagamento no Asaas |
| asaas_subscription_id | VARCHAR(100) | opcional |
| valor | DECIMAL(10,2) | |
| status | VARCHAR(30) | pendente | pago | vencido | cancelado | estornado |
| vencimento | DATE | |
| pago_em | TIMESTAMP | opcional |
| url_boleto | TEXT | opcional |
| url_pix | TEXT | opcional |
| descricao | TEXT | opcional |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| evento | VARCHAR(100) | PAYMENT_RECEIVED, PAYMENT_OVERDUE, etc. |
| asaas_payment_id | VARCHAR(100) | opcional |
| asaas_customer_id | VARCHAR(100) | opcional |
| payload_json | JSONB | Payload bruto do Asaas |
| processado | BOOLEAN | Default false |
| erro_detalhe | TEXT | opcional — se processado=false |
| recebido_em | TIMESTAMP | Default NOW() |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| empresa_id | UUID → empresas | ON DELETE CASCADE |
| nome | VARCHAR(200) | |
| especialidade | VARCHAR(200) | Ex: Corte + Barba |
| foto_url | TEXT | opcional |
| telefone | VARCHAR(20) | Para notificações WhatsApp |
| VARCHAR(200) | Fallback se sem telefone | |
| ativo | BOOLEAN | Default true |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| barbeiro_id | UUID → barbeiros | ON DELETE CASCADE |
| dia_semana | INTEGER | 0=dom, 1=seg, 2=ter, 3=qua, 4=qui, 5=sex, 6=sáb |
| hora_inicio | TIME | Ex: 09:00 |
| hora_fim | TIME | Ex: 18:00. Deve ser > hora_inicio |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| empresa_id | UUID → empresas | ON DELETE CASCADE |
| nome | VARCHAR(200) | Ex: Corte Simples |
| descricao | TEXT | opcional |
| duracao_minutos | INTEGER | Base para calcular slots |
| preco | DECIMAL(10,2) | Ex: 40.00 |
| ativo | BOOLEAN | Default true |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| empresa_id | UUID → empresas | ON DELETE CASCADE |
| nome | VARCHAR(200) | |
| telefone | VARCHAR(20) | Chave de deduplicação (empresa_id + telefone) |
| VARCHAR(200) | opcional |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| empresa_id | UUID → empresas | ON DELETE CASCADE |
| cliente_id | UUID → clientes | |
| barbeiro_id | UUID → barbeiros | |
| servico_id | UUID → servicos | |
| data_hora_inicio | TIMESTAMP | Início do atendimento |
| data_hora_fim | TIMESTAMP | inicio + duracao_minutos |
| status | VARCHAR(30) | confirmado | concluido | cancelado | no-show |
| lembrete_enviado | BOOLEAN | Default false — flag do job |
| confirmado_pelo_cliente | BOOLEAN | null=sem resposta, true=confirmou |
| confirmado_em | TIMESTAMP | opcional |
| observacoes | TEXT | opcional |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| agendamento_id | UUID → agendamentos | ON DELETE CASCADE |
| token | UUID UNIQUE | gen_random_uuid() — vai no link |
| usado | BOOLEAN | Default false |
| resposta | VARCHAR(10) | sim | nao | null |
| expira_em | TIMESTAMP | NOW() + 24h ao criar |
| respondido_em | TIMESTAMP | opcional |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| agendamento_id | UUID → agendamentos | opcional |
| tipo | VARCHAR(50) | aviso_barbeiro | lembrete_cliente | resposta_processada |
| destinatario | VARCHAR(100) | Telefone ou email |
| canal | VARCHAR(20) | whatsapp | email |
| status | VARCHAR(20) | enviado | falhou | pendente |
| mensagem_texto | TEXT | opcional |
| erro_detalhe | TEXT | opcional — se falhou |
| enviado_em | TIMESTAMP | Default NOW() |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| empresa_id | UUID → empresas | ON DELETE CASCADE |
| instancia_nome | VARCHAR(100) UNIQUE | = slug da empresa |
| status | VARCHAR(30) | desconectado | aguardando_qr | conectado |
| numero_conectado | VARCHAR(30) | opcional — número do WhatsApp |
| qr_code_base64 | TEXT | opcional — atualizado no polling |
| ultima_verificacao | TIMESTAMP | opcional |
| coluna | tipo | descrição |
|---|---|---|
| id | UUID PK | |
| sessao_id | UUID UNIQUE | gen_random_uuid() — identifica a sessão |
| empresa_id | UUID → empresas | opcional |
| contexto | VARCHAR(20) | publico | interno |
| etapa_atual | VARCHAR(50) | inicio | servico | barbeiro | data | dados | resumo | confirmado |
| dados_sessao_json | JSONB | Estado completo da conversa |
| mensagens_json | JSONB | Histórico de mensagens |
| agendamento_confirmado_id | UUID → agendamentos | preenchido ao confirmar |
| iniciada_em / finalizada_em | TIMESTAMP |
Índices Obrigatórios
| índice | tabela / colunas | propósito |
|---|---|---|
| idx_agendamentos_empresa_data | agendamentos(empresa_id, data_hora_inicio) | Queries de agenda por empresa e data |
| idx_agendamentos_barbeiro_data | agendamentos(barbeiro_id, data_hora_inicio) | Verificação de conflito de horários |
| idx_clientes_empresa_telefone | clientes(empresa_id, telefone) | Deduplicação de clientes no agendamento |
| idx_horarios_barbeiro | horarios_trabalho(barbeiro_id, dia_semana) | Busca de expediente por dia |
| idx_cobrancas_empresa | cobrancas(empresa_id, status) | Histórico financeiro por empresa |
| idx_cobrancas_asaas | cobrancas(asaas_payment_id) | Lookup rápido pelo ID do Asaas no webhook |
| idx_empresas_asaas | empresas(asaas_customer_id) | Identificar empresa pelo customer_id do Asaas |
Evolution API — WhatsApp
Cada empresa tem instância própria com nome = slug da empresa.
EVOLUTION_API_URL=https://sua-evolution-api.com
EVOLUTION_API_KEY=sua-chave-aqui
// Header em todas as chamadas:
{ "apikey": "{EVOLUTION_API_KEY}" }
Endpoints
state: "close" → desconectado ❌
Formatação de Telefone
// Antes de enviar qualquer mensagem: 1. Remover caracteres não numéricos: replace(/\D/g, '') 2. Adicionar 55 se não começar com 55 3. Validar: deve ter 12 ou 13 dígitos 4. Se inválido: registrar em notificacoes_log (status='falhou') e NÃO enviar // Exemplos: "(11) 99999-9999" → "5511999999999" ✅ "11999990001" → "5511999990001" ✅ "999" → inválido, log + skip ❌
NOTIFICATION_CHANNEL
NOTIFICATION_CHANNEL=whatsapp → envia via Evolution API NOTIFICATION_CHANNEL=email → envia via SMTP NOTIFICATION_CHANNEL=both → tenta ambos NOTIFICATION_CHANNEL=none → registra como pendente, NÃO envia — desenvolvimento
Multi-IaSolux — Hub Asaas
Conta Asaas centralizada no servidor 209 (209.50.240.142). O barbearia não tem chave Asaas própria.
MULTI_IASOLUX_URL=https://multi.iasolux.com.br MULTI_INTERNAL_TOKEN=66efa8cfd3925f9efe67c565b971ad6c441ccb739960ff045f438ceb6481106f BARBEARIA_WEBHOOK_TOKEN=<token-gerado-ao-registrar-projeto> // Header em todas as chamadas ao multi: { "X-Internal-Token": "{MULTI_INTERNAL_TOKEN}" } // externalReference — formato padrão do projeto: "barbearia:{empresa.id}"
Endpoints do Multi
"externalReference": "barbearia:cliente:{empresa_id}" }
"customer_id": "cus_XXXXX",
"valor": 39.99,
"metodo": "BOLETO", // ou PIX
"dueDate": "2026-06-01",
"descricao": "BarbearIA — Plano Profissional — Navalha Dourada",
"externalReference": "barbearia:{empresa.id}" }
/api/webhooks/asaas.Setup Inicial (1x)
# No servidor 209: ssh root@209.50.240.142 # Gerar token do projeto barbearia: openssl rand -hex 32 # Editar /root/multi-iasolux/.env # Adicionar no JSON de PROJETOS: "barbearia": { "webhook": "https://barbearia.iasolux.com.br/api/webhooks/asaas", "token": "<token-gerado-acima>" } # Reiniciar: pm2 restart multi-iasolux # ou pm2 list pra ver o nome # Verificar: curl https://multi.iasolux.com.br/health # Deve mostrar "barbearia" em projetos_registrados
Fluxo de Webhook
Asaas → POST multi.iasolux.com.br/webhook/asaas
→ multi extrai "barbearia" do externalReference
→ POST barbearia.iasolux.com.br/api/webhooks/asaas
header: asaas-access-token: {BARBEARIA_WEBHOOK_TOKEN}
// Eventos tratados pelo barbearia:
PAYMENT_RECEIVED → empresa.status = 'ativa', dias_atraso = 0
PAYMENT_CONFIRMED → idem
PAYMENT_OVERDUE → dias_atraso ≤ 5: 'inadimplente' / > 5: 'bloqueada'
PAYMENT_DELETED → cobranca.status = 'cancelado'
PAYMENT_REFUNDED → cobranca.status = 'estornado'
SUBSCRIPTION_DELETED → empresa.status = 'cancelada'
outros → asaas_webhooks_log com processado=false
Regras Absolutas de Negócio
🔴 Nunca violar — sem exceção
- Nunca criar agendamento sem verificar conflito de horários no banco (race condition)
- Nunca cadastrar cliente duplicado — sempre verificar empresa_id + telefone antes do INSERT
- Nunca permitir acesso a empresa bloqueada ou cancelada (painel E link público)
- Nunca processar webhook do Asaas sem validar o header asaas-access-token
- Nunca deixar o agente AI inventar dados — sempre consultar o banco
- Nunca avançar fase de construção sem a anterior integrada e funcionando
- Nunca enviar notificação sem registrar em notificacoes_log
- Se NOTIFICATION_CHANNEL=none: registrar como pendente, não tentar enviar
Como Qualquer Claude Retoma
🔄 Passo a passo para retomar o projeto
- Ler
/root/projeto/barbearia/00-ORGANOGRAMA.md— visão geral completa - Identificar qual fase está com status 🔄 (em andamento) ou ⬜ (pendente)
- Ler o checkpoint da fase atual (ex: 01-FASE1-FUNDACAO.md) para saber o que falta
- Verificar código existente em
/root/barbearia-saas/ - Continuar a partir do checklist — marcar [x] ao concluir cada item
- Ao concluir a fase: atualizar status para ✅ e registrar no HISTÓRICO
📁 Arquivos de referência
/root/projeto/barbearia/00-ORGANOGRAMA.md— organograma e convenções/root/projeto/barbearia/01-FASE1-FUNDACAO.md— DB + Seed + Middleware/root/projeto/barbearia/02-FASE2-ASAAS.md— Integração Asaas via Multi/root/projeto/barbearia/03-FASE3-MASTER.md— Painel Master/root/projeto/barbearia/04-FASE4-PAINEL-TENANT.md— Painel da Barbearia/root/projeto/barbearia/05-FASE5-LINK-PUBLICO.md— Link Público/root/projeto/barbearia/06-FASE6-AI-NOTIFICACOES.md— AI + Notificações/root/multi-iasolux/server.js(servidor 209) — hub Asaas/root/iasolux-monitor/apps/api/src/services/payment-asaas.ts— referência de integração
Documentação gerada em 2026-05-05 · barbearia.iasolux.com.br · iasolux