feat(turnero): template whitelist checklist + 2-step picker in chat

- configuracion.php (sesion tab): loads approved message_templates and
  enabled turnero_chat_plantillas; renders styled checklist; guardarPlantillasChat()
  POSTs to chat_save_plantillas.php
- chat.php: replaces free-text template modal with 2-step modal —
  step 1 lists enabled templates (chat_get_plantillas.php), step 2
  fetches variables via get_template_details.php and renders labeled
  inputs; sendTemplate() builds params array and POSTs to chat_send_message.php

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-03 14:37:21 -05:00
co-authored by Claude Sonnet 4.6
parent 8a7e7a6658
commit 548ad3140d
2 changed files with 214 additions and 48 deletions
+73
View File
@@ -91,6 +91,22 @@ try {
foreach ($cfgRows as $r) { $cfg[$r['clave']] = $r['valor']; }
} catch (\Throwable $e) { $_loadErrors[] = 'lab_config: ' . $e->getMessage(); }
$plantillasAprobadas = [];
$plantillasHabilitadas = [];
try {
$plantillasAprobadas = $pdo->query(
"SELECT id, name, template_name, language_code, body_text
FROM message_templates WHERE status='approved' ORDER BY name ASC"
)->fetchAll(PDO::FETCH_ASSOC);
$pdo->exec("CREATE TABLE IF NOT EXISTS turnero_chat_plantillas (
template_id INT NOT NULL, PRIMARY KEY (template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$plantillasHabilitadas = array_column(
$pdo->query("SELECT template_id FROM turnero_chat_plantillas")->fetchAll(PDO::FETCH_ASSOC),
'template_id'
);
} catch (\Throwable $e) { $_loadErrors[] = 'plantillas_chat: ' . $e->getMessage(); }
$tab = $_GET['tab'] ?? 'lugares';
?>
<!DOCTYPE html>
@@ -127,6 +143,14 @@ $tab = $_GET['tab'] ?? 'lugares';
.session-open { background:#dcfce7; color:#15803d; }
.session-closed{ background:#f1f5f9; color:#64748b; }
.consent-tag { font-size:.7rem; background:#ede9fe; color:#5b21b6; border-radius:99px; padding:2px 8px; white-space:normal; word-break:break-word; max-width:260px; }
.plantilla-chk-row { display:flex; align-items:flex-start; gap:.65rem; padding:.6rem .8rem; border:1.5px solid #e2e8f0; border-radius:8px; cursor:pointer; background:#fff; transition:border-color .15s,background .15s; }
.plantilla-chk-row:hover { background:#f8fafc; border-color:#cbd5e1; }
.plantilla-chk-row.is-checked { border-color:#3b82f6; background:#eff6ff; }
.plantilla-chk-row input[type=checkbox] { margin-top:.2rem; flex-shrink:0; accent-color:#3b82f6; }
.plantilla-chk-body { display:flex; flex-direction:column; gap:.1rem; min-width:0; }
.plantilla-chk-name { font-size:.875rem; font-weight:600; color:#1e293b; }
.plantilla-chk-meta { font-size:.75rem; color:#64748b; }
.plantilla-chk-preview { font-size:.75rem; color:#94a3b8; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:480px; }
</style>
</head>
<body>
@@ -892,6 +916,41 @@ $tab = $_GET['tab'] ?? 'lugares';
</div>
</div>
<!-- Plantillas habilitadas en el chat turnero -->
<div class="mt-4">
<p class="section-title"><i class="fas fa-comment-dots me-1"></i>Plantillas habilitadas en el chat</p>
<p class="text-muted small mb-3">Marca las plantillas aprobadas disponibles en el chat del turnero.</p>
<?php if (empty($plantillasAprobadas)): ?>
<div class="alert alert-warning py-2 px-3" style="font-size:.85rem">
<i class="fas fa-exclamation-triangle me-1"></i>
No hay plantillas aprobadas. Sincroniza primero desde WhatsApp Business.
</div>
<?php else: ?>
<div id="plantillas-chat-list" style="display:grid;gap:.4rem;margin-bottom:.75rem">
<?php foreach ($plantillasAprobadas as $pt):
$checked = in_array((int)$pt['id'], array_map('intval', $plantillasHabilitadas));
?>
<label class="plantilla-chk-row<?= $checked ? ' is-checked' : '' ?>">
<input type="checkbox" value="<?= (int)$pt['id'] ?>" <?= $checked ? 'checked' : '' ?>
onchange="this.closest('label').classList.toggle('is-checked',this.checked)">
<div class="plantilla-chk-body">
<span class="plantilla-chk-name"><?= htmlspecialchars($pt['name']) ?></span>
<span class="plantilla-chk-meta"><?= htmlspecialchars($pt['template_name']) ?> &bull; <?= htmlspecialchars($pt['language_code']) ?></span>
<?php if (!empty($pt['body_text'])): ?>
<span class="plantilla-chk-preview"><?= htmlspecialchars(mb_substr($pt['body_text'], 0, 100)) . (mb_strlen($pt['body_text']) > 100 ? '…' : '') ?></span>
<?php endif; ?>
</div>
</label>
<?php endforeach; ?>
</div>
<div class="d-flex justify-content-end">
<button class="btn btn-primary btn-sm" onclick="guardarPlantillasChat()">
<i class="fas fa-save me-1"></i>Guardar plantillas habilitadas
</button>
</div>
<?php endif; ?>
</div>
<!-- ════════════════════════════════════════
TAB 5 — PANTALLA TV
════════════════════════════════════════ -->
@@ -1358,6 +1417,20 @@ async function subirFotoPerfil(input) {
}
}
async function guardarPlantillasChat() {
const ids = [...document.querySelectorAll('#plantillas-chat-list input[type=checkbox]:checked')]
.map(el => parseInt(el.value, 10));
try {
const res = await fetch(API + 'chat_save_plantillas.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids })
});
const json = await res.json();
if (!json.ok) { toast(json.error || 'Error al guardar', 'error'); return; }
toast('Plantillas guardadas (' + ids.length + ')');
} catch (e) { toast('Error de conexión', 'error'); }
}
async function guardarWhatsApp() {
const template = document.getElementById('wa-template')?.value.trim();
const templateMuestra = document.getElementById('wa-template-muestra')?.value.trim();