feat: endpoints usan api_base_url como base, rutas relativas en UI

- Banner en tab Endpoints muestra la URL base de General
- Inputs de URL aceptan rutas relativas (?param=x o /ruta)
- Preview live de la URL completa (base+ruta) bajo cada input
- Backend prepend api_base_url en test y sync si URL no es absoluta
- Compatible hacia atrás: URLs absolutas existentes siguen funcionando

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-04 15:19:46 -05:00
co-authored by Claude Sonnet 4.6
parent bfbc70451d
commit 43933bfe3a
+67 -10
View File
@@ -1113,12 +1113,23 @@ HTML;
$epStmt->execute([$id]);
$allEps = $epStmt->fetchAll(\PDO::FETCH_ASSOC);
$baseRaw = rtrim($company['api_base_url'] ?? '', '/');
$baseHtml = self::h($baseRaw);
$epRows = '';
foreach ($allEps as $ep) {
$epKey = self::h($ep['endpoint_key']);
$epName = self::h($ep['name'] ?? '');
$epUrl = self::h($ep['url'] ?? '');
$epMeth = $ep['method'] ?? 'GET';
// Preview full URL
$rawUrl = $ep['url'] ?? '';
if ($rawUrl === '' || str_starts_with($rawUrl, 'http')) {
$previewUrl = self::h($rawUrl);
} else {
$sep = str_starts_with($rawUrl, '?') ? '' : '/';
$previewUrl = self::h($baseRaw . $sep . ltrim($rawUrl, '/'));
}
$epDir = $ep['direction'] ?? 'download';
$epParams = self::h($ep['params'] ?? '');
$epAct = $ep['is_active'] ? 'checked' : '';
@@ -1215,9 +1226,10 @@ VR;
<option {$mSel('GET')}>GET</option><option {$mSel('POST')}>POST</option>
</select>
</td>
<td style="min-width:200px">
<input type="text" value="{$epUrl}" id="epu_{$epId}" placeholder="https://..." oninput="onUrlChange({$epId})" style="width:100%;font-size:12px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px">
<div style="font-size:10px;color:#9ca3af;margin-top:2px">Usa {'{var}'} para variables. {$lastAt}</div>
<td style="min-width:220px">
<input type="text" value="{$epUrl}" id="epu_{$epId}" placeholder="?peticion=xxx o /ruta" oninput="onUrlChange({$epId})" style="width:100%;font-size:12px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px">
<div id="ep-preview-{$epId}" style="font-size:10px;color:#6b7280;font-family:monospace;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{$previewUrl}">{$previewUrl}</div>
<div style="font-size:10px;color:#9ca3af;margin-top:1px">Usa {'{var}'} para variables. {$lastAt}</div>
</td>
<td><input type="checkbox" {$epAct} id="epa_{$epId}" title="Activo"></td>
<td style="white-space:nowrap">
@@ -1246,15 +1258,21 @@ ROW;
$epHtml = <<<EP
<div class="card-h" style="margin-bottom:12px">🔌 Endpoints API</div>
<div style="background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:10px 14px;margin-bottom:14px;font-size:12px;display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<span>🔗 <strong>URL base:</strong></span>
<code style="background:#fff8e1;padding:2px 6px;border-radius:4px;font-size:12px">{$baseHtml}</code>
<a href="/admin/company/edit?id={$id}&tab=general" style="color:#1e40af;font-size:11px">cambiar en General </a>
<span style="color:#92400e;font-size:11px">· Los endpoints usan rutas relativas a esta URL</span>
</div>
<p style="font-size:12px;color:#7a8291;margin-bottom:14px">
Define los endpoints del ERP. Usa <code>{'{variable}'}</code> en la URL para variables que el bot pedirá al usuario.<br>
Ejemplo: <code>?peticion=produccion&amp;desde={'{desde}'}&amp;hasta={'{hasta}'}</code>
Escribe solo la ruta o sufijo: <code>?peticion=produccion</code> o <code>/mi/ruta</code>. Se combina con la URL base.<br>
Usa <code>{'{variable}'}</code> para variables que el bot pedirá al usuario. Ejemplo: <code>?desde={'{desde}'}&amp;hasta={'{hasta}'}</code>
</p>
<div class="card" style="overflow-x:auto">
<table style="width:100%;min-width:700px">
<thead>
<tr style="font-size:12px;text-align:left">
<th>Key</th><th>Nombre</th><th>Dirección</th><th>Método</th><th>URL</th><th>Activo</th><th></th>
<th>Key</th><th>Nombre</th><th>Dirección</th><th>Método</th><th>Ruta / sufijo</th><th>Activo</th><th></th>
</tr>
</thead>
<tbody id="epTableBody">{$epRows}</tbody>
@@ -1290,8 +1308,9 @@ ROW;
</div>
</div>
<div class="form-group">
<label>URL</label>
<input type="text" id="newEpUrl" placeholder="https://app.palmas360.com/...?peticion=xxx">
<label>Ruta / sufijo</label>
<input type="text" id="newEpUrl" placeholder="?peticion=xxx o /mi/ruta" oninput="updateNewEpPreview()">
<div id="newEpPreview" style="font-size:11px;color:#6b7280;font-family:monospace;margin-top:4px"></div>
</div>
<div id="newEpMsg" style="margin-bottom:8px"></div>
<button class="btn-primary" onclick="addNewEp({$id})">Agregar</button>
@@ -1620,8 +1639,33 @@ function collectVarConfigs(epId) {
return JSON.stringify(configs);
}
const BASE_URL = '{$baseHtml}';
function buildFullUrl(path) {
if (!path || path.startsWith('http')) return path;
const base = BASE_URL.replace(/\/$/, '');
const sep = path.startsWith('?') ? '' : '/';
return base + sep + path.replace(/^\//, '');
}
function updateEpPreview(epId) {
const path = document.getElementById('epu_'+epId)?.value || '';
const el = document.getElementById('ep-preview-'+epId);
if (!el) return;
const full = buildFullUrl(path);
el.textContent = full || '';
el.title = full || '';
}
function updateNewEpPreview() {
const path = document.getElementById('newEpUrl')?.value || '';
const el = document.getElementById('newEpPreview');
if (el) el.textContent = buildFullUrl(path) || '';
}
// When URL changes, refresh the vars section
function onUrlChange(epId) {
updateEpPreview(epId);
const url = document.getElementById('epu_'+epId).value;
const matches = [...url.matchAll(/\{([^}]+)\}/g)].map(m => m[1]);
const vrRows = document.getElementById('vr-rows-'+epId);
@@ -1797,9 +1841,15 @@ HTML;
exit;
}
// Llamar al endpoint
// Llamar al endpoint — prepend base URL si es ruta relativa
$epUrl = $ep['url'];
if (!str_starts_with($epUrl, 'http')) {
$base = rtrim($company['api_base_url'] ?? '', '/');
$sep = str_starts_with($epUrl, '?') ? '' : '/';
$epUrl = $base . $sep . ltrim($epUrl, '/');
}
$apiKey = $company['api_key'] ?? '';
$ch = curl_init($ep['url']);
$ch = curl_init($epUrl);
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
@@ -2050,6 +2100,13 @@ HTML;
$company = CompanyRepository::findById($companyId);
$apiKey = $company['api_key'] ?? '';
// Prepend api_base_url for relative paths
if ($url !== '' && !str_starts_with($url, 'http')) {
$base = rtrim($company['api_base_url'] ?? '', '/');
$sep = str_starts_with($url, '?') ? '' : '/';
$url = $base . $sep . ltrim($url, '/');
}
$ch = curl_init($url);
$opts = [
CURLOPT_RETURNTRANSFER => true,