O problema
Os estúdios e salões que atendo na Eneven marcavam horário por mensagem, caderno e print. Quando a cliente confirma e não aparece, o horário fica vazio e ninguém mais pode marcar nele. Eu queria um sistema barato, que o próprio dono configura sozinho e que lembra a cliente no WhatsApp.
Como eu fiz
- Aplicação única em Next.js (App Router) com TypeScript: página pública de agendamento, painel do dono e API.
- Multiempresa: cada estabelecimento tem o link próprio (agenda.eneven.com.br/nome) e os dados isolados por tenant no PostgreSQL, com Row Level Security.
- Modelagem e migrations com Drizzle. Uma trava no banco impede duas clientes no mesmo horário, mesmo com dois cliques no mesmo segundo.
- A página pública mostra só horários livres, já descontando duração do serviço e intervalo. A cliente adiciona ao calendário e remarca ou cancela pelo link.
- Lembrete automático no WhatsApp pela API oficial da Meta, com botões Confirmar, Remarcar e Cancelar. A resposta atualiza a agenda sozinha. Um relógio interno enfileira e envia os lembretes a cada 5 minutos.
- Cobrança por assinatura no cartão pela API de assinaturas do Mercado Pago, mais Pix e boleto avulsos. O acesso é controlado por plano, com teste grátis de 30 dias.
- Painel para o dono: agenda por mês e dia, clientes, serviços, equipe com acesso por convite, indicadores do mês e tema claro ou escuro.
- Área da plataforma para eu administrar os estabelecimentos, com modo suporte e registro das ações.
- Login com senha ou Google, e-mails transacionais, notificação push no celular e limite de tentativas nas rotas sensíveis.
- Testes com Vitest e Playwright, deploy em container Docker numa VPS pelo Easypanel, com backup diário do banco.
Telas do sistema
O que aprendi
Em sistema multiempresa, o isolamento dos dados precisa estar no banco e não só no código da aplicação. Aprendi também a tratar o que não está sob meu controle: a Meta pode recusar uma mensagem e o Mercado Pago pode avisar um pagamento duas vezes. Cada caso precisa deixar rastro para eu conferir depois.