Actualizar plan-tranquilidad.html
This commit is contained in:
@@ -3,367 +3,319 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
<title>Contratar Plan Tranquilidad - IntegraRepara</title>
|
<title>Mi Portal - Asistencia Técnica</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script src="https://unpkg.com/lucide@latest"></script>
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
|
||||||
<style>
|
<style>
|
||||||
:root { --primary: #2563eb; --secondary: #f59e0b; --app-bg: #f8fafc; }
|
body { background-color: #f8fafc; -webkit-tap-highlight-color: transparent; }
|
||||||
body { background-color: var(--app-bg); -webkit-tap-highlight-color: transparent; }
|
.fade-in { animation: fadeIn 0.4s ease-out forwards; }
|
||||||
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
.glass-header { background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%); }
|
.glass-card { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); }
|
||||||
|
|
||||||
.plan-card {
|
|
||||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
||||||
border: 2px solid transparent;
|
|
||||||
}
|
|
||||||
.plan-card.active {
|
|
||||||
border-color: var(--primary);
|
|
||||||
background-color: #fff;
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#signature-pad {
|
|
||||||
background: #fff;
|
|
||||||
touch-action: none;
|
|
||||||
cursor: crosshair;
|
|
||||||
border: 2px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-icon {
|
|
||||||
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse-soft {
|
|
||||||
0% { transform: scale(1); }
|
|
||||||
50% { transform: scale(1.02); }
|
|
||||||
100% { transform: scale(1); }
|
|
||||||
}
|
|
||||||
.animate-offer { animation: pulse-soft 2s infinite; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="text-slate-800 font-sans antialiased pb-20">
|
<body class="text-slate-800 font-sans antialiased pb-10">
|
||||||
|
|
||||||
<header class="glass-header text-white px-6 pt-12 pb-24 rounded-b-[3rem] relative shadow-2xl overflow-hidden">
|
<header class="bg-white shadow-sm border-b border-slate-100 sticky top-0 z-50">
|
||||||
<div class="absolute top-0 right-0 -mr-20 -mt-20 w-64 h-64 bg-white/10 rounded-full blur-3xl"></div>
|
<div class="px-6 py-4 flex items-center gap-4">
|
||||||
|
<div id="company-logo-container" class="w-10 h-10 bg-slate-100 rounded-xl overflow-hidden flex items-center justify-center shrink-0">
|
||||||
<button onclick="window.history.back()" class="absolute top-8 left-6 w-10 h-10 bg-white/20 rounded-full flex items-center justify-center backdrop-blur-md active:scale-90 transition-transform z-10">
|
<i data-lucide="building-2" class="w-5 h-5 text-slate-400"></i>
|
||||||
<i data-lucide="chevron-left" class="w-6 h-6"></i>
|
</div>
|
||||||
</button>
|
<div>
|
||||||
|
<h1 id="company-name" class="font-black text-slate-800 tracking-tight leading-none text-lg">Cargando...</h1>
|
||||||
<div class="relative z-10 text-center space-y-2">
|
<p id="client-greeting" class="text-xs font-bold text-slate-400 mt-0.5">Bienvenido</p>
|
||||||
<span class="bg-blue-400/30 text-blue-100 text-[10px] font-black px-3 py-1 rounded-full uppercase tracking-[0.2em] backdrop-blur-sm">Servicio Exclusivo</span>
|
</div>
|
||||||
<h1 class="text-3xl font-black tracking-tight leading-none">Plan Tranquilidad <span class="text-blue-300">Hogar</span></h1>
|
|
||||||
<p class="text-blue-100/80 text-sm font-medium">Olvídate de las averías eléctricas para siempre</p>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="px-6 -mt-16 space-y-6 relative z-20">
|
<main class="p-6 space-y-6 max-w-lg mx-auto">
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div id="loader" class="text-center py-20 space-y-4">
|
||||||
<div class="bg-white p-5 rounded-[2.5rem] shadow-xl border border-slate-100">
|
<i data-lucide="loader-2" class="w-8 h-8 text-blue-600 animate-spin mx-auto"></i>
|
||||||
<div class="space-y-5">
|
<p class="text-xs font-bold text-slate-400 uppercase tracking-widest">Conectando...</p>
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="benefit-icon w-12 h-12 rounded-2xl flex items-center justify-center shrink-0 shadow-sm">
|
|
||||||
<i data-lucide="zap" class="text-blue-600 w-6 h-6"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="font-black text-slate-800 text-sm uppercase tracking-tight">Urgencias 24h Premium</h3>
|
|
||||||
<p class="text-xs text-slate-500 leading-relaxed">Llegamos en < 3h. Incluye desplazamiento y 3h de mano de obra (2 veces/año).</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="benefit-icon w-12 h-12 rounded-2xl flex items-center justify-center shrink-0 shadow-sm">
|
|
||||||
<i data-lucide="hammer" class="text-blue-600 w-6 h-6"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="font-black text-slate-800 text-sm uppercase tracking-tight">Manitas Eléctrico Incluido</h3>
|
|
||||||
<p class="text-xs text-slate-500 leading-relaxed">1 visita anual (2h) para instalar tus lámparas, ventiladores o enchufes.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="benefit-icon w-12 h-12 rounded-2xl flex items-center justify-center shrink-0 shadow-sm">
|
|
||||||
<i data-lucide="shield-check" class="text-blue-600 w-6 h-6"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="font-black text-slate-800 text-sm uppercase tracking-tight">Revisión de Seguridad Anual</h3>
|
|
||||||
<p class="text-xs text-slate-500 leading-relaxed">Chequeo de tu cuadro eléctrico y apriete de bornes anti-incendio.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div id="content" class="hidden space-y-8">
|
||||||
<h3 class="text-[11px] font-black text-slate-400 uppercase tracking-[0.15em] ml-2">Selecciona tu ahorro</h3>
|
|
||||||
|
|
||||||
<div onclick="selectPlan('yearly')" id="plan-yearly" class="plan-card active bg-white p-6 rounded-[2.5rem] shadow-lg flex justify-between items-center cursor-pointer overflow-hidden relative">
|
<div id="subscription-container"></div>
|
||||||
<div class="absolute top-0 right-0 bg-secondary text-white text-[9px] font-black px-4 py-1 rounded-bl-2xl uppercase tracking-tighter animate-offer" style="background-color: var(--secondary);">Recomendado</div>
|
|
||||||
<div class="flex items-center gap-4">
|
<div id="quotes-container"></div>
|
||||||
<div id="radio-yearly" class="w-6 h-6 rounded-full border-2 border-blue-600 flex items-center justify-center">
|
|
||||||
<div class="w-3 h-3 bg-blue-600 rounded-full"></div>
|
<div id="services-container" class="space-y-4">
|
||||||
</div>
|
<h2 class="text-xs font-black text-slate-400 uppercase tracking-widest flex items-center gap-2 ml-2">
|
||||||
<div>
|
<i data-lucide="clipboard-list" class="w-4 h-4"></i> Tus Averías y Avisos
|
||||||
<p class="font-black text-slate-800 text-base">Plan Anual Único</p>
|
</h2>
|
||||||
<p class="text-[11px] text-emerald-600 font-bold">Ahorras 2 cuotas mensuales</p>
|
<div id="services-list" class="space-y-4"></div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<p class="text-2xl font-black text-blue-600 leading-none">59,95€</p>
|
|
||||||
<p class="text-[10px] font-bold text-blue-400 uppercase mt-1">IVA Incluido</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div onclick="selectPlan('monthly')" id="plan-monthly" class="plan-card bg-slate-100/50 p-6 rounded-[2.5rem] shadow-sm flex justify-between items-center cursor-pointer border-slate-200">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div id="radio-monthly" class="w-6 h-6 rounded-full border-2 border-slate-300 flex items-center justify-center">
|
|
||||||
<div class="w-3 h-3 bg-blue-600 rounded-full hidden"></div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="font-black text-slate-800 text-base">Suscripción Mensual</p>
|
|
||||||
<p class="text-[11px] text-slate-500 font-medium">Sin permanencia</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-right">
|
|
||||||
<p class="text-xl font-black text-slate-700 leading-none">6,90€<span class="text-xs text-slate-400">+IVA</span></p>
|
|
||||||
<p class="text-[10px] font-bold text-slate-400 uppercase mt-1">Al mes</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white p-6 rounded-[2.5rem] shadow-xl border border-slate-100 space-y-4">
|
|
||||||
<div class="text-center">
|
|
||||||
<h3 class="text-[11px] font-black text-slate-400 uppercase tracking-widest">Firma Digital del Contrato</h3>
|
|
||||||
<p class="text-[10px] text-slate-400 mt-1 italic">Usa tu dedo o puntero para firmar</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<canvas id="signature-pad" class="w-full h-44 rounded-3xl shadow-inner"></canvas>
|
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<button onclick="clearSignature()" class="flex items-center gap-1 text-[10px] font-black text-rose-500 uppercase tracking-tighter">
|
|
||||||
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i> Borrar Firma
|
|
||||||
</button>
|
|
||||||
<div class="flex items-center gap-1 text-emerald-600">
|
|
||||||
<i data-lucide="shield-check" class="w-3.5 h-3.5"></i>
|
|
||||||
<span class="text-[9px] font-bold uppercase">Validación Biométrica</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="py-4">
|
|
||||||
<button onclick="processContract()" id="btnAction" class="w-full bg-blue-600 text-white font-black py-6 rounded-[2rem] shadow-[0_15px_30px_-5px_rgba(37,99,235,0.4)] active:scale-95 transition-all uppercase tracking-[0.15em] text-sm flex justify-center items-center gap-3">
|
|
||||||
<i data-lucide="crown" class="w-5 h-5"></i> Firmar y Activar Mi Plan
|
|
||||||
</button>
|
|
||||||
<p class="text-center text-[9px] text-slate-400 mt-4 px-6 leading-relaxed font-medium">
|
|
||||||
Al activar, confirmas que has leído las condiciones. Se generará un PDF firmado para tu expediente.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div id="pdf-contract-template" class="hidden">
|
|
||||||
<div style="padding: 20px 40px; font-family: 'Helvetica', Arial, sans-serif; color: #1e293b; line-height: 1.5; font-size: 11px; background: white;">
|
|
||||||
|
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 3px solid #2563eb; padding-bottom: 20px; margin-bottom: 30px;">
|
|
||||||
<div style="flex-grow: 1;">
|
|
||||||
<h2 style="margin:0; color:#2563eb; font-size: 28px; font-weight: 900; letter-spacing: -1.5px;">IntegraRepara</h2>
|
|
||||||
<p style="margin: 3px 0 0 0; color: #64748b; font-size: 10px; text-transform: uppercase;">Servicios Técnicos Profesionales</p>
|
|
||||||
</div>
|
|
||||||
<div style="text-align: right; font-size: 10px; color: #64748b; width: 250px;">
|
|
||||||
<strong style="color: #0f172a; font-size: 14px;">CONTRATO PLAN TRANQUILIDAD</strong><br>
|
|
||||||
Protección Eléctrica Residencial<br>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 25px; background: #f8fafc; padding: 25px; border-radius: 12px; border: 1px solid #e2e8f0;">
|
|
||||||
|
|
||||||
<h2 style="font-size: 14px; font-weight: 900; color: #0f172a; margin: 0 0 15px 0;">CONDICIONES GENERALES DEL SERVICIO</h2>
|
|
||||||
|
|
||||||
<p style="margin: 0;"><strong>1. OBJETO DEL CONTRATO:</strong> IntegraRepara prestará servicios de asistencia técnica eléctrica, mantenimiento preventivo y Bricolaje Eléctrico para la vivienda habitual del Cliente bajo la modalidad de suscripción elegida.</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>2. COBERTURA DE URGENCIAS ELÉCTRICAS:</strong> Averías súbitas que provoquen falta total de suministro o riesgo inminente de incendio.
|
|
||||||
<ul style="margin: 5px 0 0 20px;">
|
|
||||||
<li>Tiempo de respuesta: Compromiso de asistencia en < 3 horas.</li>
|
|
||||||
<li>Límite de intervenciones: Máximo <strong>2 urgencias/año</strong>.</li>
|
|
||||||
<li>Incluido: Desplazamiento y hasta 3h de mano de obra gratis/intervención.</li>
|
|
||||||
<li>NO Incluido: El coste de materiales (diferenciales, magnetotérmicos, etc.), que serán facturados aparte previa aceptación.</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>3. SERVICIO DE BRICOLAJE ELÉCTRICO (MANITAS):</strong> Intervenciones no urgentes.
|
|
||||||
<ul style="margin: 5px 0 0 20px;">
|
|
||||||
<li>Límite: <strong>Un (1) servicio anual de 2h</strong> de duración continuada.</li>
|
|
||||||
<li>Incluido: Instalación de lámparas, sustitución de enchufes/interruptores, montaje de ventiladores (material cliente).</li>
|
|
||||||
<li>Materiales: Es condición indispensable que el Cliente disponga de los materiales necesarios al llegar el técnico.</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>4. REVISIÓN PREVENTIVA ANUAL:</strong> Visita de seguridad para testeo de diferenciales, reapriete de conexiones en el cuadro general.</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>5. PERIODO DE CARENCIA:</strong> El servicio tiene una carencia de <strong>15 días naturales</strong> desde la contratación. No se cubren averías producidas antes o durante este periodo.</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>6. EXCLUSIONES GENERALES:</strong> Reparación de electrodomésticos, preexistentes, instalaciones fuera de normativa (REBT) o boletines.</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>7. PRECIO Y MODALIDAD:</strong> Cargo automático según la modalidad elegida:
|
|
||||||
<ul style="margin: 5px 0 0 20px;">
|
|
||||||
<li>Mensual: 6,90 € + IVA al mes.</li>
|
|
||||||
<li>Anual: 59,95 € (IVA Incluido).</li>
|
|
||||||
<li>Precios válidos para el primer año.</li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>8. DURACIÓN Y CANCELACIÓN:</strong> Anual prorrogable. Mensual cancelable desde el portal. Anual cancelable avisando 15 días antes de la renovación.</p>
|
|
||||||
|
|
||||||
<p style="margin: 10px 0 0 0;"><strong>9. ACEPTACIÓN:</strong> La marcación de la casilla de aceptación y la firma digital equivalen a la firma manuscrita, confirmando que el Cliente ha leído y acepta todos los puntos.</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 15px; border: 1px solid #e2e8f0; padding: 15px; border-radius: 8px; font-size: 10px;">
|
|
||||||
<p style="margin: 0;"><strong>Modalidad contratada:</strong> <span id="pdf-plan-name" style="color:#2563eb; font-weight:900;">Plan Anual (59,95€ IVA Incl.)</span></p>
|
|
||||||
<p id="pdf-timestamp" style="margin: 3px 0 0 0;">Documento firmado digitalmente el 01/01/2026</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 50px;">
|
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
|
||||||
<div style="width: 280px; text-align: center;">
|
|
||||||
<p style="font-size: 10px; text-transform: uppercase; font-weight: 800; color: #94a3b8; margin-bottom: 15px; text-align: left;">Firma del Titular:</p>
|
|
||||||
<img id="pdf-signature-img" style="width: 220px; height: 110px; border-bottom: 2px solid #0f172a;">
|
|
||||||
</div>
|
|
||||||
<div style="width: 220px; text-align: right;">
|
|
||||||
<div style="border: 3px double #2563eb; color: #2563eb; padding: 15px; border-radius: 15px; font-weight: 900; font-size: 12px; text-align: center; transform: rotate(-8deg);">
|
|
||||||
SISTEMA DE ASISTENCIA<br>
|
|
||||||
<span style="font-size: 16px;">INTEGRA REPARA</span><br>
|
|
||||||
VALIDADO DIGITALMENTE
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 50px; text-align: center; color: #94a3b8; font-size: 9px; border-top: 1px solid #e2e8f0; padding-top: 20px;">
|
|
||||||
Este documento tiene validez legal como contrato de prestación de servicios bajo firma digital verificada.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
let selectedPlan = 'yearly';
|
// CONFIGURACIÓN API
|
||||||
const canvas = document.getElementById('signature-pad');
|
const API_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
||||||
const ctx = canvas.getContext('2d');
|
? 'http://localhost:3000'
|
||||||
let drawing = false;
|
: 'https://integrarepara-api.integrarepara.es';
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const token = urlParams.get('token');
|
||||||
|
const serviceId = urlParams.get('service') || '';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', loadPortalData);
|
||||||
|
|
||||||
|
async function loadPortalData() {
|
||||||
|
if (!token) {
|
||||||
|
return showError("Enlace inválido o caducado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/public/portal/${token}?service=${serviceId}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!data.ok) throw new Error(data.error || "No se pudo cargar la información.");
|
||||||
|
|
||||||
|
// 1. Rellenar Cabecera
|
||||||
|
document.getElementById('company-name').innerText = data.company.name;
|
||||||
|
document.getElementById('client-greeting').innerText = `Hola, ${data.client.name}`;
|
||||||
|
if (data.company.logo) {
|
||||||
|
document.getElementById('company-logo-container').innerHTML = `<img src="${data.company.logo}" class="w-full h-full object-cover">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Renderizar Módulo de Seguro (SaaS)
|
||||||
|
renderSubscription(data.subscription);
|
||||||
|
|
||||||
|
// 3. Renderizar Presupuestos
|
||||||
|
renderQuotes(data.quotes);
|
||||||
|
|
||||||
|
// 4. Renderizar Averías
|
||||||
|
renderServices(data.services);
|
||||||
|
|
||||||
|
// Mostrar contenido
|
||||||
|
document.getElementById('loader').classList.add('hidden');
|
||||||
|
document.getElementById('content').classList.remove('hidden');
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
|
|
||||||
function setupCanvas() {
|
} catch (error) {
|
||||||
const ratio = Math.max(window.devicePixelRatio || 1, 1);
|
showError(error.message);
|
||||||
canvas.width = canvas.offsetWidth * ratio;
|
}
|
||||||
canvas.height = canvas.offsetHeight * ratio;
|
|
||||||
ctx.scale(ratio, ratio);
|
|
||||||
ctx.strokeStyle = "#1e293b"; //slate-800
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.lineCap = "round";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', setupCanvas);
|
// ==========================================
|
||||||
setTimeout(setupCanvas, 100);
|
// 🛡️ RENDERIZADO DEL SEGURO
|
||||||
|
// ==========================================
|
||||||
|
function renderSubscription(sub) {
|
||||||
|
const container = document.getElementById('subscription-container');
|
||||||
|
|
||||||
function getPos(e) {
|
if (sub) {
|
||||||
const rect = canvas.getBoundingClientRect();
|
// EL CLIENTE TIENE UN PLAN ACTIVO
|
||||||
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
|
const isImpagado = sub.payment_status === 'impagado';
|
||||||
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
|
const bgClass = isImpagado ? 'bg-rose-50 border-rose-200' : 'bg-gradient-to-br from-blue-600 to-blue-800 text-white border-blue-700 shadow-xl shadow-blue-900/20';
|
||||||
return { x: clientX - rect.left, y: clientY - rect.top };
|
const textClass = isImpagado ? 'text-rose-900' : 'text-white';
|
||||||
|
const muteTextClass = isImpagado ? 'text-rose-500' : 'text-blue-200';
|
||||||
|
|
||||||
|
const pctBricos = sub.bricos_limit > 0 ? Math.min(100, (sub.bricos_used / sub.bricos_limit) * 100) : 0;
|
||||||
|
const pctUrg = sub.urgencies_limit > 0 ? Math.min(100, (sub.urgencies_used / sub.urgencies_limit) * 100) : 0;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="${bgClass} p-6 rounded-3xl border relative overflow-hidden fade-in">
|
||||||
|
${!isImpagado ? '<div class="absolute -right-10 -top-10 w-32 h-32 bg-white/10 rounded-full blur-2xl"></div>' : ''}
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start relative z-10 mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-[10px] font-black uppercase tracking-widest ${muteTextClass} mb-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="shield-check" class="w-3.5 h-3.5"></i> Tu Plan Activo
|
||||||
|
</h3>
|
||||||
|
<p class="text-2xl font-black ${textClass} leading-none tracking-tight">${sub.plan_name}</p>
|
||||||
|
</div>
|
||||||
|
${isImpagado
|
||||||
|
? `<span class="bg-rose-100 text-rose-700 px-3 py-1 rounded-lg text-[9px] font-black uppercase tracking-widest border border-rose-200 flex items-center gap-1"><div class="w-1.5 h-1.5 bg-rose-500 rounded-full animate-pulse"></div> IMPAGO</span>`
|
||||||
|
: `<span class="bg-white/20 px-3 py-1 rounded-lg text-[9px] font-black uppercase tracking-widest backdrop-blur-sm border border-white/10 text-white">AL DÍA</span>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 relative z-10">
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-xs font-bold mb-1.5 ${textClass}">
|
||||||
|
<span>Urgencias 24h</span>
|
||||||
|
<span>${sub.urgencies_used} / ${sub.urgencies_limit}</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-black/20 rounded-full h-1.5 overflow-hidden">
|
||||||
|
<div class="${isImpagado ? 'bg-rose-500' : 'bg-white'} h-full rounded-full transition-all" style="width: ${pctUrg}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-xs font-bold mb-1.5 ${textClass}">
|
||||||
|
<span>Servicio Manitas</span>
|
||||||
|
<span>${sub.bricos_used} / ${sub.bricos_limit}</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-black/20 rounded-full h-1.5 overflow-hidden">
|
||||||
|
<div class="${isImpagado ? 'bg-rose-500' : 'bg-white'} h-full rounded-full transition-all" style="width: ${pctBricos}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${sub.renewal_date ? `
|
||||||
|
<div class="mt-5 pt-4 border-t ${isImpagado ? 'border-rose-200 text-rose-500' : 'border-white/20 text-blue-100'} text-[9px] font-bold uppercase tracking-widest flex justify-between items-center">
|
||||||
|
<span>Próxima Renovación</span>
|
||||||
|
<span>${new Date(sub.renewal_date).toLocaleDateString('es-ES')}</span>
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
|
${isImpagado ? `
|
||||||
|
<div class="mt-4 bg-rose-100/50 p-3 rounded-xl text-xs font-bold text-rose-700 text-center">
|
||||||
|
Por favor, contacta con nosotros para regularizar el pago y reactivar tus coberturas.
|
||||||
|
</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
// EL CLIENTE NO TIENE PLAN (Módulo de Ventas / Upselling)
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="bg-gradient-to-r from-slate-800 to-slate-900 p-6 rounded-3xl shadow-xl text-white relative overflow-hidden fade-in flex flex-col items-center text-center">
|
||||||
|
<div class="w-12 h-12 bg-white/10 rounded-2xl flex items-center justify-center mb-3 backdrop-blur-sm">
|
||||||
|
<i data-lucide="zap" class="w-6 h-6 text-amber-400"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-black tracking-tight mb-2">Protege tu hogar todo el año</h3>
|
||||||
|
<p class="text-xs text-slate-300 font-medium mb-5 px-4">Olvídate de las averías imprevistas con nuestro Plan Tranquilidad. Incluye urgencias gratuitas y revisión anual.</p>
|
||||||
|
<a href="plan-tranquilidad.html?token=${token}" class="bg-white text-slate-900 font-black px-6 py-3 rounded-xl text-[10px] uppercase tracking-widest w-full active:scale-95 transition-transform shadow-lg">Ver Planes y Precios</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startDrawing(e) {
|
// ==========================================
|
||||||
if (e.touches && e.touches.length > 1) return; // Ignore multi-touch
|
// 📄 RENDERIZADO DE PRESUPUESTOS
|
||||||
drawing = true;
|
// ==========================================
|
||||||
const pos = getPos(e);
|
function renderQuotes(quotes) {
|
||||||
ctx.beginPath();
|
const container = document.getElementById('quotes-container');
|
||||||
ctx.moveTo(pos.x, pos.y);
|
if (!quotes || quotes.length === 0) return;
|
||||||
|
|
||||||
|
const html = quotes.map(q => {
|
||||||
|
let statusBadge = '';
|
||||||
|
if(q.status === 'pending') statusBadge = '<span class="bg-amber-100 text-amber-700 px-2 py-1 rounded text-[9px] font-black uppercase">Pendiente</span>';
|
||||||
|
else if(q.status === 'accepted' || q.status === 'converted') statusBadge = '<span class="bg-emerald-100 text-emerald-700 px-2 py-1 rounded text-[9px] font-black uppercase">Aceptado</span>';
|
||||||
|
else if(q.status === 'rejected') statusBadge = '<span class="bg-rose-100 text-rose-700 px-2 py-1 rounded text-[9px] font-black uppercase">Rechazado</span>';
|
||||||
|
else if(q.status === 'paid') statusBadge = '<span class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-[9px] font-black uppercase">Pagado</span>';
|
||||||
|
|
||||||
|
let itemsHtml = q.items.map(i => `<div class="flex justify-between text-xs text-slate-500 mb-1"><span>${i.qty}x ${i.concept}</span><span>${parseFloat(i.price * i.qty).toFixed(2)}€</span></div>`).join('');
|
||||||
|
|
||||||
|
let actionsHtml = '';
|
||||||
|
if(q.status === 'pending') {
|
||||||
|
actionsHtml = `
|
||||||
|
<div class="flex gap-2 mt-4">
|
||||||
|
<button onclick="respondQuote(${q.id}, 'reject')" class="flex-1 bg-rose-50 text-rose-600 font-bold py-2 rounded-xl text-xs active:scale-95 transition-transform border border-rose-100">Rechazar</button>
|
||||||
|
<button onclick="respondQuote(${q.id}, 'accept')" class="flex-1 bg-blue-600 text-white font-bold py-2 rounded-xl text-xs active:scale-95 transition-transform shadow-md">Aceptar Presupuesto</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw(e) {
|
return `
|
||||||
if (!drawing) return;
|
<div class="bg-white rounded-3xl p-5 shadow-sm border border-slate-200 mb-4 fade-in">
|
||||||
e.preventDefault(); // Prevent scrolling
|
<div class="flex justify-between items-start mb-4 border-b border-slate-100 pb-4">
|
||||||
const pos = getPos(e);
|
<div>
|
||||||
ctx.lineTo(pos.x, pos.y);
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">${q.quote_ref}</p>
|
||||||
ctx.stroke();
|
<h3 class="font-black text-slate-800 text-sm mt-0.5">${q.title}</h3>
|
||||||
|
</div>
|
||||||
|
${statusBadge}
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
${itemsHtml}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center pt-3 border-t border-slate-100">
|
||||||
|
<span class="text-xs font-bold text-slate-400 uppercase">Total (IVA Incl.)</span>
|
||||||
|
<span class="text-xl font-black text-slate-800">${q.amount}€</span>
|
||||||
|
</div>
|
||||||
|
${actionsHtml}
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<h2 class="text-xs font-black text-slate-400 uppercase tracking-widest flex items-center gap-2 ml-2 mb-4 mt-8">
|
||||||
|
<i data-lucide="file-text" class="w-4 h-4"></i> Tus Presupuestos
|
||||||
|
</h2>
|
||||||
|
${html}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.addEventListener('mousedown', startDrawing);
|
// ==========================================
|
||||||
canvas.addEventListener('mousemove', draw);
|
// 🛠️ RENDERIZADO DE AVERÍAS
|
||||||
canvas.addEventListener('mouseup', () => drawing = false);
|
// ==========================================
|
||||||
canvas.addEventListener('touchstart', startDrawing);
|
function renderServices(services) {
|
||||||
canvas.addEventListener('touchmove', draw);
|
const container = document.getElementById('services-list');
|
||||||
canvas.addEventListener('touchend', () => drawing = false);
|
if (!services || services.length === 0) {
|
||||||
|
container.innerHTML = `<div class="bg-white p-6 rounded-3xl border border-slate-200 text-center"><p class="text-sm font-bold text-slate-400">No tienes ninguna avería en curso.</p></div>`;
|
||||||
function clearSignature() {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectPlan(type) {
|
|
||||||
selectedPlan = type;
|
|
||||||
document.getElementById('plan-monthly').classList.toggle('active', type === 'monthly');
|
|
||||||
document.getElementById('plan-yearly').classList.toggle('active', type === 'yearly');
|
|
||||||
|
|
||||||
document.getElementById('plan-monthly').classList.toggle('bg-white', type === 'monthly');
|
|
||||||
document.getElementById('plan-monthly').classList.toggle('bg-slate-100/50', type !== 'monthly');
|
|
||||||
|
|
||||||
document.querySelector('#radio-monthly div').classList.toggle('hidden', type !== 'monthly');
|
|
||||||
document.querySelector('#radio-yearly div').classList.toggle('hidden', type !== 'yearly');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processContract() {
|
|
||||||
const blank = document.createElement('canvas');
|
|
||||||
blank.width = canvas.width; blank.height = canvas.height;
|
|
||||||
if (canvas.toDataURL() === blank.toDataURL()) {
|
|
||||||
alert("⚠️ Por favor, firma el contrato en el recuadro antes de continuar.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const btn = document.getElementById('btnAction');
|
container.innerHTML = services.map(s => {
|
||||||
btn.disabled = true;
|
const dateText = s.scheduled_date
|
||||||
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Generando Contrato Firmado...';
|
? `<i data-lucide="calendar" class="w-3.5 h-3.5 inline"></i> Cita: ${s.scheduled_date.split('-').reverse().join('/')} ${s.scheduled_time ? `a las ${s.scheduled_time}` : ''}`
|
||||||
lucide.createIcons();
|
: `<i data-lucide="clock" class="w-3.5 h-3.5 inline"></i> Pendiente de agendar`;
|
||||||
|
|
||||||
// 1. Inyectar datos
|
return `
|
||||||
document.getElementById('pdf-signature-img').src = canvas.toDataURL("image/png");
|
<div class="bg-white p-5 rounded-3xl shadow-sm border border-slate-200 fade-in">
|
||||||
const planText = selectedPlan === 'yearly' ? 'Plan Anual (59,95€ IVA Incl.)' : 'Suscripción Mensual (6,90€ + IVA)';
|
<div class="flex justify-between items-start mb-3">
|
||||||
document.getElementById('pdf-plan-name').innerText = planText;
|
<div class="pr-4">
|
||||||
const timestamp = "Documento firmado digitalmente el " + new Date().toLocaleDateString('es-ES') + " a las " + new Date().toLocaleTimeString('es-ES');
|
<h3 class="font-black text-slate-800 text-sm leading-tight">${s.title}</h3>
|
||||||
document.getElementById('pdf-timestamp').innerText = timestamp;
|
<p class="text-[10px] font-bold text-blue-600 uppercase tracking-widest mt-1 bg-blue-50 w-fit px-2 py-0.5 rounded-md">${s.status_name}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-500 font-medium leading-relaxed mb-4 bg-slate-50 p-3 rounded-xl border border-slate-100">${s.description}</p>
|
||||||
|
|
||||||
const element = document.getElementById('pdf-contract-template');
|
<div class="flex items-center gap-2 text-[11px] font-bold ${s.scheduled_date ? 'text-emerald-600' : 'text-amber-500'} bg-slate-50 px-3 py-2 rounded-lg">
|
||||||
element.classList.remove('hidden');
|
${dateText}
|
||||||
|
</div>
|
||||||
|
|
||||||
// 2. Opciones del PDF mejoradas para quitar márgenes fantasmas
|
${s.assigned_worker ? `
|
||||||
const opt = {
|
<div class="mt-4 pt-4 border-t border-slate-100 flex items-center gap-3">
|
||||||
margin: [0.3, 0, 0.3, 0], // Margen superior, izquierdo, inferior, derecho (en pulgadas)
|
<div class="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-slate-400 shrink-0"><i data-lucide="user" class="w-4 h-4"></i></div>
|
||||||
filename: `Contrato_Plan_Tranquilidad_${(new Date().toISOString()).split('T')[0]}.pdf`,
|
<div>
|
||||||
image: { type: 'jpeg', quality: 0.98 },
|
<p class="text-[9px] font-black uppercase tracking-widest text-slate-400">Técnico Asignado</p>
|
||||||
html2canvas: {
|
<p class="text-xs font-bold text-slate-700">${s.assigned_worker}</p>
|
||||||
scale: 2,
|
</div>
|
||||||
useCORS: true,
|
</div>` : ''}
|
||||||
scrollY: 0, // 👈 ESTO quita el margen de arriba si habías hecho scroll
|
</div>`;
|
||||||
windowHeight: element.scrollHeight
|
}).join('');
|
||||||
},
|
}
|
||||||
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
|
|
||||||
};
|
// ==========================================
|
||||||
|
// ⚙️ ACCIONES
|
||||||
|
// ==========================================
|
||||||
|
async function respondQuote(id, action) {
|
||||||
|
const btnText = action === 'accept' ? 'Aceptando...' : 'Rechazando...';
|
||||||
|
if(!confirm(`¿Estás seguro de que deseas ${action === 'accept' ? 'ACEPTAR' : 'RECHAZAR'} este presupuesto?`)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await html2pdf().set(opt).from(element).save();
|
const res = await fetch(`${API_URL}/public/portal/${token}/budget/${id}/respond`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action })
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
if(res.ok) {
|
||||||
alert("✅ Contrato generado con éxito.\n\nAhora vamos a conectar con Stripe para formalizar el pago seguro.");
|
if (action === 'accept') {
|
||||||
btn.disabled = false;
|
alert("✅ Presupuesto aceptado. Te redirigimos a la pasarela de pago para formalizarlo.");
|
||||||
btn.innerHTML = '<i data-lucide="crown" class="w-5 h-5"></i> Firmar y Activar Mi Plan';
|
// Lógica de Stripe si está habilitada, llamando al checkout
|
||||||
lucide.createIcons();
|
const checkoutRes = await fetch(`${API_URL}/public/portal/${token}/budget/${id}/checkout`, { method: 'POST' });
|
||||||
}, 1500);
|
const checkData = await checkoutRes.json();
|
||||||
|
if (checkData.ok && checkData.checkout_url) window.location.href = checkData.checkout_url;
|
||||||
} catch (err) {
|
else {
|
||||||
alert("❌ Error al crear el contrato PDF.");
|
alert("El técnico ha sido avisado. Se pondrán en contacto contigo para agendar.");
|
||||||
btn.disabled = false;
|
window.location.reload();
|
||||||
} finally {
|
|
||||||
element.classList.add('hidden');
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
alert("Presupuesto rechazado. Gracias por informarnos.");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { alert("Error de conexión"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(msg) {
|
||||||
|
document.getElementById('loader').innerHTML = `
|
||||||
|
<div class="w-12 h-12 bg-rose-100 text-rose-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i data-lucide="alert-triangle" class="w-6 h-6"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm font-black text-slate-800">${msg}</p>
|
||||||
|
`;
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user