1112 lines
79 KiB
HTML
1112 lines
79 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="es">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>MP Manager — Monte Providencia CRM & Control Center</title>
|
||
<!-- Google Fonts -->
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
||
<!-- FontAwesome para Iconos -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<!-- Hojas de Estilo -->
|
||
<link rel="stylesheet" href="/static/css/style.css?v=1.0.11">
|
||
</head>
|
||
<body>
|
||
<div class="app-container">
|
||
|
||
<!-- SIDEBAR DE SUCURSALES -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="brand-logo">
|
||
<i class="fa-solid fa-crown brand-icon"></i>
|
||
<h2>MP Manager</h2>
|
||
</div>
|
||
<p class="brand-subtitle">Monte Providencia</p>
|
||
</div>
|
||
|
||
<div class="search-box">
|
||
<i class="fa-solid fa-magnifying-glass"></i>
|
||
<input type="text" id="branch-search" placeholder="Buscar sucursal...">
|
||
</div>
|
||
|
||
<div class="branch-list-wrapper">
|
||
<div class="branch-section-title">Visión General</div>
|
||
<div id="global-overview-container" class="branch-list" style="margin-bottom: 12px;">
|
||
<!-- Consolidado Global va aquí -->
|
||
</div>
|
||
|
||
<div class="branch-section-title">Marca Principal</div>
|
||
<div id="brand-account-container">
|
||
<!-- Monte Providencia Marca va aquí -->
|
||
</div>
|
||
|
||
<div class="branch-section-title">Sucursales</div>
|
||
<div id="branches-container" class="branch-list">
|
||
<!-- Sucursales dinámicas van aquí -->
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- CONTENIDO PRINCIPAL -->
|
||
<main class="main-content">
|
||
|
||
<!-- HEADER GLOBAL -->
|
||
<header class="global-header">
|
||
<div class="active-info">
|
||
<span class="active-badge"><i class="fa-solid fa-store"></i></span>
|
||
<div class="active-info-content">
|
||
<h1 id="active-branch-name">Cargando...</h1>
|
||
<p id="active-branch-owner" class="text-secondary">Monte Providencia</p>
|
||
<div id="active-branch-details" class="active-info-details">
|
||
<span>Cargando cuenta activa...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="header-actions">
|
||
<!-- TOGGLE GLOBAL DE MODO (Dry-Run / Live).
|
||
Universal: aplica a TODA la plataforma (Contactos, Comparativa, Kanban, Scripts).
|
||
Verde = Dry-Run (seguro, simulación). Rojo = Live (escribe en Bucéfalo). -->
|
||
<div id="global-mode-toggle" class="global-mode-toggle is-dryrun" title="Modo Simulación (Dry-Run): las acciones se simulan sin escribir en Bucéfalo.">
|
||
<span class="global-mode-icon">
|
||
<i class="fa-solid fa-shield-halved"></i>
|
||
</span>
|
||
<div class="global-mode-text">
|
||
<span class="global-mode-label">Modo</span>
|
||
<span class="global-mode-value" id="global-mode-value">Simulación</span>
|
||
</div>
|
||
<label class="toggle-switch global-mode-switch">
|
||
<input type="checkbox" id="global-mode-checkbox">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Control de Sincronización Global -->
|
||
<div id="sync-status-bar" class="sync-status-bar hidden">
|
||
<div class="sync-progress-bar-wrapper">
|
||
<div id="sync-progress-fill" class="sync-progress-fill"></div>
|
||
</div>
|
||
<span id="sync-progress-text" class="sync-progress-text">Sync: 0/0 (sucursal)</span>
|
||
</div>
|
||
|
||
<div class="sync-dropdown">
|
||
<button id="btn-sync-global" class="btn btn-primary sync-main-btn">
|
||
<i class="fa-solid fa-rotate"></i> Sincronizar Todo
|
||
</button>
|
||
<button id="btn-sync-menu" class="btn btn-primary sync-menu-btn" aria-label="Opciones de sincronización">
|
||
<i class="fa-solid fa-chevron-down"></i>
|
||
</button>
|
||
<div id="sync-menu" class="sync-menu hidden">
|
||
<button id="btn-sync-metadata" class="sync-menu-item">
|
||
<i class="fa-solid fa-database"></i>
|
||
<span>Sincronizar Metadata</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<button id="btn-sync-active" class="btn btn-secondary">
|
||
<i class="fa-solid fa-arrows-rotate"></i> Sincronizar Sucursal
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- TABS DE NAVEGACIÓN -->
|
||
<nav class="navigation-tabs">
|
||
<button class="tab-link active" data-tab="tab-dashboard">
|
||
<i class="fa-solid fa-chart-pie"></i> Dashboard
|
||
</button>
|
||
<button class="tab-link" data-tab="tab-comparativa">
|
||
<i class="fa-solid fa-scale-balanced"></i> Comparativa Marca vs Sucursales
|
||
</button>
|
||
<button class="tab-link" data-tab="tab-contacts">
|
||
<i class="fa-solid fa-users"></i> Contactos
|
||
</button>
|
||
<button class="tab-link" data-tab="tab-opportunities">
|
||
<i class="fa-solid fa-clipboard-list"></i> Kanban Oportunidades
|
||
</button>
|
||
<button class="tab-link" data-tab="tab-workflows">
|
||
<i class="fa-solid fa-diagram-project"></i> Workflows GHL
|
||
</button>
|
||
<button class="tab-link" data-tab="tab-scripts">
|
||
<i class="fa-solid fa-terminal"></i> Scripts y Auditorías
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- SECCIONES DE TABS -->
|
||
<div class="tab-content-wrapper">
|
||
|
||
<!-- 1. DASHBOARD TAB -->
|
||
<section id="tab-dashboard" class="tab-section active">
|
||
<div class="metrics-grid">
|
||
<div class="metric-card">
|
||
<div class="metric-icon primary"><i class="fa-solid fa-user-group"></i></div>
|
||
<div class="metric-details">
|
||
<h3 id="dash-contacts-count">0</h3>
|
||
<p>Contactos Sincronizados</p>
|
||
</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-icon success"><i class="fa-solid fa-hand-holding-dollar"></i></div>
|
||
<div class="metric-details">
|
||
<h3 id="dash-won-value">$0.00</h3>
|
||
<p>Oportunidades Ganadas</p>
|
||
<span class="sub-metric" id="dash-won-count">0 oportunidades</span>
|
||
</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-icon warning"><i class="fa-solid fa-business-time"></i></div>
|
||
<div class="metric-details">
|
||
<h3 id="dash-open-value">$0.00</h3>
|
||
<p>Oportunidades Abiertas</p>
|
||
<span class="sub-metric" id="dash-open-count">0 oportunidades</span>
|
||
</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-icon danger"><i class="fa-solid fa-thumbs-down"></i></div>
|
||
<div class="metric-details">
|
||
<h3 id="dash-lost-value">$0.00</h3>
|
||
<p>Oportunidades Perdidas</p>
|
||
<span class="sub-metric" id="dash-lost-count">0 oportunidades</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel de Detalles de Sucursal (Individual) -->
|
||
<div class="dashboard-details-grid" id="dashboard-details-grid">
|
||
<div class="card panel-card">
|
||
<div class="card-header">
|
||
<h3><i class="fa-solid fa-wave-square"></i> Distribución de Estado</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="status-distribution-container" class="status-distribution-container">
|
||
<!-- Barras de distribución dinámicas -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card panel-card">
|
||
<div class="card-header">
|
||
<h3><i class="fa-solid fa-diagram-project"></i> Pipelines y Etapas</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="pipeline-overview-container" class="pipeline-overview-container">
|
||
<p class="text-secondary">Selecciona una cuenta para ver sus pipelines y etapas.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Panel de Master de Sucursales (Global) -->
|
||
<div id="global-dashboard-details" class="hidden">
|
||
<div class="card panel-card" style="height: auto; min-height: 480px;">
|
||
<div class="card-header" style="flex-wrap: wrap; gap: 16px;">
|
||
<h3><i class="fa-solid fa-list-check"></i> Master de Sucursales (Overview Global)</h3>
|
||
<div class="search-box" style="margin: 0; width: 320px; position: relative;">
|
||
<i class="fa-solid fa-magnifying-glass" style="position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 14px;"></i>
|
||
<input type="text" id="global-master-search" placeholder="Buscar sucursal..." style="width: 100%; background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-glass); border-radius: var(--radius-sm); padding: 10px 12px 10px 40px; font-size: 14px; outline: none; transition: var(--transition-fast);">
|
||
</div>
|
||
</div>
|
||
<div class="card-body" style="padding: 0;">
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 50px;">#</th>
|
||
<th>Sucursal</th>
|
||
<th>Contactos</th>
|
||
<th title="Contactos sin oportunidad asociada en esta sucursal. Clic para ver el listado.">Sin Opp <i class="fa-solid fa-circle-question" style="font-size: 11px; color: var(--text-muted);"></i></th>
|
||
<th>Oportunidades</th>
|
||
<th title="Total de pipelines disponibles en esta sucursal.">Pipelines</th>
|
||
<th>Ganado (Won)</th>
|
||
<th>Abierto (Open)</th>
|
||
<th>Última Sincronización</th>
|
||
<th style="text-align: center;">Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="global-master-rows">
|
||
<tr>
|
||
<td colspan="10" class="text-center">Cargando catálogo consolidado...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 2. COMPARATIVA MARCA vs SUCURSALES TAB -->
|
||
<section id="tab-comparativa" class="tab-section">
|
||
<div class="comparativa-header">
|
||
<div>
|
||
<h2 class="comparativa-title"><i class="fa-solid fa-scale-balanced"></i> Comparativa Marca vs Sucursales</h2>
|
||
<p class="text-secondary comparativa-subtitle">Conteo total de contactos y oportunidades de la cuenta de Marca contra la suma de todas las sucursales. Las cuentas demo se excluyen automáticamente.</p>
|
||
</div>
|
||
<div class="comparativa-actions">
|
||
<span id="comparativa-last-run" class="text-muted" style="font-size: 12px;"></span>
|
||
<button id="btn-comparativa-reload" class="btn btn-primary">
|
||
<i class="fa-solid fa-rotate"></i> Recalcular
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Banner de estado: loading / error / vacio. Solo visible cuando aplica. -->
|
||
<div id="comparativa-status-banner" class="comparativa-status-banner" style="display: none;" role="status" aria-live="polite">
|
||
<div class="comparativa-status-icon"><i class="fa-solid fa-circle-notch fa-spin"></i></div>
|
||
<div class="comparativa-status-body">
|
||
<div class="comparativa-status-title" id="comparativa-status-title">Calculando comparativa…</div>
|
||
<div class="comparativa-status-msg" id="comparativa-status-msg">Esto puede tomar unos segundos en bases grandes.</div>
|
||
</div>
|
||
<button id="comparativa-status-retry" class="btn btn-secondary btn-sm" style="display: none;">
|
||
<i class="fa-solid fa-rotate"></i> Reintentar
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tarjetas de totales -->
|
||
<div class="comparativa-totals-grid">
|
||
<div class="card comparativa-total-card">
|
||
<div class="comparativa-total-label">Contactos en Marca</div>
|
||
<div class="comparativa-total-value" id="comp-brand-contacts">—</div>
|
||
<div class="comparativa-total-sub" id="comp-brand-name">Cargando...</div>
|
||
</div>
|
||
<div class="card comparativa-total-card">
|
||
<div class="comparativa-total-label">Contactos en Sucursales (suma)</div>
|
||
<div class="comparativa-total-value" id="comp-branches-contacts">—</div>
|
||
<div class="comparativa-total-sub" id="comp-branches-count">—</div>
|
||
</div>
|
||
<div class="card comparativa-total-card comparativa-diff-card" id="comp-contacts-diff-card">
|
||
<div class="comparativa-total-label">Diferencia Contactos</div>
|
||
<div class="comparativa-total-value" id="comp-contacts-diff">—</div>
|
||
<div class="comparativa-total-sub" id="comp-contacts-diff-status">Marca − Sucursales</div>
|
||
</div>
|
||
<div class="card comparativa-total-card">
|
||
<div class="comparativa-total-label">Oportunidades en Marca</div>
|
||
<div class="comparativa-total-value" id="comp-brand-opps">—</div>
|
||
<div class="comparativa-total-sub"> </div>
|
||
</div>
|
||
<div class="card comparativa-total-card">
|
||
<div class="comparativa-total-label">Oportunidades en Sucursales (suma)</div>
|
||
<div class="comparativa-total-value" id="comp-branches-opps">—</div>
|
||
<div class="comparativa-total-sub"> </div>
|
||
</div>
|
||
<div class="card comparativa-total-card comparativa-diff-card" id="comp-opps-diff-card">
|
||
<div class="comparativa-total-label">Diferencia Oportunidades</div>
|
||
<div class="comparativa-total-value" id="comp-opps-diff">—</div>
|
||
<div class="comparativa-total-sub" id="comp-opps-diff-status">Marca − Sucursales</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cuentas demo excluidas -->
|
||
<div class="card comparativa-info-card" id="comp-demos-card" style="display: none;">
|
||
<div class="card-header">
|
||
<h3><i class="fa-solid fa-eye-slash"></i> Cuentas demo excluidas</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<p class="text-secondary" style="margin: 0 0 8px 0; font-size: 13px;">Estas cuentas no participan en el conteo porque su nombre contiene "demo":</p>
|
||
<ul id="comp-demos-list" class="comparativa-demos-list"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Resumen de huecos -->
|
||
<div class="comparativa-buckets-grid">
|
||
<div class="card comparativa-bucket-card" data-bucket="missing_in_brand">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-user-large-slash"></i> Contactos en sucursal sin contraparte en Marca</h4>
|
||
<p class="text-secondary">Existen físicamente en una sucursal pero no se replicaron a Marca.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-missing_in_brand">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="missing_in_brand">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="missing_in_brand">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-sync-missing-contacts" class="btn btn-primary btn-sm" type="button" title="Sincroniza estos contactos hacia la cuenta de Marca: double-check por teléfono → email → nombre. Si ya existe en Marca (falso positivo del audit), se omite. Si no, se crea con todos los datos y custom fields mapeados.">
|
||
<i class="fa-solid fa-user-plus"></i> Sincronizar a Marca
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-missing_in_brand" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="missing_in_assigned_branch">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-map-pin"></i> Contactos en Marca ausentes de su sucursal asignada</h4>
|
||
<p class="text-secondary">El campo TIENDA del contacto apunta a una sucursal donde no aparece físicamente <strong>y tampoco existe en ninguna otra sucursal</strong>.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-missing_in_assigned_branch">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="missing_in_assigned_branch">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="missing_in_assigned_branch">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-sync-missing-in-assigned-branch" class="btn btn-primary btn-sm" type="button" title="Selecciona los contactos del listado y crea sus duplicados en la sucursal asignada (resuelta por TIENDA + verificador). Antes de crear, doble-check por teléfono → email → nombre en la sucursal destino y globalmente.">
|
||
<i class="fa-solid fa-user-plus"></i> Crear seleccionados en sucursal
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-missing_in_assigned_branch" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="present_in_other_branch_not_assigned">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-shuffle"></i> Contactos Marca en sucursal incorrecta</h4>
|
||
<p class="text-secondary">El campo TIENDA del contacto apunta a una sucursal, pero el contacto realmente está en OTRA. Usa el botón para corregir la TIENDA del contacto Marca.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-present_in_other_branch_not_assigned">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="present_in_other_branch_not_assigned">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="present_in_other_branch_not_assigned">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-present_in_other_branch_not_assigned" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="probable_duplicate_in_brand">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-clone"></i> Probables duplicados en Marca</h4>
|
||
<p class="text-secondary">El contacto en Marca tiene teléfono/email, pero <strong>en la sucursal asignada existe un homónimo</strong> (mismo nombre) con teléfono/email distinto. Lo más probable: el bueno está en sucursal, el de Marca es un duplicado con otro número.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-probable_duplicate_in_brand">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="probable_duplicate_in_brand">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="probable_duplicate_in_brand">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-probable_duplicate_in_brand" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="brand_not_in_any_branch">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-circle-question"></i> Contactos en Marca sin contraparte en NINGUNA sucursal</h4>
|
||
<p class="text-secondary">Leads de Marca (imports, formularios globales) que nunca se materializaron en una sucursal.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-brand_not_in_any_branch">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="brand_not_in_any_branch">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="brand_not_in_any_branch">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-sync-brand-to-branch" class="btn btn-primary btn-sm" type="button" title="Crea el contacto en su sucursal correspondiente (resuelta por TIENDA + verificador). Solo procesa contactos con TIENDA poblada. Double-check en la sucursal por teléfono → email → nombre antes de crear.">
|
||
<i class="fa-solid fa-arrow-right-arrow-left"></i> Sincronizar a Sucursales
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-brand_not_in_any_branch" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="brand_without_tienda">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-tag"></i> Contactos en Marca sin TIENDA poblada</h4>
|
||
<p class="text-secondary">El campo TIENDA está vacío, por lo que no se puede asignar a una sucursal.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-brand_without_tienda">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="brand_without_tienda">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="brand_without_tienda">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-fix-tienda-from-sucursal" class="btn btn-primary btn-sm" type="button" title="Llena el campo TIENDA en Marca a partir del campo Sucursal (resuelto via verificador). Solo procesa contactos cuyo Sucursal mapea inequivocamente a una sucursal con TIENDA conocida.">
|
||
<i class="fa-solid fa-wand-magic-sparkles"></i> Llenar TIENDA desde Sucursal
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-brand_without_tienda" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="brand_unknown_tienda">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-triangle-exclamation"></i> Contactos en Marca con TIENDA desconocida</h4>
|
||
<p class="text-secondary">El valor TIENDA no coincide con ninguna fila del verificador de sucursales.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-brand_unknown_tienda">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="brand_unknown_tienda">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="brand_unknown_tienda">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-brand_unknown_tienda" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="missing_opps_in_brand">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-clipboard-question"></i> Oportunidades en sucursal sin réplica en Marca</h4>
|
||
<p class="text-secondary">Opps que existen en sucursal pero no se replicaron al contacto en Marca.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-missing_opps_in_brand">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="missing_opps_in_brand">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="missing_opps_in_brand">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-sync-missing-opps" class="btn btn-primary btn-sm" type="button" title="Sincroniza estas oportunidades hacia la cuenta de Marca: busca el contacto por teléfono → email → nombre, crea el contacto si no existe, espera 10s y crea/actualiza la opp en Marca.">
|
||
<i class="fa-solid fa-rotate"></i> Sincronizar a Marca
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-missing_opps_in_brand" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="contacts_missing_id_field">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-id-card"></i> Contactos sin ID Contacto Sucursal</h4>
|
||
<p class="text-secondary">Contactos cuyo campo de enlace está vacío o con id de longitud inválida (debe ser 20 caracteres). En sucursales se puede llenar automáticamente (= id nativo del contacto). En Marca aparece como aviso — su campo se resuelve por matcheo/sync.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-contacts_missing_id_field">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="contacts_missing_id_field">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="contacts_missing_id_field">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-fill-contact-id-sucursal" class="btn btn-primary btn-sm" type="button" title="Llena el campo 'ID Contacto Sucursal' con el id nativo de cada contacto. SOLO sucursales (Marca queda intacta).">
|
||
<i class="fa-solid fa-wand-magic-sparkles"></i> Llenar campo (sucursales)
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-contacts_missing_id_field" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="opps_missing_id_field">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-fingerprint"></i> Oportunidades sin ID Oportunidad Sucursal</h4>
|
||
<p class="text-secondary">Opps cuyo campo de enlace está vacío o con id de longitud inválida (debe ser 20 caracteres). En sucursales se puede llenar automáticamente (= id nativo de la opp). En Marca aparece como aviso — su campo se resuelve por matcheo/sync.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-opps_missing_id_field">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="opps_missing_id_field">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="opps_missing_id_field">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-fill-opp-id-sucursal" class="btn btn-primary btn-sm" type="button" title="Llena el campo 'ID Oportunidad Sucursal' con el id nativo de cada opp. SOLO sucursales (Marca queda intacta).">
|
||
<i class="fa-solid fa-wand-magic-sparkles"></i> Llenar campo (sucursales)
|
||
</button>
|
||
<button id="btn-match-brand-opp-id-sucursal" class="btn btn-primary btn-sm" type="button" title="Matchea cada opp de Marca con su opp de sucursal y rellena el campo en Marca con el id de la opp de sucursal. SOLO Marca.">
|
||
<i class="fa-solid fa-arrows-left-right-to-line"></i> Matchear Marca con sucursales
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-opps_missing_id_field" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="brand_duplicate_link_opps">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-clone"></i> Réplicas duplicadas en Marca (mismo ID Opp Sucursal)</h4>
|
||
<p class="text-secondary">Dos o más opps de Marca que apuntan a la MISMA opp de sucursal: réplicas duplicadas (causa típica del descuadre positivo Marca>Sucursales). Se conserva la canónica y se eliminan las sobrantes. Reversible por run_id.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-brand_duplicate_link_opps">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="brand_duplicate_link_opps">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="brand_duplicate_link_opps">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
<button id="btn-cleanup-duplicate-opps" class="btn btn-primary btn-sm" type="button" title="Elimina las réplicas duplicadas sobrantes en Marca conservando la canónica (jerarquía: valor → status → más antigua). Default simulación; reversible por run_id.">
|
||
<i class="fa-solid fa-broom"></i> Limpiar duplicados
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-brand_duplicate_link_opps" hidden></div>
|
||
</div>
|
||
|
||
<div class="card comparativa-bucket-card" data-bucket="intra_brand_duplicates">
|
||
<div class="comparativa-bucket-header">
|
||
<div>
|
||
<h4><i class="fa-solid fa-people-arrows"></i> Duplicados intra-Marca (mismo nombre, sin teléfono ni email)</h4>
|
||
<p class="text-secondary">Grupos de contactos en Marca con el mismo nombre y sin identificadores fuertes. Imposibles de distinguir automáticamente — revísalos manualmente desde Bucéfalo y elimina los duplicados con el botón rojo.</p>
|
||
</div>
|
||
<span class="comparativa-bucket-badge" id="comp-bucket-intra_brand_duplicates">0</span>
|
||
</div>
|
||
<div class="comparativa-bucket-actions">
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-toggle" data-bucket="intra_brand_duplicates">
|
||
<i class="fa-solid fa-list"></i> Ver listado
|
||
</button>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="intra_brand_duplicates">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
</div>
|
||
<div class="comparativa-bucket-body" id="comp-body-intra_brand_duplicates" hidden></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal de sync -->
|
||
<div id="sync-missing-opps-modal" class="modal-overlay" hidden>
|
||
<div class="modal-dialog">
|
||
<div class="modal-header">
|
||
<h3 id="sync-modal-title"><i class="fa-solid fa-rotate"></i> Sincronizar opps faltantes a Marca</h3>
|
||
<button class="modal-close" id="sync-modal-close" aria-label="Cerrar"><i class="fa-solid fa-xmark"></i></button>
|
||
</div>
|
||
<div class="modal-body" id="sync-modal-body">
|
||
<p class="text-secondary">Cargando plan...</p>
|
||
</div>
|
||
<div class="modal-footer" id="sync-modal-footer">
|
||
<button id="sync-modal-cancel" class="btn btn-secondary">Cancelar</button>
|
||
<button id="sync-modal-apply" class="btn btn-primary" hidden>
|
||
<i class="fa-solid fa-check"></i> Aplicar cambios en GHL
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Desglose por sucursal -->
|
||
<div class="card comparativa-per-branch-card">
|
||
<div class="card-header">
|
||
<h3><i class="fa-solid fa-list-check"></i> Desglose por sucursal</h3>
|
||
<button class="btn btn-secondary btn-sm btn-comparativa-export" data-bucket="per_branch">
|
||
<i class="fa-solid fa-file-csv"></i> Exportar CSV
|
||
</button>
|
||
</div>
|
||
<div class="card-body" style="padding: 0;">
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 50px;">#</th>
|
||
<th>Sucursal</th>
|
||
<th style="text-align: right;">Contactos</th>
|
||
<th style="text-align: right;">Oportunidades</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="comp-per-branch-rows">
|
||
<tr><td colspan="4" class="text-center">Cargando...</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 2. CONTACTOS TAB -->
|
||
<section id="tab-contacts" class="tab-section">
|
||
<div class="toolbar" style="gap: 12px; flex-wrap: wrap; align-items: center;">
|
||
<div class="search-box">
|
||
<i class="fa-solid fa-magnifying-glass"></i>
|
||
<input type="text" id="contact-table-search" placeholder="Filtrar contactos locales...">
|
||
</div>
|
||
<button id="btn-filter-without-opp" class="btn btn-secondary btn-sm" type="button" title="Mostrar solo contactos que no tienen oportunidad asociada en esta sucursal." aria-pressed="false">
|
||
<i class="fa-solid fa-user-slash"></i> Solo sin oportunidad
|
||
</button>
|
||
<button id="btn-bulk-create-opps" class="btn btn-primary btn-sm" type="button" style="display: none;" title="Crea oportunidades para todos los contactos sin oportunidad de esta sucursal en una sola operación. Cada cambio queda registrado en auditoría con un run_id reversible.">
|
||
<i class="fa-solid fa-layer-group"></i> Crear oportunidades para todos
|
||
</button>
|
||
<span id="contacts-filter-status" class="text-muted" style="font-size: 12px;"></span>
|
||
</div>
|
||
|
||
<div class="card table-card">
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Nombre</th>
|
||
<th>Teléfono</th>
|
||
<th>Email</th>
|
||
<th title="Campo nativo de Bucéfalo (source): origen del contacto — formulario, Facebook, sucursal, etc.">Fuente</th>
|
||
<th>Etiquetas</th>
|
||
<th title="Custom field del contacto: Marca del Vehículo">Marca</th>
|
||
<th title="Custom field del contacto: Versión del Vehículo">Versión</th>
|
||
<th title="Custom field del contacto: Año del Vehículo">Año</th>
|
||
<th>Fecha Añadido</th>
|
||
<th id="contacts-action-header" class="contacts-action-col" style="display: none;">Acción</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="contacts-table-rows">
|
||
<tr>
|
||
<td colspan="9" class="text-center">Selecciona una sucursal para ver los contactos.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pagination-bar">
|
||
<span id="pagination-info">Mostrando 0 - 0 de 0</span>
|
||
<div class="pagination-buttons">
|
||
<button id="btn-page-prev" class="btn btn-secondary btn-sm" disabled><i class="fa-solid fa-chevron-left"></i> Anterior</button>
|
||
<button id="btn-page-next" class="btn btn-secondary btn-sm" disabled>Siguiente <i class="fa-solid fa-chevron-right"></i></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 3. KANBAN TAB (OPORTUNIDADES) -->
|
||
<section id="tab-opportunities" class="tab-section">
|
||
<div class="toolbar">
|
||
<div class="pipeline-selector-wrapper">
|
||
<label for="pipeline-select"><i class="fa-solid fa-filter"></i> Seleccionar Pipeline:</label>
|
||
<select id="pipeline-select" class="form-control">
|
||
<option value="">-- Cargar pipelines --</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="kanban-board" class="kanban-board">
|
||
<div class="board-empty-state">
|
||
<i class="fa-solid fa-folder-open"></i>
|
||
<p>No se encontraron pipelines ni oportunidades para esta cuenta.</p>
|
||
<span class="text-secondary">Asegúrate de sincronizar la sucursal para cargar sus datos desde GHL.</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 4. WORKFLOWS TAB -->
|
||
<section id="tab-workflows" class="tab-section">
|
||
<div class="toolbar" style="gap: 16px; flex-wrap: wrap;">
|
||
<div class="search-box">
|
||
<i class="fa-solid fa-magnifying-glass"></i>
|
||
<input type="text" id="workflow-table-search" placeholder="Buscar workflows por nombre, ID o trigger...">
|
||
</div>
|
||
|
||
<div class="pipeline-selector-wrapper" style="margin: 0;">
|
||
<label for="workflow-branch-select"><i class="fa-solid fa-store"></i> Cuenta/Sucursal:</label>
|
||
<select id="workflow-branch-select" class="form-control">
|
||
<option value="all">-- Todas las sucursales --</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="pipeline-selector-wrapper" style="margin: 0;">
|
||
<label for="workflow-status-select"><i class="fa-solid fa-toggle-on"></i> Estado:</label>
|
||
<select id="workflow-status-select" class="form-control">
|
||
<option value="all">-- Todos los estados --</option>
|
||
<option value="active">Activo</option>
|
||
<option value="inactive">Pausado</option>
|
||
<option value="draft">Borrador</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="pipeline-selector-wrapper" style="margin: 0;">
|
||
<label for="workflow-date-field"><i class="fa-solid fa-calendar-days"></i> Filtrar por:</label>
|
||
<select id="workflow-date-field" class="form-control">
|
||
<option value="created_at">Fecha de Creación</option>
|
||
<option value="updated_at">Fecha de Modificación</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="pipeline-selector-wrapper" style="margin: 0;">
|
||
<label for="workflow-date-after"><i class="fa-solid fa-arrow-right"></i> Después de:</label>
|
||
<input type="datetime-local" id="workflow-date-after" class="form-control" step="60">
|
||
</div>
|
||
|
||
<div class="pipeline-selector-wrapper" style="margin: 0;">
|
||
<label for="workflow-date-before"><i class="fa-solid fa-arrow-left"></i> Antes de:</label>
|
||
<input type="datetime-local" id="workflow-date-before" class="form-control" step="60">
|
||
</div>
|
||
|
||
<button id="btn-workflow-clear-dates" class="btn btn-secondary" title="Limpiar filtros de fecha" style="padding: 6px 12px;">
|
||
<i class="fa-solid fa-xmark"></i> Limpiar fechas
|
||
</button>
|
||
|
||
<div style="margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; align-items: center;">
|
||
<span id="workflow-session-status" class="text-secondary" style="font-size: 12px;"></span>
|
||
<button id="btn-refresh-session" class="btn btn-secondary" title="Abre una ventana del navegador para renovar las cookies de Bucéfalo. Necesario para acciones de toggle / renombrar / eliminar workflows.">
|
||
<i class="fa-solid fa-key"></i> Renovar sesión Bucéfalo
|
||
</button>
|
||
<button id="btn-export-workflows" class="btn btn-secondary">
|
||
<i class="fa-solid fa-file-excel"></i> Exportar Excel
|
||
</button>
|
||
<button id="btn-sync-workflows" class="btn btn-secondary">
|
||
<i class="fa-solid fa-arrows-rotate"></i> Sincronizar Workflows
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="workflows-bulk-bar" class="hidden" style="padding:12px 16px; margin-bottom:8px; background: rgba(124, 58, 237, 0.08); border:1px solid rgba(124, 58, 237, 0.25); border-radius:8px;">
|
||
<div id="workflows-bulk-actions" style="display:flex; gap:12px; align-items:center;">
|
||
<span id="workflows-bulk-count" style="font-weight:bold;">0 seleccionados</span>
|
||
<button id="btn-bulk-publish" class="btn btn-secondary" title="Activar (Publicar) todos los seleccionados">
|
||
<i class="fa-solid fa-toggle-on" style="color:#34D399;"></i> Publicar
|
||
</button>
|
||
<button id="btn-bulk-draft" class="btn btn-secondary" title="Poner todos los seleccionados en Borrador">
|
||
<i class="fa-solid fa-toggle-off"></i> Poner en Borrador
|
||
</button>
|
||
<button id="btn-bulk-delete" class="btn btn-secondary" title="Eliminar todos los seleccionados (irreversible)" style="border-color: rgba(248, 113, 113, 0.4);">
|
||
<i class="fa-solid fa-trash-can" style="color:#F87171;"></i> Eliminar
|
||
</button>
|
||
<button id="btn-bulk-scan-anomalies" class="btn btn-secondary" title="Escanear anomalías en los nodos de los workflows seleccionados (read-only)">
|
||
<i class="fa-solid fa-stethoscope" style="color:#F59E0B;"></i> Escanear anomalías
|
||
</button>
|
||
<button id="btn-bulk-clear" class="btn btn-secondary" title="Limpiar selección">
|
||
<i class="fa-solid fa-xmark"></i> Limpiar selección
|
||
</button>
|
||
</div>
|
||
<div id="workflows-bulk-progress" class="hidden" style="display:flex; flex-direction:column; gap:8px;">
|
||
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
|
||
<span id="bulk-progress-title" style="font-weight:bold;">
|
||
<i class="fa-solid fa-spinner fa-spin"></i> Procesando...
|
||
</span>
|
||
<div style="display:flex; align-items:center; gap:10px;">
|
||
<span id="bulk-progress-counter" style="font-family:monospace;">0/0</span>
|
||
<button id="btn-bulk-cancel" class="btn btn-secondary hidden" title="Detener el escaneo en curso" style="padding:4px 10px; font-size:12px; border-color: rgba(248, 113, 113, 0.4);">
|
||
<i class="fa-solid fa-stop" style="color:#F87171;"></i> Cancelar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div style="width:100%; height:8px; background: rgba(255,255,255,0.08); border-radius:4px; overflow:hidden;">
|
||
<div id="bulk-progress-fill" style="height:100%; width:0%; background: linear-gradient(90deg, #7C3AED, #60A5FA); transition: width 0.4s ease;"></div>
|
||
</div>
|
||
<div id="bulk-progress-current" style="font-size: 13px; color: var(--text-muted);">
|
||
Esperando logs del proceso...
|
||
</div>
|
||
<div id="bulk-progress-summary" class="hidden" style="display:flex; gap:16px; flex-wrap:wrap; align-items:center; font-size: 13px;">
|
||
<span id="bulk-progress-success-count" style="color:#34D399;"></span>
|
||
<span id="bulk-progress-skipped-count" style="color: var(--text-muted);"></span>
|
||
<span id="bulk-progress-failed-count" style="color:#F87171;"></span>
|
||
<span id="bulk-progress-anomalies-count" class="hidden" style="color:#F59E0B; font-weight:bold;"></span>
|
||
<button id="btn-bulk-retry-pending" class="btn btn-primary hidden" style="margin-left:auto;" title="Reintentar los workflows que no llegaron a procesarse">
|
||
<i class="fa-solid fa-rotate-right"></i> Reintentar pendientes (<span id="bulk-retry-count">0</span>)
|
||
</button>
|
||
<button id="btn-bulk-download-report" class="btn btn-secondary hidden" title="Descargar el CSV con los hallazgos">
|
||
<i class="fa-solid fa-file-csv"></i> Descargar CSV
|
||
</button>
|
||
<button id="btn-bulk-download-xlsx" class="btn btn-secondary hidden" title="Descargar Excel con los hallazgos coloreados por tipo">
|
||
<i class="fa-solid fa-file-excel" style="color:#34D399;"></i> Descargar Excel
|
||
</button>
|
||
<button id="btn-bulk-close-progress" class="btn btn-secondary">
|
||
<i class="fa-solid fa-check"></i> Cerrar
|
||
</button>
|
||
</div>
|
||
<div id="bulk-anomalies-panel" class="hidden" style="margin-top:8px; padding:12px; background: rgba(245, 158, 11, 0.08); border:1px solid rgba(245, 158, 11, 0.35); border-radius:8px;">
|
||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:8px;">
|
||
<i class="fa-solid fa-triangle-exclamation" style="color:#F59E0B;"></i>
|
||
<strong id="bulk-anomalies-title" style="color:#F59E0B;">Anomalías detectadas</strong>
|
||
<button id="btn-bulk-anomalies-toggle" class="btn btn-secondary" style="margin-left:auto; padding:2px 10px; font-size: 12px;" title="Mostrar/ocultar lista">
|
||
<i class="fa-solid fa-chevron-down"></i> <span id="bulk-anomalies-toggle-label">Ver detalle</span>
|
||
</button>
|
||
</div>
|
||
<div id="bulk-anomalies-list" class="hidden" style="max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card table-card">
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:36px; text-align:center;">
|
||
<input type="checkbox" id="workflows-select-all" title="Seleccionar todos los visibles">
|
||
</th>
|
||
<th class="workflow-sortable" data-sort-field="account_name">Cuenta/Sucursal <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="id">ID Workflow <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="name">Nombre del Workflow <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="status">Estado <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="trigger">Trigger <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="created_at">Fecha de Creación <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="updated_at">Fecha de Modificación <span class="sort-indicator"></span></th>
|
||
<th class="workflow-sortable" data-sort-field="synced_at">Sincronizado en Local <span class="sort-indicator"></span></th>
|
||
<th style="text-align: center;">Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="workflows-table-rows">
|
||
<tr>
|
||
<td colspan="10" class="text-center">Cargando workflows...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 5. SCRIPTS TAB (CENTRO DE SCRIPTS) -->
|
||
<section id="tab-scripts" class="tab-section">
|
||
<div class="scripts-layout-grid">
|
||
|
||
<!-- Columna Izquierda: Lista de Scripts -->
|
||
<div class="scripts-catalog-column">
|
||
<div class="card scripts-card">
|
||
<div class="card-header">
|
||
<h3><i class="fa-solid fa-cubes"></i> Scripts Disponibles</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="script-search-box">
|
||
<i class="fa-solid fa-magnifying-glass"></i>
|
||
<input type="search" id="script-search-input" placeholder="Buscar por nombre, descripción, categoría o flag...">
|
||
</div>
|
||
<div class="script-category-tabs">
|
||
<button class="btn-category active" data-category="all">Todos</button>
|
||
<button class="btn-category" data-category="Búsqueda y Análisis">Búsqueda</button>
|
||
<button class="btn-category" data-category="Fix y Corrección">Fixes</button>
|
||
<button class="btn-category" data-category="Auditorías">Auditorías</button>
|
||
<button class="btn-category" data-category="Cron Jobs">Crons</button>
|
||
<button class="btn-category" data-category="Sin registrar">Sin registrar</button>
|
||
</div>
|
||
|
||
<div id="scripts-list" class="scripts-list">
|
||
<!-- Tarjetas de scripts dinámicas -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Columna Derecha: Consola Terminal SSE -->
|
||
<div class="scripts-terminal-column">
|
||
<div class="card terminal-card">
|
||
<div class="card-header terminal-header">
|
||
<div class="terminal-title">
|
||
<span class="terminal-dot red"></span>
|
||
<span class="terminal-dot yellow"></span>
|
||
<span class="terminal-dot green"></span>
|
||
<h3><i class="fa-solid fa-code"></i> Terminal de Salida en Vivo</h3>
|
||
<span id="terminal-status-badge" class="terminal-status-badge idle">Listo</span>
|
||
</div>
|
||
<div class="terminal-actions">
|
||
<div class="universal-dryrun-toggle" id="universal-dryrun-wrapper" title="Modo Simulación (Dry-Run Seguro)">
|
||
<span class="toggle-label">Simular (Dry-Run)</span>
|
||
<label class="toggle-switch">
|
||
<input type="checkbox" id="universal-dryrun-checkbox" checked>
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<button id="btn-copy-terminal" class="btn btn-secondary btn-sm">
|
||
<i class="fa-solid fa-copy"></i> Copiar
|
||
</button>
|
||
<button id="btn-clear-terminal" class="btn btn-secondary btn-sm">
|
||
<i class="fa-solid fa-trash-can"></i> Limpiar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<nav class="terminal-tabs" id="terminal-tabs">
|
||
<button class="terminal-tab active" data-term-tab="output" type="button">
|
||
<i class="fa-solid fa-terminal"></i> Salida en Vivo
|
||
</button>
|
||
<button class="terminal-tab" data-term-tab="errors" type="button">
|
||
<i class="fa-solid fa-triangle-exclamation"></i> Errores
|
||
<span class="terminal-error-count" id="terminal-error-count">0</span>
|
||
</button>
|
||
</nav>
|
||
<!-- Barra sticky de controles del run activo. Vive fuera de #terminal-screen
|
||
para que no se mezcle con los logs ni se trunque por el cap del buffer. -->
|
||
<div class="terminal-run-bar hidden" id="terminal-run-bar">
|
||
<div class="terminal-run-bar-stats" id="terminal-run-stats" title="Líneas visibles · líneas descartadas">
|
||
<i class="fa-solid fa-list"></i>
|
||
<span id="terminal-run-counter">0 líneas</span>
|
||
</div>
|
||
<div class="terminal-run-bar-controls" id="terminal-run-controls" data-task-id="">
|
||
<!-- Botones Pausar / Reanudar / Detener / Revertir se insertan dinámicamente. -->
|
||
</div>
|
||
<div class="terminal-run-bar-actions">
|
||
<button id="btn-download-log" class="btn btn-secondary btn-sm hidden" title="Descargar log completo del run">
|
||
<i class="fa-solid fa-download"></i> Descargar log
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="terminal-body" id="terminal-screen" data-term-panel="output">
|
||
<div class="terminal-placeholder">
|
||
<i class="fa-solid fa-terminal"></i>
|
||
<p>Centro de Control de Scripts Python</p>
|
||
<span>Selecciona un script y haz clic en "Ejecutar" para ver la salida en tiempo real.</span>
|
||
</div>
|
||
</div>
|
||
<!-- Badge flotante que aparece cuando el usuario hizo scroll arriba durante un run. -->
|
||
<button id="terminal-resume-scroll" class="terminal-resume-scroll hidden" type="button" title="Volver al final del log">
|
||
<i class="fa-solid fa-arrow-down"></i> Volver al final
|
||
</button>
|
||
<div class="terminal-body terminal-errors-panel hidden" id="terminal-errors" data-term-panel="errors">
|
||
<div class="terminal-errors-summary" id="terminal-errors-summary">
|
||
<div class="terminal-errors-summary-main">
|
||
<strong id="terminal-errors-total">0</strong>
|
||
<span>errores registrados en esta sesión</span>
|
||
</div>
|
||
<div class="terminal-errors-summary-meta" id="terminal-errors-breakdown">
|
||
Sin scripts con fallos.
|
||
</div>
|
||
</div>
|
||
<div class="terminal-errors-list" id="terminal-errors-list">
|
||
<div class="terminal-placeholder">
|
||
<i class="fa-solid fa-shield-halved"></i>
|
||
<p>Sin errores registrados</p>
|
||
<span>Los errores detectados durante la ejecución aparecerán aquí con su script, Task ID y log.</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- MODALES / NOTIFICACIONES TOAST -->
|
||
<div id="toast-container" class="toast-container"></div>
|
||
|
||
<!-- Modal de Información de Cuenta, Pipelines y Fases -->
|
||
<div id="modal-location-info" class="modal-backdrop hidden">
|
||
<div class="modal-card" style="width: 650px; max-width: 95%; max-height: 85vh; display: flex; flex-direction: column;">
|
||
<div class="modal-header">
|
||
<h3><i class="fa-solid fa-circle-info"></i> Detalles de la Cuenta y Pipelines</h3>
|
||
<button class="btn-close-modal" onclick="closeLocationInfoModal()">×</button>
|
||
</div>
|
||
<div class="modal-body" style="overflow-y: auto; flex: 1; padding: 24px; display: flex; flex-direction: column; gap: 20px;">
|
||
<!-- Loader -->
|
||
<div id="location-info-loader" class="text-center" style="padding: 40px 0;">
|
||
<i class="fa-solid fa-circle-notch fa-spin fa-2x" style="color: var(--color-primary);"></i>
|
||
<p style="margin-top: 12px; color: var(--text-muted);">Cargando detalles de la cuenta y pipelines...</p>
|
||
</div>
|
||
|
||
<!-- Contenido -->
|
||
<div id="location-info-content" class="hidden" style="display: flex; flex-direction: column; gap: 24px;">
|
||
<!-- Bloque de Metadata de Cuenta -->
|
||
<div>
|
||
<h4 style="color: var(--text-primary); margin-bottom: 12px; border-bottom: 1px solid var(--border-glass); padding-bottom: 8px; display: flex; align-items: center; gap: 8px;">
|
||
<i class="fa-solid fa-store" style="color: var(--color-primary-glow);"></i> Datos de GHL (Get Location)
|
||
</h4>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 14px;">
|
||
<div>
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Nombre Comercial</span>
|
||
<strong id="loc-info-name">-</strong>
|
||
</div>
|
||
<div>
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Location ID</span>
|
||
<code id="loc-info-id">-</code>
|
||
</div>
|
||
<div>
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Dueño de la Compañía</span>
|
||
<span id="loc-info-owner">-</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Teléfono</span>
|
||
<span id="loc-info-phone">-</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Email</span>
|
||
<span id="loc-info-email">-</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Zona Horaria</span>
|
||
<span id="loc-info-timezone">-</span>
|
||
</div>
|
||
<div style="grid-column: span 2;">
|
||
<span class="text-secondary" style="display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted);">Dirección Completa</span>
|
||
<span id="loc-info-address">-</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bloque de Pipelines y Fases -->
|
||
<div>
|
||
<h4 style="color: var(--text-primary); margin-bottom: 12px; border-bottom: 1px solid var(--border-glass); padding-bottom: 8px; display: flex; align-items: center; gap: 8px;">
|
||
<i class="fa-solid fa-diagram-project" style="color: var(--color-success-glow);"></i> Pipelines y Fases Existentes
|
||
</h4>
|
||
<div id="location-pipelines-container" style="display: flex; flex-direction: column; gap: 16px;">
|
||
<!-- Pipelines se renderizarán aquí -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer" style="padding: 16px 24px; border-top: 1px solid var(--border-glass); display: flex; justify-content: flex-end;">
|
||
<button class="btn btn-secondary" onclick="closeLocationInfoModal()">Cerrar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal de Renombrar Workflow -->
|
||
<div id="modal-rename-workflow" class="modal-backdrop hidden">
|
||
<div class="modal-card">
|
||
<div class="modal-header">
|
||
<h3><i class="fa-solid fa-pen-to-square"></i> Renombrar Workflow</h3>
|
||
<button class="btn-close-modal" onclick="closeRenameWorkflowModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="rename-wf-id">
|
||
<input type="hidden" id="rename-wf-location">
|
||
<div class="form-group" style="display: flex; flex-direction: column; gap: 8px;">
|
||
<label for="rename-wf-input">Nuevo Nombre del Workflow:</label>
|
||
<input type="text" id="rename-wf-input" class="form-control" placeholder="Escribe el nuevo nombre..." style="width: 100%; box-sizing: border-box;">
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="closeRenameWorkflowModal()">Cancelar</button>
|
||
<button id="btn-save-rename-workflow" class="btn btn-primary" onclick="submitRenameWorkflow()">Guardar Cambios</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal de Metadata de Script -->
|
||
<div id="modal-script-metadata" class="modal-backdrop hidden">
|
||
<div class="modal-card script-editor-modal">
|
||
<div class="modal-header">
|
||
<h3><i class="fa-solid fa-tags"></i> Editar Metadata del Script</h3>
|
||
<button class="btn-close-modal" onclick="closeScriptMetadataModal()">×</button>
|
||
</div>
|
||
<div class="modal-body script-editor-body">
|
||
<input type="hidden" id="script-metadata-name">
|
||
<div class="script-form-grid">
|
||
<label>Título
|
||
<input type="text" id="script-meta-title" class="form-control">
|
||
</label>
|
||
<label>Categoría
|
||
<select id="script-meta-category" class="form-control">
|
||
<option value="Búsqueda y Análisis">Búsqueda y Análisis</option>
|
||
<option value="Fix y Corrección">Fix y Corrección</option>
|
||
<option value="Auditorías">Auditorías</option>
|
||
<option value="Cron Jobs">Cron Jobs</option>
|
||
<option value="Sin registrar">Sin registrar</option>
|
||
</select>
|
||
</label>
|
||
<label class="script-form-full">Descripción
|
||
<textarea id="script-meta-description" class="form-control" rows="4"></textarea>
|
||
</label>
|
||
<label>Placeholder de argumentos
|
||
<input type="text" id="script-meta-args" class="form-control">
|
||
</label>
|
||
<label>Modo dry-run
|
||
<select id="script-meta-dry-run-mode" class="form-control">
|
||
<option value="">Ninguno</option>
|
||
<option value="dry_run_flag">Usa --dry-run</option>
|
||
<option value="apply_flag">Usa --apply</option>
|
||
</select>
|
||
</label>
|
||
<label class="script-check-row"><input type="checkbox" id="script-meta-supports-locations"> Soporta sucursales</label>
|
||
<label class="script-check-row"><input type="checkbox" id="script-meta-mutator"> Modifica datos</label>
|
||
<label class="script-check-row"><input type="checkbox" id="script-meta-supports-audit"> Soporta auditoría</label>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="closeScriptMetadataModal()">Cancelar</button>
|
||
<button class="btn btn-primary" onclick="saveScriptMetadata()">Guardar Metadata</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/static/js/live_terminal.js?v=1.0.0" defer></script>
|
||
<script src="/static/js/app.js?v=1.0.13" defer></script>
|
||
</body>
|
||
</html>
|