Schema do banco
PostgreSQL via Prisma. Modelos em PascalCase, campos em snake_case (para espelhar os contratos de API). Todo modelo de negócio carrega tenant_id e passa pelo middleware de isolamento: exceto os modelos-filho que herdam o tenant via relação com o pai (ex: AnimalWeightLog por animal_id) e as duas tabelas do painel interno (PlatformUser, SubscriptionStatusLog).
Núcleo multi-tenant
Tenant
Uma empresa cliente. Raiz de todo o isolamento: toda tabela abaixo carrega tenant_id apontando para cá (direta ou indiretamente).
| id, name, document (CNPJ/CPF, único), phone, email | dados cadastrais |
| plan | campo | fazenda | grupo |
| status | trial | active | suspended | canceled |
| trial_ends_at | fim do período de teste grátis (14 dias a partir do cadastro) |
| lead_source_utm_source/medium/campaign | origem do lead, se veio de campanha |
PlatformUser
Equipe da Pleno Digital (painel interno, Módulo 6). NÃO carrega tenant_id: vive inteiramente fora do isolamento multi-tenant, por desenho. Autenticado por uma instância NextAuth própria (cookie e secret diferentes dos de tenant).
| id, name, email (único), password_hash | credenciais |
| role | MASTER_ADMIN | EQUIPE: equipe não vê KPIs financeiros nem executa ações administrativas |
| active | desativação sem exclusão |
TenantProfile
Quais 'perfis' de negócio um tenant tem ativos. Um tenant pode ter fazenda e prestador simultaneamente: não são exclusivos.
| tenant_id, profile_type | fazenda | prestador |
| active | perfil pode ser desativado sem perder histórico |
User
Uma pessoa com acesso ao painel de um tenant. Email é globalmente único no sistema (não só por tenant): o login recebe apenas email+senha, sem seletor de empresa.
| tenant_id, name, email (único), password_hash | credenciais |
| role | OWNER | ADMIN | OPERADOR | VISUALIZADOR |
| phone | usado para vincular o WhatsAppContact |
| active | desativação preserva histórico de ações |
Property
Uma propriedade rural ('fazenda') do tenant. Um tenant pode ter várias.
| tenant_id, name, address, area_hectares | dados cadastrais |
| archived_at | arquivamento: nunca deletada, para preservar histórico de animais/talhões vinculados |
Módulo Rebanho
Animal
Um animal individual, identificado por brinco (ear_tag), único por tenant (não por propriedade).
| tenant_id, property_id, ear_tag, breed, sex, birth_date | dados cadastrais |
| status | active | sold | deceased |
| current_weight | atualizado a cada novo AnimalWeightLog |
AnimalWeightLog
Histórico de pesagens de um animal. Usado para calcular o GMD (ganho médio diário) entre as duas últimas pesagens.
| tenant_id, animal_id, weight, measured_at | uma pesagem |
Vaccine
Catálogo de vacinas do tenant. Populado com aftosa, brucelose, raiva e clostridiose ao ativar o perfil fazenda.
| tenant_id, name, default_interval_days | intervalo padrão até a próxima dose |
AnimalVaccination
Uma aplicação de vacina em um animal. next_due_at é calculado automaticamente (applied_at + intervalo).
| tenant_id, animal_id, vaccine_id, applied_at, next_due_at | aplicação e próximo vencimento |
| cost | opcional: se preenchido, gera uma FinancialEntry de despesa |
AnimalMovement
Compra, venda, transferência entre propriedades ou morte de um animal.
| tenant_id, animal_id, movement_type | purchase | sale | transfer | death |
| from_property_id, to_property_id | usados em transferência |
| value | opcional: venda gera receita, compra gera despesa em FinancialEntry |
Módulo Lavoura
Plot
Um talhão dentro de uma propriedade.
| tenant_id, property_id, name, area_hectares, current_crop | dados cadastrais |
CropCycle
Um ciclo de plantio→colheita em um talhão. Regra: um talhão só pode ter um ciclo planted/growing ativo por vez.
| tenant_id, plot_id, crop_name, planted_at, expected_harvest_at | início do ciclo |
| status | planted | growing | harvested |
| harvested_at, yield_amount, yield_unit | preenchidos ao registrar a colheita |
PlotInput
Um insumo aplicado durante um ciclo (fertilizante, defensivo, semente).
| tenant_id, cycle_id, input_type | fertilizer | pesticide | seed |
| name, quantity, unit, cost, applied_at | detalhes da aplicação: cost gera FinancialEntry |
Módulo Prestador de Serviço
ServiceClient
Um cliente do prestador de serviço (não confundir com o tenant: este é o cliente DO tenant).
| tenant_id, name, document, phone, email, notes | dados cadastrais |
Service
Um tipo de serviço no catálogo do tenant, com precificação.
| tenant_id, name, pricing_type | hour | day | fixed |
| unit_price | editar não afeta ordens já criadas |
ServiceOrder
Um serviço prestado a um cliente. total_value = quantity × unit_price do serviço no momento da criação.
| tenant_id, service_client_id, service_id, quantity, total_value | o que foi prestado e o valor |
| status | scheduled → completed → invoiced (sequencial, sem pular etapa) |
| performed_at | invoiced gera uma FinancialEntry de receita pendente |
Financeiro (compartilhado)
FinancialEntry
Um lançamento financeiro: manual ou gerado automaticamente por outro módulo (venda de animal, insumo com custo, ordem faturada).
| tenant_id, entry_type | income | expense |
| category, amount, due_date, paid_at, notes | detalhes do lançamento |
| related_module, related_id | rebanho | lavoura | servico | geral: origem do lançamento |
| status | pending | paid | overdue |
Agente WhatsApp
WhatsAppContact
Vínculo entre um número de telefone e um User de um tenant. Criado automaticamente no primeiro contato reconhecido.
| tenant_id, phone, user_id, last_interaction_at | único por (tenant_id, phone) |
AgentConversationLog
Toda mensagem recebida ou enviada pelo agente. As últimas 5 de cada contato viram contexto de curto prazo enviado ao LLM.
| tenant_id, whatsapp_contact_id, direction | in | out |
| message_type, content, intent_detected, action_taken | conteúdo e o que foi entendido/feito |
Alertas
Alert
Um alerta automático gerado pelo job diário. Idempotente por (related_module + related_id + alert_type): não duplica para o mesmo evento.
| tenant_id, alert_type | vaccine_due | harvest_near | bill_due | low_balance | trial_ending |
| related_module, related_id, message | origem e texto pronto para envio |
| status | pending | sent | dismissed |
| scheduled_for, sent_at | controle de envio via WhatsApp |
Cobrança (Asaas)
Subscription
A assinatura recorrente de um tenant no Asaas. Um tenant tem no máximo uma (unique tenant_id).
| tenant_id (único), plan | campo | fazenda | grupo |
| asaas_customer_id, asaas_subscription_id | IDs no Asaas |
| status | active | overdue | canceled |
| next_due_date | atualizado pelo webhook a cada pagamento confirmado |
SubscriptionStatusLog
Histórico de toda transição de status de uma assinatura (Módulo 6): automática (webhook do Asaas) ou manual (master_admin forçando via /plataforma). Sem tenant_id (só consumida pelo painel interno).
| subscription_id, from_status, to_status | a transição em si (from_status nulo na primeira linha) |
| changed_by_platform_user_id | nulo = automática (webhook); preenchido = forçada manualmente |
| reason | motivo, só preenchido em mudanças manuais |