Añadir mapeador.html
This commit is contained in:
240
mapeador.html
Normal file
240
mapeador.html
Normal file
@@ -0,0 +1,240 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mapeador de Variables - IntegraRepara</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<style>
|
||||
.fade-in { animation: fadeIn 0.3s ease-in-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
.scroller::-webkit-scrollbar { width: 6px; }
|
||||
.scroller::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 text-gray-800 font-sans h-screen flex overflow-hidden">
|
||||
|
||||
<div id="sidebar-container" class="h-full shrink-0"></div>
|
||||
|
||||
<div class="flex-1 flex flex-col h-full min-w-0">
|
||||
<div id="header-container"></div>
|
||||
|
||||
<main class="flex-1 overflow-y-auto p-6 scroller">
|
||||
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
|
||||
<i data-lucide="map" class="text-purple-600"></i> Mapeador de Variables
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500">Conecta los datos del proveedor con tu base de datos interna.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 bg-white p-1 rounded-lg shadow-sm border border-gray-200">
|
||||
<select id="providerSelect" onchange="loadKeys()" class="bg-transparent text-sm font-bold text-gray-700 outline-none px-3 py-2 cursor-pointer">
|
||||
<option value="multiasistencia">Multiasistencia</option>
|
||||
<option value="homeserve">HomeServe</option>
|
||||
</select>
|
||||
<button onclick="saveMapping()" class="bg-slate-800 hover:bg-slate-700 text-white px-4 py-2 rounded-md text-sm font-bold transition flex items-center gap-2">
|
||||
<i data-lucide="save" class="w-4 h-4"></i> Guardar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-purple-50 border-l-4 border-purple-500 p-4 mb-6 rounded-r-lg flex items-start gap-3">
|
||||
<i data-lucide="info" class="w-5 h-5 text-purple-600 mt-0.5"></i>
|
||||
<div class="text-sm text-purple-800">
|
||||
<p class="font-bold">¿Cómo funciona esto?</p>
|
||||
<p>El robot ha escaneado las webs de los proveedores y ha encontrado estas etiquetas.
|
||||
Si quieres guardar un dato, escribe un nombre corto en <strong>"Tu Variable Interna"</strong> (ej: <code>poliza</code>, <code>f_efecto</code>).
|
||||
Si no te interesa, marca <strong>Ignorar</strong>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow border border-gray-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm text-left">
|
||||
<thead class="text-xs text-gray-500 uppercase bg-gray-50 border-b">
|
||||
<tr>
|
||||
<th class="px-6 py-3 w-1/3">Etiqueta Original (Proveedor)</th>
|
||||
<th class="px-6 py-3 w-1/3">Ejemplo de Valor</th>
|
||||
<th class="px-6 py-3 w-1/4">Tu Variable Interna</th>
|
||||
<th class="px-6 py-3 text-center">Ignorar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="mappingTable" class="divide-y divide-gray-100">
|
||||
<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400">Selecciona un proveedor para cargar datos...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="toast" class="fixed bottom-5 right-5 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-xl translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center gap-3"><span id="toastMsg"></span></div>
|
||||
|
||||
<script src="js/layout.js"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (!localStorage.getItem("token")) window.location.href = "index.html";
|
||||
// Cargar por defecto Multiasistencia
|
||||
loadKeys();
|
||||
});
|
||||
|
||||
async function loadKeys() {
|
||||
const provider = document.getElementById('providerSelect').value;
|
||||
const tbody = document.getElementById('mappingTable');
|
||||
|
||||
// Estado de carga
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="px-6 py-10 text-center text-gray-400 flex flex-col items-center"><i data-lucide="loader-2" class="w-8 h-8 animate-spin mb-2 text-purple-500"></i> Analizando datos descargados...</td></tr>';
|
||||
lucide.createIcons();
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/discovery/keys/${provider}`, {
|
||||
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
tbody.innerHTML = "";
|
||||
|
||||
if (!data.keys || data.keys.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-10 text-center text-gray-400">
|
||||
<div class="flex flex-col items-center">
|
||||
<i data-lucide="search-x" class="w-10 h-10 mb-2 opacity-50"></i>
|
||||
<p class="font-bold">No se encontraron datos</p>
|
||||
<p class="text-xs">Asegúrate de que el robot ha ejecutado al menos una vez y ha encontrado servicios.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
data.keys.forEach(k => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = "hover:bg-gray-50 transition group";
|
||||
|
||||
// Generar un placeholder "slugify" (ej: "Fecha Efecto" -> "fecha_efecto")
|
||||
let placeholder = k.original.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^\w\s-]/g, '') // Quitar caracteres raros
|
||||
.replace(/\s+/g, '_'); // Espacios a guiones bajos
|
||||
|
||||
// Estilo si está ignorado
|
||||
const isIgnored = k.ignored;
|
||||
const rowOpacity = isIgnored ? "opacity-50 grayscale" : "";
|
||||
|
||||
tr.innerHTML = `
|
||||
<td class="px-6 py-4 font-medium text-gray-900 break-words ${rowOpacity}">
|
||||
${k.original}
|
||||
</td>
|
||||
<td class="px-6 py-4 ${rowOpacity}">
|
||||
<div class="text-gray-500 font-mono text-xs bg-gray-100 p-2 rounded border border-gray-200 max-w-xs truncate" title="${k.sample}">
|
||||
${k.sample}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 ${rowOpacity}">
|
||||
<input type="text"
|
||||
class="target-input w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:border-purple-500 focus:ring-2 focus:ring-purple-200 outline-none text-purple-700 font-bold placeholder-gray-300 transition"
|
||||
placeholder="${placeholder}"
|
||||
value="${k.mappedTo || ''}"
|
||||
data-original="${k.original}"
|
||||
${isIgnored ? 'disabled' : ''}>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<input type="checkbox"
|
||||
class="ignore-check w-5 h-5 text-purple-600 rounded border-gray-300 focus:ring-purple-500 cursor-pointer"
|
||||
${isIgnored ? 'checked' : ''}
|
||||
onchange="toggleRow(this)">
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="px-6 py-4 text-center text-red-500">Error cargando datos. Revisa la consola.</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
// Efecto visual al marcar "Ignorar"
|
||||
function toggleRow(checkbox) {
|
||||
const row = checkbox.closest('tr');
|
||||
const input = row.querySelector('.target-input');
|
||||
const cell1 = row.cells[0];
|
||||
const cell2 = row.cells[1];
|
||||
const cell3 = row.cells[2];
|
||||
|
||||
if (checkbox.checked) {
|
||||
input.disabled = true;
|
||||
cell1.classList.add('opacity-50', 'grayscale');
|
||||
cell2.classList.add('opacity-50', 'grayscale');
|
||||
cell3.classList.add('opacity-50', 'grayscale');
|
||||
} else {
|
||||
input.disabled = false;
|
||||
cell1.classList.remove('opacity-50', 'grayscale');
|
||||
cell2.classList.remove('opacity-50', 'grayscale');
|
||||
cell3.classList.remove('opacity-50', 'grayscale');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveMapping() {
|
||||
const provider = document.getElementById('providerSelect').value;
|
||||
const rows = document.querySelectorAll('#mappingTable tr');
|
||||
const mappings = [];
|
||||
|
||||
// Recorremos la tabla para sacar los datos
|
||||
rows.forEach(row => {
|
||||
const input = row.querySelector('.target-input');
|
||||
const checkbox = row.querySelector('.ignore-check');
|
||||
|
||||
// Solo guardamos filas válidas (que tengan input)
|
||||
if (input) {
|
||||
const original = input.getAttribute('data-original');
|
||||
const target = input.value.trim(); // Lo que escribió el usuario
|
||||
const ignored = checkbox.checked;
|
||||
|
||||
// Guardamos si: está ignorado O tiene un nombre asignado
|
||||
if (ignored || target.length > 0) {
|
||||
mappings.push({
|
||||
original: original,
|
||||
target: target,
|
||||
ignored: ignored
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mappings.length === 0) {
|
||||
showToast("No has configurado ninguna variable.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/discovery/save`, {
|
||||
method: 'POST',
|
||||
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||
body: JSON.stringify({ provider, mappings })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
showToast("✅ Configuración guardada correctamente");
|
||||
} else {
|
||||
const err = await res.json();
|
||||
showToast("Error: " + err.error, true);
|
||||
}
|
||||
} catch (e) { showToast("Error de conexión", true); }
|
||||
}
|
||||
|
||||
function showToast(msg, isError = false) {
|
||||
const t = document.getElementById('toast'), m = document.getElementById('toastMsg');
|
||||
t.className = `fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-xl transition-all duration-300 z-50 flex items-center gap-3 ${isError ? 'bg-red-600' : 'bg-slate-800'} text-white font-medium`;
|
||||
m.innerText = msg; t.classList.remove('translate-y-20', 'opacity-0');
|
||||
setTimeout(() => t.classList.add('translate-y-20', 'opacity-0'), 3000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user