Files
web/configuracion.html

1856 lines
142 KiB
HTML

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configuración - IntegraRepara</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<style>
:root {
--primary: #2563eb;
--secondary: #f59e0b;
--app-bg: #f4f7f9;
}
.fade-in { animation: fadeIn 0.3s ease-in-out; }
.scroller::-webkit-scrollbar { width: 6px; }
.scroller::-webkit-scrollbar-thumb { background-color: #cbd5e1; border-radius: 4px; }
.toggle-checkbox:checked { transform: translateX(100%); border-color: #2563eb; }
.toggle-checkbox:checked + .toggle-label { background-color: #2563eb; }
.toggle-checkbox { top: 0; left: 0; z-index: 10; transition: all 0.3s ease; border-color: #cbd5e1; }
.toggle-label { background-color: #e2e8f0; transition: all 0.3s ease; cursor: pointer; }
.keyword-tag { animation: popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
@keyframes popIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.var-btn { padding: 0.25rem 0.5rem; background-color: white; border: 1px solid #e2e8f0; border-radius: 0.25rem; font-family: monospace; font-size: 0.75rem; font-weight: 500; color: #4b5563; transition: all 0.2s; }
.var-btn:hover { border-color: #2563eb; color: #2563eb; }
</style>
</head>
<body class="bg-gray-50 text-gray-800 font-sans h-screen overflow-hidden flex">
<div id="sidebar-container" class="h-full shrink-0"></div>
<div class="flex-1 flex flex-col h-full relative min-w-0">
<div id="header-container"></div>
<main class="flex-1 flex flex-col overflow-hidden relative">
<div class="bg-gray-50 p-6 pb-0 z-20 shrink-0">
<h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2">
<i data-lucide="settings" class="text-blue-600"></i> Configuración Avanzada
</h2>
<div class="flex border-b border-gray-200 overflow-x-auto no-scrollbar">
<button onclick="showTab('templates')" id="tab-templates" class="tab-btn px-6 py-3 text-sm font-bold text-blue-600 border-b-2 border-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="file-text" class="inline w-4 h-4 mr-1"></i> Plantillas
</button>
<button onclick="showTab('whatsapp')" id="tab-whatsapp" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="message-circle" class="inline w-4 h-4 mr-1"></i> WhatsApp
</button>
<button onclick="showTab('providers')" id="tab-providers" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="truck" class="inline w-4 h-4 mr-1"></i> Proveedores e IA
</button>
<button onclick="showTab('portal')" id="tab-portal" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="globe" class="inline w-4 h-4 mr-1"></i> Portal Cliente
</button>
<button onclick="showTab('app-operario')" id="tab-app-operario" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="smartphone" class="inline w-4 h-4 mr-1"></i> App Operario
</button>
<button onclick="showTab('others')" id="tab-others" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="sliders" class="inline w-4 h-4 mr-1"></i> Otras Configuraciones
</button>
<button onclick="showTab('billing')" id="tab-billing" class="tab-btn px-6 py-3 text-sm font-medium text-gray-500 hover:text-blue-600 hover:bg-gray-100 transition whitespace-nowrap">
<i data-lucide="building" class="inline w-4 h-4 mr-1"></i> Datos de Empresa
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto scroller p-6 bg-gray-50">
<div id="view-billing" class="tab-content hidden h-full fade-in pb-10">
<div class="max-w-4xl mx-auto mt-6">
<div class="bg-white p-8 rounded-[2rem] shadow-sm border border-slate-100 space-y-6">
<div class="flex items-center gap-4 border-b border-slate-100 pb-4">
<div class="w-12 h-12 bg-blue-50 text-blue-600 rounded-full flex items-center justify-center shrink-0">
<i data-lucide="building" class="w-6 h-6"></i>
</div>
<div>
<h3 class="text-xl font-black text-slate-800 tracking-tight">Datos de Empresa / Facturación</h3>
<p class="text-sm text-slate-500 font-medium">Información fiscal y bancaria para presupuestos y facturas.</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Nombre y Apellidos / Razón Social</label>
<input type="text" id="billName" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">DNI / CIF</label>
<input type="text" id="billDni" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Dirección de Facturación</label>
<input type="text" id="billAddress" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Población</label>
<input type="text" id="billCity" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Provincia</label>
<input type="text" id="billState" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Código Postal</label>
<input type="text" id="billZip" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Número de Cuenta (IBAN) para presupuestos</label>
<input type="text" id="billIban" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all" placeholder="ESXX XXXX XXXX XXXX XXXX XXXX">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-2">Observaciones para presupuestos</label>
<textarea id="billObs" rows="3" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all" placeholder="Ej: Presupuesto válido por 30 días..."></textarea>
</div>
<div class="md:col-span-2 mt-4 pt-6 border-t border-slate-100">
<div class="flex items-center justify-between mb-4">
<div>
<h4 class="text-sm font-black text-slate-800 uppercase tracking-tight flex items-center gap-2">
<i data-lucide="credit-card" class="w-5 h-5 text-indigo-600"></i> Pasarela de Pago (Stripe)
</h4>
<p class="text-xs text-slate-500 mt-1">Permite a tus clientes pagar presupuestos directamente con Tarjeta o Apple Pay.</p>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="stripe_enabled" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="stripe_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 bg-indigo-50/50 p-5 rounded-2xl border border-indigo-100">
<div>
<label class="block text-[10px] font-black text-indigo-800 uppercase tracking-widest mb-1.5">Clave Pública (Public Key)</label>
<input type="text" id="stripe_pk" class="w-full bg-white border border-indigo-200 text-xs font-mono font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-indigo-500 transition-all" placeholder="pk_live_...">
</div>
<div>
<label class="block text-[10px] font-black text-indigo-800 uppercase tracking-widest mb-1.5">Clave Secreta (Secret Key)</label>
<input type="password" id="stripe_sk" class="w-full bg-white border border-indigo-200 text-xs font-mono font-bold text-slate-700 p-3 rounded-xl outline-none focus:ring-2 focus:ring-indigo-500 transition-all" placeholder="sk_live_...">
</div>
</div>
</div>
</div>
<div class="pt-4 flex justify-end">
<button onclick="saveBillingConfig()" id="btnSaveBilling" class="bg-blue-600 hover:bg-blue-700 text-white font-black py-3 px-8 rounded-xl shadow-lg transition-all uppercase tracking-widest text-xs flex items-center gap-2">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Datos
</button>
</div>
</div>
</div>
</div>
<div id="view-templates" class="tab-content h-full fade-in flex flex-col md:flex-row gap-6">
<div class="w-full md:w-1/3 bg-white rounded-xl shadow border border-gray-100 overflow-hidden flex flex-col">
<div class="p-4 bg-gray-50 border-b border-gray-200 font-bold text-gray-700 text-sm uppercase tracking-wide">Tipos de Mensaje</div>
<div class="flex-1 overflow-y-auto p-2 space-y-1">
<button onclick="selectTemplate('welcome', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-blue-100 text-blue-600 p-1.5 rounded-md group-hover:bg-blue-200 transition"><i data-lucide="hand" class="w-4 h-4"></i></span> Bienvenida / Alta</button>
<button onclick="selectTemplate('assigned', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-orange-100 text-orange-600 p-1.5 rounded-md group-hover:bg-orange-200 transition"><i data-lucide="user-check" class="w-4 h-4"></i></span> Asignado a Operario</button>
<button onclick="selectTemplate('no_contact', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-orange-100 text-orange-600 p-1.5 rounded-md group-hover:bg-orange-200 transition"><i data-lucide="phone-off" class="w-4 h-4"></i></span> Cliente No Localizado</button>
<button onclick="selectTemplate('appointment', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-green-100 text-green-600 p-1.5 rounded-md group-hover:bg-green-200 transition"><i data-lucide="calendar-check" class="w-4 h-4"></i></span> Cita Creada</button>
<button onclick="selectTemplate('update', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-purple-100 text-purple-600 p-1.5 rounded-md group-hover:bg-purple-200 transition"><i data-lucide="refresh-cw" class="w-4 h-4"></i></span> Modificación de Servicio</button>
<button onclick="selectTemplate('on_way', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-teal-100 text-teal-600 p-1.5 rounded-md group-hover:bg-teal-200 transition"><i data-lucide="truck" class="w-4 h-4"></i></span> Técnico de Camino</button>
<button onclick="selectTemplate('survey', this)" class="tpl-btn w-full text-left p-3 rounded-lg hover:bg-blue-50 text-sm font-medium text-gray-600 transition-colors flex items-center gap-3 group"><span class="bg-yellow-100 text-yellow-600 p-1.5 rounded-md group-hover:bg-yellow-200 transition"><i data-lucide="star" class="w-4 h-4"></i></span> Encuesta de Calidad</button>
</div>
</div>
<div class="flex-1 bg-white rounded-xl shadow border border-gray-100 flex flex-col overflow-hidden">
<div class="p-4 border-b bg-gray-50 flex justify-between items-center shrink-0">
<div><span class="block font-bold text-gray-800" id="editorTitle">Selecciona una plantilla</span><span class="text-xs text-gray-500">Edita el mensaje que se enviará por WhatsApp/Email</span></div>
<button onclick="saveTemplate()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-bold shadow-lg transition-all transform active:scale-95 flex items-center gap-2"><i data-lucide="save" class="w-4 h-4"></i> Guardar</button>
</div>
<div class="p-3 border-b bg-gray-50/50 flex flex-wrap gap-2 items-center">
<span class="text-[10px] font-bold text-gray-400 uppercase tracking-wide mr-1">Variables:</span>
<button onclick="insertVar('{{NOMBRE}}')" class="var-btn">{{NOMBRE}}</button>
<button onclick="insertVar('{{DIRECCION}}')" class="var-btn">{{DIRECCION}}</button>
<button onclick="insertVar('{{FECHA}}')" class="var-btn">{{FECHA}}</button>
<button onclick="insertVar('{{HORA}}')" class="var-btn">{{HORA}}</button>
<button onclick="insertVar('{{COMPANIA}}')" class="var-btn">{{COMPANIA}}</button>
<button onclick="insertVar('{{REFERENCIA}}')" class="var-btn">{{REFERENCIA}}</button>
<button onclick="insertVar('{{ENLACE}}')" class="var-btn text-purple-600 border-purple-200 hover:border-purple-400 bg-purple-50">{{ENLACE}}</button>
</div>
<textarea id="tplContent" class="flex-1 p-6 outline-none resize-none text-sm leading-relaxed text-gray-700 font-sans" placeholder="Selecciona un tipo de mensaje a la izquierda..."></textarea>
</div>
</div>
<div id="view-whatsapp" class="tab-content hidden h-full fade-in pb-10">
<div class="max-w-5xl mx-auto mt-6">
<div class="mb-8">
<h2 class="text-2xl font-black text-slate-800 tracking-tight flex items-center gap-3">
<span class="bg-green-100 p-2.5 rounded-xl text-green-600 shadow-sm"><i data-lucide="message-circle"></i></span>
Centro de Notificaciones Automáticas
</h2>
<p class="text-sm text-slate-500 mt-2 font-medium">Vincula tu número y decide qué eventos disparan un mensaje al cliente.</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div class="bg-white rounded-3xl shadow-sm border border-slate-100 p-8 flex flex-col h-fit">
<h3 class="text-lg font-bold text-slate-800 border-b border-slate-100 pb-4 mb-6 flex items-center gap-2">
<i data-lucide="smartphone" class="w-5 h-5 text-blue-600"></i> Estado del Dispositivo
</h3>
<div id="waStatusContainer" class="w-full flex flex-col items-center justify-center min-h-[200px] bg-slate-50 rounded-2xl border-2 border-dashed border-slate-200 p-6 mb-6">
<div class="animate-pulse flex flex-col items-center">
<div class="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin mb-4"></div>
<span class="text-sm font-bold text-slate-400">Iniciando instancia...</span>
</div>
</div>
<button onclick="checkWhatsappStatus()" class="w-full bg-slate-800 hover:bg-slate-700 text-white font-bold py-3 px-6 rounded-xl transition shadow-md flex items-center justify-center gap-2">
<i data-lucide="refresh-cw" class="w-4 h-4"></i> Actualizar Estado de Conexión
</button>
</div>
<div class="bg-white rounded-3xl shadow-sm border border-slate-100 overflow-hidden flex flex-col h-fit">
<div class="p-6 border-b border-slate-100 bg-slate-50/50 flex justify-between items-center">
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
<i data-lucide="zap" class="w-5 h-5 text-amber-500"></i> Reglas de Envío
</h3>
<button onclick="saveWaSettings()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-xs font-bold shadow-md transition-all active:scale-95 flex items-center gap-2">
<i data-lucide="save" class="w-3.5 h-3.5"></i> Guardar Ajustes
</button>
</div>
<div class="p-2">
<div class="p-4 bg-indigo-50/50 hover:bg-indigo-50 transition-colors flex items-center justify-between border-b border-indigo-100 mb-2">
<div class="flex items-start gap-3">
<div class="bg-indigo-600 p-2 rounded-lg text-white shadow-sm mt-0.5"><i data-lucide="brain-circuit" class="w-4 h-4"></i></div>
<div>
<p class="font-black text-indigo-900 text-sm italic">ChatGPT Inteligencia Artificial</p>
<p class="text-[10px] text-indigo-500 font-bold uppercase tracking-tight">Análisis de respuestas y soporte automático</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_ai_enabled" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_ai_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 border-b border-slate-100 mb-2 bg-slate-50">
<label class="block text-sm font-medium text-gray-700">Instrucciones extra para la IA (Opcional)</label>
<p class="text-xs text-gray-500 mb-2">Dale personalidad o reglas específicas a tu asistente.</p>
<textarea id="ai_custom_prompt" rows="3" class="w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm p-2" placeholder="Escribe aquí las reglas personalizadas..."></textarea>
</div>
<div class="p-4 border-b border-slate-100 mb-2 bg-slate-50">
<label class="block text-sm font-medium text-gray-700">Condiciones de Cobertura por Compañía (Opcional)</label>
<p class="text-xs text-gray-500 mb-2">Cuando el cliente pregunte por costes, materiales o qué cubre el seguro, la IA usará el texto de la compañía que coincida con el expediente. La palabra clave se busca dentro del campo "Compañía" del expediente (sin importar mayúsculas ni el resto del texto alrededor).</p>
<div id="detectedCompaniesBox" class="hidden mb-3">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2">Buscar compañía vista en tus expedientes</p>
<div class="relative">
<i data-lucide="search" class="w-3.5 h-3.5 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none"></i>
<input type="text" id="companySearchInput" autocomplete="off"
class="w-full border border-slate-300 rounded-lg pl-8 pr-3 py-2 text-xs font-bold text-slate-700 outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Escribe para buscar (ej: santander, repsol...)"
oninput="renderCompanyOptions(this.value)"
onfocus="renderCompanyOptions(this.value)">
<div id="companyOptionsList" class="hidden absolute z-20 mt-1 w-full max-h-56 overflow-y-auto bg-white border border-slate-200 rounded-lg shadow-lg"></div>
</div>
</div>
<div id="coverageRulesList" class="space-y-3"></div>
<button type="button" onclick="addCoverageRule()" class="mt-2 text-xs font-bold text-blue-600 hover:text-blue-800 flex items-center gap-1">
<i data-lucide="plus-circle" class="w-4 h-4"></i> Añadir compañía manualmente
</button>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between border-b border-slate-100">
<div>
<p class="font-bold text-slate-700 text-sm">Modo "Escribiendo..."</p>
<p class="text-xs text-slate-400 mt-0.5">Añade un retraso natural antes de enviar.</p>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_delay" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_delay" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between border-b border-slate-100">
<div class="flex items-start gap-3">
<div class="bg-blue-100 p-2 rounded-lg text-blue-600 mt-0.5"><i data-lucide="user-plus" class="w-4 h-4"></i></div>
<div>
<p class="font-bold text-slate-700 text-sm">Mensaje de Bienvenida</p>
<p class="text-xs text-slate-400 mt-0.5">Al registrar el servicio en el sistema.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_evt_welcome" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_evt_welcome" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between border-b border-slate-100">
<div class="flex items-start gap-3">
<div class="bg-orange-100 p-2 rounded-lg text-orange-600 mt-0.5"><i data-lucide="user-check" class="w-4 h-4"></i></div>
<div>
<p class="font-bold text-slate-700 text-sm">Asignado a Operario</p>
<p class="text-xs text-slate-400 mt-0.5">Se envía cuando asignas un técnico al servicio.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_evt_assigned" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_evt_assigned" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between border-b border-slate-100">
<div class="flex items-start gap-3">
<div class="bg-purple-100 p-2 rounded-lg text-purple-600 mt-0.5"><i data-lucide="calendar-clock" class="w-4 h-4"></i></div>
<div>
<p class="font-bold text-slate-700 text-sm">Cambio de Cita</p>
<p class="text-xs text-slate-400 mt-0.5">Cuando se agenda o modifica la fecha de visita.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_evt_date" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_evt_date" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between border-b border-slate-100">
<div class="flex items-start gap-3">
<div class="bg-amber-100 p-2 rounded-lg text-amber-600 mt-0.5"><i data-lucide="car" class="w-4 h-4"></i></div>
<div>
<p class="font-bold text-slate-700 text-sm">Técnico de Camino</p>
<p class="text-xs text-slate-400 mt-0.5">Avisa al cliente minutos antes de llegar.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_evt_onway" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_evt_onway" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between border-b border-slate-100">
<div class="flex items-start gap-3">
<div class="bg-emerald-100 p-2 rounded-lg text-emerald-600 mt-0.5"><i data-lucide="check-circle" class="w-4 h-4"></i></div>
<div>
<p class="font-bold text-slate-700 text-sm">Servicio Finalizado</p>
<p class="text-xs text-slate-400 mt-0.5">Notifica el cierre de la avería.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_evt_finished" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_evt_finished" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div class="p-4 hover:bg-slate-50 transition-colors flex items-center justify-between">
<div class="flex items-start gap-3">
<div class="bg-rose-100 p-2 rounded-lg text-rose-600 mt-0.5"><i data-lucide="star" class="w-4 h-4"></i></div>
<div>
<p class="font-bold text-slate-700 text-sm">Encuesta de Calidad</p>
<p class="text-xs text-slate-400 mt-0.5">Se envía automáticamente tras finalizar.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="cfg_evt_survey" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cfg_evt_survey" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="view-providers" class="tab-content hidden h-full fade-in">
<div class="max-w-6xl mx-auto space-y-12">
<div>
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-800 flex items-center gap-2"><i data-lucide="key" class="text-blue-600"></i> Accesos Web</h2>
<p class="text-gray-500 text-sm mt-1">Configura tus credenciales para que el robot descargue los servicios automáticamente.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 hover:border-blue-300 transition-all flex flex-col justify-between">
<div>
<div class="flex items-center gap-3 mb-6">
<div class="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center text-blue-700 font-bold text-xl shadow-sm">M</div>
<div>
<h3 class="font-bold text-gray-800 text-lg">Multiasistencia</h3>
<p class="text-xs text-gray-500 font-medium">Conexión Web</p>
</div>
</div>
<form onsubmit="saveProviderCreds(event, 'multiasistencia')" class="space-y-4 relative z-10">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">Usuario / Código</label>
<input type="text" id="user_multiasistencia" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none" placeholder="Ej: 123456">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">Contraseña</label>
<input type="password" id="pass_multiasistencia" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none" placeholder="••••••••">
</div>
<div class="mt-4 flex items-center justify-between bg-blue-50/50 p-4 rounded-xl border border-blue-100">
<div class="flex flex-col">
<span class="text-sm font-bold text-blue-900 italic">Auto-Asignar Urgencias</span>
<span class="text-[10px] text-blue-500 font-bold uppercase tracking-tight">Envío directo a operarios</span>
</div>
<div class="relative inline-block w-12 h-6 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" id="auto_multiasistencia" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer transition-transform duration-300 ease-in-out"/>
<label for="auto_multiasistencia" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer transition-colors duration-300 ease-in-out"></label>
</div>
</div>
<button type="submit" class="w-full bg-blue-600 text-white font-bold py-2 rounded-lg hover:bg-blue-700 transition shadow-md flex justify-center gap-2 mt-4">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Acceso
</button>
</form>
</div>
</div>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 hover:border-red-300 transition-all flex flex-col justify-between">
<div>
<div class="flex items-center gap-3 mb-6">
<div class="w-12 h-12 bg-red-100 rounded-xl flex items-center justify-center text-red-700 font-bold text-xl shadow-sm">H</div>
<div>
<h3 class="font-bold text-gray-800 text-lg">HomeServe</h3>
<p class="text-xs text-gray-500 font-medium">Conexión Web</p>
</div>
</div>
<form onsubmit="saveProviderCreds(event, 'homeserve')" class="space-y-4 relative z-10">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">Código Proveedor</label>
<input type="text" id="user_homeserve" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-red-500 outline-none" placeholder="Ej: P-9999">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">Contraseña</label>
<input type="password" id="pass_homeserve" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-red-500 outline-none" placeholder="••••••••">
</div>
<div class="mt-4 flex items-center justify-between bg-red-50/50 p-4 rounded-xl border border-red-100">
<div class="flex flex-col">
<span class="text-sm font-bold text-red-900 italic">Auto-Asignar Urgencias</span>
<span class="text-[10px] text-red-500 font-bold uppercase tracking-tight">Envío directo a operarios</span>
</div>
<div class="relative inline-block w-12 h-6 align-middle select-none transition duration-200 ease-in">
<input type="checkbox" id="auto_homeserve" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer transition-transform duration-300 ease-in-out"/>
<label for="auto_homeserve" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer transition-colors duration-300 ease-in-out"></label>
</div>
</div>
<button type="submit" class="w-full bg-red-600 text-white font-bold py-2 rounded-lg hover:bg-red-700 transition shadow-md flex justify-center gap-2 mt-4">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Acceso
</button>
</form>
</div>
</div>
</div>
</div>
<div class="border-t border-gray-200 pt-10 mt-10">
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-800 flex items-center gap-2"><i data-lucide="bot" class="text-red-600"></i> Robot HomeServe: Reglas Automáticas</h2>
<p class="text-gray-500 text-sm mt-1">Configura qué debe escribir el robot en la web de HomeServe cuando cambias un estado en IntegraRepara.</p>
</div>
<div class="space-y-6">
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="bg-orange-100 text-orange-600 p-2 rounded-lg"><i data-lucide="user-check" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-slate-800">1. Al Asignar a un Operario</h3>
<p class="text-xs text-slate-500">Se activa cuando le pasas un siniestro nuevo a un técnico.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="hs_cfg_assign_enabled" onchange="toggleHsConfig('assign')" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="hs_cfg_assign_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div id="hs_cfg_assign_body" class="p-5 space-y-4 hidden bg-white">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Estado en HomeServe</label>
<select id="hs_cfg_assign_status" class="w-full border border-slate-300 rounded-lg p-2.5 text-xs outline-none focus:border-red-500">
<option value="303">303 - En espera de Cliente por aceptacion Presupuesto</option>
<option value="307">307 - En espera de Profesional por fecha de inicio de trabajos</option>
<option value="313">313 - En espera de Profesional por secado de cala/pintura</option>
<option value="318">318 - En espera de Profesional por confirmacion del Siniestro</option>
<option value="319">319 - En espera de Profesional por material</option>
<option value="320">320 - En espera de Profesional por espera de otro gremio</option>
<option value="321">321 - En espera de Profesional por presupuesto/valoracion</option>
<option value="323">323 - En espera de Profesional por mejora del tiempo</option>
<option value="326">326 - En espera de Cliente por pago de Factura</option>
<option value="336">336 - En espera de Profesional por averia en observacion</option>
<option value="342">342 - En espera de Profesional pendiente cobro franquicia</option>
<option value="345">345 - En espera de Profesional en realizacion pendiente Terminar</option>
<option value="348">348 - En espera de Cliente por indicaciones</option>
<option value="352">352 - En espera de Perjudicado por indicaciones</option>
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Días Siguiente Acción</label>
<input type="number" id="hs_cfg_assign_days" value="1" min="0" class="w-full border border-slate-300 rounded-lg p-2.5 text-sm outline-none focus:border-red-500" placeholder="Ej: 1">
<p class="text-[10px] text-slate-400 mt-1">Se saltará fines de semana.</p>
</div>
<div class="flex items-center pt-5">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" id="hs_cfg_assign_inform" class="w-4 h-4 text-red-600 border-slate-300 rounded focus:ring-red-500">
<span class="text-sm font-bold text-slate-700">Marcar "Informado al Cliente"</span>
</label>
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Texto para "Observaciones" (Soporta variables)</label>
<textarea id="hs_cfg_assign_obs" rows="2" class="w-full border border-slate-300 rounded-lg p-3 text-sm outline-none focus:border-red-500" placeholder="Ej: Aviso asignado al operario {{NOMBRE_OPERARIO}} el {{FECHA}}."></textarea>
<p class="text-[10px] text-slate-400 mt-1">Variables: {{NOMBRE}}, {{FECHA}}, {{HORA}}, {{TELEFONO}}, {{REFERENCIA}}, {{NOMBRE_OPERARIO}}</p>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="bg-emerald-100 text-emerald-600 p-2 rounded-lg"><i data-lucide="calendar-check" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-slate-800">2. Al Confirmar Cita</h3>
<p class="text-xs text-slate-500">Se activa cuando se fija fecha y hora con el cliente.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="hs_cfg_date_enabled" onchange="toggleHsConfig('date')" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="hs_cfg_date_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div id="hs_cfg_date_body" class="p-5 space-y-4 hidden bg-white">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Estado en HomeServe</label>
<select id="hs_cfg_date_status" class="w-full border border-slate-300 rounded-lg p-2.5 text-xs outline-none focus:border-red-500">
<option value="303">303 - En espera de Cliente por aceptacion Presupuesto</option>
<option value="307">307 - En espera de Profesional por fecha de inicio de trabajos</option>
<option value="313">313 - En espera de Profesional por secado de cala/pintura</option>
<option value="318">318 - En espera de Profesional por confirmacion del Siniestro</option>
<option value="319">319 - En espera de Profesional por material</option>
<option value="320">320 - En espera de Profesional por espera de otro gremio</option>
<option value="321">321 - En espera de Profesional por presupuesto/valoracion</option>
<option value="323">323 - En espera de Profesional por mejora del tiempo</option>
<option value="326">326 - En espera de Cliente por pago de Factura</option>
<option value="336">336 - En espera de Profesional por averia en observacion</option>
<option value="342">342 - En espera de Profesional pendiente cobro franquicia</option>
<option value="345">345 - En espera de Profesional en realizacion pendiente Terminar</option>
<option value="348">348 - En espera de Cliente por indicaciones</option>
<option value="352">352 - En espera de Perjudicado por indicaciones</option>
</select>
</div>
<div class="flex items-center pt-5">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" id="hs_cfg_date_inform" checked class="w-4 h-4 text-red-600 border-slate-300 rounded focus:ring-red-500">
<span class="text-sm font-bold text-slate-700">Marcar "Informado al Cliente"</span>
</label>
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Texto para "Observaciones"</label>
<textarea id="hs_cfg_date_obs" rows="2" class="w-full border border-slate-300 rounded-lg p-3 text-sm outline-none focus:border-red-500" placeholder="Ej: Cita confirmada con el cliente para el {{FECHA}} a las {{HORA}}."></textarea>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="bg-red-100 text-red-600 p-2 rounded-lg"><i data-lucide="phone-off" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-slate-800">3. Cliente No Localizado</h3>
<p class="text-xs text-slate-500">Se activa cuando pulsas el botón de "No contesta" en el buzón.</p>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="hs_cfg_notfound_enabled" onchange="toggleHsConfig('notfound')" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="hs_cfg_notfound_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div id="hs_cfg_notfound_body" class="p-5 space-y-4 hidden bg-white">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Estado en HomeServe</label>
<select id="hs_cfg_notfound_status" class="w-full border border-slate-300 rounded-lg p-2.5 text-xs outline-none focus:border-red-500">
<option value="303">303 - En espera de Cliente por aceptacion Presupuesto</option>
<option value="307">307 - En espera de Profesional por fecha de inicio de trabajos</option>
<option value="313">313 - En espera de Profesional por secado de cala/pintura</option>
<option value="318">318 - En espera de Profesional por confirmacion del Siniestro</option>
<option value="319">319 - En espera de Profesional por material</option>
<option value="320">320 - En espera de Profesional por espera de otro gremio</option>
<option value="321">321 - En espera de Profesional por presupuesto/valoracion</option>
<option value="323">323 - En espera de Profesional por mejora del tiempo</option>
<option value="326">326 - En espera de Cliente por pago de Factura</option>
<option value="336">336 - En espera de Profesional por averia en observacion</option>
<option value="342">342 - En espera de Profesional pendiente cobro franquicia</option>
<option value="345">345 - En espera de Profesional en realizacion pendiente Terminar</option>
<option value="348">348 - En espera de Cliente por indicaciones</option>
<option value="352">352 - En espera de Perjudicado por indicaciones</option>
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Días Siguiente Acción</label>
<input type="number" id="hs_cfg_notfound_days" value="1" min="0" class="w-full border border-slate-300 rounded-lg p-2.5 text-sm outline-none focus:border-red-500">
<p class="text-[10px] text-slate-400 mt-1">Sumará estos días a la fecha actual.</p>
</div>
<div class="flex items-center pt-5">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" id="hs_cfg_notfound_inform" class="w-4 h-4 text-red-600 border-slate-300 rounded focus:ring-red-500">
<span class="text-sm font-bold text-slate-700">Marcar "Informado al Cliente"</span>
</label>
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Texto para "Observaciones"</label>
<textarea id="hs_cfg_notfound_obs" rows="2" class="w-full border border-slate-300 rounded-lg p-3 text-sm outline-none focus:border-red-500" placeholder="Ej: Intento de llamada al {{TELEFONO}} el {{FECHA}} a las {{HORA}}. No contesta. Se le envía WhatsApp con enlace web."></textarea>
</div>
</div>
</div>
<div class="flex justify-end mt-4">
<button onclick="saveRobotConfig()" class="bg-red-600 hover:bg-red-700 text-white px-6 py-3 rounded-xl text-sm font-bold shadow-md transition-all flex items-center gap-2">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Reglas del Robot
</button>
</div>
</div>
</div>
<div class="border-t border-gray-200 pt-10 mt-10">
<div class="mb-6">
<h2 class="text-2xl font-bold text-gray-800 flex items-center gap-2"><i data-lucide="bot" class="text-blue-600"></i> Robot Multiasistencia: Reglas Automáticas</h2>
<p class="text-gray-500 text-sm mt-1">Configura qué debe escribir el robot en la web de Multiasistencia cuando cambias un estado en IntegraRepara.</p>
</div>
<div class="space-y-6">
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="bg-orange-100 text-orange-600 p-2 rounded-lg"><i data-lucide="user-check" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-slate-800">1. Al Asignar a un Operario</h3>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="multi_cfg_assign_enabled" onchange="toggleMultiConfig('assign')" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="multi_cfg_assign_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div id="multi_cfg_assign_body" class="p-5 space-y-4 hidden bg-white">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Estado en Multiasistencia</label>
<select id="multi_cfg_assign_status" class="w-full border border-slate-300 rounded-lg p-2.5 text-xs outline-none focus:border-blue-500">
<option value="1">1 - Contacto (Llamada al cliente)</option>
<option value="2">2 - Visita (Cita concertada)</option>
<option value="37">37 - Pendiente Instrucciones</option>
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Días Siguiente Acción</label>
<input type="number" id="multi_cfg_assign_days" value="1" min="0" class="w-full border border-slate-300 rounded-lg p-2.5 text-sm outline-none focus:border-blue-500">
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Texto para "Observaciones"</label>
<textarea id="multi_cfg_assign_obs" rows="2" class="w-full border border-slate-300 rounded-lg p-3 text-sm outline-none focus:border-blue-500"></textarea>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="bg-emerald-100 text-emerald-600 p-2 rounded-lg"><i data-lucide="calendar-check" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-slate-800">2. Al Confirmar Cita</h3>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="multi_cfg_date_enabled" onchange="toggleMultiConfig('date')" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="multi_cfg_date_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div id="multi_cfg_date_body" class="p-5 space-y-4 hidden bg-white">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Estado en Multiasistencia</label>
<select id="multi_cfg_date_status" class="w-full border border-slate-300 rounded-lg p-2.5 text-xs outline-none focus:border-blue-500">
<option value="2">2 - Visita (Cita concertada)</option>
<option value="1">1 - Contacto</option>
</select>
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Texto para "Observaciones"</label>
<textarea id="multi_cfg_date_obs" rows="2" class="w-full border border-slate-300 rounded-lg p-3 text-sm outline-none focus:border-blue-500"></textarea>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
<div class="p-4 bg-slate-50 border-b border-slate-200 flex justify-between items-center">
<div class="flex items-center gap-3">
<div class="bg-red-100 text-red-600 p-2 rounded-lg"><i data-lucide="phone-off" class="w-5 h-5"></i></div>
<div>
<h3 class="font-bold text-slate-800">3. Cliente No Localizado</h3>
</div>
</div>
<div class="relative inline-block w-12 align-middle select-none">
<input type="checkbox" id="multi_cfg_notfound_enabled" onchange="toggleMultiConfig('notfound')" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="multi_cfg_notfound_enabled" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-200 cursor-pointer"></label>
</div>
</div>
<div id="multi_cfg_notfound_body" class="p-5 space-y-4 hidden bg-white">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Estado en Multiasistencia</label>
<select id="multi_cfg_notfound_status" class="w-full border border-slate-300 rounded-lg p-2.5 text-xs outline-none focus:border-blue-500">
<option value="37">37 - Pendiente Instrucciones</option>
<option value="1">1 - Contacto</option>
</select>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Días Siguiente Acción</label>
<input type="number" id="multi_cfg_notfound_days" value="1" min="0" class="w-full border border-slate-300 rounded-lg p-2.5 text-sm outline-none focus:border-blue-500">
</div>
</div>
<div>
<label class="block text-xs font-bold text-slate-600 mb-1">Texto para "Observaciones"</label>
<textarea id="multi_cfg_notfound_obs" rows="2" class="w-full border border-slate-300 rounded-lg p-3 text-sm outline-none focus:border-blue-500"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="border-t border-gray-200 pt-10">
<div class="mb-6 flex justify-between items-end">
<div>
<h2 class="text-2xl font-bold text-gray-800 flex items-center gap-2"><i data-lucide="brain-circuit" class="text-purple-600"></i> Reglas de Asignación Automática (IA)</h2>
<p class="text-gray-500 text-sm mt-1">Configura las palabras clave para que el robot asigne el gremio automáticamente al descargar el servicio.</p>
</div>
</div>
<div class="bg-white rounded-xl shadow border border-gray-200 overflow-hidden">
<div class="p-4 bg-gray-50 border-b border-gray-200 grid grid-cols-12 gap-4 font-bold text-gray-500 text-xs uppercase tracking-wider">
<div class="col-span-3">Gremio Especialista</div>
<div class="col-span-8">Palabras Clave (Separadas por comas)</div>
<div class="col-span-1 text-center">Acción</div>
</div>
<div id="iaRulesList" class="divide-y divide-gray-100">
<p class="text-center text-sm text-gray-400 py-8">Cargando gremios...</p>
</div>
</div>
</div>
</div>
</div>
<div id="view-portal" class="tab-content hidden h-full fade-in pb-10">
<div class="max-w-6xl mx-auto mt-6">
<div class="bg-white p-8 rounded-[2rem] shadow-sm border border-slate-100 space-y-6">
<div class="flex items-center gap-4 border-b border-slate-100 pb-4">
<div class="w-12 h-12 bg-indigo-50 text-indigo-600 rounded-full flex items-center justify-center shrink-0">
<i data-lucide="smartphone" class="w-6 h-6"></i>
</div>
<div>
<h3 class="text-xl font-black text-slate-800 tracking-tight">Portal del Cliente</h3>
<p class="text-sm text-slate-500 font-medium">Personaliza la web que ven tus clientes al recibir el WhatsApp.</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="space-y-5">
<div>
<label class="block text-xs font-black text-slate-600 uppercase tracking-widest mb-2">Nombre Visible (Empresa)</label>
<input type="text" id="confCompanyName" placeholder="Ej: IntegraRepara SL" class="w-full bg-slate-50 border border-slate-200 text-sm font-bold text-slate-700 p-4 rounded-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div>
<div>
<label class="block text-xs font-black text-slate-600 uppercase tracking-widest mb-2">Logotipo</label>
<div class="flex items-center gap-4">
<div id="logoPreview" class="w-20 h-20 bg-slate-50 border-2 border-dashed border-slate-300 rounded-xl flex items-center justify-center overflow-hidden shrink-0">
<i data-lucide="image" class="text-slate-300 w-8 h-8"></i>
</div>
<div class="flex-1">
<input type="file" id="confLogoInput" accept="image/png, image/jpeg, image/webp" class="hidden" onchange="encodeLogo(this)">
<button onclick="document.getElementById('confLogoInput').click()" class="bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 font-bold py-2 px-4 rounded-lg text-sm transition-all shadow-sm">
Subir Imagen
</button>
<p class="text-[10px] text-slate-400 mt-1">Formatos: PNG, JPG (Máx. 1MB). Recomendado: Cuadrado o Apaisado.</p>
<input type="hidden" id="confLogoBase64">
</div>
</div>
</div>
<div>
<label class="block text-xs font-black text-slate-600 uppercase tracking-widest mb-2">Imagen de Publicidad</label>
<div class="flex items-center gap-4">
<div id="adPreview" class="w-20 h-20 bg-slate-50 border-2 border-dashed border-slate-300 rounded-xl flex items-center justify-center overflow-hidden shrink-0">
<i data-lucide="image" class="text-slate-300 w-8 h-8"></i>
</div>
<div class="flex-1">
<input type="file" id="confAdInput" accept="image/png, image/jpeg, image/webp" class="hidden" onchange="encodeAd(this)">
<button onclick="document.getElementById('confAdInput').click()" class="bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 font-bold py-2 px-4 rounded-lg text-sm transition-all shadow-sm">
Subir Publicidad
</button>
<p class="text-[10px] text-slate-400 mt-1">Banner o Promoción que verá el cliente (Máx. 1MB).</p>
<input type="hidden" id="confAdBase64">
</div>
</div>
</div>
</div>
<div class="space-y-5 bg-slate-50 p-6 rounded-2xl border border-slate-100">
<h4 class="text-sm font-black text-slate-800 uppercase tracking-tight flex items-center gap-2"><i data-lucide="clock" class="w-4 h-4 text-blue-500"></i> Horario de Citas (Rutas)</h4>
<p class="text-xs text-slate-500 mb-4">El portal calculará los huecos cada 30 min dentro de estas franjas.</p>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1.5">Mañanas (Desde - Hasta)</label>
<div class="flex items-center gap-2">
<select id="mStart" class="w-full bg-white border border-slate-200 text-xs font-bold text-slate-700 p-2.5 rounded-lg outline-none focus:ring-2 focus:ring-blue-500">
<option value="08:00">08:00</option><option value="08:30">08:30</option>
<option value="09:00" selected>09:00</option><option value="09:30">09:30</option>
<option value="10:00">10:00</option><option value="10:30">10:30</option>
</select>
<span class="text-slate-400 font-black">-</span>
<select id="mEnd" class="w-full bg-white border border-slate-200 text-xs font-bold text-slate-700 p-2.5 rounded-lg outline-none focus:ring-2 focus:ring-blue-500">
<option value="13:00">13:00</option><option value="13:30">13:30</option>
<option value="14:00" selected>14:00</option><option value="14:30">14:30</option>
<option value="15:00">15:00</option>
</select>
</div>
</div>
<div>
<label class="block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1.5">Tardes (Desde - Hasta)</label>
<div class="flex items-center gap-2">
<select id="aStart" class="w-full bg-white border border-slate-200 text-xs font-bold text-slate-700 p-2.5 rounded-lg outline-none focus:ring-2 focus:ring-blue-500">
<option value="15:00">15:00</option><option value="15:30">15:30</option>
<option value="16:00" selected>16:00</option><option value="16:30">16:30</option>
<option value="17:00">17:00</option>
</select>
<span class="text-slate-400 font-black">-</span>
<select id="aEnd" class="w-full bg-white border border-slate-200 text-xs font-bold text-slate-700 p-2.5 rounded-lg outline-none focus:ring-2 focus:ring-blue-500">
<option value="18:00">18:00</option><option value="18:30">18:30</option>
<option value="19:00" selected>19:00</option><option value="19:30">19:30</option>
<option value="20:00">20:00</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="mt-8 pt-6 border-t border-slate-100">
<h4 class="text-sm font-black text-slate-800 uppercase tracking-tight flex items-center gap-2 mb-4">
<i data-lucide="hammer" class="w-4 h-4 text-blue-500"></i> Gremios Visibles
</h4>
<p class="text-xs text-slate-500 mb-4 font-medium">Selecciona qué profesionales podrá elegir el cliente en el menú desplegable al solicitar su asistencia.</p>
<div id="portalGuildsList" class="grid grid-cols-2 md:grid-cols-3 gap-3">
<p class="text-xs text-slate-400">Cargando gremios...</p>
</div>
</div>
<div class="pt-4 flex justify-end">
<button onclick="savePortalConfig()" id="btnSavePortal" class="bg-blue-600 hover:bg-blue-700 text-white font-black py-3 px-8 rounded-xl shadow-lg shadow-blue-500/30 transition-all uppercase tracking-widest text-xs flex items-center gap-2">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Portal
</button>
</div>
</div>
</div>
</div>
<div id="view-app-operario" class="tab-content hidden h-full fade-in pb-10">
<div class="max-w-4xl mx-auto mt-6">
<div class="bg-white p-8 rounded-[2rem] shadow-sm border border-slate-100 space-y-8">
<div class="flex items-center gap-4 border-b border-slate-100 pb-4">
<div class="w-12 h-12 bg-blue-50 text-blue-600 rounded-full flex items-center justify-center shrink-0">
<i data-lucide="palette" class="w-6 h-6"></i>
</div>
<div>
<h3 class="text-xl font-black text-slate-800 tracking-tight">Personalización de la App</h3>
<p class="text-sm text-slate-500 font-medium">Configura los colores corporativos para el menú y las fichas de los operarios.</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
<div class="space-y-6">
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-3">Color Principal (Cabeceras y Botones)</label>
<div class="flex items-center gap-4 bg-slate-50 p-3 rounded-xl border border-slate-200">
<input type="color" id="color-primary" class="w-12 h-12 rounded-lg cursor-pointer border-none bg-transparent">
<input type="text" id="color-primary-hex" class="flex-1 bg-transparent text-sm font-mono font-bold outline-none uppercase" placeholder="#2563EB">
</div>
</div>
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-3">Color Secundario (Acentos y Badges)</label>
<div class="flex items-center gap-4 bg-slate-50 p-3 rounded-xl border border-slate-200">
<input type="color" id="color-secondary" class="w-12 h-12 rounded-lg cursor-pointer border-none bg-transparent">
<input type="text" id="color-secondary-hex" class="flex-1 bg-transparent text-sm font-mono font-bold outline-none uppercase" placeholder="#F59E0B">
</div>
</div>
<div>
<label class="block text-xs font-black text-slate-500 uppercase tracking-widest mb-3">Fondo de Pantalla (App)</label>
<div class="flex items-center gap-4 bg-slate-50 p-3 rounded-xl border border-slate-200">
<input type="color" id="color-bg" class="w-12 h-12 rounded-lg cursor-pointer border-none bg-transparent">
<input type="text" id="color-bg-hex" class="flex-1 bg-transparent text-sm font-mono font-bold outline-none uppercase" placeholder="#F4F7F9">
</div>
</div>
</div>
<div class="flex flex-col items-center">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-4">Vista Previa en Vivo</p>
<div id="app-preview" class="w-64 h-[400px] border-[6px] border-slate-800 rounded-[2.5rem] shadow-2xl overflow-hidden relative bg-gray-100 scale-90 origin-top">
<div id="prev-header" class="h-20 w-full p-4 text-white" style="background-color: #2563eb;">
<div class="w-8 h-2 bg-white/30 rounded mb-2"></div>
<div class="w-20 h-4 bg-white rounded"></div>
</div>
<div class="p-4 grid grid-cols-2 gap-2">
<div class="h-16 bg-white rounded-xl border border-slate-200 shadow-sm flex items-center justify-center">
<div id="prev-icon" class="w-6 h-6 rounded-lg" style="background-color: #f59e0b;"></div>
</div>
<div class="h-16 bg-white rounded-xl border border-slate-200 shadow-sm"></div>
</div>
<div class="absolute bottom-6 left-4 right-4">
<div id="prev-btn" class="h-10 w-full rounded-xl shadow-md" style="background-color: #2563eb;"></div>
</div>
</div>
</div>
</div>
<div class="pt-6 flex justify-end">
<button onclick="saveAppConfig()" id="btnSaveApp" class="bg-blue-600 hover:bg-blue-700 text-white font-black py-4 px-10 rounded-2xl shadow-xl shadow-blue-500/30 transition-all uppercase tracking-widest text-xs flex items-center gap-2 active:scale-95">
<i data-lucide="save" class="w-5 h-5"></i> Aplicar a todos los dispositivos
</button>
</div>
</div>
</div>
</div>
<div id="view-others" class="tab-content hidden h-full fade-in flex flex-col pb-10">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-6xl mx-auto w-full">
<div class="bg-white rounded-xl shadow border border-gray-100 flex flex-col h-[500px]">
<div class="p-4 bg-gray-50 border-b border-gray-200 shrink-0"><h3 class="font-bold text-gray-700 flex items-center gap-2"><i data-lucide="shield" class="w-4 h-4 text-blue-600"></i> Compañías de Seguros</h3></div>
<div class="p-4 border-b border-gray-100 bg-white"><div class="flex gap-2"><input type="text" id="newCompanyInput" placeholder="Nombre..." class="flex-1 border rounded-lg px-3 py-2 text-sm outline-none focus:border-blue-500"><button onclick="addCompany()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-xs font-bold transition-colors">Añadir</button></div></div>
<div id="listCompanies" class="flex-1 overflow-y-auto scroller p-2 space-y-1"><p class="text-center text-xs text-gray-400 mt-10">Cargando...</p></div>
</div>
<div class="bg-white rounded-xl shadow border border-gray-100 flex flex-col h-[500px]">
<div class="p-4 bg-gray-50 border-b border-gray-200 shrink-0"><h3 class="font-bold text-gray-700 flex items-center gap-2"><i data-lucide="activity" class="w-4 h-4 text-blue-600"></i> Estados del Servicio</h3></div>
<div class="p-4 border-b border-gray-100 bg-white"><div class="flex gap-2"><input type="text" id="newStatusInput" placeholder="Nuevo estado..." class="flex-1 border rounded-lg px-3 py-2 text-sm outline-none focus:border-blue-500"><select id="newStatusColor" class="border rounded-lg px-2 text-sm text-gray-600 outline-none focus:border-blue-500 bg-gray-50"><option value="gray">Gris</option><option value="blue">Azul</option><option value="green">Verde</option><option value="red">Rojo</option><option value="yellow">Amarillo</option><option value="purple">Morado</option></select><button onclick="addStatus()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-xs font-bold transition-colors">Añadir</button></div></div>
<div id="listStatuses" class="flex-1 overflow-y-auto scroller p-2 space-y-1"><p class="text-center text-xs text-gray-400 mt-10">Cargando...</p></div>
</div>
</div>
</div>
</div>
</main>
</div>
<div id="iaModal" class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden z-[100] flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-xl w-full max-w-lg overflow-hidden fade-in">
<div class="p-5 border-b bg-gray-50 flex justify-between items-center">
<h3 class="font-bold text-gray-800 text-lg flex items-center gap-2"><i data-lucide="brain-circuit" class="text-purple-600"></i> Reglas IA para <span id="iaModalGuildName" class="text-blue-600"></span></h3>
<button onclick="closeIaModal()" class="text-gray-400 hover:text-red-500"><i data-lucide="x"></i></button>
</div>
<div class="p-6">
<input type="hidden" id="iaModalGuildId">
<label class="block text-sm font-bold text-gray-700 mb-2">Palabras Clave</label>
<p class="text-xs text-gray-500 mb-3">Escribe palabras clave separadas por comas. Si el robot detecta alguna de ellas en la descripción del servicio, lo asignará a este gremio automáticamente.</p>
<textarea id="iaModalKeywords" rows="5" class="w-full border border-gray-300 rounded-xl p-3 text-sm focus:ring-2 focus:ring-purple-500 outline-none" placeholder="Ej: fuga de agua, tubería rota, grifo, desatasco..."></textarea>
</div>
<div class="p-4 border-t bg-gray-50 flex justify-end gap-3">
<button onclick="closeIaModal()" class="px-4 py-2 bg-white border border-gray-200 rounded-lg text-sm font-bold text-gray-600 hover:bg-gray-100 transition">Cancelar</button>
<button onclick="saveIaRule()" class="px-6 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg text-sm font-bold shadow transition flex items-center gap-2"><i data-lucide="save" class="w-4 h-4"></i> Guardar Regla</button>
</div>
</div>
</div>
<div id="toast" class="fixed bottom-5 right-5 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-2xl transform translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center gap-3"><span id="toastMsg">Msg</span></div>
<script src="js/layout.js"></script>
<script>
let cachedTemplates = {};
let currentTemplateType = null;
let localGuilds = [];
document.addEventListener("DOMContentLoaded", () => {
if (!localStorage.getItem("token")) window.location.href = "index.html";
showTab('templates');
loadTemplates();
loadWaSettings();
loadDetectedCompanies();
});
// ==========================================
// LÓGICA DE NAVEGACIÓN Y TABS
// ==========================================
function showTab(tabId) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
document.getElementById(`view-${tabId}`).classList.remove('hidden');
document.querySelectorAll('.tab-btn').forEach(el => {
el.classList.remove('text-blue-600', 'border-b-2', 'border-blue-600', 'font-bold');
el.classList.add('text-gray-500', 'font-medium');
});
const btn = document.getElementById(`tab-${tabId}`);
if(btn) {
btn.classList.add('text-blue-600', 'border-b-2', 'border-blue-600', 'font-bold');
btn.classList.remove('text-gray-500', 'font-medium');
}
if(tabId === 'others') { loadCompanies(); loadStatusesConfig(); }
if(tabId === 'whatsapp') { checkWhatsappStatus(); }
if(tabId === 'portal') { loadPortalConfig(); }
if(tabId === 'app-operario') { loadAppConfig(); }
if(tabId === 'providers') {
loadProviderCredentials();
loadIaRules();
loadRobotConfig();
}
// 🚀 AÑADE ESTA LÍNEA AQUÍ ABAJO:
if(tabId === 'billing') { loadBillingConfig(); }
}
// ==========================================
// LÓGICA DE INTELIGENCIA ARTIFICIAL (GREMIOS)
// ==========================================
async function loadIaRules() {
const list = document.getElementById('iaRulesList');
list.innerHTML = '<p class="text-center text-sm text-gray-400 py-8"><i data-lucide="loader-2" class="w-6 h-6 animate-spin mx-auto mb-2"></i> Cargando...</p>';
lucide.createIcons();
try {
const resGuilds = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const dataGuilds = await resGuilds.json();
if (!dataGuilds.ok || dataGuilds.guilds.length === 0) {
list.innerHTML = '<p class="text-center text-sm text-orange-500 py-8">No tienes gremios creados. Crea gremios en "Otras Configuraciones" primero.</p>';
return;
}
localGuilds = dataGuilds.guilds;
list.innerHTML = '';
localGuilds.forEach(g => {
const keywords = Array.isArray(g.ia_keywords) ? g.ia_keywords : [];
const tagsHtml = keywords.length > 0
? keywords.map(kw => `<span class="inline-block bg-purple-100 text-purple-700 text-[10px] font-bold px-2 py-1 rounded-md keyword-tag m-0.5 border border-purple-200">${kw}</span>`).join('')
: '<span class="text-xs text-gray-400 italic">Sin reglas asignadas.</span>';
const row = document.createElement('div');
row.className = "p-4 hover:bg-purple-50/30 transition-colors grid grid-cols-12 gap-4 items-center group";
row.innerHTML = `
<div class="col-span-3 font-bold text-gray-800 flex items-center gap-2">
<i data-lucide="hammer" class="w-4 h-4 text-gray-400 group-hover:text-purple-500 transition-colors"></i> ${g.name}
</div>
<div class="col-span-8 flex flex-wrap items-center gap-1">${tagsHtml}</div>
<div class="col-span-1 text-center">
<button onclick="openIaModal(${g.id}, '${g.name.replace(/'/g, "\\'")}')" class="text-gray-400 hover:text-purple-600 bg-white hover:bg-purple-50 border border-gray-200 hover:border-purple-200 p-2 rounded-lg transition-all shadow-sm">
<i data-lucide="edit-2" class="w-4 h-4"></i>
</button>
</div>
`;
list.appendChild(row);
});
lucide.createIcons();
} catch (e) {
console.error("Error cargando reglas IA:", e);
list.innerHTML = '<p class="text-center text-sm text-red-500 py-8">Error al cargar las reglas de IA.</p>';
}
}
function openIaModal(guildId, guildName) {
const guild = localGuilds.find(g => g.id === guildId);
const keywords = Array.isArray(guild.ia_keywords) ? guild.ia_keywords : [];
document.getElementById('iaModalGuildId').value = guildId;
document.getElementById('iaModalGuildName').innerText = guildName;
document.getElementById('iaModalKeywords').value = keywords.join(', ');
document.getElementById('iaModal').classList.remove('hidden');
}
function closeIaModal() { document.getElementById('iaModal').classList.add('hidden'); }
async function saveIaRule() {
const guildId = document.getElementById('iaModalGuildId').value;
const rawKeywords = document.getElementById('iaModalKeywords').value;
const keywordsArray = rawKeywords.split(',').map(k => k.trim().toLowerCase()).filter(k => k.length > 0);
try {
const res = await fetch(`${API_URL}/guilds/${guildId}/ia-rules`, {
method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ keywords: keywordsArray })
});
if (res.ok) {
showToast("✅ Reglas de IA guardadas correctamente.");
closeIaModal();
loadIaRules();
} else { showToast("❌ Error al guardar las reglas.", true); }
} catch (e) { showToast("❌ Error de conexión.", true); }
}
// ==========================================
// LÓGICA PORTAL OPERARIO (COLORES)
// ==========================================
['primary', 'secondary', 'bg'].forEach(type => {
const picker = document.getElementById(`color-${type}`);
const text = document.getElementById(`color-${type}-hex`);
if(picker && text) {
picker.addEventListener('input', (e) => {
text.value = e.target.value.toUpperCase();
updateAppPreview();
});
text.addEventListener('input', (e) => {
if(/^#[0-9A-F]{6}$/i.test(e.target.value)) {
picker.value = e.target.value;
updateAppPreview();
}
});
}
});
function updateAppPreview() {
const p = document.getElementById('color-primary').value;
const s = document.getElementById('color-secondary').value;
const b = document.getElementById('color-bg').value;
document.getElementById('prev-header').style.backgroundColor = p;
document.getElementById('prev-icon').style.backgroundColor = s;
document.getElementById('prev-btn').style.backgroundColor = p;
document.getElementById('app-preview').style.backgroundColor = b;
}
async function loadAppConfig() {
try {
const res = await fetch(`${API_URL}/config/company`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok && data.config && data.config.app_settings) {
const s = data.config.app_settings;
document.getElementById('color-primary').value = s.primary || "#2563EB";
document.getElementById('color-primary-hex').value = s.primary || "#2563EB";
document.getElementById('color-secondary').value = s.secondary || "#F59E0B";
document.getElementById('color-secondary-hex').value = s.secondary || "#F59E0B";
document.getElementById('color-bg').value = s.bg || "#F4F7F9";
document.getElementById('color-bg-hex').value = s.bg || "#F4F7F9";
updateAppPreview();
}
} catch (e) { console.error("Error cargando colores"); }
}
async function saveAppConfig() {
const btn = document.getElementById('btnSaveApp');
const original = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Aplicando...';
lucide.createIcons();
const settings = {
primary: document.getElementById('color-primary').value,
secondary: document.getElementById('color-secondary').value,
bg: document.getElementById('color-bg').value
};
try {
const res = await fetch(`${API_URL}/config/company`, {
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ app_settings: settings })
});
if (res.ok) {
showToast("✅ Colores de la App actualizados");
localStorage.setItem('app_theme', JSON.stringify(settings));
}
} catch (e) { showToast("Error al guardar", true); }
finally { btn.innerHTML = original; lucide.createIcons(); }
}
// ==========================================
// LÓGICA WHATSAPP
// ==========================================
async function loadWaSettings() {
try {
const res = await fetch(`${API_URL}/whatsapp/settings`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
const s = data.settings || {};
if(document.getElementById('cfg_ai_enabled')) {
document.getElementById('cfg_ai_enabled').checked = s.wa_ai_enabled || false;
}
if(document.getElementById('ai_custom_prompt')) {
document.getElementById('ai_custom_prompt').value = s.ai_custom_prompt || "";
}
document.getElementById('coverageRulesList').innerHTML = "";
(s.coverage_rules || []).forEach(r => addCoverageRule(r.keyword, r.text));
document.getElementById('cfg_delay').checked = s.wa_delay_enabled !== false;
document.getElementById('cfg_evt_welcome').checked = s.wa_evt_welcome || false;
document.getElementById('cfg_evt_assigned').checked = s.wa_evt_assigned || false;
document.getElementById('cfg_evt_date').checked = s.wa_evt_date || false;
document.getElementById('cfg_evt_onway').checked = s.wa_evt_onway || false;
document.getElementById('cfg_evt_finished').checked = s.wa_evt_finished || false;
document.getElementById('cfg_evt_survey').checked = s.wa_evt_survey || false;
} catch(e) { console.error("Error cargando ajustes WA", e); }
}
// 🧾 Condiciones de cobertura por compañía: cada fila es { keyword, text }. La IA
// busca esta "keyword" (sin mayúsculas ni tildes) DENTRO del campo Compañía del
// expediente, así que da igual si viene envuelta en texto de Multiasistencia,
// HomeServe, etc. — con que contenga la palabra clave (ej. "santander") es suficiente.
function addCoverageRule(keyword = "", text = "") {
const wrap = document.createElement('div');
wrap.className = "coverage-rule-row bg-white border border-slate-200 rounded-lg p-3 space-y-2";
wrap.innerHTML = `
<div class="flex items-center gap-2">
<input type="text" class="cr-keyword flex-1 border-gray-300 rounded-md shadow-sm text-sm p-2 font-bold" placeholder="Palabra clave (ej: santander)">
<button type="button" onclick="this.closest('.coverage-rule-row').remove()" class="text-rose-500 hover:text-rose-700 p-1 shrink-0"><i data-lucide="trash-2" class="w-4 h-4"></i></button>
</div>
<textarea class="cr-text w-full border-gray-300 rounded-md shadow-sm text-xs p-2" rows="2" placeholder="Condiciones de cobertura (ej: Cubre hasta 30€ en materiales y 2h de mano de obra. A partir de ahí lo paga el cliente)"></textarea>
`;
document.getElementById('coverageRulesList').appendChild(wrap);
wrap.querySelector('.cr-keyword').value = keyword;
wrap.querySelector('.cr-text').value = text;
if (window.lucide) lucide.createIcons();
}
// Quita mayúsculas y tildes para poder comparar/filtrar compañías sin que
// "Santander", "SANTANDER" o " Santander " cuenten como cosas distintas.
const normalizarTextoCfg = (str) => String(str || "").toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").trim();
let detectedCompaniesCache = [];
// Saca las compañías tal cual aparecen en tus expedientes recientes, deduplicadas
// (por texto normalizado, para que variantes de mayúsculas/tildes/espacios no salgan
// repetidas), para poder buscarlas y añadirlas como palabra clave sin adivinar el formato.
async function loadDetectedCompanies() {
try {
const res = await fetch(`${API_URL}/services/active`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (!data.ok) return;
const seen = new Map(); // clave normalizada -> primer texto original visto
(data.services || []).forEach(s => {
const raw = s.raw_data || {};
const c = (raw["Compañía"] || raw["COMPAÑIA"] || "").toString().trim();
if (!c) return;
const key = normalizarTextoCfg(c);
if (!seen.has(key)) seen.set(key, c);
});
detectedCompaniesCache = [...seen.values()].sort((a, b) => a.localeCompare(b, 'es'));
document.getElementById('detectedCompaniesBox').classList.toggle('hidden', detectedCompaniesCache.length === 0);
} catch (e) { console.error("Error cargando compañías detectadas", e); }
}
// Filtra el desplegable del combo de búsqueda mientras el usuario escribe.
function renderCompanyOptions(query) {
const list = document.getElementById('companyOptionsList');
const q = normalizarTextoCfg(query);
const matches = detectedCompaniesCache.filter(c => normalizarTextoCfg(c).includes(q));
if (matches.length === 0) {
list.innerHTML = `<div class="px-3 py-2 text-xs text-slate-400 italic">Sin coincidencias</div>`;
} else {
list.innerHTML = matches.map(c => `
<button type="button" onclick='selectDetectedCompany(${JSON.stringify(c)})' class="w-full text-left px-3 py-2 text-xs font-bold text-slate-600 hover:bg-blue-50 hover:text-blue-700 transition-colors border-b border-slate-50 last:border-0">${c}</button>
`).join('');
}
list.classList.remove('hidden');
}
// Al elegir una compañía del combo, se añade debajo su fila de condiciones lista
// para rellenar, y se limpia/cierra el buscador.
function selectDetectedCompany(company) {
addCoverageRule(company);
const input = document.getElementById('companySearchInput');
input.value = "";
document.getElementById('companyOptionsList').classList.add('hidden');
const rows = document.querySelectorAll('#coverageRulesList .coverage-rule-row');
const lastRow = rows[rows.length - 1];
if (lastRow) lastRow.querySelector('.cr-text').focus();
}
document.addEventListener('click', (e) => {
const box = document.getElementById('detectedCompaniesBox');
const optionsList = document.getElementById('companyOptionsList');
if (box && optionsList && !box.contains(e.target)) {
optionsList.classList.add('hidden');
}
});
async function saveWaSettings() {
const coverageRules = [...document.querySelectorAll('#coverageRulesList .coverage-rule-row')]
.map(row => ({
keyword: row.querySelector('.cr-keyword').value.trim(),
text: row.querySelector('.cr-text').value.trim()
}))
.filter(r => r.keyword && r.text);
const settings = {
wa_ai_enabled: document.getElementById('cfg_ai_enabled').checked,
ai_custom_prompt: document.getElementById('ai_custom_prompt') ? document.getElementById('ai_custom_prompt').value : "",
coverage_rules: coverageRules,
wa_delay_enabled: document.getElementById('cfg_delay').checked,
wa_evt_welcome: document.getElementById('cfg_evt_welcome').checked,
wa_evt_assigned: document.getElementById('cfg_evt_assigned').checked,
wa_evt_date: document.getElementById('cfg_evt_date').checked,
wa_evt_onway: document.getElementById('cfg_evt_onway').checked,
wa_evt_finished: document.getElementById('cfg_evt_finished').checked,
wa_evt_survey: document.getElementById('cfg_evt_survey').checked
};
try {
const res = await fetch(`${API_URL}/whatsapp/settings`, {
method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(settings)
});
if (res.ok) {
showToast("✅ Ajustes de mensajería e IA guardados");
} else {
showToast("❌ Error al guardar", true);
}
} catch(e) {
console.error(e);
showToast("❌ Error de conexión", true);
}
}
async function checkWhatsappStatus() {
const container = document.getElementById('waStatusContainer');
container.innerHTML = `<div class="flex flex-col items-center animate-pulse"><div class="w-10 h-10 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin mb-4"></div><span class="text-sm font-bold text-slate-400">Verificando instancia...</span></div>`;
lucide.createIcons();
try {
const res = await fetch(`${API_URL}/whatsapp/status`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (!data.ok) throw new Error(data.error || "Error desconocido");
if (data.state === "open") {
container.innerHTML = `<div class="flex flex-col items-center"><div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-4 shadow-sm border border-green-200"><i data-lucide="check" class="w-8 h-8 text-green-600"></i></div><h4 class="text-xl font-bold text-green-700">Conectado</h4><p class="text-xs text-green-600 mt-1 font-medium bg-green-50 px-2 py-1 rounded">Instancia: ${data.instanceName}</p></div>`;
} else if (data.qr) {
container.innerHTML = `<div class="flex flex-col items-center w-full"><div id="qrcode" class="bg-white p-3 border rounded-xl shadow-sm"></div><p class="text-xs text-orange-500 mt-4 font-bold animate-pulse">Esperando escaneo...</p></div>`;
if(data.qr.startsWith('data:image')) { document.getElementById('qrcode').innerHTML = `<img src="${data.qr}" class="w-48 h-48 rounded-lg">`; }
else { new QRCode(document.getElementById("qrcode"), { text: data.qr, width: 180, height: 180 }); }
} else { container.innerHTML = `<p class="text-orange-500 font-bold">Iniciando... intenta de nuevo en unos segundos.</p>`; }
} catch(e) {
container.innerHTML = `<div class="text-center text-red-500 p-4"><i data-lucide="alert-triangle" class="w-10 h-10 mx-auto mb-2 opacity-80"></i><p class="font-bold">Error de conexión</p><p class="text-xs mt-1 bg-red-50 p-2 rounded">${e.message}</p></div>`;
}
lucide.createIcons();
}
// ==========================================
// LÓGICA PLANTILLAS
// ==========================================
async function loadTemplates() {
try {
const res = await fetch(`${API_URL}/templates`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if(data.ok) {
data.templates.forEach(t => { cachedTemplates[t.type] = t.content; });
if (!currentTemplateType) { const firstBtn = document.querySelector('.tpl-btn'); if(firstBtn) firstBtn.click(); }
}
} catch(e) { console.error("Error templates"); }
}
function selectTemplate(type, btn) {
currentTemplateType = type;
document.querySelectorAll('.tpl-btn').forEach(b => {
b.classList.remove('bg-blue-50', 'ring-1', 'ring-blue-200');
const s = b.querySelector('span');
if(s.classList.contains('bg-blue-600')) s.classList.replace('bg-blue-600', 'bg-blue-100'), s.classList.replace('text-white', 'text-blue-600');
if(s.classList.contains('bg-orange-600')) s.classList.replace('bg-orange-600', 'bg-orange-100'), s.classList.replace('text-white', 'text-orange-600');
});
btn.classList.add('bg-blue-50', 'ring-1', 'ring-blue-200');
const span = btn.querySelector('span');
if (span.classList.contains('bg-blue-100')) span.classList.replace('bg-blue-100', 'bg-blue-600'), span.classList.replace('text-blue-600', 'text-white');
else if (span.classList.contains('bg-orange-100')) span.classList.replace('bg-orange-100', 'bg-orange-600'), span.classList.replace('text-orange-600', 'text-white');
document.getElementById('editorTitle').innerText = btn.innerText.trim();
document.getElementById('tplContent').value = cachedTemplates[type] || "";
}
function insertVar(text) {
const textarea = document.getElementById('tplContent');
if (!textarea) return;
const start = textarea.selectionStart, end = textarea.selectionEnd, val = textarea.value;
textarea.value = val.substring(0, start) + text + val.substring(end);
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = start + text.length;
}
async function saveTemplate() {
if(!currentTemplateType) { showToast("Selecciona plantilla", true); return; }
const content = document.getElementById('tplContent').value;
try {
const res = await fetch(`${API_URL}/templates`, {
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ type: currentTemplateType, content: content })
});
if(res.ok) { cachedTemplates[currentTemplateType] = content; showToast("Guardado"); } else { showToast("Error", true); }
} catch(e) { showToast("Error", true); }
}
// ==========================================
// LÓGICA OTROS (COMPAÑÍAS Y ESTADOS)
// ==========================================
async function loadCompanies() {
const list = document.getElementById('listCompanies');
list.innerHTML = '<p class="text-center text-xs text-gray-400 mt-10">Cargando...</p>';
try {
const res = await fetch(`${API_URL}/companies`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
list.innerHTML = "";
if(data.companies.length === 0) { list.innerHTML = '<p class="text-center text-xs text-gray-400 mt-4">Sin compañías.</p>'; return; }
data.companies.forEach(c => {
const div = document.createElement('div');
div.className = "p-3 border border-gray-100 rounded-lg bg-gray-50 flex justify-between items-center group hover:border-blue-200 transition-colors";
div.innerHTML = `<span class="font-bold text-gray-700 text-sm">${c.name}</span><button onclick="deleteCompany(${c.id})" class="text-gray-300 hover:text-red-500"><i data-lucide="trash-2" class="w-4 h-4"></i></button>`;
list.appendChild(div);
});
lucide.createIcons();
} catch(e) { list.innerHTML = '<p class="text-red-500 text-xs text-center">Error</p>'; }
}
async function addCompany() {
const input = document.getElementById('newCompanyInput'), name = input.value.trim();
if(!name) return;
try {
const res = await fetch(`${API_URL}/companies`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ name: name }) });
if(res.ok) { showToast("Añadido"); input.value = ""; loadCompanies(); }
} catch(e) { showToast("Error", true); }
}
async function deleteCompany(id) {
if(!confirm("¿Borrar?")) return;
await fetch(`${API_URL}/companies/${id}`, { method: 'DELETE', headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
loadCompanies();
}
async function loadStatusesConfig() {
const list = document.getElementById('listStatuses');
list.innerHTML = '<p class="text-center text-xs text-gray-400 mt-10">Cargando...</p>';
try {
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
list.innerHTML = "";
data.statuses.forEach(s => {
const div = document.createElement('div');
div.className = "p-3 border border-gray-100 rounded-lg bg-gray-50 flex justify-between items-center group hover:border-blue-200 transition-colors";
const actionBtn = s.is_system ? `<i data-lucide="lock" class="w-4 h-4 text-slate-300" title="Estado del sistema"></i>` : `<button onclick="deleteStatus(${s.id})" class="text-gray-300 hover:text-red-500"><i data-lucide="trash-2" class="w-4 h-4"></i></button>`;
div.innerHTML = `<div class="flex items-center gap-3"><div class="w-3 h-3 rounded-full bg-${s.color}-500"></div><span class="font-bold text-gray-700 text-sm">${s.name}</span></div>${actionBtn}`;
list.appendChild(div);
});
lucide.createIcons();
} catch(e) { list.innerHTML = '<p class="text-red-500 text-xs text-center">Error</p>'; }
}
async function addStatus() {
const input = document.getElementById('newStatusInput'), color = document.getElementById('newStatusColor').value, name = input.value.trim();
if(!name) return;
try {
const res = await fetch(`${API_URL}/statuses`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ name: name, color: color }) });
if(res.ok) { showToast("Añadido"); input.value = ""; loadStatusesConfig(); }
} catch(e) { showToast("Error", true); }
}
async function deleteStatus(id) {
if(!confirm("¿Borrar estado?")) return;
try {
const res = await fetch(`${API_URL}/statuses/${id}`, { method: 'DELETE', headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
if(res.ok) { showToast("Estado eliminado"); loadStatusesConfig(); }
else { const j = await res.json(); showToast(j.error || "Error", true); }
} catch(e) { showToast("Error de conexión", true); }
}
// ==========================================
// LÓGICA PROVEEDORES CREDENCIALES
// ==========================================
async function loadProviderCredentials() {
try {
const res = await fetch(`${API_URL}/providers/credentials`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if(data.ok) {
data.credentials.forEach(c => {
const providerName = c.provider.toLowerCase();
const userInp = document.getElementById(`user_${providerName}`);
const autoInp = document.getElementById(`auto_${providerName}`); // <-- Busca el botón
if(userInp) userInp.value = c.username;
if(autoInp) autoInp.checked = c.auto_dispatch || false; // <-- Enciende o apaga
});
}
} catch(e) { console.error("Error creds"); }
}
// ==========================================
// LÓGICA PORTAL CLIENTE
// ==========================================
async function loadPortalConfig() {
try {
// 1. Cargamos TODOS tus gremios de la base de datos
const resGuilds = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const dataGuilds = await resGuilds.json();
let allGuilds = dataGuilds.ok ? dataGuilds.guilds : [];
// 2. Cargamos la configuración del portal
const res = await fetch(`${API_URL}/config/company`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok && data.config) {
document.getElementById('confCompanyName').value = data.config.full_name || "";
if (data.config.company_logo) {
document.getElementById('confLogoBase64').value = data.config.company_logo;
document.getElementById('logoPreview').innerHTML = `<img src="${data.config.company_logo}" class="w-full h-full object-contain">`;
}
if (data.config.company_ad) {
document.getElementById('confAdBase64').value = data.config.company_ad;
document.getElementById('adPreview').innerHTML = `<img src="${data.config.company_ad}" class="w-full h-full object-contain">`;
}
const s = data.config.portal_settings || {};
if(s.m_start) document.getElementById('mStart').value = s.m_start;
if(s.m_end) document.getElementById('mEnd').value = s.m_end;
if(s.a_start) document.getElementById('aStart').value = s.a_start;
if(s.a_end) document.getElementById('aEnd').value = s.a_end;
// 3. Pintamos los checkboxes de los gremios
const visibleGuilds = s.visible_guilds || [];
const container = document.getElementById('portalGuildsList');
container.innerHTML = ''; // Limpiar el "Cargando..."
if(allGuilds.length === 0) {
container.innerHTML = '<p class="text-xs text-slate-400">No tienes gremios creados aún.</p>';
} else {
allGuilds.forEach(g => {
// Si el ID de este gremio está guardado, lo marcamos
const isChecked = visibleGuilds.includes(g.id) ? 'checked' : '';
container.innerHTML += `
<label class="flex items-center gap-3 p-3 bg-white border border-slate-200 rounded-xl cursor-pointer hover:bg-slate-50 hover:border-blue-300 transition-colors shadow-sm">
<input type="checkbox" value="${g.id}" class="portal-guild-cb w-4 h-4 text-blue-600 rounded border-slate-300 focus:ring-blue-500" ${isChecked}>
<span class="text-sm font-bold text-slate-700 truncate">${g.name}</span>
</label>
`;
});
}
}
} catch (e) { console.error("Error portal"); }
}
function encodeLogo(input) {
const file = input.files[0];
if (!file || file.size > 1024 * 1024) { alert("Máximo 1MB"); return; }
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('confLogoBase64').value = e.target.result;
document.getElementById('logoPreview').innerHTML = `<img src="${e.target.result}" class="w-full h-full object-contain">`;
};
reader.readAsDataURL(file);
}
function encodeAd(input) {
const file = input.files[0];
if (!file || file.size > 1024 * 1024) { alert("Máximo 1MB"); return; }
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('confAdBase64').value = e.target.result;
document.getElementById('adPreview').innerHTML = `<img src="${e.target.result}" class="w-full h-full object-contain">`;
};
reader.readAsDataURL(file);
}
async function savePortalConfig() {
const btn = document.getElementById('btnSavePortal'), original = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...';
lucide.createIcons();
// 🛠️ Recogemos todos los checkboxes que estén marcados
const selectedGuilds = Array.from(document.querySelectorAll('.portal-guild-cb:checked')).map(cb => parseInt(cb.value));
const payload = {
company_name: document.getElementById('confCompanyName').value,
company_logo: document.getElementById('confLogoBase64').value,
company_ad: document.getElementById('confAdBase64').value,
portal_settings: {
m_start: document.getElementById('mStart').value,
m_end: document.getElementById('mEnd').value,
a_start: document.getElementById('aStart').value,
a_end: document.getElementById('aEnd').value,
visible_guilds: selectedGuilds // <--- Guardamos los IDs
}
};
try {
const res = await fetch(`${API_URL}/config/company`, {
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(payload)
});
if (res.ok) showToast("Configuración del Portal guardada");
} catch (e) { showToast("Error conexión", true); }
finally { btn.innerHTML = original; lucide.createIcons(); }
}
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);
}
// ==========================================
// LÓGICA REGLAS ROBOTS (HOMESERVE Y MULTI)
// ==========================================
function toggleHsConfig(type) {
const isChecked = document.getElementById(`hs_cfg_${type}_enabled`).checked;
const body = document.getElementById(`hs_cfg_${type}_body`);
if (isChecked) body.classList.remove('hidden');
else body.classList.add('hidden');
}
function toggleMultiConfig(type) {
const isChecked = document.getElementById(`multi_cfg_${type}_enabled`).checked;
const body = document.getElementById(`multi_cfg_${type}_body`);
if (isChecked) body.classList.remove('hidden');
else body.classList.add('hidden');
}
async function loadRobotConfig() {
try {
const res = await fetch(`${API_URL}/whatsapp/settings`, {
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
const data = await res.json();
const s = data.settings || {};
// Cargar HomeServe
const hsSettings = s.robot_homeserve || {};
if (hsSettings.assign) {
document.getElementById('hs_cfg_assign_enabled').checked = hsSettings.assign.enabled;
document.getElementById('hs_cfg_assign_status').value = hsSettings.assign.status || "307";
document.getElementById('hs_cfg_assign_days').value = hsSettings.assign.days_next ?? 1;
document.getElementById('hs_cfg_assign_inform').checked = hsSettings.assign.check_inform || false;
document.getElementById('hs_cfg_assign_obs').value = hsSettings.assign.obs || "";
if (hsSettings.assign.enabled) toggleHsConfig('assign');
}
if (hsSettings.date) {
document.getElementById('hs_cfg_date_enabled').checked = hsSettings.date.enabled;
document.getElementById('hs_cfg_date_status').value = hsSettings.date.status || "307";
document.getElementById('hs_cfg_date_inform').checked = hsSettings.date.check_inform || false;
document.getElementById('hs_cfg_date_obs').value = hsSettings.date.obs || "";
if (hsSettings.date.enabled) toggleHsConfig('date');
}
if (hsSettings.notfound) {
document.getElementById('hs_cfg_notfound_enabled').checked = hsSettings.notfound.enabled;
document.getElementById('hs_cfg_notfound_status').value = hsSettings.notfound.status || "348";
document.getElementById('hs_cfg_notfound_days').value = hsSettings.notfound.days_next ?? 1;
document.getElementById('hs_cfg_notfound_inform').checked = hsSettings.notfound.check_inform || false;
document.getElementById('hs_cfg_notfound_obs').value = hsSettings.notfound.obs || "";
if (hsSettings.notfound.enabled) toggleHsConfig('notfound');
}
// Cargar Multiasistencia
const multiSettings = s.robot_multi || {};
if (multiSettings.assign) {
document.getElementById('multi_cfg_assign_enabled').checked = multiSettings.assign.enabled;
document.getElementById('multi_cfg_assign_status').value = multiSettings.assign.status || "1";
document.getElementById('multi_cfg_assign_days').value = multiSettings.assign.days_next ?? 1;
document.getElementById('multi_cfg_assign_obs').value = multiSettings.assign.obs || "";
if (multiSettings.assign.enabled) toggleMultiConfig('assign');
}
if (multiSettings.date) {
document.getElementById('multi_cfg_date_enabled').checked = multiSettings.date.enabled;
document.getElementById('multi_cfg_date_status').value = multiSettings.date.status || "2";
document.getElementById('multi_cfg_date_obs').value = multiSettings.date.obs || "";
if (multiSettings.date.enabled) toggleMultiConfig('date');
}
if (multiSettings.notfound) {
document.getElementById('multi_cfg_notfound_enabled').checked = multiSettings.notfound.enabled;
document.getElementById('multi_cfg_notfound_status').value = multiSettings.notfound.status || "37";
document.getElementById('multi_cfg_notfound_days').value = multiSettings.notfound.days_next ?? 1;
document.getElementById('multi_cfg_notfound_obs').value = multiSettings.notfound.obs || "";
if (multiSettings.notfound.enabled) toggleMultiConfig('notfound');
}
} catch(e) { console.error("Error cargando ajustes del robot", e); }
}
async function saveProviderCreds(event, provider) {
event.preventDefault();
const btn = event.submitter;
const originalText = btn.innerHTML;
const username = document.getElementById(`user_${provider}`).value;
const password = document.getElementById(`pass_${provider}`).value;
const auto_dispatch = document.getElementById(`auto_${provider}`).checked;
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...';
lucide.createIcons();
try {
const res = await fetch(`${API_URL}/providers/credentials`, {
method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ provider, username, password, auto_dispatch })
});
if (res.ok) {
showToast(`✅ Acceso de ${provider} actualizado`);
} else {
showToast("❌ Error al guardar acceso", true);
}
} catch (e) {
showToast("❌ Error de red", true);
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
lucide.createIcons();
}
}
async function saveRobotConfig() {
const btn = document.querySelector('button[onclick="saveRobotConfig()"]');
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...';
lucide.createIcons();
const payload = {
robot_homeserve: {
assign: {
enabled: document.getElementById('hs_cfg_assign_enabled').checked,
status: document.getElementById('hs_cfg_assign_status').value,
days_next: parseInt(document.getElementById('hs_cfg_assign_days').value) || 0,
check_inform: document.getElementById('hs_cfg_assign_inform').checked,
obs: document.getElementById('hs_cfg_assign_obs').value
},
date: {
enabled: document.getElementById('hs_cfg_date_enabled').checked,
status: document.getElementById('hs_cfg_date_status').value,
check_inform: document.getElementById('hs_cfg_date_inform').checked,
obs: document.getElementById('hs_cfg_date_obs').value
},
notfound: {
enabled: document.getElementById('hs_cfg_notfound_enabled').checked,
status: document.getElementById('hs_cfg_notfound_status').value,
days_next: parseInt(document.getElementById('hs_cfg_notfound_days').value) || 0,
check_inform: document.getElementById('hs_cfg_notfound_inform').checked,
obs: document.getElementById('hs_cfg_notfound_obs').value
}
},
robot_multi: {
assign: {
enabled: document.getElementById('multi_cfg_assign_enabled').checked,
status: document.getElementById('multi_cfg_assign_status').value,
days_next: parseInt(document.getElementById('multi_cfg_assign_days').value) || 0,
obs: document.getElementById('multi_cfg_assign_obs').value
},
date: {
enabled: document.getElementById('multi_cfg_date_enabled').checked,
status: document.getElementById('multi_cfg_date_status').value,
obs: document.getElementById('multi_cfg_date_obs').value
},
notfound: {
enabled: document.getElementById('multi_cfg_notfound_enabled').checked,
status: document.getElementById('multi_cfg_notfound_status').value,
days_next: parseInt(document.getElementById('multi_cfg_notfound_days').value) || 0,
obs: document.getElementById('multi_cfg_notfound_obs').value
}
}
};
try {
const res = await fetch(`${API_URL}/whatsapp/settings`, {
method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(payload)
});
if (res.ok) {
showToast("✅ Reglas de los Robots guardadas");
} else {
showToast("❌ Error al guardar", true);
}
} catch(e) {
showToast("❌ Error de conexión", true);
} finally {
btn.innerHTML = originalHtml;
lucide.createIcons();
}
}
// ==========================================
// LÓGICA DATOS DE EMPRESA / FACTURACIÓN
// ==========================================
async function loadBillingConfig() {
try {
const res = await fetch(`${API_URL}/config/company`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok && data.config && data.config.billing_settings) {
const s = data.config.billing_settings;
document.getElementById('billName').value = s.name || "";
document.getElementById('billDni').value = s.dni || "";
document.getElementById('billAddress').value = s.address || "";
document.getElementById('billCity').value = s.city || "";
document.getElementById('billState').value = s.state || "";
document.getElementById('billZip').value = s.zip || "";
document.getElementById('billIban').value = s.iban || "";
document.getElementById('billObs').value = s.obs || "";
// Cargar Stripe
document.getElementById('stripe_enabled').checked = s.stripe_enabled || false;
document.getElementById('stripe_pk').value = s.stripe_pk || "";
document.getElementById('stripe_sk').value = s.stripe_sk || "";
}
} catch (e) { console.error("Error cargando datos de facturación", e); }
}
async function saveBillingConfig() {
const btn = document.getElementById('btnSaveBilling');
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...';
lucide.createIcons();
const settings = {
name: document.getElementById('billName').value,
dni: document.getElementById('billDni').value,
address: document.getElementById('billAddress').value,
city: document.getElementById('billCity').value,
state: document.getElementById('billState').value,
zip: document.getElementById('billZip').value,
iban: document.getElementById('billIban').value,
obs: document.getElementById('billObs').value,
// Guardar Stripe
stripe_enabled: document.getElementById('stripe_enabled').checked,
stripe_pk: document.getElementById('stripe_pk').value.trim(),
stripe_sk: document.getElementById('stripe_sk').value.trim()
};
try {
const res = await fetch(`${API_URL}/config/company`, {
method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ billing_settings: settings })
});
if (res.ok) {
showToast("✅ Datos de empresa guardados");
} else {
showToast("❌ Error al guardar", true);
}
} catch (e) {
showToast("❌ Error de conexión", true);
} finally {
btn.innerHTML = originalHtml;
lucide.createIcons();
}
}
</script>
</body>
</html>