feat: integración WEBSms en configuración
- Nueva sección WEBSms en lab_configuracion.php con: - Switch activo/inactivo (sms_activo) - Campo URL base de la API (sms_url) - Campo API key / Bearer token (sms_api_key) - Campos de prueba: número + mensaje + botón Enviar - api/lab/test_sms.php: proxy al endpoint POST /api/sms/send con Authorization: Bearer, valida respuesta ok/error - save_config.php: permite guardar sms_url, sms_api_key, sms_activo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
301b39ad0c
commit
21e7c6a836
@@ -18,6 +18,9 @@ $permitidas = [
|
||||
'doc_color', 'doc_logo_base64', 'doc_pie_pagina',
|
||||
'gemini_api_key',
|
||||
'ia_activa',
|
||||
'sms_url',
|
||||
'sms_api_key',
|
||||
'sms_activo',
|
||||
];
|
||||
|
||||
$guardadas = 0;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* POST /api/lab/test_sms.php
|
||||
* Envía un SMS de prueba via WEBSms.
|
||||
* Body JSON: { url, key, numero, mensaje }
|
||||
*/
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
requireAdmin();
|
||||
|
||||
$datos = inputJson();
|
||||
$baseUrl = rtrim(trim($datos['url'] ?? ''), '/');
|
||||
$key = trim($datos['key'] ?? '');
|
||||
$numero = trim($datos['numero'] ?? '');
|
||||
$mensaje = trim($datos['mensaje'] ?? 'Prueba de conexión WEBSms');
|
||||
|
||||
if (!$baseUrl) jsonError('URL base requerida.');
|
||||
if (!$key) jsonError('API key requerida.');
|
||||
if (!$numero) jsonError('Número requerido.');
|
||||
if (!filter_var($baseUrl, FILTER_VALIDATE_URL)) jsonError('URL base inválida.');
|
||||
|
||||
// Sanitizar número: solo dígitos
|
||||
$numero = preg_replace('/\D/', '', $numero);
|
||||
if (strlen($numero) < 7) jsonError('Número inválido.');
|
||||
|
||||
$payload = json_encode([
|
||||
'numero' => $numero,
|
||||
'mensaje' => $mensaje,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$endpoint = $baseUrl . '/api/sms/send';
|
||||
|
||||
$ch = curl_init($endpoint);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $key,
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
]);
|
||||
$raw = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err) jsonError('Error de conexión: ' . $err, 502);
|
||||
if (!$raw) jsonError('Sin respuesta del servidor SMS.', 502);
|
||||
|
||||
$resp = json_decode($raw, true);
|
||||
if ($code >= 200 && $code < 300 && !empty($resp['ok'])) {
|
||||
jsonOk(['id' => $resp['id'] ?? null]);
|
||||
}
|
||||
|
||||
$msg = $resp['error'] ?? "Error HTTP {$code}";
|
||||
jsonError($msg, 502);
|
||||
@@ -199,6 +199,58 @@ require_once __DIR__ . '/shared/components/sidebar.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── WEBSms ──────────────────────────────────────────── -->
|
||||
<div class="section-card">
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<h6 class="mb-0"><i class="fas fa-sms me-1"></i>Integración WEBSms</h6>
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" id="c-sms-activo" role="switch"
|
||||
<?= !empty($cfg['sms_activo']) && $cfg['sms_activo'] === '1' ? 'checked' : '' ?>>
|
||||
<label class="form-check-label small fw-semibold" for="c-sms-activo">Activo</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small mb-3">
|
||||
Permite enviar SMS a pacientes desde el sistema. Requiere una cuenta en WEBSms.
|
||||
</p>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label fw-semibold small">URL base de la API</label>
|
||||
<input type="url" class="form-control" id="c-sms-url"
|
||||
value="<?= htmlspecialchars($cfg['sms_url'] ?? '') ?>"
|
||||
placeholder="https://api.websms.com">
|
||||
<div class="form-text">Sin barra final. El sistema agrega <code>/api/sms/send</code>.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label fw-semibold small">API Key (Bearer token)</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="c-sms-key"
|
||||
value="<?= htmlspecialchars($cfg['sms_api_key'] ?? '') ?>"
|
||||
placeholder="sk_…"
|
||||
autocomplete="off">
|
||||
<button class="btn btn-outline-secondary" type="button"
|
||||
onclick="this.previousElementSibling.type = this.previousElementSibling.type === 'password' ? 'text' : 'password'">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label fw-semibold small">Probar envío</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fas fa-phone"></i></span>
|
||||
<input type="tel" class="form-control" id="c-sms-test-num"
|
||||
placeholder="573001234567" maxlength="15">
|
||||
<input type="text" class="form-control" id="c-sms-test-msg"
|
||||
placeholder="Mensaje de prueba" maxlength="160" value="Prueba de conexión WEBSms">
|
||||
<button class="btn btn-outline-primary" type="button" id="btn-probar-sms" onclick="probarSms()">
|
||||
<i class="fas fa-paper-plane me-1"></i>Enviar
|
||||
</button>
|
||||
</div>
|
||||
<div id="sms-test-result" class="form-text mt-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-lg w-100" onclick="guardar()">
|
||||
<i class="fas fa-save me-2"></i>Guardar configuración
|
||||
</button>
|
||||
@@ -322,6 +374,9 @@ async function guardar() {
|
||||
doc_pie_pagina: $('c-doc-pie').value.trim(),
|
||||
gemini_api_key: $('c-gemini-key').value.trim(),
|
||||
ia_activa: $('c-ia-activa').checked ? '1' : '0',
|
||||
sms_url: $('c-sms-url').value.trim().replace(/\/$/, ''),
|
||||
sms_api_key: $('c-sms-key').value.trim(),
|
||||
sms_activo: $('c-sms-activo').checked ? '1' : '0',
|
||||
};
|
||||
|
||||
if (!payload.empresa_nombre) {
|
||||
@@ -354,6 +409,42 @@ async function guardar() {
|
||||
}
|
||||
}
|
||||
|
||||
async function probarSms() {
|
||||
const url = $('c-sms-url').value.trim().replace(/\/$/, '');
|
||||
const key = $('c-sms-key').value.trim();
|
||||
const num = $('c-sms-test-num').value.trim();
|
||||
const msg = $('c-sms-test-msg').value.trim();
|
||||
const res = document.getElementById('sms-test-result');
|
||||
const btn = document.getElementById('btn-probar-sms');
|
||||
|
||||
if (!url) { res.innerHTML = '<span class="text-danger">Ingresa la URL base primero.</span>'; return; }
|
||||
if (!key) { res.innerHTML = '<span class="text-danger">Ingresa la API key primero.</span>'; return; }
|
||||
if (!num) { res.innerHTML = '<span class="text-danger">Ingresa un número de destino.</span>'; return; }
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||
res.innerHTML = '';
|
||||
|
||||
try {
|
||||
const r = await fetch('api/lab/test_sms.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url, key, numero: num, mensaje: msg || 'Prueba de conexión WEBSms' }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.ok) {
|
||||
res.innerHTML = `<span class="text-success"><i class="fas fa-check-circle me-1"></i>SMS enviado — ID: <code>${d.id || '—'}</code></span>`;
|
||||
} else {
|
||||
res.innerHTML = `<span class="text-danger"><i class="fas fa-times-circle me-1"></i>${d.error || 'Error desconocido'}</span>`;
|
||||
}
|
||||
} catch(_) {
|
||||
res.innerHTML = '<span class="text-danger"><i class="fas fa-times-circle me-1"></i>No se pudo conectar.</span>';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane me-1"></i>Enviar';
|
||||
}
|
||||
}
|
||||
|
||||
async function probarIA() {
|
||||
const key = $('c-gemini-key').value.trim();
|
||||
const res = document.getElementById('ia-test-result');
|
||||
|
||||
Reference in New Issue
Block a user