345 lines
20 KiB
HTML
345 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<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">
|
|
<title>Contratar Plan Tranquilidad</title>
|
|
<script src="https://cdn.tailwindcss.com"></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>
|
|
:root { --primary: #2563eb; --secondary: #f59e0b; --app-bg: #f8fafc; }
|
|
body { background-color: var(--app-bg); -webkit-tap-highlight-color: transparent; }
|
|
.glass-header { background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%); }
|
|
.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; }
|
|
.fade-in { animation: fadeIn 0.4s ease-out forwards; }
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
</style>
|
|
</head>
|
|
<body class="text-slate-800 font-sans antialiased pb-20 min-h-screen flex flex-col">
|
|
|
|
<header class="glass-header text-white px-6 pt-12 pb-24 rounded-b-[3rem] relative shadow-2xl overflow-hidden shrink-0">
|
|
<div class="absolute top-0 right-0 -mr-20 -mt-20 w-64 h-64 bg-white/10 rounded-full blur-3xl"></div>
|
|
|
|
<button onclick="goBackToPortal()" 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="chevron-left" class="w-6 h-6"></i>
|
|
</button>
|
|
|
|
<div class="relative z-10 text-center space-y-2">
|
|
<span id="company-name-badge" 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">Cargando...</span>
|
|
<h1 class="text-3xl font-black tracking-tight leading-none">Plan <span class="text-blue-300">Tranquilidad</span></h1>
|
|
<p class="text-blue-100/80 text-sm font-medium">Asistencia técnica garantizada para tu hogar</p>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="px-6 -mt-16 relative z-20 flex-1 flex flex-col">
|
|
|
|
<div id="loader" class="bg-white p-10 rounded-[2.5rem] shadow-xl text-center space-y-4">
|
|
<i data-lucide="loader-2" class="w-8 h-8 text-blue-600 animate-spin mx-auto"></i>
|
|
<p class="text-xs font-bold text-slate-500 uppercase tracking-widest">Cargando planes...</p>
|
|
</div>
|
|
|
|
<div id="already-subscribed-container" class="hidden bg-white p-8 rounded-[2.5rem] shadow-xl text-center space-y-6 fade-in flex-1">
|
|
<div class="w-20 h-20 bg-emerald-100 text-emerald-600 rounded-full flex items-center justify-center mx-auto shadow-inner">
|
|
<i data-lucide="shield-check" class="w-10 h-10"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-black tracking-tight text-slate-800">¡Tu hogar ya está protegido!</h2>
|
|
<p class="text-sm text-slate-500 mt-2 font-medium">Actualmente tienes activo el <strong id="active-plan-name" class="text-blue-600">Plan</strong>.</p>
|
|
<p class="text-xs text-slate-400 mt-4 bg-slate-50 p-4 rounded-2xl border border-slate-100">No es necesario volver a contratar. Puedes consultar el estado de tus coberturas (urgencias y bricos) directamente desde tu portal principal.</p>
|
|
</div>
|
|
<button onclick="goBackToPortal()" class="w-full bg-slate-800 text-white font-black py-4 rounded-2xl shadow-lg active:scale-95 transition-all uppercase tracking-widest text-xs mt-4">
|
|
Volver a mi Portal
|
|
</button>
|
|
</div>
|
|
|
|
<div id="contract-container" class="hidden space-y-6 fade-in pb-10">
|
|
|
|
<div class="space-y-4">
|
|
<h3 class="text-[11px] font-black text-slate-400 uppercase tracking-[0.15em] ml-2">Selecciona tu plan</h3>
|
|
<div id="plans-wrapper" class="space-y-4"></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 dentro del recuadro</p>
|
|
</div>
|
|
<canvas id="signature-pad" class="w-full h-44 rounded-3xl shadow-inner"></canvas>
|
|
<div class="flex justify-between items-center px-2">
|
|
<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-2">
|
|
<button onclick="processContract()" id="btnAction" class="w-full bg-blue-600 text-white font-black py-5 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 Pagar
|
|
</button>
|
|
<p class="text-center text-[9px] text-slate-400 mt-4 px-6 leading-relaxed font-medium">
|
|
Al pulsar, confirmas que aceptas las condiciones. Se generará un PDF firmado y serás redirigido a la pasarela de pago segura (Stripe).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</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 id="pdf-company-name" style="margin:0; color:#2563eb; font-size: 28px; font-weight: 900; letter-spacing: -1.5px;">Empresa</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 DE SUSCRIPCIÓN</strong><br>
|
|
<span id="pdf-client-name">Cliente</span><br>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="pdf-dynamic-text" style="margin-bottom: 25px; background: #f8fafc; padding: 25px; border-radius: 12px; border: 1px solid #e2e8f0; white-space: pre-wrap;"></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;">...</span></p>
|
|
<p id="pdf-timestamp" style="margin: 3px 0 0 0;">Documento firmado digitalmente el ...</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 VERIFICADO<br>
|
|
<span id="pdf-stamp-company" style="font-size: 16px;">EMPRESA</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>
|
|
const API_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
|
? 'http://localhost:3000' : 'https://integrarepara-api.integrarepara.es';
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const token = urlParams.get('token');
|
|
|
|
let loadedPlans = [];
|
|
let globalConfig = {};
|
|
let clientData = {};
|
|
let selectedPlanId = null;
|
|
|
|
// CANVAS
|
|
const canvas = document.getElementById('signature-pad');
|
|
const ctx = canvas.getContext('2d');
|
|
let drawing = false;
|
|
|
|
function setupCanvas() {
|
|
const ratio = Math.max(window.devicePixelRatio || 1, 1);
|
|
canvas.width = canvas.offsetWidth * ratio;
|
|
canvas.height = canvas.offsetHeight * ratio;
|
|
ctx.scale(ratio, ratio);
|
|
ctx.strokeStyle = "#1e293b";
|
|
ctx.lineWidth = 3;
|
|
ctx.lineCap = "round";
|
|
}
|
|
|
|
function getPos(e) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
|
|
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
|
|
return { x: clientX - rect.left, y: clientY - rect.top };
|
|
}
|
|
function startDrawing(e) { if (e.touches && e.touches.length > 1) return; drawing = true; const pos = getPos(e); ctx.beginPath(); ctx.moveTo(pos.x, pos.y); }
|
|
function draw(e) { if (!drawing) return; e.preventDefault(); const pos = getPos(e); ctx.lineTo(pos.x, pos.y); ctx.stroke(); }
|
|
canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', () => drawing = false);
|
|
canvas.addEventListener('touchstart', startDrawing); canvas.addEventListener('touchmove', draw); canvas.addEventListener('touchend', () => drawing = false);
|
|
function clearSignature() { ctx.clearRect(0, 0, canvas.width, canvas.height); }
|
|
|
|
// REDIRECCIÓN VOLVER
|
|
function goBackToPortal() {
|
|
window.location.href = `index.html?token=${token}`; // Ajusta 'index.html' si tu portal principal se llama 'portal.html'
|
|
}
|
|
|
|
// CARGA INICIAL
|
|
async function init() {
|
|
if(!token) return alert("Enlace inválido. Falta el token de seguridad.");
|
|
lucide.createIcons();
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/public/portal/${token}/protection`);
|
|
const data = await res.json();
|
|
|
|
if (!data.ok) throw new Error(data.error || "Error de conexión");
|
|
|
|
document.getElementById('company-name-badge').innerText = data.company.name;
|
|
document.getElementById('loader').classList.add('hidden');
|
|
|
|
// 🛑 ESCUDO: Si el cliente ya tiene seguro, bloqueamos la página
|
|
if (data.hasActivePlan && data.activePlanDetails) {
|
|
document.getElementById('active-plan-name').innerText = data.activePlanDetails.plan_name;
|
|
document.getElementById('already-subscribed-container').classList.remove('hidden');
|
|
return; // Detenemos la ejecución aquí, no mostramos los planes
|
|
}
|
|
|
|
// Si no tiene seguro, cargamos los datos y mostramos el contrato
|
|
loadedPlans = data.plans;
|
|
globalConfig = data.config;
|
|
clientData = data.client;
|
|
|
|
document.getElementById('pdf-company-name').innerText = data.company.name;
|
|
document.getElementById('pdf-stamp-company').innerText = data.company.name.toUpperCase();
|
|
document.getElementById('pdf-client-name').innerText = `Asegurado: ${clientData.name}`;
|
|
document.getElementById('pdf-dynamic-text').innerText = globalConfig.contract_text || "El mantenimiento y asistencia quedan cubiertos por las condiciones de la empresa proveedora.";
|
|
|
|
renderPlans();
|
|
|
|
// Mostrar contenedor
|
|
document.getElementById('contract-container').classList.remove('hidden');
|
|
|
|
// Esperar a que el contenedor esté visible para configurar el canvas correctamente
|
|
setTimeout(() => {
|
|
setupCanvas();
|
|
window.addEventListener('resize', setupCanvas);
|
|
}, 100);
|
|
|
|
lucide.createIcons();
|
|
|
|
} catch(e) {
|
|
document.getElementById('loader').innerHTML = `<p class="text-rose-500 font-bold uppercase">${e.message}</p>`;
|
|
}
|
|
}
|
|
|
|
function renderPlans() {
|
|
const wrapper = document.getElementById('plans-wrapper');
|
|
if (loadedPlans.length === 0) {
|
|
wrapper.innerHTML = `<p class="text-center text-xs font-bold text-slate-400 bg-white p-6 rounded-3xl">El catálogo de planes se está actualizando. Vuelve pronto.</p>`;
|
|
return;
|
|
}
|
|
|
|
wrapper.innerHTML = loadedPlans.map((p, index) => {
|
|
const isRecommended = p.type === 'anual'; // Marcamos el anual como recomendado visualmente
|
|
return `
|
|
<div onclick="selectPlan(${p.id})" id="card-${p.id}" class="plan-card ${index === 0 ? 'active' : 'bg-slate-50 border-slate-200'} p-6 rounded-[2.5rem] shadow-sm flex justify-between items-center cursor-pointer overflow-hidden relative">
|
|
${isRecommended ? `<div class="absolute top-0 right-0 bg-amber-500 text-white text-[9px] font-black px-4 py-1 rounded-bl-2xl uppercase tracking-tighter animate-offer">Recomendado</div>` : ''}
|
|
<div class="flex items-center gap-4">
|
|
<div class="w-6 h-6 rounded-full border-2 ${index === 0 ? 'border-blue-600' : 'border-slate-300'} flex items-center justify-center indicator-circle">
|
|
<div class="w-3 h-3 bg-blue-600 rounded-full ${index === 0 ? '' : 'hidden'} indicator-dot"></div>
|
|
</div>
|
|
<div>
|
|
<p class="font-black text-slate-800 text-base">${p.name}</p>
|
|
<p class="text-[10px] text-slate-500 font-bold uppercase tracking-widest">${p.urgencies_limit} Urgencias | ${p.bricos_limit} Bricos</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right pl-4 border-l border-slate-100">
|
|
<p class="text-2xl font-black text-blue-600 leading-none">${p.price}€</p>
|
|
<p class="text-[9px] font-black text-slate-400 uppercase mt-1">/ ${p.type}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
if (loadedPlans.length > 0) selectPlan(loadedPlans[0].id);
|
|
}
|
|
|
|
function selectPlan(id) {
|
|
selectedPlanId = id;
|
|
document.querySelectorAll('.plan-card').forEach(c => {
|
|
c.classList.remove('active', 'bg-white', 'shadow-xl');
|
|
c.classList.add('bg-slate-50', 'border-slate-200');
|
|
c.querySelector('.indicator-circle').classList.replace('border-blue-600', 'border-slate-300');
|
|
c.querySelector('.indicator-dot').classList.add('hidden');
|
|
});
|
|
|
|
const activeCard = document.getElementById(`card-${id}`);
|
|
activeCard.classList.add('active', 'bg-white', 'shadow-xl');
|
|
activeCard.classList.remove('bg-slate-50', 'border-slate-200');
|
|
activeCard.querySelector('.indicator-circle').classList.replace('border-slate-300', 'border-blue-600');
|
|
activeCard.querySelector('.indicator-dot').classList.remove('hidden');
|
|
}
|
|
|
|
async function processContract() {
|
|
if (!selectedPlanId) return alert("Por favor, selecciona un plan.");
|
|
|
|
const blank = document.createElement('canvas');
|
|
blank.width = canvas.width; blank.height = canvas.height;
|
|
if (canvas.toDataURL() === blank.toDataURL()) return alert("⚠️ Firma requerida: Por favor, firma el contrato en el recuadro blanco.");
|
|
|
|
const btn = document.getElementById('btnAction');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Generando Contrato...';
|
|
lucide.createIcons();
|
|
|
|
// 1. Inyectar firma y datos en PDF
|
|
const planSeleccionado = loadedPlans.find(p => p.id === selectedPlanId);
|
|
document.getElementById('pdf-signature-img').src = canvas.toDataURL("image/png");
|
|
document.getElementById('pdf-plan-name').innerText = `${planSeleccionado.name} (${planSeleccionado.price}€ / ${planSeleccionado.type})`;
|
|
document.getElementById('pdf-timestamp').innerText = "Documento firmado digitalmente el " + new Date().toLocaleDateString('es-ES') + " a las " + new Date().toLocaleTimeString('es-ES');
|
|
|
|
const element = document.getElementById('pdf-contract-template');
|
|
element.classList.remove('hidden');
|
|
|
|
const opt = {
|
|
margin: [0.3, 0, 0.3, 0],
|
|
filename: `Contrato_Proteccion_${new Date().getTime()}.pdf`,
|
|
image: { type: 'jpeg', quality: 0.98 },
|
|
html2canvas: { scale: 2, useCORS: true, scrollY: 0, windowHeight: element.scrollHeight },
|
|
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
|
|
};
|
|
|
|
try {
|
|
// 2. Crear PDF (Internamente) y generar Base64
|
|
const pdfBase64 = await html2pdf().set(opt).from(element).output('datauristring');
|
|
|
|
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Conectando con pasarela...';
|
|
lucide.createIcons();
|
|
|
|
// 3. Mandar al Servidor (Que crea el alta y genera el link de Stripe)
|
|
const payload = { plan_id: selectedPlanId, signature: canvas.toDataURL("image/png"), pdf_document: pdfBase64 };
|
|
|
|
const res = await fetch(`${API_URL}/public/portal/${token}/protection/subscribe`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
const apiData = await res.json();
|
|
|
|
if (apiData.ok && apiData.checkout_url) {
|
|
// 4. Redirigir a Stripe
|
|
window.location.href = apiData.checkout_url;
|
|
} else {
|
|
throw new Error(apiData.error || "No se pudo conectar con la pasarela de pago.");
|
|
}
|
|
|
|
} catch (err) {
|
|
alert("❌ Error: " + err.message);
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i data-lucide="crown" class="w-5 h-5"></i> Firmar y Pagar';
|
|
lucide.createIcons();
|
|
} finally {
|
|
element.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
</script>
|
|
</body>
|
|
</html> |