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:

  1. Imutabilidade: Dados contratuais não mudam mesmo se o cadastro for atualizado
  2. Auditoria: Registro histórico preciso
  3. Compliance: Atendimento à LGPD (direito ao esquecimento)
  4. 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