400 lines
31 KiB
HTML
400 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Seguros SaaS - IntegraRepara</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
.fade-in { animation: fadeIn 0.2s ease-in-out; }
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
|
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
.tab-btn { transition: all 0.3s ease; border-bottom: 2px solid transparent; }
|
|
.tab-btn.active { color: #2563eb; border-color: #2563eb; background-color: #eff6ff; }
|
|
.modal-backdrop { background: rgba(15, 23, 42, 0.45); backdrop-filter: blur(4px); }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-100 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 min-w-0">
|
|
<div id="header-container"></div>
|
|
|
|
<main class="flex-1 flex flex-col overflow-hidden relative p-6 space-y-6">
|
|
|
|
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4 bg-white p-6 rounded-2xl shadow-sm border border-gray-200 shrink-0">
|
|
<div class="flex items-center gap-4">
|
|
<div class="w-12 h-12 bg-blue-600 rounded-xl flex items-center justify-center text-white shadow-lg">
|
|
<i data-lucide="shield-check" class="w-7 h-7"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-xl font-black text-gray-800 tracking-tight">Seguros / Planes de Protección</h1>
|
|
<p class="text-gray-400 text-xs font-bold uppercase tracking-widest">MÓDULO SAAS PARA ASEGURADORAS Y SUSCRIPCIONES</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
<button onclick="openPlanModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest shadow-md hover:bg-blue-700 transition-all flex items-center gap-2">
|
|
<i data-lucide="plus" class="w-3.5 h-3.5"></i> Nuevo Plan
|
|
</button>
|
|
<button onclick="openClientModal()" class="bg-white border border-gray-200 text-gray-700 px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest shadow-sm hover:border-blue-300 transition-all flex items-center gap-2">
|
|
<i data-lucide="user-plus" class="w-3.5 h-3.5"></i> Nuevo Suscriptor
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex bg-gray-100 p-1 rounded-xl shrink-0 w-fit">
|
|
<button onclick="switchTab('dashboard')" id="btn-tab-dashboard" class="tab-btn active px-5 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center gap-2">
|
|
<i data-lucide="layout-dashboard" class="w-3 h-3"></i> Resumen
|
|
</button>
|
|
<button onclick="switchTab('clients')" id="btn-tab-clients" class="tab-btn px-5 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center gap-2">
|
|
<i data-lucide="users" class="w-3 h-3"></i> Suscriptores
|
|
</button>
|
|
<button onclick="switchTab('plans')" id="btn-tab-plans" class="tab-btn px-5 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center gap-2">
|
|
<i data-lucide="badge-euro" class="w-3 h-3"></i> Planes
|
|
</button>
|
|
<button onclick="switchTab('config')" id="btn-tab-config" class="tab-btn px-5 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center gap-2">
|
|
<i data-lucide="settings" class="w-3 h-3"></i> Configuración
|
|
</button>
|
|
</div>
|
|
|
|
<div id="tab-dashboard" class="tab-content flex-1 flex flex-col min-h-0 fade-in overflow-hidden">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 mb-6 shrink-0" id="statsGrid"></div>
|
|
|
|
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6 flex-1 min-h-0">
|
|
<div class="xl:col-span-2 bg-white rounded-2xl shadow-sm border border-gray-200 flex flex-col overflow-hidden">
|
|
<div class="p-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50 shrink-0">
|
|
<div>
|
|
<h3 class="text-sm font-black text-gray-800">Actividad reciente</h3>
|
|
<p class="text-[11px] text-gray-400 font-semibold">Altas, cobros, renovaciones y uso de coberturas</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 overflow-y-auto no-scrollbar p-4 space-y-3" id="activityList"></div>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 flex flex-col overflow-hidden">
|
|
<div class="p-4 border-b border-gray-100 bg-gray-50/50">
|
|
<h3 class="text-sm font-black text-gray-800">Planes más vendidos</h3>
|
|
<p class="text-[11px] text-gray-400 font-semibold">Resumen rápido del catálogo</p>
|
|
</div>
|
|
<div class="flex-1 overflow-y-auto no-scrollbar p-4 space-y-4" id="topPlansList"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tab-clients" class="tab-content hidden flex-1 flex flex-col min-h-0 fade-in">
|
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6 shrink-0">
|
|
<div class="relative w-full md:w-80">
|
|
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-2.5 text-gray-400"></i>
|
|
<input id="searchClientInput" type="text" placeholder="Buscar suscriptor..." class="w-full pl-10 pr-4 py-2 bg-white border border-gray-200 rounded-lg text-xs font-bold outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 bg-white rounded-2xl shadow-sm border border-gray-200 flex flex-col overflow-hidden">
|
|
<div class="flex-1 overflow-y-auto no-scrollbar">
|
|
<table class="w-full text-left">
|
|
<thead class="sticky top-0 bg-white shadow-sm text-[10px] font-black text-gray-400 uppercase tracking-widest">
|
|
<tr>
|
|
<th class="px-6 py-4">Socio / Datos</th>
|
|
<th class="px-6 py-4">Plan / Cuota</th>
|
|
<th class="px-6 py-4 text-center">Uso (Bricos/Urg)</th>
|
|
<th class="px-6 py-4">Estado Pago</th>
|
|
<th class="px-6 py-4">Renovación</th>
|
|
<th class="px-6 py-4 text-right">Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="clientsTableBody" class="text-sm font-bold text-gray-600 divide-y divide-gray-50"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tab-plans" class="tab-content hidden flex-1 min-h-0 fade-in overflow-y-auto no-scrollbar pb-10">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6" id="plansGrid"></div>
|
|
</div>
|
|
|
|
<div id="tab-config" class="tab-content hidden flex-1 min-h-0 fade-in overflow-y-auto no-scrollbar pb-10">
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
<div class="lg:col-span-1 space-y-6">
|
|
<div class="bg-white p-6 rounded-2xl border border-gray-200 shadow-sm">
|
|
<h3 class="text-xs font-black text-gray-800 uppercase tracking-widest mb-4 flex items-center gap-2 border-b pb-3"><i data-lucide="building-2" class="w-4 h-4 text-blue-600"></i> Datos Empresa</h3>
|
|
<div class="space-y-4">
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Nombre</label><input id="cfg_name" type="text" class="w-full px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Email</label><input id="cfg_email" type="text" class="w-full px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Teléfono</label><input id="cfg_phone" type="text" class="w-full px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white p-6 rounded-2xl border border-gray-200 shadow-sm">
|
|
<h3 class="text-xs font-black text-gray-800 uppercase tracking-widest mb-4 flex items-center gap-2 border-b pb-3 text-amber-600"><i data-lucide="refresh-cw" class="w-4 h-4"></i> Renovaciones</h3>
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between bg-gray-50 rounded-xl px-4 py-3"><span class="text-xs font-black text-gray-600 uppercase">Automática</span><input id="cfg_auto_renew" type="checkbox" class="w-5 h-5"></div>
|
|
<div class="flex items-center justify-between bg-gray-50 rounded-xl px-4 py-3"><span class="text-xs font-black text-gray-600 uppercase">Aviso previo</span><input id="cfg_pre_notice" type="checkbox" class="w-5 h-5"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white p-6 rounded-2xl border border-gray-200 shadow-sm">
|
|
<h3 class="text-xs font-black text-gray-800 uppercase tracking-widest mb-4 flex items-center gap-2 border-b pb-3 text-blue-600"><i data-lucide="credit-card" class="w-4 h-4"></i> Cobro</h3>
|
|
<div>
|
|
<select id="cfg_billing_method" class="w-full px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm">
|
|
<option value="stripe">Stripe</option>
|
|
<option value="recibo">Recibo</option>
|
|
<option value="transferencia">Transferencia</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="lg:col-span-2">
|
|
<div class="bg-white p-6 rounded-2xl border border-gray-200 shadow-sm h-full flex flex-col">
|
|
<div class="flex justify-between items-center mb-4 border-b pb-3">
|
|
<h3 class="text-xs font-black text-gray-800 uppercase tracking-widest flex items-center gap-2"><i data-lucide="file-signature" class="w-4 h-4 text-blue-600"></i> Contrato Global</h3>
|
|
<button onclick="saveConfig()" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest shadow-md hover:bg-blue-700 transition-all flex items-center gap-2"><i data-lucide="save" class="w-3.5 h-3.5"></i> Guardar Cambios</button>
|
|
</div>
|
|
<textarea id="cfg_contract" class="flex-1 w-full p-6 bg-gray-50 border rounded-2xl text-xs font-medium leading-relaxed text-gray-500 focus:ring-2 focus:ring-blue-500 outline-none min-h-[400px]"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<div id="modalBackdrop" class="hidden fixed inset-0 z-40 modal-backdrop" onclick="closeModals()"></div>
|
|
|
|
<div id="planModal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
|
|
<div class="bg-white w-full max-w-2xl rounded-2xl shadow-2xl border border-gray-200 overflow-hidden pointer-events-auto">
|
|
<div class="p-5 border-b border-gray-100 flex items-center justify-between">
|
|
<h3 class="text-sm font-black text-gray-800 uppercase tracking-widest">Nuevo Plan</h3>
|
|
<button onclick="closeModals()" class="text-gray-400 hover:text-gray-700"><i data-lucide="x" class="w-5 h-5"></i></button>
|
|
</div>
|
|
<div class="p-5 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Nombre</label><input id="plan_name" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Tipo</label><select id="plan_type" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"><option value="mensual">Mensual</option><option value="anual">Anual</option></select></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Precio Oferta (€)</label><input id="plan_price" type="number" step="0.01" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Precio Renovación (€)</label><input id="plan_renewal" type="number" step="0.01" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Urgencias / año</label><input id="plan_urgencies" type="number" value="0" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Bricos / año</label><input id="plan_bricos" type="number" value="0" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div class="md:col-span-2"><label class="text-[10px] font-black text-gray-400 uppercase">Coberturas (separadas por coma)</label><input id="plan_coverages" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm" placeholder="Electricidad, Fontanería"></div>
|
|
</div>
|
|
<div class="p-5 border-t border-gray-100 flex justify-end gap-2 bg-gray-50/50">
|
|
<button onclick="closeModals()" class="px-4 py-2 rounded-lg border border-gray-200 text-[10px] font-black uppercase tracking-widest">Cancelar</button>
|
|
<button onclick="savePlan()" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-[10px] font-black uppercase tracking-widest shadow-md">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="clientModal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
|
|
<div class="bg-white w-full max-w-3xl rounded-2xl shadow-2xl border border-gray-200 overflow-hidden pointer-events-auto">
|
|
<div class="p-5 border-b border-gray-100 flex items-center justify-between">
|
|
<h3 class="text-sm font-black text-gray-800 uppercase tracking-widest">Nuevo Suscriptor</h3>
|
|
<button onclick="closeModals()" class="text-gray-400 hover:text-gray-700"><i data-lucide="x" class="w-5 h-5"></i></button>
|
|
</div>
|
|
<div class="p-5 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Nombre completo</label><input id="client_name" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">DNI</label><input id="client_dni" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Teléfono</label><input id="client_phone" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Plan</label><select id="client_plan" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></select></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Estado pago</label><select id="client_payment_status" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"><option value="pagado">Pagado</option><option value="impagado">Impagado</option></select></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Estado</label><select id="client_status" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"><option value="activo">Activo</option><option value="suspendido">Suspendido</option></select></div>
|
|
<div><label class="text-[10px] font-black text-gray-400 uppercase">Renovación</label><input id="client_renewal" type="date" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm"></div>
|
|
<div class="flex gap-2">
|
|
<div class="flex-1"><label class="text-[10px] font-black text-gray-400 uppercase">Bricos consumidos</label><input id="client_bricos" type="number" value="0" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm" title="Solo cambiar si el cliente ya ha gastado bricos de su plan este año"></div>
|
|
<div class="flex-1"><label class="text-[10px] font-black text-gray-400 uppercase">Urg. gastadas</label><input id="client_urg" type="number" value="0" class="w-full mt-1 px-4 py-2.5 bg-gray-50 border rounded-xl font-bold text-sm" title="Solo cambiar si el cliente ya ha gastado urgencias de su plan este año"></div> </div>
|
|
</div>
|
|
<div class="p-5 border-t border-gray-100 flex justify-end gap-2 bg-gray-50/50">
|
|
<button onclick="closeModals()" class="px-4 py-2 rounded-lg border border-gray-200 text-[10px] font-black uppercase tracking-widest">Cancelar</button>
|
|
<button onclick="saveClient()" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-[10px] font-black uppercase tracking-widest shadow-md">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="js/layout.js"></script>
|
|
<script>
|
|
let allClients = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadAllData();
|
|
document.getElementById('searchClientInput').addEventListener('input', renderClients);
|
|
if(window.lucide) lucide.createIcons();
|
|
});
|
|
|
|
async function fetchAPI(endpoint, method = 'GET', body = null) {
|
|
const options = { method, headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}`, 'Content-Type': 'application/json' }};
|
|
if (body) options.body = JSON.stringify(body);
|
|
const res = await fetch(`${API_URL}${endpoint}`, options);
|
|
return res.json();
|
|
}
|
|
|
|
async function loadAllData() {
|
|
const [dashRes, plansRes, clientsRes, confRes] = await Promise.all([
|
|
fetchAPI('/protection/dashboard'),
|
|
fetchAPI('/protection/plans'),
|
|
fetchAPI('/protection/subscribers'),
|
|
fetchAPI('/protection/config')
|
|
]);
|
|
|
|
if(dashRes.ok) {
|
|
renderStats(dashRes.stats);
|
|
renderActivity(dashRes.activity);
|
|
renderTopPlans(dashRes.topPlans);
|
|
}
|
|
if(plansRes.ok) {
|
|
renderPlans(plansRes.plans);
|
|
const sel = document.getElementById('client_plan');
|
|
sel.innerHTML = plansRes.plans.map(p => `<option value="${p.id}">${p.name} (${p.price}€)</option>`).join('');
|
|
}
|
|
if(clientsRes.ok) {
|
|
allClients = clientsRes.subscribers;
|
|
renderClients();
|
|
}
|
|
if(confRes.ok && confRes.config) {
|
|
document.getElementById('cfg_name').value = confRes.config.name || '';
|
|
document.getElementById('cfg_email').value = confRes.config.email || '';
|
|
document.getElementById('cfg_phone').value = confRes.config.phone || '';
|
|
document.getElementById('cfg_auto_renew').checked = !!confRes.config.auto_renew;
|
|
document.getElementById('cfg_pre_notice').checked = !!confRes.config.pre_notice;
|
|
document.getElementById('cfg_billing_method').value = confRes.config.billing_method || 'stripe';
|
|
document.getElementById('cfg_contract').value = confRes.config.contract_text || '';
|
|
}
|
|
if(window.lucide) lucide.createIcons();
|
|
}
|
|
|
|
async function savePlan() {
|
|
const data = {
|
|
name: document.getElementById('plan_name').value,
|
|
type: document.getElementById('plan_type').value,
|
|
price: parseFloat(document.getElementById('plan_price').value || 0),
|
|
renewal: parseFloat(document.getElementById('plan_renewal').value || 0),
|
|
urgencies: parseInt(document.getElementById('plan_urgencies').value || 0),
|
|
bricos: parseInt(document.getElementById('plan_bricos').value || 0),
|
|
coverages: document.getElementById('plan_coverages').value
|
|
};
|
|
if(!data.name) return toast('Falta el nombre');
|
|
await fetchAPI('/protection/plans', 'POST', data);
|
|
closeModals(); loadAllData(); toast('Plan creado');
|
|
}
|
|
|
|
async function saveClient() {
|
|
const data = {
|
|
plan_id: document.getElementById('client_plan').value,
|
|
name: document.getElementById('client_name').value,
|
|
dni: document.getElementById('client_dni').value,
|
|
phone: document.getElementById('client_phone').value,
|
|
payment_status: document.getElementById('client_payment_status').value,
|
|
status: document.getElementById('client_status').value,
|
|
renewal_date: document.getElementById('client_renewal').value,
|
|
bricos_used: document.getElementById('client_bricos').value,
|
|
urgencies_used: document.getElementById('client_urg').value
|
|
};
|
|
if(!data.name || !data.plan_id) return toast('Faltan datos clave');
|
|
await fetchAPI('/protection/subscribers', 'POST', data);
|
|
closeModals(); loadAllData(); toast('Suscriptor creado');
|
|
}
|
|
|
|
async function saveConfig() {
|
|
const data = {
|
|
name: document.getElementById('cfg_name').value,
|
|
email: document.getElementById('cfg_email').value,
|
|
phone: document.getElementById('cfg_phone').value,
|
|
auto_renew: document.getElementById('cfg_auto_renew').checked,
|
|
pre_notice: document.getElementById('cfg_pre_notice').checked,
|
|
billing_method: document.getElementById('cfg_billing_method').value,
|
|
contract_text: document.getElementById('cfg_contract').value
|
|
};
|
|
await fetchAPI('/protection/config', 'POST', data);
|
|
toast('Configuración guardada');
|
|
}
|
|
|
|
async function toggleStatus(id, field, currentValue) {
|
|
const newValue = (field === 'payment_status')
|
|
? (currentValue === 'pagado' ? 'impagado' : 'pagado')
|
|
: (currentValue === 'activo' ? 'suspendido' : 'activo');
|
|
await fetchAPI(`/protection/subscribers/${id}/toggle`, 'PUT', { field, value: newValue });
|
|
loadAllData();
|
|
}
|
|
|
|
function renderStats(s) {
|
|
document.getElementById('statsGrid').innerHTML = `
|
|
<div class="bg-white p-4 rounded-2xl border border-gray-200 shadow-sm"><p class="text-[9px] font-black text-gray-400 uppercase mb-1">Total Asegurados</p><p class="text-xl font-black text-gray-800">${s.total || 0}</p></div>
|
|
<div class="bg-white p-4 rounded-2xl border border-gray-200 shadow-sm"><p class="text-[9px] font-black text-gray-400 uppercase mb-1">Ingresos MRR</p><p class="text-xl font-black text-blue-600">${Number(s.mrr || 0).toFixed(2)}€</p></div>
|
|
<div class="bg-white p-4 rounded-2xl border border-gray-200 shadow-sm"><p class="text-[9px] font-black text-gray-400 uppercase mb-1">Urgencias/Bricos Mes</p><p class="text-xl font-black text-amber-500">${s.urgUsed || 0} / ${s.briUsed || 0}</p></div>
|
|
<div class="bg-white p-4 rounded-2xl border border-gray-200 shadow-sm"><p class="text-[9px] font-black text-gray-400 uppercase mb-1">Pagos Fallidos</p><p class="text-xl font-black text-rose-500">${s.unpaid || 0}</p></div>
|
|
`;
|
|
}
|
|
|
|
function renderActivity(acts) {
|
|
document.getElementById('activityList').innerHTML = acts.map(a => `
|
|
<div class="p-4 rounded-xl border border-gray-100 bg-white flex items-start gap-3">
|
|
<div class="w-9 h-9 rounded-xl bg-blue-50 text-blue-600 flex items-center justify-center shrink-0"><i data-lucide="${a.type==='alta'?'user-plus':(a.type==='cobro'?'credit-card':'alert-circle')}" class="w-4 h-4"></i></div>
|
|
<div><p class="text-sm font-black text-gray-800">${a.description}</p><p class="text-[11px] font-semibold text-gray-400">${new Date(a.created_at).toLocaleDateString()}</p></div>
|
|
</div>
|
|
`).join('') || '<p class="text-xs text-gray-400">Sin actividad</p>';
|
|
}
|
|
|
|
function renderTopPlans(plans) {
|
|
document.getElementById('topPlansList').innerHTML = plans.map(p => `
|
|
<div class="p-4 rounded-2xl border border-gray-100 bg-gray-50 flex items-center justify-between">
|
|
<div><p class="text-sm font-black text-gray-800">${p.name}</p><p class="text-[11px] text-gray-400 font-semibold">${p.type.toUpperCase()} · ${p.price}€</p></div>
|
|
<span class="px-2 py-1 rounded-lg bg-white text-blue-700 text-[10px] font-black uppercase tracking-widest">${p.users} socios</span>
|
|
</div>
|
|
`).join('') || '<p class="text-xs text-gray-400">Sin planes</p>';
|
|
}
|
|
|
|
function renderPlans(plans) {
|
|
document.getElementById('plansGrid').innerHTML = plans.map(p => `
|
|
<div class="bg-white rounded-2xl border border-gray-200 shadow-sm p-6">
|
|
<h3 class="text-lg font-black text-gray-900">${p.name}</h3><p class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-4">${p.type}</p>
|
|
<div class="space-y-3 text-sm font-bold text-gray-600">
|
|
<div class="flex justify-between"><span>Cuota</span><span class="text-blue-600">${p.price}€</span></div>
|
|
<div class="flex justify-between"><span>Renovación</span><span class="text-amber-600">${p.renewal_price}€</span></div>
|
|
<div class="flex justify-between"><span>Urg / Bricos</span><span>${p.urgencies_limit} / ${p.bricos_limit}</span></div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function renderClients() {
|
|
const search = document.getElementById('searchClientInput').value.toLowerCase();
|
|
const filtered = allClients.filter(c => c.client_name.toLowerCase().includes(search) || (c.client_dni||'').toLowerCase().includes(search));
|
|
|
|
document.getElementById('clientsTableBody').innerHTML = filtered.map(c => {
|
|
const prog = c.urgencies_limit ? Math.min(100, (c.urgencies_used / c.urgencies_limit) * 100) : 0;
|
|
return `
|
|
<tr class="hover:bg-blue-50/50 transition-colors">
|
|
<td class="px-6 py-4"><div class="flex items-center gap-3"><div class="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-xs font-black uppercase">${c.client_name.substring(0,2)}</div><div class="flex flex-col"><span class="text-sm font-black">${c.client_name}</span><span class="text-[9px] font-medium text-gray-400">DNI: ${c.client_dni||'-'} • ${c.client_phone||'-'}</span></div></div></td>
|
|
<td class="px-6 py-4 text-xs font-black text-gray-500"><span class="bg-blue-50 text-blue-700 px-2 py-1 rounded-md uppercase">${c.plan_name||'Sin Plan'}</span></td>
|
|
<td class="px-6 py-4"><div class="flex flex-col items-center gap-1"><span class="text-[10px] text-gray-400">Bricos: ${c.bricos_used}/${c.bricos_limit||0} | Urg: ${c.urgencies_used}/${c.urgencies_limit||0}</span><div class="w-24 h-1.5 bg-gray-100 rounded-full overflow-hidden"><div class="h-full bg-blue-500" style="width:${prog}%"></div></div></div></td>
|
|
<td class="px-6 py-4"><span class="${c.payment_status==='pagado'?'text-emerald-500':'text-rose-500'} font-black text-[10px] uppercase tracking-widest flex items-center gap-1.5"><div class="w-1.5 h-1.5 rounded-full ${c.payment_status==='pagado'?'bg-emerald-500':'bg-rose-500'} animate-pulse"></div>${c.payment_status}</span></td>
|
|
<td class="px-6 py-4 text-xs font-black text-gray-500">${c.renewal_date ? new Date(c.renewal_date).toLocaleDateString() : '-'}</td>
|
|
<td class="px-6 py-4 text-right">
|
|
<button onclick="toggleStatus(${c.id}, 'payment_status', '${c.payment_status}')" class="p-2 text-gray-400 hover:text-blue-600" title="Cambiar Pago"><i data-lucide="credit-card" class="w-4 h-4"></i></button>
|
|
<button onclick="toggleStatus(${c.id}, 'status', '${c.status}')" class="p-2 text-gray-400 hover:text-amber-600" title="Cambiar Estado"><i data-lucide="power" class="w-4 h-4"></i></button>
|
|
</td>
|
|
</tr>`;
|
|
}).join('');
|
|
if(window.lucide) lucide.createIcons();
|
|
}
|
|
|
|
// Utilidades UI
|
|
function switchTab(tab) {
|
|
document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
|
|
document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
|
|
document.getElementById('tab-' + tab).classList.remove('hidden');
|
|
document.getElementById('btn-tab-' + tab).classList.add('active');
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
function openPlanModal() { document.getElementById('modalBackdrop').classList.remove('hidden'); document.getElementById('planModal').classList.remove('hidden'); }
|
|
function openClientModal() { document.getElementById('modalBackdrop').classList.remove('hidden'); document.getElementById('clientModal').classList.remove('hidden'); }
|
|
function closeModals() { document.getElementById('modalBackdrop').classList.add('hidden'); document.getElementById('planModal').classList.add('hidden'); document.getElementById('clientModal').classList.add('hidden'); }
|
|
function toast(msg) {
|
|
const t = document.createElement('div');
|
|
t.className = 'fixed bottom-5 right-5 bg-slate-900 text-white px-4 py-3 rounded-xl shadow-2xl text-xs font-black uppercase tracking-widest z-[999] fade-in';
|
|
t.textContent = msg; document.body.appendChild(t); setTimeout(() => t.remove(), 2500);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |