feat(chat): botón Nuevo mensaje con búsqueda de paciente y picker de plantilla
- chat_buscar_paciente.php: GET ?q= busca en lab_pacientes por nombre,
documento o teléfono (mín. 2 chars, máx. 20 resultados)
- chat_start_conversation.php: POST {phone, template_name, lang, params}
— normaliza teléfono, busca/crea usuario en users, envía plantilla
vía WhatsAppService('turnero'), devuelve {ok, user_id}
- chat.php: botón "+ Nuevo" en sidebar header; modal 3 pasos:
1) buscar paciente (debounce 350ms) o ingresar número libre
2) lista de plantillas habilitadas
3) variables de la plantilla + Enviar
Al enviar recarga el sidebar y abre la conversación automáticamente
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
67efd470de
commit
ab72750410
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* GET ?q= — Busca pacientes en lab_pacientes por nombre o documento.
|
||||
* Usado por el modal "Nuevo mensaje" del chat turnero.
|
||||
*/
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
requireAuthentication();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$q = trim($_GET['q'] ?? '');
|
||||
if (strlen($q) < 2) { echo json_encode(['ok' => true, 'data' => []]); exit; }
|
||||
|
||||
try {
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$like = '%' . $q . '%';
|
||||
$rows = $pdo->prepare(
|
||||
"SELECT id, nombre_completo, telefono, numero_documento
|
||||
FROM lab_pacientes
|
||||
WHERE is_active = 1
|
||||
AND (nombre_completo LIKE ? OR numero_documento LIKE ? OR telefono LIKE ?)
|
||||
ORDER BY nombre_completo ASC
|
||||
LIMIT 20"
|
||||
);
|
||||
$rows->execute([$like, $like, $like]);
|
||||
echo json_encode(['ok' => true, 'data' => $rows->fetchAll(PDO::FETCH_ASSOC)]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* POST — Inicia una conversación nueva desde el chat turnero.
|
||||
* Body: { phone, template_name, lang, params }
|
||||
* Busca o crea el usuario en `users`, envía la plantilla y devuelve user_id.
|
||||
*/
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
require_once __DIR__ . '/../../../services/WhatsAppService.php';
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405); echo json_encode(['ok' => false, 'error' => 'Método no permitido']); exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$phone = trim($input['phone'] ?? '');
|
||||
$templateName = trim($input['template_name'] ?? '');
|
||||
$lang = trim($input['lang'] ?? 'es_CO');
|
||||
$params = $input['params'] ?? [];
|
||||
|
||||
if (!$phone || !$templateName) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => 'phone y template_name son requeridos']); exit;
|
||||
}
|
||||
|
||||
// Normalizar teléfono: solo dígitos, agregar 57 si es colombiano de 10 dígitos
|
||||
$phoneClean = preg_replace('/\D/', '', $phone);
|
||||
if (strlen($phoneClean) === 10 && $phoneClean[0] === '3') {
|
||||
$phoneClean = '57' . $phoneClean;
|
||||
}
|
||||
if (strlen($phoneClean) < 10) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => 'Número de teléfono inválido']); exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// Buscar usuario existente (puede estar guardado con o sin prefijo 57)
|
||||
$user = $pdo->prepare(
|
||||
"SELECT id, name FROM users WHERE phone_number = ? OR phone_number = ? LIMIT 1"
|
||||
);
|
||||
$user->execute([$phoneClean, ltrim($phoneClean, '57')]);
|
||||
$userRow = $user->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$userRow) {
|
||||
// Buscar nombre en lab_pacientes por teléfono
|
||||
$pac = $pdo->prepare(
|
||||
"SELECT nombre_completo FROM lab_pacientes WHERE telefono LIKE ? AND is_active=1 LIMIT 1"
|
||||
);
|
||||
$pac->execute(['%' . substr($phoneClean, -10) . '%']);
|
||||
$pacRow = $pac->fetch(PDO::FETCH_ASSOC);
|
||||
$nombre = $pacRow['nombre_completo'] ?? $phoneClean;
|
||||
|
||||
// Crear usuario mínimo
|
||||
$ins = $pdo->prepare(
|
||||
"INSERT INTO users (name, phone_number, created_at) VALUES (?, ?, NOW())"
|
||||
);
|
||||
$ins->execute([$nombre, $phoneClean]);
|
||||
$userId = (int)$pdo->lastInsertId();
|
||||
} else {
|
||||
$userId = (int)$userRow['id'];
|
||||
}
|
||||
|
||||
// Enviar plantilla
|
||||
$operatorId = $_SESSION['admin_id'] ?? $_SESSION['user_id'] ?? null;
|
||||
$meta = ['canal' => 'turnero'];
|
||||
if ($operatorId) $meta['operator_id'] = $operatorId;
|
||||
|
||||
$wa = new WhatsAppService('turnero');
|
||||
$wa->sendTemplateMessage($phoneClean, $templateName, $lang, $params, [], null, $meta);
|
||||
|
||||
echo json_encode(['ok' => true, 'user_id' => $userId]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
error_log('chat_start_conversation: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -37,6 +37,12 @@ html, body { height: 100%; overflow: hidden; font-family: -apple-system, BlinkMa
|
||||
.sidebar-header .badge-config {
|
||||
font-size: .7rem; background: rgba(0,0,0,.2); padding: 3px 8px; border-radius: 12px;
|
||||
}
|
||||
.btn-nuevo-chat {
|
||||
background: rgba(255,255,255,.2); border: none; color: #fff; border-radius: 8px;
|
||||
padding: 5px 10px; cursor: pointer; font-size: .82rem; font-weight: 700;
|
||||
display: flex; align-items: center; gap: 4px; transition: background .15s; flex-shrink: 0;
|
||||
}
|
||||
.btn-nuevo-chat:hover { background: rgba(255,255,255,.35); }
|
||||
.search-box { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; }
|
||||
.search-box input {
|
||||
width: 100%; padding: 8px 12px; border: 1px solid #e0e0e0;
|
||||
@@ -271,6 +277,38 @@ html, body { height: 100%; overflow: hidden; font-family: -apple-system, BlinkMa
|
||||
.chat-window { position: absolute; inset: 0; z-index: 5; transform: translateX(100%); height: 100%; transition: transform .2s ease; }
|
||||
.chat-window.mobile-active { transform: translateX(0); }
|
||||
.back-btn { display: flex; }
|
||||
/* ── Modal nuevo mensaje ── */
|
||||
#nuevo-modal { display:none; position:fixed; inset:0; z-index:1100;
|
||||
background:rgba(0,0,0,.45); align-items:center; justify-content:center; padding:16px; }
|
||||
#nuevo-modal.show { display:flex; }
|
||||
.nv-dialog { background:#fff; border-radius:14px; width:100%; max-width:420px;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,.22); overflow:hidden; display:flex; flex-direction:column; max-height:90vh; }
|
||||
.nv-hdr { display:flex; align-items:center; gap:8px; padding:13px 16px;
|
||||
border-bottom:1px solid #f1f5f9; font-size:.92rem; font-weight:700; color:#1e293b; flex-shrink:0; }
|
||||
.nv-hdr > span { flex:1; }
|
||||
.nv-body { padding:14px 16px; overflow-y:auto; flex:1; }
|
||||
.nv-footer { display:flex; gap:8px; justify-content:flex-end; padding:12px 16px;
|
||||
border-top:1px solid #f1f5f9; flex-shrink:0; }
|
||||
.nv-search { width:100%; padding:8px 12px; border:1.5px solid #e2e8f0; border-radius:8px;
|
||||
font-size:.87rem; outline:none; box-sizing:border-box; margin-bottom:10px; }
|
||||
.nv-search:focus { border-color:#25d366; }
|
||||
.nv-pac-item { display:flex; flex-direction:column; gap:1px; padding:9px 10px; cursor:pointer;
|
||||
border-radius:8px; transition:background .12s; border:1px solid transparent; }
|
||||
.nv-pac-item:hover { background:#f0fdf4; border-color:#bbf7d0; }
|
||||
.nv-pac-name { font-size:.87rem; font-weight:600; color:#1e293b; }
|
||||
.nv-pac-sub { font-size:.75rem; color:#64748b; }
|
||||
.nv-divider { display:flex; align-items:center; gap:8px; margin:10px 0;
|
||||
font-size:.75rem; color:#94a3b8; }
|
||||
.nv-divider::before,.nv-divider::after { content:''; flex:1; height:1px; background:#e2e8f0; }
|
||||
.nv-destinatario { display:flex; align-items:center; gap:8px; padding:8px 10px;
|
||||
background:#f0fdf4; border:1px solid #bbf7d0; border-radius:8px; margin-bottom:10px; font-size:.85rem; }
|
||||
.nv-destinatario strong { flex:1; color:#15803d; }
|
||||
.nv-back-btn { background:none; border:none; cursor:pointer; color:#64748b;
|
||||
font-size:.85rem; padding:2px 7px; border-radius:4px; }
|
||||
.nv-back-btn:hover { background:#f1f5f9; }
|
||||
.nv-close-btn { background:none; border:none; cursor:pointer; color:#94a3b8;
|
||||
font-size:.95rem; padding:2px 7px; border-radius:4px; }
|
||||
.nv-close-btn:hover { color:#475569; background:#f1f5f9; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -295,6 +333,9 @@ html, body { height: 100%; overflow: hidden; font-family: -apple-system, BlinkMa
|
||||
<i class="fas fa-exclamation-circle"></i> Sin config
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<button class="btn-nuevo-chat" onclick="nuevoMensaje.abrir()" title="Nuevo mensaje">
|
||||
<i class="fas fa-plus"></i> Nuevo
|
||||
</button>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchInput" placeholder="Buscar contacto…" autocomplete="off">
|
||||
@@ -382,6 +423,69 @@ html, body { height: 100%; overflow: hidden; font-family: -apple-system, BlinkMa
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- ── Modal nuevo mensaje (3 pasos) ── -->
|
||||
<div id="nuevo-modal">
|
||||
<!-- Paso 1: destinatario -->
|
||||
<div class="nv-dialog" id="nv-step-dest">
|
||||
<div class="nv-hdr">
|
||||
<span><i class="fas fa-user-plus me-2" style="color:#25d366"></i>Nuevo mensaje</span>
|
||||
<button class="nv-close-btn" onclick="nuevoMensaje.cerrar()">✕</button>
|
||||
</div>
|
||||
<div class="nv-body">
|
||||
<input class="nv-search" id="nv-pac-search" placeholder="🔍 Buscar paciente por nombre o cédula…"
|
||||
autocomplete="off" oninput="nuevoMensaje.buscarPaciente(this.value)">
|
||||
<div id="nv-pac-results" style="display:flex;flex-direction:column;gap:4px;min-height:40px"></div>
|
||||
<div class="nv-divider">o ingresa el número directamente</div>
|
||||
<input class="nv-search" id="nv-phone-input" placeholder="📱 Número de WhatsApp (ej: 3001234567)"
|
||||
autocomplete="off" inputmode="tel"
|
||||
oninput="nuevoMensaje.onPhoneInput(this.value)">
|
||||
<div id="nv-phone-hint" style="font-size:.76rem;color:#94a3b8;margin-top:-6px;margin-bottom:4px"></div>
|
||||
</div>
|
||||
<div class="nv-footer">
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="nuevoMensaje.cerrar()">Cancelar</button>
|
||||
<button class="btn btn-success btn-sm fw-semibold" id="nv-btn-siguiente"
|
||||
onclick="nuevoMensaje.irAPlantilla()" disabled>
|
||||
Siguiente <i class="fas fa-arrow-right ms-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Paso 2: plantilla -->
|
||||
<div class="nv-dialog" id="nv-step-tpl" style="display:none">
|
||||
<div class="nv-hdr">
|
||||
<button class="nv-back-btn" onclick="nuevoMensaje.volverADest()"><i class="fas fa-arrow-left"></i></button>
|
||||
<span>Seleccionar plantilla</span>
|
||||
<button class="nv-close-btn" onclick="nuevoMensaje.cerrar()">✕</button>
|
||||
</div>
|
||||
<div class="nv-body">
|
||||
<div class="nv-destinatario">
|
||||
<i class="fas fa-user-circle" style="color:#25d366;font-size:1.1rem"></i>
|
||||
<strong id="nv-dest-label">—</strong>
|
||||
</div>
|
||||
<div id="nv-tpl-list" style="display:flex;flex-direction:column;gap:4px;overflow-y:auto;max-height:300px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Paso 3: variables -->
|
||||
<div class="nv-dialog" id="nv-step-vars" style="display:none">
|
||||
<div class="nv-hdr">
|
||||
<button class="nv-back-btn" onclick="nuevoMensaje.volverAPlantilla()"><i class="fas fa-arrow-left"></i></button>
|
||||
<span id="nv-vars-title">Completar variables</span>
|
||||
<button class="nv-close-btn" onclick="nuevoMensaje.cerrar()">✕</button>
|
||||
</div>
|
||||
<div class="nv-body">
|
||||
<div id="nv-preview-box" style="display:none;background:#f0fdf4;border-left:3px solid #25d366;
|
||||
border-radius:0 6px 6px 0;padding:8px 12px;font-size:.82rem;color:#374151;
|
||||
margin-bottom:10px;white-space:pre-wrap;word-break:break-word"></div>
|
||||
<div id="nv-vars-body" style="display:flex;flex-direction:column;gap:.6rem"></div>
|
||||
</div>
|
||||
<div class="nv-footer">
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="nuevoMensaje.volverAPlantilla()">Atrás</button>
|
||||
<button class="btn btn-success btn-sm fw-semibold" onclick="nuevoMensaje.enviar()">
|
||||
<i class="fas fa-paper-plane me-1"></i>Enviar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Modal plantilla (2 pasos) ── -->
|
||||
<div id="tpl-modal">
|
||||
<!-- Paso 1: lista -->
|
||||
@@ -415,7 +519,9 @@ const API_SEND = 'modules/turnero/api/chat_send_message.php';
|
||||
const API_READ = 'modules/turnero/api/chat_mark_read.php';
|
||||
const API_MEDIA = 'modules/turnero/api/chat_upload_media.php';
|
||||
const API_REACT = 'modules/turnero/api/chat_react.php';
|
||||
const API_PLANTILLAS = 'modules/turnero/api/chat_get_plantillas.php';
|
||||
const API_PLANTILLAS = 'modules/turnero/api/chat_get_plantillas.php';
|
||||
const API_BUSCAR_PAC = 'modules/turnero/api/chat_buscar_paciente.php';
|
||||
const API_START_CONV = 'modules/turnero/api/chat_start_conversation.php';
|
||||
let _tplSelected = null;
|
||||
const BASE = (function() {
|
||||
const s = window.location.pathname;
|
||||
@@ -910,6 +1016,189 @@ function cancelRecording() {
|
||||
document.getElementById('micBtn').style.color = '';
|
||||
}
|
||||
|
||||
// ── Nuevo mensaje ────────────────────────────────────────────────────────────
|
||||
const nuevoMensaje = (() => {
|
||||
let _phone = '';
|
||||
let _destLabel = '';
|
||||
let _tpl = null;
|
||||
let _buscarTimer = null;
|
||||
|
||||
function _show(stepId) {
|
||||
['nv-step-dest','nv-step-tpl','nv-step-vars'].forEach(id => {
|
||||
document.getElementById(id).style.display = id === stepId ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function abrir() {
|
||||
_phone = ''; _destLabel = ''; _tpl = null;
|
||||
document.getElementById('nv-pac-search').value = '';
|
||||
document.getElementById('nv-phone-input').value = '';
|
||||
document.getElementById('nv-phone-hint').textContent = '';
|
||||
document.getElementById('nv-pac-results').innerHTML = '';
|
||||
document.getElementById('nv-btn-siguiente').disabled = true;
|
||||
_show('nv-step-dest');
|
||||
document.getElementById('nuevo-modal').classList.add('show');
|
||||
document.getElementById('nv-pac-search').focus();
|
||||
}
|
||||
|
||||
function cerrar() {
|
||||
document.getElementById('nuevo-modal').classList.remove('show');
|
||||
}
|
||||
|
||||
function _setDest(phone, label) {
|
||||
_phone = phone; _destLabel = label;
|
||||
document.getElementById('nv-btn-siguiente').disabled = !phone;
|
||||
}
|
||||
|
||||
function onPhoneInput(val) {
|
||||
const digits = val.replace(/\D/,'');
|
||||
const hint = document.getElementById('nv-phone-hint');
|
||||
if (!digits) { _setDest('', ''); hint.textContent = ''; return; }
|
||||
if (digits.length >= 10) {
|
||||
hint.textContent = '✅ Número válido';
|
||||
hint.style.color = '#16a34a';
|
||||
_setDest(digits, '+' + (digits.length === 10 ? '57' : '') + digits);
|
||||
} else {
|
||||
hint.textContent = digits.length + ' dígitos — mínimo 10';
|
||||
hint.style.color = '#94a3b8';
|
||||
_setDest('', '');
|
||||
}
|
||||
// Clear patient selection if typing a phone
|
||||
document.getElementById('nv-pac-search').value = '';
|
||||
document.getElementById('nv-pac-results').innerHTML = '';
|
||||
}
|
||||
|
||||
function buscarPaciente(q) {
|
||||
document.getElementById('nv-phone-input').value = '';
|
||||
document.getElementById('nv-phone-hint').textContent = '';
|
||||
_setDest('', '');
|
||||
clearTimeout(_buscarTimer);
|
||||
const el = document.getElementById('nv-pac-results');
|
||||
if (q.length < 2) { el.innerHTML = ''; return; }
|
||||
el.innerHTML = '<div style="padding:8px;font-size:.8rem;color:#94a3b8"><i class="fas fa-spinner fa-spin"></i> Buscando…</div>';
|
||||
_buscarTimer = setTimeout(async () => {
|
||||
try {
|
||||
const res = await fetch(BASE + API_BUSCAR_PAC + '?q=' + encodeURIComponent(q));
|
||||
const json = await res.json();
|
||||
if (!json.ok || !json.data?.length) {
|
||||
el.innerHTML = '<div style="padding:8px;font-size:.8rem;color:#94a3b8">Sin resultados</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = '';
|
||||
json.data.forEach(p => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'nv-pac-item';
|
||||
const tel = p.telefono || '—';
|
||||
div.innerHTML =
|
||||
`<span class="nv-pac-name">${escHtml(p.nombre_completo)}</span>` +
|
||||
`<span class="nv-pac-sub">${escHtml(p.numero_documento || '')} · 📱 ${escHtml(tel)}</span>`;
|
||||
div.onclick = () => {
|
||||
const phone = (p.telefono || '').replace(/\D/g,'');
|
||||
if (!phone) { alert('Este paciente no tiene teléfono registrado.'); return; }
|
||||
_setDest(phone, p.nombre_completo + ' · ' + tel);
|
||||
document.getElementById('nv-pac-search').value = p.nombre_completo;
|
||||
el.innerHTML = '';
|
||||
};
|
||||
el.appendChild(div);
|
||||
});
|
||||
} catch(e) {
|
||||
el.innerHTML = '<div style="padding:8px;font-size:.8rem;color:#ef4444">Error al buscar</div>';
|
||||
}
|
||||
}, 350);
|
||||
}
|
||||
|
||||
async function irAPlantilla() {
|
||||
if (!_phone) return;
|
||||
document.getElementById('nv-dest-label').textContent = _destLabel || _phone;
|
||||
_show('nv-step-tpl');
|
||||
const listEl = document.getElementById('nv-tpl-list');
|
||||
listEl.innerHTML = '<div style="padding:16px;text-align:center;color:#94a3b8;font-size:.84rem"><i class="fas fa-spinner fa-spin"></i> Cargando…</div>';
|
||||
try {
|
||||
const res = await fetch(BASE + API_PLANTILLAS);
|
||||
const json = await res.json();
|
||||
if (!json.ok || !json.data?.length) {
|
||||
listEl.innerHTML = '<div style="padding:16px;text-align:center;color:#94a3b8;font-size:.84rem">No hay plantillas habilitadas.<br><small>Configúralas en Configuración → Sesión.</small></div>';
|
||||
return;
|
||||
}
|
||||
listEl.innerHTML = '';
|
||||
json.data.forEach(tpl => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'tpl-item';
|
||||
div.innerHTML =
|
||||
`<span class="tpl-item-name">${escHtml(tpl.name)}</span>` +
|
||||
`<span class="tpl-item-meta">${escHtml(tpl.template_name)} · ${escHtml(tpl.language_code)}</span>` +
|
||||
(tpl.body_text ? `<span class="tpl-item-body">${escHtml(tpl.body_text.substring(0,80))}${tpl.body_text.length>80?'…':''}</span>` : '');
|
||||
div.onclick = () => irAVariables(tpl);
|
||||
listEl.appendChild(div);
|
||||
});
|
||||
} catch(e) {
|
||||
listEl.innerHTML = '<div style="padding:16px;text-align:center;color:#ef4444;font-size:.84rem">Error al cargar plantillas</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function irAVariables(tpl) {
|
||||
_tpl = tpl;
|
||||
document.getElementById('nv-vars-title').textContent = tpl.name;
|
||||
const previewBox = document.getElementById('nv-preview-box');
|
||||
if (tpl.body_text) { previewBox.textContent = tpl.body_text; previewBox.style.display = ''; }
|
||||
else previewBox.style.display = 'none';
|
||||
|
||||
const varsBody = document.getElementById('nv-vars-body');
|
||||
varsBody.innerHTML = '<div style="padding:6px;color:#94a3b8;font-size:.82rem"><i class="fas fa-spinner fa-spin"></i> Cargando…</div>';
|
||||
_show('nv-step-vars');
|
||||
|
||||
try {
|
||||
const res = await fetch(BASE + 'api/get_template_details.php?id=' + tpl.id);
|
||||
const json = await res.json();
|
||||
const vars = json.template?.variables ?? [];
|
||||
if (!vars.length) {
|
||||
varsBody.innerHTML = '<div style="padding:6px;font-size:.82rem;color:#64748b">Esta plantilla no requiere variables.</div>';
|
||||
} else {
|
||||
varsBody.innerHTML = '';
|
||||
vars.forEach(v => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'tpl-var-row';
|
||||
const ph = v.example || v.placeholder || v.label;
|
||||
row.innerHTML = `<label>{{${v.index}}} — ${escHtml(v.label)}</label>` +
|
||||
`<input type="text" data-var-index="${v.index}" placeholder="${escHtml(ph)}">`;
|
||||
varsBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
varsBody.innerHTML = '<div style="padding:6px;color:#ef4444;font-size:.82rem">Error al cargar variables</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function volverADest() { _tpl = null; _show('nv-step-dest'); }
|
||||
function volverAPlantilla() { _tpl = null; _show('nv-step-tpl'); }
|
||||
|
||||
async function enviar() {
|
||||
if (!_phone || !_tpl) return;
|
||||
const inputs = [...document.querySelectorAll('#nv-vars-body input[data-var-index]')];
|
||||
const params = inputs.map(el => ({ name: el.dataset.varIndex, value: el.value.trim() }));
|
||||
const missing = params.findIndex(p => !p.value);
|
||||
if (missing !== -1) { inputs[missing].focus(); return; }
|
||||
|
||||
const templateName = _tpl.template_name;
|
||||
const templateLang = _tpl.language_code;
|
||||
cerrar();
|
||||
|
||||
try {
|
||||
const res = await fetch(BASE + API_START_CONV, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone: _phone, template_name: templateName, lang: templateLang, params })
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!json.ok) { alert('Error: ' + (json.error || 'No se pudo enviar')); return; }
|
||||
// Abrir la conversación recién creada
|
||||
await loadContacts();
|
||||
if (json.user_id) openChat(json.user_id);
|
||||
} catch(e) { alert('Error de conexión al enviar el mensaje'); }
|
||||
}
|
||||
|
||||
return { abrir, cerrar, buscarPaciente, onPhoneInput, irAPlantilla, volverADest, volverAPlantilla, enviar };
|
||||
})();
|
||||
|
||||
// ── Plantilla (2 pasos) ───────────────────────────────────────────────────────
|
||||
async function openTemplatePicker() {
|
||||
if (!state.activeUserId) return;
|
||||
|
||||
Reference in New Issue
Block a user