Sistema de Contratos - Vanguru
Visão Geral
O sistema de contratos do Vanguru formaliza o acordo entre o prestador de serviço (motorista), o passageiro e o responsável pagante, suportando tanto gestão manual (Free) quanto automação completa (Premium).
Entidade Contract
Estrutura de Dados
class Contract {
// Identificação
final String? id;
final String contractNumber;
// Partes Envolvidas
final String passengerId;
final String responsibleId;
final String businessOwnerId;
// Snapshots (dados no momento da criação)
final ResponsibleSnapshot responsibleSnapshot;
final PassengerSnapshot passengerSnapshot;
// Termos Financeiros
final double monthlyValue;
final int contractedMonths;
final double totalValue;
// Tier (Free ou Premium)
final ContractTier tier;
// Integração com Asaas (Premium apenas)
final String? asaasSubscriptionId;
final SubscriptionStatus? subscriptionStatus;
// Controle de Parcelas
final int paidInstallments;
final bool autoCancelOnCompletion;
// Datas
final DateTime startDate;
final DateTime endDate;
final DateTime? signatureDate;
// Status e Controle
final ContractStatus status;
final PaymentFrequency paymentFrequency;
final int dayOfMonthDue;
// Opt-in e Termos
final bool responsibleAccepted;
final DateTime? responsibleAcceptedAt;
final String? termsVersion;
}
Enums
ContractTier
enum ContractTier {
free, // Sem integração Asaas
premium, // Com integração Asaas
}
ContractStatus
enum ContractStatus {
draft, // Rascunho
pending, // Aguardando aceite
active, // Ativo e gerando cobranças
suspended, // Suspenso temporariamente
completed, // Concluído
canceled, // Cancelado
}
SubscriptionStatus
enum SubscriptionStatus {
active, // Ativo no Asaas
inactive, // Inativo no Asaas
deleted, // Deletado no Asaas
}
Snapshots
Por Que Usar Snapshots?
Snapshots preservam os dados das partes no momento da assinatura do contrato, garantindo:
- Imutabilidade: Dados contratuais não mudam mesmo se o cadastro for atualizado
- Auditoria: Registro histórico preciso
- Compliance: Atendimento à LGPD (direito ao esquecimento)
- Validade Jurídica: Contrato reflete exatamente o que foi acordado
ResponsibleSnapshot
class ResponsibleSnapshot {
final String name;
final String cpf; // Mascarado
final String email;
final String phone;
final AddressSnapshot address;
final DateTime snapshotDate;
}
PassengerSnapshot
class PassengerSnapshot {
final String name;
final DateTime birthDate;
final String institutionName;
final AddressSnapshot pickupAddress;
final DateTime snapshotDate;
}
Fluxos de Uso
Criação de Contrato (Free)
1. Usuário preenche formulário de contrato
- Seleciona passageiro
- Seleciona responsável pagante
- Define valor mensal
- Define quantidade de meses
- Define dia de vencimento
- Define frequência (mensal, bimestral, etc.)
2. Sistema cria snapshots
- Captura dados do passageiro
- Captura dados do responsável
- Armazena com timestamp
3. Contrato criado (status: draft)
4. Usuário envia para aceite
5. Sistema:
- Muda status para "pending"
- Gera link de aceite (ou registra aceite manual)
6. Responsável aceita termos
7. Sistema:
- Muda status para "active"
- Registra data de aceite
- Versão dos termos aceitos
- NÃO cria Subscription (tier: free)
8. Cobranças geradas manualmente pelo usuário
Criação de Contrato (Premium)
1-6. Mesmos passos do Free
7. Sistema:
- Muda status para "active"
- Registra data de aceite
- Cria Customer no Asaas (se não existir)
- Cria Subscription no Asaas
- Armazena asaasSubscriptionId
- Define subscriptionStatus = "active"
8. Asaas gera cobranças automaticamente (40 dias antes)
9. Webhooks notificam eventos:
- PAYMENT_CREATED
- PAYMENT_RECEIVED
- PAYMENT_OVERDUE
10. Sistema incrementa paidInstallments
11. Ao atingir contractedMonths:
- Se autoCancelOnCompletion = true
- Cancela Subscription no Asaas
- Muda status para "completed"
Suspensão de Contrato
Free:
1. Usuário suspende contrato
2. Status muda para "suspended"
3. Usuário para de gerar cobranças manualmente
Premium:
1. Usuário suspende contrato
2. Sistema deleta Subscription no Asaas
3. Status muda para "suspended"
4. subscriptionStatus = "deleted"
5. Cobranças automáticas param
Reativação de Contrato
Free:
1. Usuário reativa contrato
2. Status muda para "active"
3. Usuário volta a gerar cobranças
Premium:
1. Usuário reativa contrato
2. Sistema cria novo Subscription no Asaas
3. Ajusta nextDueDate baseado em parcelas já pagas
4. Status muda para "active"
5. subscriptionStatus = "active"
6. Cobranças automáticas retomam
Integração com Asaas Subscription
Modelo Híbrido
O Vanguru usa um modelo híbrido que combina:
- Contrato (nosso): Formalização jurídica, snapshots, controle de término
- Subscription (Asaas): Automação de cobranças, integração com gateway
Vantagens
Aproveitamento do Asaas
- ✅ Geração automática de cobranças
- ✅ Antecipação (40 dias antes do vencimento)
- ✅ Webhooks para notificações
- ✅ Edição fácil via API
- ✅ Menos código para manter
Controle Adicional do Contrato
- ✅ Formalização jurídica
- ✅ Término definido (quantidade de meses)
- ✅ Histórico completo no Firestore
- ✅ Flexibilidade para regras customizadas
- ✅ Auditoria completa
Sincronização
// Criação de contrato Premium
Future<void> createPremiumContract(Contract contract) async {
// 1. Salvar no Firestore
await _firestore.collection('contracts').add(contract.toMap());
// 2. Criar subscription no Asaas
final subscription = await _asaasApi.createSubscription(
customerId: contract.responsibleSnapshot.asaasCustomerId,
billingType: 'BOLETO',
value: contract.monthlyValue,
nextDueDate: contract.startDate,
cycle: _mapFrequencyToCycle(contract.paymentFrequency),
description: 'Transporte Escolar - ${contract.passengerSnapshot.name}',
);
// 3. Atualizar contrato com subscriptionId
await _firestore.collection('contracts').doc(contract.id).update({
'asaasSubscriptionId': subscription.id,
'subscriptionStatus': 'active',
});
}
// Webhook: PAYMENT_CREATED
void handlePaymentCreated(WebhookEvent event) {
final subscriptionId = event.payment.subscription;
// Buscar contrato pelo subscriptionId
final contract = await _contractRepository
.findBySubscriptionId(subscriptionId);
// Incrementar contador
final updatedInstallments = contract.paidInstallments + 1;
// Verificar se completou
if (updatedInstallments >= contract.contractedMonths) {
if (contract.autoCancelOnCompletion) {
// Cancelar subscription
await _asaasApi.deleteSubscription(subscriptionId);
// Atualizar contrato
await _contractRepository.update(
contract.id,
status: ContractStatus.completed,
subscriptionStatus: SubscriptionStatus.deleted,
paidInstallments: updatedInstallments,
);
}
} else {
// Apenas atualizar contador
await _contractRepository.update(
contract.id,
paidInstallments: updatedInstallments,
);
}
}
Validações
Criação de Contrato
- ✅ Passageiro deve existir e estar ativo
- ✅ Responsável deve existir
- ✅ Valor mensal > 0
- ✅ Quantidade de meses > 0
- ✅ Data de início >= hoje
- ✅ Dia de vencimento entre 1 e 28
- ✅ Frequência de pagamento válida
Aceite de Contrato
- ✅ Contrato deve estar em status "pending"
- ✅ Responsável deve confirmar aceite
- ✅ Termos de uso devem ser aceitos
- ✅ Versão dos termos deve ser registrada
Suspensão
- ✅ Contrato deve estar "active"
- ✅ Não pode ter pagamentos pendentes (opcional)
Cancelamento
- ✅ Contrato não pode estar "completed"
- ✅ Confirmação do usuário
Relatórios
Contratos Ativos
- Lista de todos os contratos ativos
- Filtros: passageiro, responsável, valor
- Ordenação: data de início, valor
Contratos Vencendo
- Contratos próximos do término
- Alertas para renovação
Inadimplência por Contrato
- Contratos com pagamentos atrasados
- Valor total em atraso
- Dias de atraso
Migração Free → Premium
Processo
1. Usuário decide migrar contrato para premium
2. Sistema verifica:
- Perfil de pagamento completo?
- Documentos enviados?
- Aprovação Asaas?
3. Se aprovado:
- Cria Customer no Asaas (se não existir)
- Cria Subscription no Asaas
- Atualiza contrato:
- tier = premium
- asaasSubscriptionId = [id]
- subscriptionStatus = active
- Mantém histórico de pagamentos anteriores
4. Próximas cobranças são automáticas
Dados Preservados
- ✅ Snapshots originais
- ✅ Data de aceite
- ✅ Histórico de pagamentos manuais
- ✅ Observações e notas
Melhores Práticas
Snapshots
- Sempre criar snapshots no momento da assinatura
- Nunca atualizar snapshots após criação
- Usar snapshots para exibição de dados contratuais
Sincronização
- Sempre atualizar Firestore após operações no Asaas
- Implementar retry em caso de falha
- Manter subscriptionStatus sincronizado
Webhooks
- Validar assinatura do webhook
- Processar de forma idempotente
- Registrar todos os eventos recebidos
Auditoria
- Registrar todas as mudanças de status
- Manter log de operações
- Preservar histórico completo
Documento: Sistema de Contratos
Versão: 1.0
Data: Janeiro 2026