Files
Portal/crear-cita.html
2026-03-14 22:00:55 +00:00

364 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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">
<title>Portal del Cliente - IntegraRepara</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
/* Fondo de puntitos corporativo - Marsalva Style */
body {
background-color: #f8fafc;
background-image: radial-gradient(#cbd5e1 1.5px, transparent 1.5px);
background-size: 24px 24px;
min-height: 100vh;
}
.fade-in { animation: fadeIn 0.4s ease-out forwards; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* Ocultar scrollbar */
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>
</head>
<body class="text-slate-800 font-sans antialiased py-10 px-4 relative flex flex-col items-center">
<div class="flex flex-col items-center justify-center w-full mb-10 z-10 relative fade-in">
<div id="logoContainer" class="w-28 h-28 bg-white rounded-[2rem] shadow-2xl shadow-slate-200 flex items-center justify-center p-4 mb-6 border border-white">
<i data-lucide="image" class="text-slate-200 w-12 h-12" id="defaultLogo"></i>
<img id="companyLogoImg" src="" class="hidden w-full h-full object-contain rounded-2xl">
</div>
<p class="text-[10px] font-black text-blue-500 uppercase tracking-[0.2em] mb-1">Portal del Cliente</p>
<h1 id="companyNameDisplay" class="text-2xl font-black text-slate-800 tracking-tight text-center uppercase">
Cargando...
</h1>
</div>
<div id="welcomeBackBox" class="w-full max-w-md hidden mb-8 fade-in text-center md:text-left px-2">
<h2 class="text-5xl font-black text-slate-800 tracking-tight leading-none italic">Hola, <br><span id="clientFirstName" class="text-blue-600 not-italic"></span></h2>
</div>
<div class="w-full max-w-md bg-white rounded-[2.5rem] shadow-2xl shadow-slate-200/60 border border-slate-100 relative overflow-hidden transition-all duration-500">
<div class="p-8 md:p-10">
<div id="step1" class="fade-in space-y-6">
<div class="text-center">
<h3 class="text-xl font-extrabold text-slate-800">Bienvenido</h3>
<p class="text-sm text-slate-500 font-medium">Introduce tu móvil para empezar</p>
</div>
<div>
<label class="text-[10px] font-bold text-slate-400 uppercase tracking-widest ml-1">Teléfono Móvil</label>
<input type="tel" id="cliPhone" placeholder="600 000 000" class="w-full mt-2 bg-slate-50 border-2 border-slate-100 px-5 py-4 rounded-2xl text-xl text-center tracking-widest font-black focus:border-blue-500 focus:bg-white focus:ring-4 focus:ring-blue-500/10 outline-none transition-all">
</div>
<button type="button" onclick="requestOTP()" id="btnStep1" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-5 rounded-2xl shadow-xl shadow-blue-200 transition-all flex items-center justify-center gap-3 active:scale-95 uppercase tracking-widest text-xs">
<span>Recibir Código</span> <i data-lucide="message-circle" class="w-4 h-4"></i>
</button>
</div>
<div id="step2" class="fade-in space-y-6 hidden">
<div class="text-center">
<div class="w-12 h-12 bg-blue-50 text-blue-600 rounded-full flex items-center justify-center mx-auto mb-4">
<i data-lucide="lock" class="w-6 h-6"></i>
</div>
<h3 class="text-xl font-extrabold text-slate-800 mb-2">Verificación</h3>
<p class="text-sm text-slate-500 font-medium px-4">Introduce el código enviado al <strong id="displayPhone" class="text-slate-800"></strong></p>
</div>
<div>
<input type="number" id="cliCode" placeholder="0 0 0 0" maxlength="4" class="w-full mt-2 bg-slate-50 border-2 border-slate-100 px-5 py-5 rounded-2xl text-4xl text-center tracking-[0.5em] font-black focus:border-blue-500 focus:bg-white outline-none transition-all">
</div>
<button type="button" onclick="verifyOTP()" id="btnStep2" class="w-full bg-slate-800 hover:bg-slate-900 text-white font-black py-5 rounded-2xl shadow-xl shadow-slate-200 transition-all flex items-center justify-center gap-3 active:scale-95 uppercase tracking-widest text-xs">
<span>Verificar Acceso</span> <i data-lucide="shield-check" class="w-4 h-4"></i>
</button>
<button type="button" onclick="goToStep(1)" class="w-full text-slate-400 text-[10px] font-bold uppercase tracking-[0.2em] mt-4 hover:text-slate-600">Cambiar número</button>
</div>
<form id="step3" class="fade-in space-y-6 hidden" onsubmit="submitFinalRequest(event)">
<div id="boxName">
<label class="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Tu Nombre Completo *</label>
<input type="text" id="cliName" placeholder="Ej: Juan García" class="w-full mt-2 bg-slate-50 border-2 border-slate-100 px-5 py-4 rounded-2xl text-sm font-bold focus:border-blue-500 outline-none transition-all">
</div>
<div id="boxAddress">
<label class="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Dirección del Servicio *</label>
<select id="cliAddressSelect" onchange="toggleNewAddressInput()" class="hidden w-full mt-2 mb-3 bg-slate-50 border-2 border-slate-100 px-5 py-4 rounded-2xl text-sm font-bold outline-none cursor-pointer text-blue-700"></select>
<input type="text" id="cliAddressInput" placeholder="Calle, Número, Piso, Código Postal..." class="w-full mt-2 bg-slate-50 border-2 border-slate-100 px-5 py-4 rounded-2xl text-sm font-bold focus:border-blue-500 outline-none transition-all">
</div>
<div>
<label class="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">¿Qué profesional necesitas? *</label>
<div class="relative mt-2">
<select id="cliGuild" required class="w-full bg-slate-50 border-2 border-slate-100 px-5 py-4 rounded-2xl text-sm font-bold text-slate-700 focus:border-blue-500 outline-none appearance-none cursor-pointer">
<option value="">Cargando especialistas...</option>
</select>
<div class="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400">
<i data-lucide="chevron-down" class="w-4 h-4"></i>
</div>
</div>
</div>
<div>
<label class="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">Describe el problema *</label>
<textarea id="cliDesc" required rows="2" placeholder="Explícanos brevemente qué ocurre..." class="w-full mt-2 bg-slate-50 border-2 border-slate-100 px-5 py-4 rounded-2xl text-sm font-medium focus:border-blue-500 outline-none resize-none transition-all"></textarea>
</div>
<div class="bg-red-50/50 border-2 border-red-100 rounded-[2rem] p-6">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<span class="font-black text-red-700 text-sm flex items-center gap-2">
<i data-lucide="flame" class="w-4 h-4"></i> ¿Es una Urgencia?
</span>
<span class="text-[10px] font-bold text-red-400 uppercase mt-0.5 tracking-wider">Asistencia Inmediata</span>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="cliUrgent" onchange="handleUrgency(this)" class="sr-only peer">
<div class="w-14 h-8 bg-slate-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-red-100 peer-checked:bg-red-600 transition-all duration-300"></div>
<div class="absolute left-1 top-1 w-6 h-6 bg-white rounded-full transition-all duration-300 peer-checked:translate-x-6 shadow-sm border border-slate-200"></div>
</label>
</div>
</div>
<button type="submit" id="btnSubmitFinal" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-5 rounded-[1.5rem] shadow-xl shadow-blue-200 transition-all flex items-center justify-center gap-3 active:scale-95 uppercase tracking-widest text-xs mt-4">
<span>Solicitar Técnico Ahora</span> <i data-lucide="arrow-right" class="w-4 h-4"></i>
</button>
<button type="button" onclick="logoutClient()" class="w-full text-slate-400 text-[10px] font-bold uppercase tracking-[0.2em] mt-6 hover:text-slate-600 transition-colors">
Cambiar de número de teléfono
</button>
</form>
</div>
</div>
<div id="urgentModal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-md z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-[3rem] p-8 md:p-10 max-w-sm w-full shadow-2xl text-center fade-in border border-white">
<div class="w-20 h-20 bg-red-100 text-red-600 rounded-full flex items-center justify-center mx-auto mb-6 animate-pulse">
<i data-lucide="alert-circle" class="w-10 h-10"></i>
</div>
<h3 class="text-2xl font-black text-slate-800 mb-3">Servicio Urgente</h3>
<p class="text-sm text-slate-500 mb-8 leading-relaxed font-medium">
Los servicios de urgencia no requieren cita previa. <br><br>
<span class="text-red-600 font-bold italic">Esta modalidad conlleva un coste adicional de tarifa de urgencia.</span>
</p>
<div class="flex flex-col gap-3">
<button type="button" onclick="acceptUrgency()" class="w-full bg-red-600 hover:bg-red-700 text-white font-black py-4 rounded-2xl shadow-lg shadow-red-100 transition-all">Acepto el cargo</button>
<button type="button" onclick="cancelUrgency()" class="w-full bg-slate-100 hover:bg-slate-200 text-slate-500 font-bold py-4 rounded-2xl transition-all text-xs uppercase tracking-widest">Cancelar</button>
</div>
</div>
</div>
<div id="successScreen" class="fixed inset-0 bg-white z-[100] hidden flex-col items-center justify-center p-8 text-center fade-in">
<div class="w-28 h-28 bg-emerald-50 text-emerald-500 rounded-[2.5rem] flex items-center justify-center mb-8 shadow-2xl shadow-emerald-100">
<i data-lucide="check-circle" class="w-14 h-14"></i>
</div>
<h2 class="text-4xl font-black text-slate-800 mb-4 tracking-tight">¡Solicitud Enviada!</h2>
<p class="text-slate-500 font-bold max-w-xs leading-relaxed">Hemos recibido tu aviso. El técnico se pondrá en contacto contigo de inmediato a través de WhatsApp.</p>
<button onclick="location.reload()" class="mt-10 text-blue-600 font-black uppercase tracking-widest text-xs">Nueva solicitud</button>
</div>
<script>
lucide.createIcons();
// 🔧 CONFIGURACIÓN
const API_URL = 'https://integrarepara-api.integrarepara.es';
const urlParams = new URLSearchParams(window.location.search);
const OWNER_ID = parseInt(urlParams.get('c')) || 1;
let currentClient = null;
// 🔄 INICIO: CARGAR DATOS Y SESIÓN
document.addEventListener("DOMContentLoaded", () => {
loadCompanyData();
const sesionGuardada = localStorage.getItem('clienteSesion');
if (sesionGuardada) {
try {
const datos = JSON.parse(sesionGuardada);
if (Date.now() < datos.expires) {
document.getElementById('cliPhone').value = datos.phone;
prepareStep3(datos.client);
goToStep(3);
} else {
localStorage.removeItem('clienteSesion');
}
} catch (e) { localStorage.removeItem('clienteSesion'); }
}
});
// 🏢 DATOS DE EMPRESA Y GREMIOS
async function loadCompanyData() {
try {
const res = await fetch(`${API_URL}/public/company/${OWNER_ID}/guilds`);
const data = await res.json();
if (data.ok) {
document.getElementById('companyNameDisplay').innerText = data.name || "IntegraRepara";
if (data.logo) {
document.getElementById('defaultLogo').classList.add('hidden');
const img = document.getElementById('companyLogoImg');
img.src = data.logo;
img.classList.remove('hidden');
}
const selectGremio = document.getElementById('cliGuild');
selectGremio.innerHTML = '<option value="">Selecciona una especialidad...</option>';
if (data.guilds && data.guilds.length > 0) {
data.guilds.forEach(g => {
selectGremio.innerHTML += `<option value="${g.id}">${g.name}</option>`;
});
} else {
selectGremio.innerHTML += `<option value="0">Servicio General</option>`;
}
}
} catch (e) {
console.error("Error cargando empresa", e);
document.getElementById('companyNameDisplay').innerText = "Servicio Técnico";
}
}
// 🚪 SALIR
function logoutClient() {
localStorage.removeItem('clienteSesion');
document.getElementById('cliPhone').value = "";
document.getElementById('cliCode').value = "";
document.getElementById('welcomeBackBox').classList.add('hidden');
goToStep(1);
}
// 🚀 NAVEGACIÓN
function goToStep(step) {
document.getElementById('step1').classList.add('hidden');
document.getElementById('step2').classList.add('hidden');
document.getElementById('step3').classList.add('hidden');
if (step === 1) document.getElementById('step1').classList.remove('hidden');
else if (step === 2) document.getElementById('step2').classList.remove('hidden');
else if (step === 3) document.getElementById('step3').classList.remove('hidden');
lucide.createIcons();
}
// 📲 PEDIR OTP
async function requestOTP() {
const phone = document.getElementById('cliPhone').value;
if (phone.length < 9) return alert("Móvil no válido");
const btn = document.getElementById('btnStep1');
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>';
lucide.createIcons();
try {
const res = await fetch(`${API_URL}/public/auth/request-otp`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, owner_id: OWNER_ID })
});
if (res.ok) {
document.getElementById('displayPhone').innerText = phone;
goToStep(2);
}
} catch (e) { alert("Error"); }
finally { btn.disabled = false; btn.innerHTML = '<span>Recibir Código</span>'; lucide.createIcons(); }
}
// 🔑 VERIFICAR OTP
async function verifyOTP() {
const phone = document.getElementById('cliPhone').value;
const code = document.getElementById('cliCode').value;
const btn = document.getElementById('btnStep2');
btn.disabled = true;
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>';
lucide.createIcons();
try {
const res = await fetch(`${API_URL}/public/auth/verify-otp`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone, code, owner_id: OWNER_ID })
});
const data = await res.json();
if (data.ok) {
const caducidad = Date.now() + (10 * 24 * 60 * 60 * 1000);
localStorage.setItem('clienteSesion', JSON.stringify({
phone: phone, client: data.exists ? data.client : null, expires: caducidad
}));
prepareStep3(data.exists ? data.client : null);
goToStep(3);
} else { alert("Código incorrecto"); }
} catch (e) { alert("Error"); }
finally { btn.disabled = false; btn.innerHTML = '<span>Verificar</span>'; lucide.createIcons(); }
}
// 📝 PASO FINAL
function prepareStep3(clientData) {
currentClient = clientData;
const welcomeBox = document.getElementById('welcomeBackBox');
if (clientData && clientData.full_name) {
welcomeBox.classList.remove('hidden');
document.getElementById('clientFirstName').innerText = clientData.full_name.split(' ')[0].toUpperCase();
document.getElementById('boxName').classList.add('hidden');
document.getElementById('cliName').value = clientData.full_name;
let addrs = [];
try { addrs = typeof clientData.addresses === 'string' ? JSON.parse(clientData.addresses) : clientData.addresses; } catch(e) {}
if (addrs && addrs.length > 0) {
const selectAddr = document.getElementById('cliAddressSelect');
selectAddr.classList.remove('hidden');
document.getElementById('cliAddressInput').classList.add('hidden');
selectAddr.innerHTML = addrs.map(a => `<option value="${a}">${a}</option>`).join('') + `<option value="NEW"> Nueva dirección...</option>`;
document.getElementById('cliAddressInput').value = addrs[0];
}
} else {
welcomeBox.classList.add('hidden');
document.getElementById('boxName').classList.remove('hidden');
}
}
function toggleNewAddressInput() {
const sel = document.getElementById('cliAddressSelect');
const inp = document.getElementById('cliAddressInput');
if (sel.value === "NEW") {
inp.classList.remove('hidden'); inp.value = ""; inp.focus();
} else {
inp.classList.add('hidden'); inp.value = sel.value;
}
}
// 🚨 URGENCIA
function handleUrgency(cb) { if (cb.checked) document.getElementById('urgentModal').classList.remove('hidden'); }
function cancelUrgency() { document.getElementById('cliUrgent').checked = false; document.getElementById('urgentModal').classList.add('hidden'); }
function acceptUrgency() { document.getElementById('urgentModal').classList.add('hidden'); }
// 📤 ENVIAR
async function submitFinalRequest(e) {
e.preventDefault();
const btn = document.getElementById('btnSubmitFinal');
btn.disabled = true; btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>'; lucide.createIcons();
let finalAddress = document.getElementById('cliAddressInput').value;
const payload = {
phone: document.getElementById('cliPhone').value,
name: document.getElementById('cliName').value,
address: finalAddress,
guild_id: document.getElementById('cliGuild').value,
description: document.getElementById('cliDesc').value,
is_urgent: document.getElementById('cliUrgent').checked,
owner_id: OWNER_ID
};
try {
const res = await fetch(`${API_URL}/public/new-request`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.ok) {
if (data.action === 'queued') {
document.getElementById('successScreen').classList.remove('hidden');
document.getElementById('successScreen').classList.add('flex');
} else if (data.action === 'calendar') {
window.location.href = data.redirectUrl;
}
} else { alert("Error al crear la solicitud"); }
} catch (err) { alert('Error de conexión'); }
finally { btn.disabled = false; btn.innerHTML = '<span>Solicitar Técnico Ahora</span>'; lucide.createIcons(); }
}
</script>
</body>
</html>