eduardo4547's picture
Upload 150 files
cb5d9d0 verified
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vista previa del app</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-100 text-slate-900">
<div class="max-w-5xl mx-auto p-6 space-y-6">
<header class="flex items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-bold">Vista previa del app</h1>
<p class="text-slate-600">
Aquí puedes ver cómo se despliega el visualizador con una API key de
ejemplo.
</p>
</div>
<a
href="/admin"
class="px-4 py-2 bg-slate-800 text-white rounded-xl hover:bg-slate-900"
>Ir al panel</a
>
</header>
<div
class="rounded-3xl overflow-hidden border border-slate-200 shadow-lg"
>
<iframe
id="preview-iframe"
src=""
class="w-full min-h-[600px] border-0"
></iframe>
</div>
<div class="rounded-3xl bg-white p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-3">Puente de desarrollo</h2>
<p class="text-slate-700 mb-4">
Esta vista previa se conecta con el app React embebido. Puedes enviar
comandos y ver las respuestas del iframe.
</p>
<div class="grid gap-4 md:grid-cols-2 mb-4">
<div>
<label class="block text-sm font-medium text-slate-700 mb-2">
Cliente de prueba
</label>
<select
id="client-select"
class="w-full rounded-2xl border border-slate-300 px-4 py-3"
>
<option value="ID_UNICO_DEL_CLIENTE_001">
ID_UNICO_DEL_CLIENTE_001
</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-2">
Comando personalizado
</label>
<input
id="custom-command"
type="text"
placeholder="Ej. set-color:#f97316"
class="w-full rounded-2xl border border-slate-300 px-4 py-3"
/>
</div>
</div>
<div class="flex flex-wrap gap-3 mb-4">
<button
id="load-client"
class="px-4 py-2 bg-indigo-600 text-white rounded-xl hover:bg-indigo-700"
>
Cargar cliente seleccionado
</button>
<button
id="send-ping"
class="px-4 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700"
>
Enviar ping
</button>
<button
id="request-status"
class="px-4 py-2 bg-slate-200 text-slate-900 rounded-xl hover:bg-slate-300"
>
Pedir estado al app
</button>
<button
id="send-custom"
class="px-4 py-2 bg-green-600 text-white rounded-xl hover:bg-green-700"
>
Enviar comando
</button>
</div>
<div
id="bridge-log"
class="rounded-2xl border border-slate-200 bg-slate-50 p-4 text-slate-700 h-52 overflow-y-auto"
></div>
</div>
<script>
const iframe = document.getElementById("preview-iframe");
const bridgeLog = document.getElementById("bridge-log");
const clientSelect = document.getElementById("client-select");
const customCommand = document.getElementById("custom-command");
function addLog(message) {
const item = document.createElement("div");
item.className = "text-sm mb-2";
item.textContent = message;
bridgeLog.appendChild(item);
bridgeLog.scrollTop = bridgeLog.scrollHeight;
}
async function loadClients() {
try {
const res = await fetch("/api/keys");
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "No se pudo cargar los clientes.");
}
clientSelect.innerHTML = data.keys
.map(
(key) =>
`<option value="${key.client_id}">${key.client_id} - ${key.nombre}</option>`,
)
.join("");
addLog(`Clientes cargados: ${data.keys.length}`);
} catch (error) {
addLog(`Error al cargar clientes: ${error.message || error}`);
}
}
async function checkEnvironment() {
try {
const res = await fetch("/health");
const data = await res.json();
if (!res.ok || !data.frontend_ready) {
addLog(
"Ambiente no listo: asegúrate de tener backend y frontend levantados.",
);
return false;
}
addLog("Ambiente OK: backend y frontend están levantados.");
return true;
} catch (error) {
addLog(
"No se pudo verificar el ambiente. El backend debe estar corriendo.",
);
return false;
}
}
async function loadPreviewToken() {
const backendOk = await checkEnvironment();
if (!backendOk) return;
const clientId = clientSelect.value || "ID_UNICO_DEL_CLIENTE_001";
try {
const res = await fetch("/api/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `client_id=${encodeURIComponent(clientId)}`,
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "No se pudo generar token.");
}
iframe.src = `/app?token=${encodeURIComponent(data.token)}`;
addLog(`Token generado para ${clientId}`);
} catch (error) {
addLog(`Error generando token: ${error.message || error}`);
}
}
function sendBridgeCommand(command, payload) {
if (!iframe.contentWindow) {
addLog("El iframe aún no está listo.");
return;
}
iframe.contentWindow.postMessage(
{ type: "preview-command", command, payload },
"*",
);
addLog(
`Enviado comando al app: ${command}${payload ? ` ${JSON.stringify(payload)}` : ""}`,
);
}
window.addEventListener("message", (event) => {
if (event.source !== iframe.contentWindow) return;
const data = event.data;
if (!data || typeof data !== "object") return;
if (data.type === "saas-session-active") {
addLog(`React indicó sesión activa: ${data.nombreCliente}`);
}
if (data.type === "app-loaded") {
addLog(`React cargado con cliente: ${data.clientId}`);
}
if (data.type === "preview-response") {
addLog(`React responde: ${JSON.stringify(data)}`);
}
});
document.getElementById("load-client").addEventListener("click", () => {
loadPreviewToken();
});
document.getElementById("send-ping").addEventListener("click", () => {
sendBridgeCommand("ping");
});
document
.getElementById("request-status")
.addEventListener("click", () => {
sendBridgeCommand("status");
});
document.getElementById("send-custom").addEventListener("click", () => {
const commandText = customCommand.value.trim();
if (!commandText) {
addLog("Ingresa un comando personalizado.");
return;
}
const [command, payloadString] = commandText.split(":", 2);
let payload;
if (payloadString) {
payload = { value: payloadString };
}
sendBridgeCommand(command, payload);
});
loadClients().then(loadPreviewToken);
</script>
<div class="rounded-3xl bg-white p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-3">Nota</h2>
<p class="text-slate-700">
Esta vista previa genera un token de acceso para el cliente
<strong>ID_UNICO_DEL_CLIENTE_001</strong> y carga el app con él.
</p>
</div>
</div>
</body>
</html>