Módulo 09: Propostas / Cotações — API
Status: Rascunho Inicial Regras de negócio: ver
regras-negocio/09_propostas.mdDomínio: verdominio/proposta.md
1. Contratos de API (RESTful)
1.1 Simulação e Criação
POST /api/propostas/simular
Auth: Consultor | Gestor
Body: { veiculo_id, plano_id, opcionais_ids: [] }
Return: {
valor_fipe_referencia,
snap_fipe_mes_ref,
valor_base_plano,
valor_opcionais,
total_estimado
}
Nota: Não persiste dados. Apenas calcula e retorna o resumo financeiro.
POST /api/propostas
Auth: Consultor | Gestor
Body: {
cliente_id,
veiculo_id,
plano_id,
opcionais_ids: [],
taxa_adesao,
desconto_mensal_pct
}
Return: { proposta_id, status: "RASCUNHO", valor_mensal_liquido }
Errors:
422 — desconto acima do teto do consultor (requer aprovação de gestor)
422 — veiculo sem codigo_fipe e plano exige FIPE
404 — veiculo não pertence ao cliente informado
1.2 Envio e Link Dinâmico
POST /api/propostas/{id}/enviar
Auth: Consultor | Gestor
Body: { canal: "WHATSAPP | EMAIL" }
Return: { link_publico: "https://crm.com/p/{hash}" }
Nota: Congela todos os valores via snapshot. Muda status para ENVIADA.
Errors:
409 — proposta não está em status RASCUNHO
GET /api/publico/propostas/{hash}
Auth: Público (sem autenticação)
Return: { dados_proposta_para_exibicao, validade, tenant_visual }
Errors:
404 — hash não encontrado
410 — proposta expirada, cancelada ou já respondida
POST /api/publico/propostas/{hash}/responder
Auth: Público (sem autenticação)
Body: { acao: "ACEITAR | RECUSAR" }
Headers capturados: IP, User-Agent (registrados na timeline)
Return: 200 OK
Errors:
409 — proposta já respondida ou expirada
1.3 Fluxo Pós-Aceite
POST /api/propostas/{id}/avancar-vistoria
Auth: Consultor | Gestor
Condição: status == ACEITA && plano.exige_vistoria == true
Return: { vistoria_id, link_vistoria }
POST /api/propostas/{id}/gerar-contrato
Auth: Consultor | Gestor
Condição: status == ACEITA && (vistoria.status == APROVADA || plano.exige_vistoria == false)
Return: { contrato_id }
1.4 Gestão
GET /api/propostas
Auth: Consultor | Gestor | Admin
Query: ?status=ENVIADA&cliente_id=uuid&veiculo_id=uuid&vencendo_em_dias=5
Return: [{ id, cliente, veiculo, plano, status, data_validade, valor_mensal_liquido }]
GET /api/propostas/{id}
Auth: Consultor (carteira própria) | Gestor | Admin
Return: { ...atributos completos, opcionais[], timeline[] }
POST /api/propostas/{id}/clonar
Auth: Consultor | Gestor
Return: { nova_proposta_id, status: "RASCUNHO" }
Nota: Clona todos os dados (veículo, plano, opcionais, taxas) como RASCUNHO.
PATCH /api/propostas/{id}/cancelar
Auth: Consultor | Gestor
Body: { motivo? }
Return: 200 OK
Condição: status in [RASCUNHO, ENVIADA]
2. Schemas de Dados
Proposta
{
"id": "uuid",
"tenant_id": "uuid",
"consultor_id": "uuid",
"cliente_id": "uuid",
"veiculo_id": "uuid",
"plano_id": "uuid",
"status": "RASCUNHO | ENVIADA | ACEITA | PERDIDA | CANCELADA | CONVERTIDA",
"hash_publico": "string",
"snap_fipe_mes_ref": "YYYY-MM",
"snap_valor_fipe": "decimal",
"snap_nome_plano": "string",
"valor_mensal_bruto": "decimal",
"valor_opcionais_total": "decimal",
"desconto_concedido_pct": "decimal",
"valor_mensal_liquido": "decimal",
"taxa_adesao_negociada": "decimal",
"data_validade": "date",
"data_aceite": "datetime | null",
"created_at": "datetime",
"updated_at": "datetime"
}
Proposta_Opcional (N:M)
{
"proposta_id": "uuid",
"opcional_id": "uuid",
"snap_nome_opcional": "string",
"snap_valor_mensal": "decimal"
}
3. Segurança e Boas Práticas
| Regra | Implementação |
|---|---|
| Silo de dados | tenant_id validado em toda operação. Consultor acessa apenas propostas da sua carteira. |
| Snapshot obrigatório | No POST /api/propostas/{id}/enviar, o backend preenche todos os campos snap_* a partir dos dados atuais antes de mudar o status. Após ENVIADA, esses campos são imutáveis. |
| Controle de alçadas | A validação de desconto_concedido_pct contra desconto_maximo_consultor_pct (e desconto_maximo_gestor_pct) ocorre no backend — tanto no POST /api/propostas quanto no POST /api/propostas/{id}/enviar. |
| Link público | O hash_publico é um token criptograficamente aleatório (mínimo 32 chars). Nunca expor o id UUID no link público. |
| Auditoria do aceite | IP e User-Agent do cliente ao responder o link público são registrados como evento na timeline da proposta (tipo: SISTEMA). |
| Exclusão mútua | Ao executar POST /api/propostas/{id}/gerar-contrato, o sistema automaticamente marca como SUBSTITUIDA todas as demais propostas ENVIADA do mesmo veiculo_id no tenant. |