Skip to main content

Módulo 09: Propostas / Cotações — API

Status: Rascunho Inicial Regras de negócio: ver regras-negocio/09_propostas.md Domínio: ver dominio/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
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

RegraImplementação
Silo de dadostenant_id validado em toda operação. Consultor acessa apenas propostas da sua carteira.
Snapshot obrigatórioNo 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çadasA 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úblicoO hash_publico é um token criptograficamente aleatório (mínimo 32 chars). Nunca expor o id UUID no link público.
Auditoria do aceiteIP 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útuaAo executar POST /api/propostas/{id}/gerar-contrato, o sistema automaticamente marca como SUBSTITUIDA todas as demais propostas ENVIADA do mesmo veiculo_id no tenant.