// cron_send_es.php — Disparo de recuperação de carrito (ES-CL) com janelas e limite por execução declare(strict_types=1); use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; // ======= EXIBIÇÃO DE ERROS NO LOG (não na saída do cron) ======= ini_set('display_errors', '1'); error_reporting(E_ALL); // ======= DEPENDÊNCIAS ======= require '/home/naturalozem/public_html/PHPMailer/src/Exception.php'; require '/home/naturalozem/public_html/PHPMailer/src/PHPMailer.php'; require '/home/naturalozem/public_html/PHPMailer/src/SMTP.php'; // ======= CONFIGURAÇÃO SMTP / REMETENTE ======= const SMTP_HOST = 'mail.naturalozem.com'; const SMTP_PORT = 587; const SMTP_USER = 'apoyo@naturalozem.com'; const SMTP_PASS = 'jFH$8mx7$Yac'; const SMTP_HELO = 'vps-14049077.revelador2025.online'; const FROM_EMAIL = 'apoyo@naturalozem.com'; const FROM_NAME = 'Equipo Naturalozem'; const ENVELOPE_FROM = 'bounces@naturalozem.com'; const BCC_MONITOR = 'luan.silva0904@gmail.com'; // ======= CONFIGURAÇÃO DB ======= const DB_HOST = 'localhost'; const DB_NAME = 'naturalozem_banco'; const DB_USER = 'naturalozem_user'; const DB_PASS = '7!utKNSdnT6U5Uno'; // ======= PRIMEIRO ENVIO (via conecta.php) ======= const FIRST_EMAIL_ENDPOINT = 'https://naturalozem.com/conecta.php'; const FIRST_EMAIL_TIMEOUT = 15; // seg // ======= POLÍTICA DE DISPARO ======= const MAX_PER_RUN = 5; // no máx 5 por execução const GAP_BETWEEN = 3; // segundos entre envios // próximas janelas após CADA envio (para calcular next_email_at) function nextGapAfterSend(int $newCount): ?string { return match ($newCount) { 1 => '+1 day', 2 => '+2 days', 3 => '+3 days', 4 => '+5 days', default => null, // 5 ou mais: não agenda }; } // elegibilidade ANTES de enviar (mínimo desde o último marco) function isEligibleToSend(array $lead): bool { $emailsSent = (int)$lead['emails_sent']; $now = new DateTime('now'); $lastUpd = isset($lead['last_update']) ? new DateTime($lead['last_update']) : null; $lastEmail = isset($lead['last_email_at']) ? new DateTime($lead['last_email_at']) : null; return match ($emailsSent) { 0 => $lastUpd ? ($now >= (clone $lastUpd)->modify('+10 minutes')) : true, 1 => $lastEmail ? ($now >= (clone $lastEmail)->modify('+1 day')) : false, 2 => $lastEmail ? ($now >= (clone $lastEmail)->modify('+2 days')) : false, 3 => $lastEmail ? ($now >= (clone $lastEmail)->modify('+3 days')) : false, 4 => $lastEmail ? ($now >= (clone $lastEmail)->modify('+5 days')) : false, default => false, }; } function db(): PDO { static $pdo = null; if ($pdo === null) { $dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4'; $opts = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; $pdo = new PDO($dsn, DB_USER, DB_PASS, $opts); } return $pdo; } // ======= CHAMADA DO PRIMEIRO E-MAIL (conecta.php) ======= function triggerFirstEmail(array $lead): bool { // Monta payload: preferir external_id; se não houver, enviar id $payload = []; if (!empty($lead['external_id'])) { $payload['external_id'] = $lead['external_id']; } else { $payload['id'] = (string)$lead['id']; } // Enviar também nome e email if (!empty($lead['email'])) $payload['email'] = $lead['email']; if (!empty($lead['name'])) $payload['name'] = $lead['name']; // (Opcional) Envie extras se o conecta.php aceitar: // if (!empty($lead['phone'])) $payload['phone'] = $lead['phone']; // if (!empty($lead['country_iso'])) $payload['country_iso'] = $lead['country_iso']; // if (!empty($lead['product_id'])) $payload['product_id'] = $lead['product_id']; // Monta query string para GET $query = http_build_query($payload); $url = FIRST_EMAIL_ENDPOINT . '?' . $query; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => FIRST_EMAIL_TIMEOUT, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HEADER => false, ]); $resp = curl_exec($ch); $err = curl_error($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($resp === false) { // error_log("conecta.php erro cURL: $err"); return false; } // Considera HTTP 2xx sucesso if ($http >= 200 && $http < 300) { // Se vier JSON com {"success":true}, respeita $data = json_decode($resp, true); if (is_array($data) && array_key_exists('success', $data)) { return !empty($data['success']); } return true; } // error_log("conecta.php HTTP $http, body: $resp"); return false; } // ======= TEXTOS (ES-CL) ======= function pick(array $arr) { return $arr[random_int(0, count($arr)-1)]; } function sentenceJoin(array $parts) { $txt = trim(preg_replace('/\s+/', ' ', implode(' ', $parts))); $txt = rtrim($txt); if (!preg_match('/[.!?…]$/u', $txt)) $txt .= '.'; return $txt; } function dotOrEmpty() { return (random_int(0,1) ? '' : '.'); } // Assuntos $S_OPENER = [ '¿Aún te interesa tu pedido?', 'Guardamos tu pedido por un momento', 'Tu producto sigue reservado', 'Parece que olvidaste algo', 'Volvimos a guardar tu carrito', 'Solo un recordatorio amable', 'Hiciste una pausa… tu pedido te espera', 'Tu orden quedó pendiente', 'Casi listo: retoma tu compra', 'Antes que se libere el stock', 'Seguimos aquí por si lo necesitas', '¿Volvemos a donde quedaste?', 'Pequeño recordatorio', 'Vuelve cuando estés lista', 'Esto quedó a medias', '¿Lo terminamos ahora?', 'Quedó algo en tu carrito', 'Revisamos tu pedido', 'Tu fórmula natural sigue disponible', 'Tu selección quedó guardada' ]; $S_MIDDLE = [ '— lo mantenemos apartado', '— está a un clic', '— retómalo cuando quieras', '— todavía disponible', '— no te preocupes', '— sin apuro', '— reservado por poco tiempo', '— seguimos aquí', '— tu cambio empieza cuando decidas', '— listo para confirmar', '— con stock limitado', '— lo guardamos para ti', '— vuelve cuando te acomode', '— cuando estés lista', '— queda muy poco', '— tiempo acotado', '— casi listo', '— te esperamos', '— aún activo', '— retómalo fácil' ]; $S_TAIL = ['🕒','🌿','✨','✅','🙌','🙂','💚','☕','📦','🛒','🔁','📌','🔔','🧾','💬',' ']; // Preheaders, saudações, etc. $PREHEADERS = [ 'Guardamos tu carrito por unas horas.', 'Puedes retomar tu compra cuando quieras.', 'Tu selección sigue disponible por tiempo limitado.', 'Un clic y lo dejas listo.', 'Ningún apuro, solo un recordatorio amable.', 'Estamos aquí para ayudarte si lo necesitas.', 'Tu pedido quedó a un paso de confirmarse.', 'Vuelve cuando estés lista, lo cuidamos por ti.', 'Si te sirve, te dejamos el acceso directo.', 'Gracias por confiar en Naturalozem.' ]; $GREETINGS = ['Hola {{NOMBRE}},','¡Hola {{NOMBRE}}!','Hola {{NOMBRE}} 🙂','Hola {{NOMBRE}} 👋','¡Qué tal, {{NOMBRE}}!']; $INTRO = [ 'Vimos que dejaste tu pedido pendiente y lo guardamos por un rato para ti', 'Notamos que hiciste una pausa: tu carrito quedó a un paso de confirmarse', 'Parece que interrumpiste la compra; tu selección sigue apartada', 'Resguardamos tu carrito para que puedas retomarlo cuando te acomode', 'Tu orden quedó en pausa: si quieres, puedes continuar donde la dejaste', 'Guardamos automáticamente tu carrito para que no pierdas lo que elegiste', 'Tu producto sigue disponible y listo para confirmar', 'Solo queríamos avisarte que tu carrito permanece activo', 'Cuidamos tu selección para que la finalices sin presión', '¿Seguimos desde el punto donde lo dejaste?' ]; $BENEFIT = [ 'Muchas personas nos cuentan que se sienten más livianas en su rutina diaria', 'Varios clientes dicen que ganaron constancia en sus hábitos y eso los hizo sentir mejor', 'Pequeños pasos sostienen cambios duraderos; lo importante es comenzar', 'Nos gusta acompañarte con opciones simples y naturales', 'La idea es que te resulte fácil y agradable cuidar de ti', 'Nuestro enfoque es amable: sin prisa, sin presión', 'Creemos en decisiones informadas y cotidianas, a tu ritmo', 'Elegiste una opción suave y natural para tu día a día', 'Cuidarte puede ser más sencillo de lo que imaginas', 'Estamos para ayudarte con información clara y cercana' ]; $SOFT_URGENCY = [ 'Tu reserva se libera automáticamente si no hay movimiento', 'Para asegurar el stock, te sugerimos retomarlo pronto', 'El stock puede variar, por eso te dejamos el acceso directo', 'Si te sirve, lo mantenemos apartado por algunas horas más', 'Si prefieres, puedes retomarlo ahora y decidir en calma', 'Si ya no te interesa, no hay problema: puedes ignorar este mensaje', 'Este es un recordatorio único y amable, sin insistir', 'Cuando estés lista, aquí estaremos', 'Queremos que decidas tranquila; solo cuidamos tu selección', 'Si cambiaste de opinión, también está perfecto' ]; $CTA_TEXT = [ 'Completar mi compra','Reanudar pedido','Confirmar mi pedido','Volver a mi carrito','Seguir donde quedé', 'Terminar compra','Retomar ahora','Continuar','Dejarlo listo','Revisar mi carrito' ]; $TRUST = [ 'Gracias por confiar en Naturalozem','Estamos aquí si necesitas ayuda','Si tienes dudas, responde a este correo', 'Tu bienestar, a tu ritmo','Siempre con información clara','Atención cercana y honesta', 'Estamos para ayudarte','Equipo Naturalozem','Cualquier consulta, cuenta con nosotros','Gracias por estar aquí' ]; $LEGAL_FOOTER = [ 'Si no deseas recibir recordatorios, puedes darte de baja en el enlace al final del correo.', 'Este mensaje se envía por haber iniciado un proceso de compra. Puedes cancelar la suscripción cuando quieras.', 'Para dejar de recibir estos correos, utiliza el enlace “Darse de baja”.', 'Respetamos tu tiempo: puedes darte de baja en un clic.', 'Este es un recordatorio transaccional. Si no lo necesitas, puedes darte de baja.', ]; $SIGN_LINES = [ 'Naturalozem • Santiago, Chile','Naturalozem • Atención al cliente','Naturalozem • Soporte', 'Naturalozem • Chile','Naturalozem • Equipo de Atención' ]; $BTN_COLORS = ['#2e7d32','#00695c','#1565c0','#6a1b9a','#546e7a','#00897b','#5d4037']; // helpers de conteúdo function buildSubject($S_OPENER,$S_MIDDLE,$S_TAIL): string { $parts = [pick($S_OPENER), pick($S_MIDDLE), pick($S_TAIL)]; $subject = trim(preg_replace('/\s+/', ' ', implode(' ', $parts))); return preg_replace('/\s+([?!,.:;])/u', '$1', $subject); } function buildBody($nombre, $link, $GREETINGS,$PREHEADERS,$INTRO,$BENEFIT,$SOFT_URGENCY,$CTA_TEXT,$TRUST,$LEGAL_FOOTER,$SIGN_LINES,$BTN_COLORS): array { $name = trim((string)$nombre); if ($name === '' || strtolower($name) === 'null') { $greet = pick(['Hola,','¡Hola!','Hola 🙂','Hola 👋','¡Qué tal!']); } else { $greet = str_replace('{{NOMBRE}}', $name, pick($GREETINGS)); } $pre = pick($PREHEADERS); $p1 = sentenceJoin([pick($INTRO)]); $p2 = sentenceJoin([pick($BENEFIT)]); if (random_int(0,1)) { $p2b = pick($BENEFIT); if ($p2b !== $p2) $p2 .= ' '.$p2b.dotOrEmpty(); } $p3 = sentenceJoin([pick($SOFT_URGENCY)]); $cta = pick($CTA_TEXT); $btn = pick($BTN_COLORS); $trust= pick($TRUST); $legal= pick($LEGAL_FOOTER); $sign = pick($SIGN_LINES); $html = <<
| $pre |
$greet |
| $p1 |
| $p2 |
| $p3 |
| $cta |
| $trust |
| $legal |
| $sign |