Passageiros - Convites e Links de Confirmação
O que é
Existem dois tipos de link que o admin pode enviar para um passageiro a partir da listagem Passageiros da excursão, e eles servem para coisas diferentes.
O link de convite é gerado quando o comprador finaliza uma reserva sem preencher os dados de todos os passageiros do grupo (escolheu a opção "Convidar depois" para uma das vagas). O sistema cria um ExcursaoPassageiroPendente, um PassageiroConviteToken válido por 48h e um passageiro placeholder marcado como cadastrado_via = convite_pendente. O link /convite/{token} abre um formulário público onde o convidado se identifica (ou se loga, se já for cliente) e seus dados substituem o placeholder.
O link mágico (auto-login) é um atalho operacional: você gera sob demanda um link de 30 minutos que entra no portal Minha Conta já autenticado como o comprador da reserva, redirecionando para o pedido específico. Serve para resolver dúvida de cliente sem pedir e-mail/senha - porém qualquer pessoa com o link entra na conta, então o sistema exige um aviso de risco antes de gerar.
Você normalmente chega aqui quando: o convidado não preencheu os dados a tempo, o cliente perdeu acesso à própria conta, ou o atendimento precisa olhar a reserva pelo ponto de vista do cliente.
Como usar
Enviar link de convite no momento da reserva
O fluxo padrão é automático: quando o comprador escolhe a opção convite para uma vaga do carrinho, o PassageiroConviteService::criarPassageirosPendentes faz tudo em transação:
- Cria um
ExcursaoPassageiroPendentecomexpira_em = agora + 48h. - Gera um
PassageiroConviteToken(string aleatória) e calcula a URL/convite/{token}viaPassageiroConviteToken::getLink(). - Se o checkout marcou
enviar_emailouenviar_whatsapp, dispara o envio:- E-mail via template
cliente.convite-passageiro(assunto "Convite para viagem - {nome da excursão}"). - WhatsApp via Evolution API, se o módulo
whatsapp-mensagensestiver ativo e a instância conectada. Sem o módulo, o sistema só registra umwa.me/...no log (fallback).
- E-mail via template
Cada convite é único - o sistema não bloqueia "duplicatas" do comprador no WhatsApp (uso de clienteId: null no EnviarWhatsAppJob) para que cada convidado receba seu próprio link.
Reenviar / prorrogar convite expirado
Quando o token vence ou é usado, o passageiro placeholder continua na listagem com a tag visual de Convite pendente:
- Vá em Viagens (Excursões) no menu lateral e abra a excursão.
- Acesse a aba Passageiros.
- Localize a linha do passageiro convidado (avatar laranja, label "Convite pendente").
- Abra o dropdown de ações (ícone de três pontos) e clique em Prorrogar convite (+48h).
- O sistema decide automaticamente:
- Se o token atual ainda existe e não foi usado nem expirou → estende
expira_emem mais 48h. - Se foi usado/expirou → cria um novo token (apaga o antigo) e renova o prazo.
- Se o token atual ainda existe e não foi usado nem expirou → estende
- O modal de compartilhamento abre com o novo link. Use Copiar, ou os botões de compartilhamento para WhatsApp/Telegram/e-mail.
A ação só aparece quando cadastrado_via = convite_pendente. Para reservas comuns o item não é renderizado.
Comprador preencher os dados pela área do cliente
O próprio comprador pode resolver pendências sem precisar reenviar o link: na Minha Conta, em cada reserva, aparecem os passageiros pendentes do grupo. O comprador preenche o formulário e o PassageiroConviteService::preencherPeloComprador atualiza o placeholder (ou cria um passageiro novo, se não houver placeholder), grava cadastrado_via = preenchido_comprador e marca o token como usado.
Esse fluxo dispensa o convidado de fazer qualquer coisa - útil quando o comprador conhece os dados (família/dependentes) e quer fechar a reserva sozinho.
Vincular dependente já cadastrado
Quando o cliente logado abre o link /convite/{token} e escolhe um dependente do próprio cadastro, o sistema chama vincularDependente. O placeholder é atualizado com:
cliente_iddo titular (mantido para CRM/financeiro consolidados);dependente_iddo dependente vinculado;- nome, CPF, RG, telefone, e-mail e data de nascimento copiados do dependente;
cadastrado_via = link_convite_dependente.
O dependente não se torna um cliente separado - continua referenciado pelo titular.
Gerar link mágico de acesso ao portal
- Na listagem Passageiros da excursão, abra o dropdown de ações da linha do passageiro.
- Clique em Enviar acesso ao portal (ícone de chave).
- O modal Acesso ao portal do cliente abre na etapa Aviso:
- Texto destacado em laranja explicando que o link autologa sem senha.
- Checkbox de confirmação: "Confirmo que entendi o risco e quero gerar o link de acesso para {nome}".
- Marque o checkbox e clique em Gerar link.
- O sistema chama
ExcursaoPassageiroController::gerarLinkMagicoque:- Identifica o cliente alvo (
comprador_idse for convidado/dependente; senãocliente_id). - Gera 64 caracteres aleatórios via
bin2hex(random_bytes(32)), salva o hash SHA-256 emauto_login_tokendo cliente e defineauto_login_expires_at = agora + 30min. - Monta
/auth/magic/{plain}?redirect=/minha-conta/reservas/{codigo}(ou/minha-contase a reserva não temOrder).
- Identifica o cliente alvo (
- Etapa Compartilhar: o modal mostra o link em campo somente-leitura com botão Copiar + grade Enviar por com WhatsApp e E-mail.
- O botão E-mail envia automaticamente um e-mail formatado com layout da sua empresa (logo, cor primária, botão CTA). Requer
?via=emailno request - o componente já manda esse parâmetro quando você clica.
O cliente alvo do magic link é sempre o comprador da reserva, não o passageiro convidado - porque é quem realmente loga e gerencia.
Casos especiais
Cliente sem e-mail cadastrado
No modal do link mágico, se cliente.email for vazio, o botão E-mail fica desabilitado e aparece o aviso "Cliente sem e-mail cadastrado - só dá pra enviar via WhatsApp ou copiar o link.". O envio por e-mail simplesmente é pulado e o JSON de resposta volta email_enviado: false.
Token usado ou expirado no link de convite
A view pública /convite/{token} chama PassageiroConviteService::validarToken antes de renderizar o formulário e, dependendo do estado, retorna uma view de erro específica:
code = invalid→ "Link inválido."code = used→ "Este link já foi utilizado."code = expired→ "Este link expirou."
Para o caso expired, use Prorrogar convite (+48h) no admin para gerar um novo token e reenviar.
Convite com placeholder pré-existente vs. sem placeholder
A partir do release de placeholders, todo pendente novo cria também um ExcursaoPassageiro com cadastrado_via = convite_pendente para que a vaga apareça na listagem desde o primeiro momento. O processarCadastro (e vincularDependente/preencherPeloComprador) detecta o placeholder via pendente.vinculado_passageiro_id e atualiza os campos em vez de criar um novo passageiro. Pendentes antigos sem placeholder ainda funcionam - o serviço cria o passageiro novo e o financeiro do zero para retrocompatibilidade.
Confirmação automática quando o financeiro já está pago
Se o convidado preenche os dados depois que o pagamento da reserva já foi confirmado (ExcursaoPassageiroFinanceiro.status = pago para o placeholder), o processarCadastro muda o status do passageiro para confirmado em vez de manter pendente. Útil em reservas pré-pagas onde só faltavam os dados do convidado.
Invalidação automática do contrato
Sempre que um passageiro é vinculado/preenchido via convite, o ContratoService::invalidarAceiteGrupo é chamado em background com motivo "Passageiro vinculado via convite: {nome}". Se o comprador já tinha aceitado o contrato, o aceite é invalidado porque a composição do grupo mudou - o sistema vai pedir nova assinatura. Mesmo comportamento para vínculo de dependente e preenchimento pelo comprador.
Sem módulo de WhatsApp ativo
Quando tenant_has_addon('whatsapp-mensagens') é falso ou a instância Evolution não está conectada, o envio por WhatsApp não dispara o EnviarWhatsAppJob. O serviço só monta https://wa.me/{telefone}?text={mensagem urlencode} e registra no log para auditoria. Nesse cenário, a melhor opção operacional é Copiar o link e colar manualmente.
Quem o magic link autentica
A regra é: comprador_id se existir, senão cliente_id. Para um pax convidado/dependente o sistema autentica o titular da compra (quem paga e administra), não o convidado, porque é o titular que tem acesso real à Minha Conta. O redirect aponta direto para a reserva (/minha-conta/reservas/{codigo}) se houver order_id, ou cai na home da Minha Conta.
Token de magic link é single-use
auto_login_used_at é gravado quando o link é consumido em /auth/magic/{plain}. Tentar abrir o mesmo link de novo dá erro - gere um novo pelo modal Enviar acesso ao portal. O hash do token também rotaciona a cada gerarLinkMagico, invalidando qualquer link anterior do mesmo cliente.
Erros comuns e como resolver
- "Cliente não encontrado." - Causa: o passageiro não tem
comprador_idnemcliente_idválidos (registro órfão). Solução: editar o passageiro e vincular a um cliente real antes de tentar gerar o magic link. - "Esta reserva não é um convite pendente." - Causa: tentativa de prorrogar via
/admin/reservas/{passageiro}/prorrogar-conviteem um passageiro cujocadastrado_vianão éconvite_pendente. Solução: a ação só faz sentido em placeholders de convite; para reenviar dados a um passageiro normal, use o Enviar acesso ao portal. - "Convite pendente não encontrado para esta reserva." - Causa: o
ExcursaoPassageiroPendentefoi deletado mas o placeholder ficou. Solução: cancelar o placeholder e recadastrar o pax manualmente. - "Link inválido." / "Este link já foi utilizado." / "Este link expirou." - Causa: token do convite vencido, consumido ou string incorreta. Solução: prorrogar pelo admin (gera novo token) e reenviar.
- "Informe o nome completo (nome e sobrenome)." - Causa: o convidado preencheu só o primeiro nome no formulário público. Solução: pedir nome + sobrenome (mínimo 5 caracteres, pelo menos 2 partes de 2+ caracteres).
- "CPF inválido." - Causa: dígitos verificadores não batem ou CPF formado só por números repetidos. Solução: conferir documento.
- "Cliente sem e-mail cadastrado" (aviso no modal do link mágico) - Causa:
cliente.emailé nulo. Solução: cadastrar e-mail no cliente, ou enviar o link via WhatsApp/cópia manual. - Convite por WhatsApp não chegou - Causa: módulo
whatsapp-mensagensinativo, instância Evolution desconectada, ou número formatado errado. Solução: verificar Módulos > WhatsApp Mensagens > Conexão. Sem o módulo, o link só é copiável manualmente.
Onde encontrar
- URL canônica:
/admin/excursoes/{excursao}/passageiros - Endpoint do magic link:
POST /admin/passageiros/{passageiro}/link-magico(com?via=emailpara também enviar por e-mail) - Endpoint de prorrogação:
POST /admin/reservas/{passageiro}/prorrogar-convite - Formulário público do convidado:
GET /convite/{token}(rotasite.convite.cadastro) - Caminho no menu: Viagens (Excursões) > {excursão} > Passageiros
- Permissões necessárias: ver as reservas para gerar magic link, editar as reservas para prorrogar convite, editar as viagens para editar dados do placeholder
- Módulo necessário: nenhum para o convite básico; WhatsApp Mensagens se quiser entrega automática por WhatsApp via Evolution API
Veja também
Cadastrar Passageiro- alternativa quando você já tem os dados completos do passageiro e não precisa de convite.Status do Passageiro- entender as mudanças de status quando o convidado conclui o cadastro.Documentos e Requisitos- próximo passo depois que os dados básicos do passageiro estão preenchidos.Gerenciar Passageiros na Excursão- visão geral da listagem agrupada por transporte onde os botões de convite e magic link aparecem.