# E-mails transacionais com Resend

## Implantação

1. Faça backup do banco.
2. Execute, nesta ordem, `database/migrations/2026_09_07_email_resend.sql`, `database/migrations/2026_09_07_email_resend_consolidacao.sql`, `database/migrations/2026_09_07_agendamento_email_confirmacao.sql` e `database/migrations/2026_09_08_agendamento_status_codigos.sql` no banco do Care.
3. Confirme que `APP_KEY` possui o mesmo valor permanente no site e no cron. Ela protege a API Key armazenada.
4. Acesse **Administrador > APIs > Resend**, informe a chave, o domínio verificado, o remetente e salve.
5. Use **Testar comunicação**. O teste faz `GET https://api.resend.com/domains` e exige que a chave permita leitura de domínios.
6. Cadastre os templates em **Painel de Comunicações > Email Eventos**.

O catálogo exibido no editor é o mesmo de **Documentos > Variáveis de Texto**. Alterações feitas nesse catálogo ficam disponíveis para documentos, WhatsApp e e-mail.

## Confirmação de agendamentos

Em **Tipos de Serviço > Editar**, selecione o **Evento de e-mail da sessão**. Cadastrar ou editar as sessões não gera a fila. Somente depois que o contrato for confirmado (`contratos.situacao = 'C'`) o sistema percorre suas sessões futuras e cria os registros de e-mail, sem repetir a combinação sessão/evento caso a confirmação seja executada novamente.

`nr_dias` é tratado como antecedência: sessão em `10/09/2026 10:10` com `nr_dias=1` gera `data_agendamento=09/09/2026 10:10`. Se o evento possuir `horario`, esse horário substitui a hora da sessão na data calculada.

Variáveis disponíveis no catálogo central:

- `##DATA_CONSULTA##`;
- `##HORA_CONSULTA##`;
- `@@link_confirmação@@` (variável principal do link de confirmação);
- `##LINK_CONFIRMACAO##` (alias mantido para compatibilidade);
- `##LINK_CANCELAMENTO##`.

Os dois links levam à página pública `/agendamento/confirmacao/{token}`. O banco armazena somente o SHA-256 do token. A página aceita uma resposta por sessão, exige contrato confirmado e rejeita consultas passadas, realizadas ou inativas.

Ao cancelar ou encerrar um contrato, os e-mails pendentes (`status IS NULL`) vinculados às sessões futuras são excluídos na mesma transação. Registros já enviados ou com erro são preservados para auditoria. Como proteção adicional, o worker só processa e-mails vinculados a contratos cuja situação ainda seja `C`.

Ao cancelar uma sessão pelo sistema, a fila pendente daquela sessão é excluída e o token público antigo é invalidado. No reagendamento, a fila pendente anterior também é excluída; em seguida, se o contrato continuar confirmado e o tipo de serviço possuir um evento ativo, o sistema gera um novo e-mail com a nova data e um novo link. Itens `Enviado` ou `Erro` não são apagados. O worker ainda confere o status e a data atual da sessão antes do envio, evitando o disparo de uma fila antiga em uma condição de corrida.

Quando uma ou mais sessões são acrescentadas a um contrato que já está confirmado, cada nova sessão é sincronizada individualmente. A fila é criada somente para os novos agendamentos que atendem às regras de evento ativo, data futura, status permitido e destinatário válido; sessões antigas sem fila não são preenchidas por essa operação.

Quando o tipo de serviço não possui `email_evento_id`, nenhuma fila é criada. O vínculo com um evento ativo do estabelecimento é obrigatório para que uma sessão seja selecionada pela automação.

### Destinatário do paciente

- Se o paciente possui vínculo ativo em `pessoa_resp`, o destinatário é um responsável ativo com e-mail válido; o responsável legal (`tipo_resp = 'L'`) tem prioridade.
- Se houver responsáveis ativos, mas nenhum possuir e-mail válido, o envio fica com erro e não usa silenciosamente o endereço do paciente.
- O e-mail do próprio paciente é utilizado somente quando ele não possui responsável ativo.
- O destinatário é revalidado pelo worker imediatamente antes do disparo. Assim, filas pendentes também acompanham alterações posteriores no cadastro do responsável e mantêm `email.e_mail` com o endereço efetivamente utilizado.

## Cron na VPS

Projeto na VPS: `/var/www/care/desenv/Care`

Exemplo para processar até 100 registros a cada minuto:

```cron
* * * * * cd /var/www/care/desenv/Care && /usr/bin/php bin/email-processar.php 100 >> storage/logs/email-cron.log 2>&1
```

Crie previamente `storage/logs` com permissão de escrita para o usuário do cron. O worker usa um lock nomeado no MySQL, portanto duas execuções simultâneas não processam a mesma fila.

## Como gerar um e-mail em outro módulo

Injete `Care\Modules\Comunicacoes\Service\EmailService` e chame:

```php
$emailService->enfileirar(
    contexto: [
        'agendamento' => ['data' => '10/09/2026', 'hora' => '14:30'],
    ],
    eventoId: $eventoId,
    pessoaId: $pessoaId,
    estabelecimentoId: $estabelecimentoId,
    dataBase: $dataDoAgendamento,
    agendamentoId: $agendamentoId,
    contratoId: $contratoId,
    usuarioId: $usuarioId,
);
```

O assunto e o HTML são renderizados e gravados como fotografias finais na fila. A situação inicial é `NULL` (mostrada como **Pendente**), conforme a regra do módulo.

## Anexos

`email_anexo.arquivo_anexado` aceita:

- caminho relativo de um arquivo dentro do armazenamento privado do Care;
- Base64 puro ou Data URI Base64;
- JSON com nome e conteúdo: `{"filename":"recibo.pdf","content":"BASE64"}`.

O worker não baixa URLs externas. Essa decisão evita SSRF e mantém os anexos sob controle do armazenamento privado. O limite validado é 40 MB após Base64, conforme o limite da API do Resend.

## Regras do worker

- processa somente `email.status IS NULL`;
- exige `DATE(data_agendamento) = CURRENT_DATE`;
- respeita `email_evento.horario <= CURRENT_TIME` quando houver horário;
- ignora eventos inativos e registros já marcados como `Enviado` ou `Erro`;
- envia `Idempotency-Key: care-email/{id}`;
- em toda tentativa concluída, grava `data_envio`, status e o JSON integral da resposta/erro.

`email_evento.assunto` contém o template parametrizado do assunto. Ao enfileirar, o valor renderizado é salvo em `email.assunto`, evitando que uma edição posterior no template altere itens que já estavam na fila. O remetente é montado como `Nome <identificacao@dominio>` a partir da configuração da clínica.

## Consolidação das tabelas antigas

A migration de consolidação copia `eventoemail` para `email_evento` e `emailanexo` para `email_anexo`. Ela também consolida `dt_entrada`, `dt_agendado` e `dt_envio` em `data_entrada`, `data_agendamento` e `data_envio`, respectivamente. Depois das cópias, remove a FK textual, as colunas duplicadas e as duas tabelas antigas. A única relação de eventos passa a ser `email.evento_email_id -> email_evento.id`.
