From bfbc70451d814a26c1b859f18a6c9c6dd62d622f Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 4 Jul 2026 13:50:39 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20clonar=20endpoints=20desde=20otra=20emp?= =?UTF-8?q?resa=20en=20pesta=C3=B1a=20Endpoints=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agrega panel de clonación en la pestaña Endpoints API del editor de empresa. Permite copiar los endpoints de otra empresa con dos modos: sobreescribir todo o solo añadir los endpoints que aún no tienen URL configurada. Co-Authored-By: Claude Sonnet 4.6 --- admin/DashboardController.php | 109 ++++++++++++++++++++++++++++++++++ public/index.php | 1 + 2 files changed, 110 insertions(+) diff --git a/admin/DashboardController.php b/admin/DashboardController.php index 8a60c5a..61e2f11 100644 --- a/admin/DashboardController.php +++ b/admin/DashboardController.php @@ -1298,6 +1298,34 @@ ROW; EP; + + // Prepend clone panel + $otherCompanies = array_filter(CompanyRepository::findAll(), fn($c) => (int)$c['id'] !== $id); + $cloneOpts = ''; + foreach ($otherCompanies as $oc) { + $cloneOpts .= ''; + } + $clonePanel = << +
📋 Clonar endpoints desde otra empresa
+
+ + +
+ +
+ +CLONE; + $epHtml = $clonePanel . $epHtml; } // For "copy from" dropdown on new company form @@ -1700,6 +1728,38 @@ async function addNewEp(cid) { msg.innerHTML='
'+j.error+'
'; } } + +function showCloneOptions() { + const src = document.getElementById('cloneSrc').value; + if (!src) { alert('Selecciona una empresa origen'); return; } + document.getElementById('cloneOptions').style.display = 'block'; +} +function cancelClone() { + document.getElementById('cloneOptions').style.display = 'none'; +} +async function doClone(mode) { + const src = document.getElementById('cloneSrc').value; + if (!src) return; + cancelClone(); + const msg = document.getElementById('cloneMsg'); + msg.innerHTML = 'Clonando...'; + const fd = new FormData(); + fd.append('source_company_id', src); + fd.append('target_company_id', {$id}); + fd.append('mode', mode); + try { + const r = await fetch('/admin/company/endpoints/clone', {method:'POST', body:fd}); + const j = await r.json(); + if (j.ok) { + msg.innerHTML = '
' + j.message + '
'; + setTimeout(() => location.reload(), 1200); + } else { + msg.innerHTML = '
' + j.error + '
'; + } + } catch(e) { + msg.innerHTML = '
Error de red
'; + } +} HTML; } @@ -2033,6 +2093,55 @@ HTML; exit; } + // ─── POST /admin/company/endpoints/clone ───────────────────────────────── + + public static function companyEndpointsClone(): void + { + SessionAuth::require(); + header('Content-Type: application/json; charset=utf-8'); + + $sourceId = (int)($_POST['source_company_id'] ?? 0); + $targetId = (int)($_POST['target_company_id'] ?? 0); + $mode = ($_POST['mode'] ?? '') === 'add_only' ? 'add_only' : 'overwrite'; + + if ($sourceId <= 0 || $targetId <= 0 || $sourceId === $targetId) { + echo json_encode(['ok' => false, 'error' => 'IDs inválidos']); + exit; + } + + try { + $stmt = db()->prepare("SELECT endpoint_key, direction, url, method FROM company_endpoints WHERE company_id = ?"); + $stmt->execute([$sourceId]); + $sourceEps = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + if (empty($sourceEps)) { + echo json_encode(['ok' => false, 'error' => 'La empresa origen no tiene endpoints configurados']); + exit; + } + + $existingKeys = []; + if ($mode === 'add_only') { + $ex = db()->prepare("SELECT endpoint_key FROM company_endpoints WHERE company_id = ? AND url IS NOT NULL AND url != ''"); + $ex->execute([$targetId]); + $existingKeys = array_column($ex->fetchAll(\PDO::FETCH_ASSOC), 'endpoint_key'); + } + + $upsert = db()->prepare("INSERT INTO company_endpoints (company_id, endpoint_key, direction, url, method) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE url=VALUES(url), method=VALUES(method)"); + $count = 0; + foreach ($sourceEps as $ep) { + if ($mode === 'add_only' && in_array($ep['endpoint_key'], $existingKeys, true)) continue; + $upsert->execute([$targetId, $ep['endpoint_key'], $ep['direction'], $ep['url'], $ep['method']]); + $count++; + } + + $label = $mode === 'add_only' ? 'añadidos' : 'sobreescritos'; + echo json_encode(['ok' => true, 'count' => $count, 'message' => "{$count} endpoint(s) {$label} correctamente"]); + } catch (\PDOException $e) { + echo json_encode(['ok' => false, 'error' => 'Error de base de datos: ' . $e->getMessage()]); + } + exit; + } + // ─── GET /admin/sync-companies ────────────────────────────────────────── public static function syncCompanies(): void diff --git a/public/index.php b/public/index.php index c747627..dfa1baa 100644 --- a/public/index.php +++ b/public/index.php @@ -282,6 +282,7 @@ $routes = [ ['POST', '/admin/company/endpoint/save', fn() => DashboardController::companyEndpointSave()], ['POST', '/admin/company/endpoint/test', fn() => DashboardController::companyEndpointTest()], ['POST', '/admin/company/endpoint/delete', fn() => DashboardController::companyEndpointDelete()], + ['POST', '/admin/company/endpoints/clone', fn() => DashboardController::companyEndpointsClone()], // ─── Admin: listar pendientes de aprobación ───────────────────────────── ['GET', '/admin/pending-list', fn() => DashboardController::pendingList()],