1)Elaborar metodo que possa interagir de dentro da tela class AgendamentoCalendarForm02 extends TWindow com o whastapp onde sera enviado ao paciente_id o solicitação para que ele possa confirmar seu agendamento o status cadastrado no sistema atualmente esta desta forma
01:AGUARDANDO CONFIRMAÇÃO	-> quando cadastra o sistema atribui este valor ao registro e onde tera que enviar solicitação ao usuario para ele confirmar o seu agendamento
02:CONFIRMADO			-> usuario retorna a resposta pelo whastapp e o sistema recebe a confirmação e atualiza no sistema
05:NÃO CONFIRMADO		-> o usuario pode nao confirmar seu agendamento

2)a class SystemUnit extends TRecord pode ser implementado novos campos para poder atender os requisitos de interagir com o whastapp por meio de api oficial

3)configura mensagem padrao e forma de interagir com o usuario 
por exemplo: 
	solicitar a confirmação do agendamento	->avisar um dia antes ou dois dias antes da data do agendamento, quantas vezes o usuario deve ser avisado no dia exemplo 3 vezes
	feliz aniversario			            ->enviar apartir de que horas e ate que horas
	nao havera expediente dia tal		    ->dispara para todos os contatos com intervalo de 40 segundos durante um dia antes
	parcela em aberto			            ->enviar apartir de que horas e ate que horas
	sentimos sua falta			            ->sem atendimento a quantos dias


class Pessoa extends TRecord
{
    const TABLENAME  = 'pessoa';
    const PRIMARYKEY = 'id';
    const IDPOLICY   =  'serial'; // {max, serial}
    const CACHECONTROL  = 'BRuntimeCache';
    
    private PessoaHorarios $horarioatendimento;
    private Cobranca $cobranca;
    private SystemUsers $fk_usuario_sistema;
    private Escolaridade $escolaridade;
    private Cbo $cbo;
    private EstadoCivil $estado_civil;
    private Especialidade $especialidade;
    private SystemUnit $empresa;
    private SystemUsers $fk_usuario_inclusao;
    private SystemUsers $fk_usuario_alteracao;
    private GrupoSanguineo $fk_tipo_sanguineo;
    private Nascionalidade $nascionalidade;
    private TipoCadastro $fk_tipo_cadastroid;
    private SystemUsers $profissional;
    
    //<classProperties>
    private $pessoa_profissional;
    public $dadosempresa;    

    
    /**
     * Constructor method
     */
    public function __construct($id = NULL, $callObjectLoad = TRUE)
    {
        //<onBeforeConstruct>

        //</onBeforeConstruct>
        parent::__construct($id, $callObjectLoad);
        parent::addAttribute('tipo_pessoa');
        parent::addAttribute('tipo_cadastroid');
        parent::addAttribute('nome');
        parent::addAttribute('fantasia_apelido');
        parent::addAttribute('profissional_saude');
        parent::addAttribute('usuario_sistema');
        parent::addAttribute('profissional_id');
        parent::addAttribute('sexo');
        parent::addAttribute('foto');
        parent::addAttribute('caminho_foto');
        parent::addAttribute('tipo_atendimento');
        parent::addAttribute('cnpj_cpf');
        parent::addAttribute('insc_estadual');
        parent::addAttribute('insc_municipal');
        parent::addAttribute('rg');
        parent::addAttribute('telefone');
        parent::addAttribute('celular');
        parent::addAttribute('email');
        parent::addAttribute('endereco');
        parent::addAttribute('numero');
        parent::addAttribute('complemento');
        parent::addAttribute('bairro');
        parent::addAttribute('cep');
        parent::addAttribute('cidade');
        parent::addAttribute('cidade_id');
        parent::addAttribute('uf');
        parent::addAttribute('escolaridade_id');
        parent::addAttribute('ano_conclusaoescolar');
        parent::addAttribute('data_ativacao');
        parent::addAttribute('ativo');
        parent::addAttribute('data_desativacao');
        parent::addAttribute('data_aniversario');
        parent::addAttribute('dia_aniversario');
        parent::addAttribute('sms');
        parent::addAttribute('cbo_id');
        parent::addAttribute('estado_civil_id');
        parent::addAttribute('registro_ans');
        parent::addAttribute('inscricao_conselho');
        parent::addAttribute('uf_conselho');
        parent::addAttribute('data_inscr_conselho');
        parent::addAttribute('especialidade_id');
        parent::addAttribute('dias_para_retorno');
        parent::addAttribute('quantos_retorno_por_dia');
        parent::addAttribute('percentual_retencao_particular');
        parent::addAttribute('percentual_retencao_convenio');
        parent::addAttribute('percentual_repasse_particular');
        parent::addAttribute('percentual_repasse_convenio');
        parent::addAttribute('horarioeconvenio');
        parent::addAttribute('certificado');
        parent::addAttribute('certificado_senha');
        parent::addAttribute('assinatura');
        parent::addAttribute('assinatura_local');
        parent::addAttribute('empresa_id');
        parent::addAttribute('cor');
        parent::addAttribute('cor_statussematendimento');
        parent::addAttribute('convenioparceria');
        parent::addAttribute('convenioparceria_tipo');
        parent::addAttribute('autoriza_terceiroatuar');
        parent::addAttribute('limitacao_terceironomeado');
        parent::addAttribute('acesso_terceironomeado');
        parent::addAttribute('data_inclusao');
        parent::addAttribute('hora_inclusao');
        parent::addAttribute('usuario_inclusao');
        parent::addAttribute('data_alteracao');
        parent::addAttribute('hora_alteracao');
        parent::addAttribute('usuario_alteracao');
        parent::addAttribute('data_ultimoprocedimento');
        parent::addAttribute('data_proximoprocedimento');
        parent::addAttribute('data_ultimocontato');
        parent::addAttribute('data_proximocontato');
        parent::addAttribute('cobranca_id');
        parent::addAttribute('condicao_pagamento');
        parent::addAttribute('peso');
        parent::addAttribute('altura');
        parent::addAttribute('tipo_sanguineo');
        parent::addAttribute('nascionalidade_id');
        parent::addAttribute('tempo_atendimento');
        parent::addAttribute('valor_contratado');
        parent::addAttribute('sessoes_quantidade');
        parent::addAttribute('sessoes_frequenciaemdias');
        parent::addAttribute('cortesia');
        parent::addAttribute('liberargravarembranco_cpf');
        parent::addAttribute('fornecedor_id');
        parent::addAttribute('produto_id');
        parent::addAttribute('paciente_psicologia');
        parent::addAttribute('rf_irrf');
        parent::addAttribute('rf_pis');
        parent::addAttribute('rf_cofins');
        parent::addAttribute('rf_csll');
        parent::addAttribute('rp_inss');
        parent::addAttribute('rm_iss');
        parent::addAttribute('re_icms');
        parent::addAttribute('controle_guias');
        parent::addAttribute('empresacolaborador_id');
        parent::addAttribute('matricula');
        parent::addAttribute('horarioatendimento_id');
        parent::addAttribute('contrato');
        parent::addAttribute('valor_contrato');
        //<onAfterConstruct>

        //</onAfterConstruct>
    }


class Agendamento extends TRecord
{
    const TABLENAME  = 'agendamento';
    const PRIMARYKEY = 'id';
    const IDPOLICY   =  'serial'; // {max, serial}
    const CACHECONTROL  = 'BRuntimeCache';
    
    private Equipamento $equipamento;
    private SystemUsers $fk_usuario_geraroutro_retorno;
    private SystemUsers $fk_usuario_fechamento;
    private SystemUnit $empresa;
    private Pessoa $paciente;
    private SystemUsers $profissional;
    private Pessoa $fornecedor;
    private Produto $produto;
    private Cobranca $cobranca;
    private AgendamentoTipo $tipo_agendamento;
    private OrigemConsulta $origemconsulta;
    private Ambiente $ambiente;
    private SystemUsers $fk_usuario_inclusao;
    private SystemUsers $fk_usuario_alteracao;
    private SystemUsers $fk_usuario_confirmacao;
    private SystemUsers $fk_usuario_desmarcou;
    private SystemUsers $fk_usuario_remarcar;
    private SystemUsers $fk_usuario_retorno;
    private SystemUsers $fk_usuario_bloqhorario;
    private SystemUsers $fk_usuario_finalizacao;
    private SystemUsers $fk_usuario_cancelamento;
    private SystemUsers $fk_usuario_recepcao;
    private SystemUsers $geracaotitulo;
    private SystemUsers $fk_usuario_naoconfirmado;
    private GuiasconvenioCab $guia_conveniocab;
    
    //<classProperties>
    private $pessoa_profissional; // Objeto Pessoa

    
    /**
     * Constructor method
     */
    public function __construct($id = NULL, $callObjectLoad = TRUE)
    {
        //<onBeforeConstruct>

        //</onBeforeConstruct>
        parent::__construct($id, $callObjectLoad);
        parent::addAttribute('empresa_id');
        parent::addAttribute('paciente_id');
        parent::addAttribute('profissional_id');
        parent::addAttribute('encaminhamento');
        parent::addAttribute('fornecedor_id');
        parent::addAttribute('produto_id');
        parent::addAttribute('valor_custo');
        parent::addAttribute('valor_venda');
        parent::addAttribute('valor_receber');
        parent::addAttribute('valor_contratado');
        parent::addAttribute('valor_taxaentregaexame');
        parent::addAttribute('percentual_desconto_autorizado');
        parent::addAttribute('percentual_desconto_utilizado');
        parent::addAttribute('percentual_desconto_maximo');
        parent::addAttribute('percentual_retencao_particular');
        parent::addAttribute('percentual_retencao_convenio');
        parent::addAttribute('cobranca_id');
        parent::addAttribute('condicao_pagamento');
        parent::addAttribute('tipo_agendamento_id');
        parent::addAttribute('status');
        parent::addAttribute('statuscor');
        parent::addAttribute('horario_inicial');
        parent::addAttribute('horario_final');
        parent::addAttribute('origemconsulta_id');
        parent::addAttribute('ambiente_id');
        parent::addAttribute('ordematendimento');
        parent::addAttribute('consulta_retorno');
        parent::addAttribute('geraroutro_retorno');
        parent::addAttribute('data_geraroutro_retorno');
        parent::addAttribute('hora_geraroutro_retorno');
        parent::addAttribute('usuario_geraroutro_retorno');
        parent::addAttribute('geroufinanceiro');
        parent::addAttribute('geroudesconto');
        parent::addAttribute('geroudesconto_id');
        parent::addAttribute('entrega_resultado');
        parent::addAttribute('agendamento_novo');
        parent::addAttribute('agendamento_anterior');
        parent::addAttribute('observacao');
        parent::addAttribute('data_inclusao');
        parent::addAttribute('hora_inclusao');
        parent::addAttribute('usuario_inclusao');
        parent::addAttribute('data_alteracao');
        parent::addAttribute('hora_alteracao');
        parent::addAttribute('usuario_alteracao');
        parent::addAttribute('data_confirmacao');
        parent::addAttribute('hora_confirmacao');
        parent::addAttribute('usuario_confirmacao');
        parent::addAttribute('data_desmarcou');
        parent::addAttribute('hora_desmarcou');
        parent::addAttribute('usuario_desmarcou');
        parent::addAttribute('data_remarcar');
        parent::addAttribute('hora_remarcar');
        parent::addAttribute('usuario_remarcar');
        parent::addAttribute('data_retorno');
        parent::addAttribute('hora_retorno');
        parent::addAttribute('usuario_retorno');
        parent::addAttribute('data_bloqhorario');
        parent::addAttribute('hora_bloqhorario');
        parent::addAttribute('usuario_bloqhorario');
        parent::addAttribute('finalizado');
        parent::addAttribute('data_finalizacao');
        parent::addAttribute('hora_finalizacao');
        parent::addAttribute('usuario_finalizacao');
        parent::addAttribute('cancelado');
        parent::addAttribute('usuario_cancelamento');
        parent::addAttribute('data_cancelamento');
        parent::addAttribute('hora_cancelamento');
        parent::addAttribute('motivo_cancelamento');
        parent::addAttribute('usuario_recepcao');
        parent::addAttribute('data_recepcao');
        parent::addAttribute('hora_recepcao');
        parent::addAttribute('usuario_naocompareceu');
        parent::addAttribute('data_naocompareceu');
        parent::addAttribute('hora_naocompareceu');
        parent::addAttribute('geracaotitulo_id');
        parent::addAttribute('guia_conveniocab_id');
        parent::addAttribute('guia_auxiliar_id');
        parent::addAttribute('rf_irrf');
        parent::addAttribute('rf_pis');
        parent::addAttribute('rf_cofins');
        parent::addAttribute('rf_csll');
        parent::addAttribute('rp_inss');
        parent::addAttribute('rm_iss');
        parent::addAttribute('re_icms');
        parent::addAttribute('equipamento_id');
        parent::addAttribute('data_naoconfirmado');
        parent::addAttribute('hora_naoconfirmado');
        parent::addAttribute('usuario_naoconfirmado');
        parent::addAttribute('data_fechamento');
        parent::addAttribute('hora_fechamento');
        parent::addAttribute('usuario_fechamento');
        parent::addAttribute('data_pedidoconvenio');
        //<onAfterConstruct>

        //</onAfterConstruct>
    }

class SystemUnit extends TRecord
{
    const TABLENAME  = 'system_unit';
    const PRIMARYKEY = 'id';
    const IDPOLICY   =  'max'; // {max, serial}

    /**
     * Constructor method
     */
    public function __construct($id = NULL, $callObjectLoad = TRUE)
    {
        //<onBeforeConstruct>

        //</onBeforeConstruct>
        parent::__construct($id, $callObjectLoad);
        parent::addAttribute('name');
        parent::addAttribute('connection_name');
        parent::addAttribute('fantasia');
        parent::addAttribute('tipodepessoa');
        parent::addAttribute('cnpj_cpf');
        parent::addAttribute('caminho_logo');
        parent::addAttribute('caminho_voucherlogo');
        parent::addAttribute('caminho_voucherqrcode');
        parent::addAttribute('endereco');
        parent::addAttribute('numero');
        parent::addAttribute('bairro');
        parent::addAttribute('cidade');
        parent::addAttribute('uf');
        parent::addAttribute('cep');
        parent::addAttribute('complemento');
        parent::addAttribute('site');
        parent::addAttribute('email');
        parent::addAttribute('telefone');
        parent::addAttribute('celular');
        parent::addAttribute('representante_legal');
        parent::addAttribute('representante_cpf');
        parent::addAttribute('contador_impressoes');
        parent::addAttribute('whatsapp');
        //<onAfterConstruct>

        //</onAfterConstruct>
    }

1. Novos campos em SystemUnit (configurações WhatsApp)
// Adicionar no constructor de SystemUnit
parent::addAttribute('whatsapp_api_url');        // URL da API oficial (ex: graph.facebook.com)
parent::addAttribute('whatsapp_api_token');       // Token Bearer da API
parent::addAttribute('whatsapp_phone_id');        // Phone Number ID da Meta
parent::addAttribute('whatsapp_business_id');     // WABA ID
parent::addAttribute('whatsapp_webhook_token');   // Token para verificação do webhook

// Configurações de envio - Confirmação de agendamento
parent::addAttribute('wpp_confirm_dias_antes');       // Quantos dias antes avisar (ex: 1 ou 2)
parent::addAttribute('wpp_confirm_qtd_tentativas');   // Quantas vezes avisar no dia (ex: 3)
parent::addAttribute('wpp_confirm_horario_inicio');   // A partir de que horas enviar (ex: 08:00)
parent::addAttribute('wpp_confirm_horario_fim');      // Até que horas enviar (ex: 18:00)
parent::addAttribute('wpp_confirm_intervalo_min');    // Intervalo entre tentativas em minutos

// Configurações - Aniversário
parent::addAttribute('wpp_aniver_horario_inicio');    // A partir de que horas
parent::addAttribute('wpp_aniver_horario_fim');       // Até que horas

// Configurações - Sem expediente
parent::addAttribute('wpp_semexpediente_intervalo'); // Intervalo em segundos entre envios (ex: 40)

// Configurações - Parcela em aberto
parent::addAttribute('wpp_parcela_horario_inicio');
parent::addAttribute('wpp_parcela_horario_fim');

// Configurações - Sentimos sua falta
parent::addAttribute('wpp_falta_dias_sem_atendimento'); // Ex: 30 dias sem atendimento
parent::addAttribute('wpp_falta_horario_inicio');
parent::addAttribute('wpp_falta_horario_fim');

// Templates de mensagem (IDs dos templates aprovados na Meta)
parent::addAttribute('wpp_template_confirmacao');
parent::addAttribute('wpp_template_aniversario');
parent::addAttribute('wpp_template_semexpediente');
parent::addAttribute('wpp_template_parcela');
parent::addAttribute('wpp_template_falta');

2. Nova tabela: whatsapp_log (rastrear envios)
CREATE TABLE whatsapp_log (
    id          SERIAL PRIMARY KEY,
    empresa_id  INTEGER NOT NULL,
    paciente_id INTEGER,
    agendamento_id INTEGER,
    tipo        VARCHAR(30),   -- confirmacao | aniversario | semexpediente | parcela | falta
    celular     VARCHAR(20),
    mensagem    TEXT,
    status_envio VARCHAR(20),  -- enviado | erro | pendente
    resposta_api TEXT,
    tentativa   INTEGER DEFAULT 1,
    data_envio  DATE,
    hora_envio  TIME,
    data_resposta DATE,
    hora_resposta TIME,
    resposta_usuario VARCHAR(20), -- confirmado | nao_confirmado
    usuario_inclusao INTEGER,
    data_inclusao DATE,
    hora_inclusao TIME
);

3. Service class WhatsAppService
<?php

class WhatsAppService
{
    private string $apiUrl;
    private string $token;
    private string $phoneId;

    public function __construct(SystemUnit $empresa)
    {
        $this->apiUrl   = $empresa->whatsapp_api_url  ?? 'https://graph.facebook.com/v19.0';
        $this->token    = $empresa->whatsapp_api_token;
        $this->phoneId  = $empresa->whatsapp_phone_id;
    }

    /**
     * Envia mensagem usando template aprovado pela Meta
     */
    public function enviarTemplate(
        string $celular,
        string $templateName,
        string $langCode = 'pt_BR',
        array  $components = []
    ): array {
        $celular = $this->normalizarCelular($celular);

        $payload = [
            'messaging_product' => 'whatsapp',
            'to'                => $celular,
            'type'              => 'template',
            'template'          => [
                'name'       => $templateName,
                'language'   => ['code' => $langCode],
                'components' => $components
            ]
        ];

        return $this->post("/messages", $payload);
    }

    /**
     * Envia mensagem de texto simples (para sessão aberta - resposta do usuário)
     */
    public function enviarTexto(string $celular, string $texto): array
    {
        $payload = [
            'messaging_product' => 'whatsapp',
            'to'                => $this->normalizarCelular($celular),
            'type'              => 'text',
            'text'              => ['body' => $texto]
        ];

        return $this->post("/messages", $payload);
    }

    /**
     * Envia mensagem interativa com botões SIM / NÃO para confirmação
     */
    public function enviarBotoesConfirmacao(
        string $celular,
        string $nomePaciente,
        string $datahora,
        string $nomeProfissional
    ): array {
        $celular = $this->normalizarCelular($celular);

        $payload = [
            'messaging_product' => 'whatsapp',
            'to'                => $celular,
            'type'              => 'interactive',
            'interactive'       => [
                'type'   => 'button',
                'body'   => [
                    'text' => "Olá *{$nomePaciente}*! 👋\n\n"
                            . "Você tem um agendamento confirmado:\n"
                            . "📅 *{$datahora}*\n"
                            . "👨‍⚕️ *{$nomeProfissional}*\n\n"
                            . "Por favor, confirme sua presença:"
                ],
                'action' => [
                    'buttons' => [
                        ['type' => 'reply', 'reply' => ['id' => 'CONFIRMAR', 'title' => '✅ Confirmar']],
                        ['type' => 'reply', 'reply' => ['id' => 'NAO_CONFIRMAR', 'title' => '❌ Não poderei ir']],
                    ]
                ]
            ]
        ];

        return $this->post("/messages", $payload);
    }

    private function post(string $endpoint, array $payload): array
    {
        $url = "{$this->apiUrl}/{$this->phoneId}{$endpoint}";

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($payload),
            CURLOPT_HTTPHEADER     => [
                "Authorization: Bearer {$this->token}",
                "Content-Type: application/json"
            ]
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return [
            'http_code' => $httpCode,
            'body'      => json_decode($response, true),
            'sucesso'   => ($httpCode === 200)
        ];
    }

    private function normalizarCelular(string $celular): string
    {
        // Remove tudo que não for número
        $celular = preg_replace('/\D/', '', $celular);

        // Garante DDI 55 (Brasil)
        if (!str_starts_with($celular, '55')) {
            $celular = '55' . $celular;
        }

        return $celular;
    }
}

4. Método de envio dentro de AgendamentoCalendarForm02
/**
 * Envia solicitação de confirmação via WhatsApp para o paciente
 * Chamado ao clicar no botão ou automaticamente pelo scheduler
 */
public function enviarConfirmacaoWhatsApp(int $agendamentoId): void
{
    try {
        TTransaction::open(TSession::getValue('login_database'));

        $agendamento = new Agendamento($agendamentoId);

        // Só envia se status for AGUARDANDO CONFIRMAÇÃO
        if ($agendamento->status !== '01') {
            new TMessage('info', 'Este agendamento não está com status "Aguardando Confirmação".');
            TTransaction::close();
            return;
        }

        $paciente   = new Pessoa($agendamento->paciente_id);
        $empresa    = new SystemUnit($agendamento->empresa_id);
        $profissional = new Pessoa($agendamento->profissional_id);

        // Valida se paciente tem celular
        $celular = $paciente->celular ?? $paciente->telefone;
        if (empty($celular)) {
            new TMessage('error', "Paciente *{$paciente->nome}* não possui celular cadastrado.");
            TTransaction::close();
            return;
        }

        // Monta dados para a mensagem
        $dataHoraFormatada = date('d/m/Y', strtotime($agendamento->data_agendamento ?? date('Y-m-d')))
                           . ' às ' . $agendamento->horario_inicial;

        $wppService = new WhatsAppService($empresa);

        // Tenta enviar com botões interativos
        $resultado = $wppService->enviarBotoesConfirmacao(
            celular:          $celular,
            nomePaciente:     $paciente->nome,
            datahora:         $dataHoraFormatada,
            nomeProfissional: $profissional->nome
        );

        // Registra log do envio
        $log = new WhatsappLog();
        $log->empresa_id      = $agendamento->empresa_id;
        $log->paciente_id     = $agendamento->paciente_id;
        $log->agendamento_id  = $agendamento->id;
        $log->tipo            = 'confirmacao';
        $log->celular         = $celular;
        $log->status_envio    = $resultado['sucesso'] ? 'enviado' : 'erro';
        $log->resposta_api    = json_encode($resultado['body']);
        $log->data_envio      = date('Y-m-d');
        $log->hora_envio      = date('H:i:s');
        $log->usuario_inclusao = TSession::getValue('userid');
        $log->data_inclusao   = date('Y-m-d');
        $log->hora_inclusao   = date('H:i:s');
        $log->store();

        if ($resultado['sucesso']) {
            new TMessage('info', "✅ Solicitação enviada com sucesso para {$paciente->nome}!");
        } else {
            $erro = $resultado['body']['error']['message'] ?? 'Erro desconhecido';
            new TMessage('error', "Falha ao enviar WhatsApp: {$erro}");
        }

        TTransaction::close();

    } catch (Exception $e) {
        TTransaction::rollback();
        new TMessage('error', $e->getMessage());
    }
}

5. Webhook para receber resposta do paciente
<?php
// arquivo: webhook_whatsapp.php (exposto publicamente via HTTPS)

class WhatsAppWebhookController
{
    /**
     * GET: verificação do webhook pela Meta
     */
    public function verify(): void
    {
        $mode      = $_GET['hub_mode']         ?? '';
        $token     = $_GET['hub_verify_token'] ?? '';
        $challenge = $_GET['hub_challenge']    ?? '';

        // Busca token da empresa (ou usa token global)
        TTransaction::open('permission');
        $empresa = SystemUnit::find(TSession::getValue('unit'));
        TTransaction::close();

        if ($mode === 'subscribe' && $token === $empresa->whatsapp_webhook_token) {
            echo $challenge;
        } else {
            http_response_code(403);
            echo 'Forbidden';
        }
    }

    /**
     * POST: recebe mensagens e status do WhatsApp
     */
    public function receive(): void
    {
        $input = json_decode(file_get_contents('php://input'), true);

        if (empty($input['entry'])) {
            http_response_code(200); // Sempre responder 200 para a Meta
            return;
        }

        foreach ($input['entry'] as $entry) {
            foreach ($entry['changes'] ?? [] as $change) {
                $value = $change['value'] ?? [];

                // Processar mensagens recebidas
                foreach ($value['messages'] ?? [] as $msg) {
                    $this->processarMensagem($msg, $value['metadata'] ?? []);
                }
            }
        }

        http_response_code(200);
        echo json_encode(['status' => 'ok']);
    }

    private function processarMensagem(array $msg, array $metadata): void
    {
        $de       = $msg['from'];        // Número do paciente
        $tipo     = $msg['type'];        // interactive | text
        $resposta = null;

        // Resposta por botão interativo
        if ($tipo === 'interactive' && isset($msg['interactive']['button_reply'])) {
            $resposta = $msg['interactive']['button_reply']['id']; // CONFIRMAR | NAO_CONFIRMAR
        }

        // Resposta por texto livre
        if ($tipo === 'text') {
            $texto = strtoupper(trim($msg['text']['body'] ?? ''));
            if (in_array($texto, ['SIM', '1', 'CONFIRMO', 'CONFIRMAR', 'S'])) {
                $resposta = 'CONFIRMAR';
            } elseif (in_array($texto, ['NÃO', 'NAO', '2', 'N', 'CANCELAR'])) {
                $resposta = 'NAO_CONFIRMAR';
            }
        }

        if (!$resposta) {
            return; // Mensagem não reconhecida, ignora
        }

        // Busca último log de confirmação pendente para este celular
        TTransaction::open(TSession::getValue('login_database'));

        $criteria = new TCriteria();
        $criteria->add(new TFilter('celular',     'like', "%{$this->extrairNumero($de)}%"));
        $criteria->add(new TFilter('tipo',        '=',   'confirmacao'));
        $criteria->add(new TFilter('status_envio','=',   'enviado'));
        $criteria->add(new TFilter('resposta_usuario', 'IS', null));
        $criteria->setProperty('order', 'id desc');
        $criteria->setProperty('limit', 1);

        $repo = new TRepository('WhatsappLog');
        $logs = $repo->load($criteria);

        if ($logs) {
            $log = $logs[0];
            $log->resposta_usuario = ($resposta === 'CONFIRMAR') ? 'confirmado' : 'nao_confirmado';
            $log->data_resposta    = date('Y-m-d');
            $log->hora_resposta    = date('H:i:s');
            $log->store();

            // Atualiza status do agendamento
            if ($log->agendamento_id) {
                $agendamento = new Agendamento($log->agendamento_id);

                if ($resposta === 'CONFIRMAR') {
                    $agendamento->status            = '02'; // CONFIRMADO
                    $agendamento->data_confirmacao  = date('Y-m-d');
                    $agendamento->hora_confirmacao  = date('H:i:s');
                    // usuario_confirmacao = pode ser um usuário sistema "WHATSAPP"
                } else {
                    $agendamento->status                = '05'; // NÃO CONFIRMADO
                    $agendamento->data_naoconfirmado    = date('Y-m-d');
                    $agendamento->hora_naoconfirmado    = date('H:i:s');
                }

                $agendamento->store();
            }
        }

        TTransaction::close();
    }

    private function extrairNumero(string $numero): string
    {
        return preg_replace('/\D/', '', $numero);
    }
}

6. Scheduler / Job automático de disparos
<?php
/**
 * Executar via cron: * * * * * php scheduler_whatsapp.php
 * Trata todos os tipos de mensagem configurados
 */

class WhatsAppScheduler
{
    public function executar(): void
    {
        TTransaction::open(TSession::getValue('login_database'));
        $empresas = SystemUnit::all();
        TTransaction::close();

        foreach ($empresas as $empresa) {
            if (empty($empresa->whatsapp_api_token)) continue;

            $this->dispararConfirmacoes($empresa);
            $this->dispararAniversarios($empresa);
            $this->dispararFalta($empresa);
            // Sem expediente e Parcela em aberto são disparados sob demanda
        }
    }

    private function dispararConfirmacoes(SystemUnit $empresa): void
    {
        $horaAtual  = date('H:i');
        $horaInicio = $empresa->wpp_confirm_horario_inicio ?? '08:00';
        $horaFim    = $empresa->wpp_confirm_horario_fim    ?? '18:00';

        if ($horaAtual < $horaInicio || $horaAtual > $horaFim) return;

        $diasAntes  = (int)($empresa->wpp_confirm_dias_antes       ?? 1);
        $maxTentativas = (int)($empresa->wpp_confirm_qtd_tentativas ?? 3);
        $dataAlvo   = date('Y-m-d', strtotime("+{$diasAntes} days"));

        TTransaction::open($empresa->connection_name);

        // Busca agendamentos aguardando confirmação na data alvo
        $criteria = new TCriteria();
        $criteria->add(new TFilter('empresa_id',      '=', $empresa->id));
        $criteria->add(new TFilter('status',          '=', '01'));
        $criteria->add(new TFilter('data_agendamento','=', $dataAlvo));
        $criteria->add(new TFilter('cancelado',       '!=','S'));

        $repo = new TRepository('Agendamento');
        $agendamentos = $repo->load($criteria);

        $wpp = new WhatsAppService($empresa);

        foreach ($agendamentos as $agendamento) {
            // Verifica quantas tentativas já foram feitas hoje
            $tentativas = $this->contarTentativasHoje($agendamento->id, 'confirmacao');
            if ($tentativas >= $maxTentativas) continue;

            $paciente     = new Pessoa($agendamento->paciente_id);
            $profissional = new Pessoa($agendamento->profissional_id);
            $celular      = $paciente->celular ?? $paciente->telefone;

            if (empty($celular)) continue;

            $dataHora = date('d/m/Y', strtotime($dataAlvo))
                      . ' às ' . $agendamento->horario_inicial;

            $resultado = $wpp->enviarBotoesConfirmacao(
                celular:          $celular,
                nomePaciente:     $paciente->nome,
                datahora:         $dataHora,
                nomeProfissional: $profissional->nome
            );

            // Registra log
            $log = new WhatsappLog();
            $log->empresa_id     = $empresa->id;
            $log->paciente_id    = $agendamento->paciente_id;
            $log->agendamento_id = $agendamento->id;
            $log->tipo           = 'confirmacao';
            $log->celular        = $celular;
            $log->tentativa      = $tentativas + 1;
            $log->status_envio   = $resultado['sucesso'] ? 'enviado' : 'erro';
            $log->resposta_api   = json_encode($resultado['body']);
            $log->data_envio     = date('Y-m-d');
            $log->hora_envio     = date('H:i:s');
            $log->store();

            sleep(2); // Evita bloqueio por rate limit
        }

        TTransaction::close();
    }

    private function dispararAniversarios(SystemUnit $empresa): void
    {
        $horaAtual  = date('H:i');
        $horaInicio = $empresa->wpp_aniver_horario_inicio ?? '08:00';
        $horaFim    = $empresa->wpp_aniver_horario_fim    ?? '12:00';

        if ($horaAtual < $horaInicio || $horaAtual > $horaFim) return;

        $hoje = date('m-d'); // Mês e dia atual

        TTransaction::open($empresa->connection_name);

        $criteria = new TCriteria();
        $criteria->add(new TFilter('empresa_id', '=', $empresa->id));
        $criteria->add(new TFilter('ativo',      '=', 'S'));
        // Filtra pelo dia e mês do aniversário (dia_aniversario está no formato mm-dd ou similar)

        $repo     = new TRepository('Pessoa');
        $pacientes = $repo->load($criteria);

        $wpp = new WhatsAppService($empresa);

        foreach ($pacientes as $paciente) {
            if (empty($paciente->data_aniversario)) continue;

            $aniver = date('m-d', strtotime($paciente->data_aniversario));
            if ($aniver !== $hoje) continue;

            // Verifica se já enviou hoje
            if ($this->jaEnviouHoje($paciente->id, 'aniversario')) continue;

            $celular = $paciente->celular ?? $paciente->telefone;
            if (empty($celular)) continue;

            $wpp->enviarTemplate(
                celular:      $celular,
                templateName: $empresa->wpp_template_aniversario ?? 'feliz_aniversario',
                components:   [
                    ['type' => 'body', 'parameters' => [
                        ['type' => 'text', 'text' => $paciente->nome]
                    ]]
                ]
            );

            $log = new WhatsappLog();
            $log->empresa_id  = $empresa->id;
            $log->paciente_id = $paciente->id;
            $log->tipo        = 'aniversario';
            $log->celular     = $celular;
            $log->status_envio= 'enviado';
            $log->data_envio  = date('Y-m-d');
            $log->hora_envio  = date('H:i:s');
            $log->store();

            sleep(2);
        }

        TTransaction::close();
    }

    private function dispararFalta(SystemUnit $empresa): void
    {
        $diasSemAtendimento = (int)($empresa->wpp_falta_dias_sem_atendimento ?? 30);
        $horaAtual  = date('H:i');
        $horaInicio = $empresa->wpp_falta_horario_inicio ?? '09:00';
        $horaFim    = $empresa->wpp_falta_horario_fim    ?? '17:00';

        if ($horaAtual < $horaInicio || $horaAtual > $horaFim) return;

        $dataLimite = date('Y-m-d', strtotime("-{$diasSemAtendimento} days"));

        TTransaction::open($empresa->connection_name);

        $criteria = new TCriteria();
        $criteria->add(new TFilter('empresa_id',              '=',  $empresa->id));
        $criteria->add(new TFilter('ativo',                   '=',  'S'));
        $criteria->add(new TFilter('data_ultimoprocedimento', '<=', $dataLimite));
        $criteria->add(new TFilter('celular',                 'is not', null));

        $repo     = new TRepository('Pessoa');
        $pacientes = $repo->load($criteria);

        $wpp = new WhatsAppService($empresa);

        foreach ($pacientes as $paciente) {
            if ($this->jaEnviouHoje($paciente->id, 'falta')) continue;

            $celular = $paciente->celular ?? $paciente->telefone;
            if (empty($celular)) continue;

            $wpp->enviarTemplate(
                celular:      $celular,
                templateName: $empresa->wpp_template_falta ?? 'sentimos_sua_falta',
                components:   [
                    ['type' => 'body', 'parameters' => [
                        ['type' => 'text', 'text' => $paciente->nome]
                    ]]
                ]
            );

            $log = new WhatsappLog();
            $log->empresa_id  = $empresa->id;
            $log->paciente_id = $paciente->id;
            $log->tipo        = 'falta';
            $log->celular     = $celular;
            $log->status_envio= 'enviado';
            $log->data_envio  = date('Y-m-d');
            $log->hora_envio  = date('H:i:s');
            $log->store();

            sleep(2);
        }

        TTransaction::close();
    }

    private function contarTentativasHoje(int $agendamentoId, string $tipo): int
    {
        $criteria = new TCriteria();
        $criteria->add(new TFilter('agendamento_id', '=', $agendamentoId));
        $criteria->add(new TFilter('tipo',           '=', $tipo));
        $criteria->add(new TFilter('data_envio',     '=', date('Y-m-d')));
        $repo = new TRepository('WhatsappLog');
        return count($repo->load($criteria));
    }

    private function jaEnviouHoje(int $pacienteId, string $tipo): bool
    {
        $criteria = new TCriteria();
        $criteria->add(new TFilter('paciente_id', '=', $pacienteId));
        $criteria->add(new TFilter('tipo',        '=', $tipo));
        $criteria->add(new TFilter('data_envio',  '=', date('Y-m-d')));
        $repo = new TRepository('WhatsappLog');
        return count($repo->load($criteria)) > 0;
    }
}

// Execução
(new WhatsAppScheduler())->executar();

7. Botão na tela AgendamentoCalendarForm02
// Dentro do método onEdit ou buildEventDetails da tela de agendamento

// Botão de envio manual de confirmação WhatsApp
$btnWhatsApp = new TButton('btn_wpp_confirmar');
$btnWhatsApp->setLabel('Solicitar confirmação WhatsApp');
$btnWhatsApp->setImage('fab:whatsapp #25D366');
$btnWhatsApp->addFunction("__adianti_load_page('AgendamentoCalendarForm02/enviarConfirmacaoWhatsApp&id={$agendamento->id}')");

// Ou via TAction
$btnWhatsApp->setAction(
    new TAction([$this, 'enviarConfirmacaoWhatsApp']),
    'Enviar solicitação de confirmação via WhatsApp'
);
```

---

## Resumo da arquitetura
```
┌─────────────────────────────────────────────────────────────────┐
│                     FLUXO DE CONFIRMAÇÃO                        │
│                                                                 │
│  Cadastro Agendamento → status: 01 (AGUARDANDO CONFIRMAÇÃO)     │
│          │                                                      │
│          ▼                                                      │
│  Scheduler (cron) ou botão manual                               │
│          │                                                      │
│          ▼                                                      │
│  WhatsAppService → API Meta → Paciente recebe botões           │
│                                [✅ Confirmar] [❌ Não posso]   │
│          │                                                      │
│          ▼                                                      │
│  Webhook recebe resposta                                        │
│          │                                                      │
│     ┌────┴────┐                                                 │
│     ▼         ▼                                                 │
│  status: 02   status: 05                                        │
│  CONFIRMADO   NÃO CONFIRMADO                                    │
└─────────────────────────────────────────────────────────────────┘

Pontos de atenção:

O webhook precisa de HTTPS público — configure no painel da Meta (developers.facebook.com)
Os templates de mensagem precisam ser aprovados pela Meta antes do uso
Para mensagens interativas com botões, a janela de sessão do WhatsApp Business é de 24h após última mensagem do cliente — fora disso só templates aprovados funcionam
Configure o cron com frequência de 15-30 minutos para não sobrecarregar a API
O whatsapp_webhook_token deve ser uma string secreta gerada por você e registrada no painel da Meta