Files
Portal/crear-cita.html
2026-03-14 17:02:37 +00:00

352 lines
21 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>Solicitar Asistencia - IntegraRepara</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body { background-color: #f8fafc; }
.fade-in { animation: fadeIn 0.4s ease-out forwards; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* Toggle Switch Custom */
.toggle-checkbox:checked { right: 0; border-color: #ef4444; }
.toggle-checkbox:checked + .toggle-label { background-color: #ef4444; }
.toggle-checkbox { right: 0; z-index: 1; border-color: #e2e8f0; transition: all 0.3s; }
.toggle-label { background-color: #e2e8f0; transition: all 0.3s; }
</style>
</head>
<body class="text-slate-800 font-sans antialiased min-h-screen flex flex-col items-center justify-center p-4">
<div class="w-full max-w-md bg-white rounded-[2rem] shadow-2xl overflow-hidden border border-slate-100 relative">
<div class="bg-blue-600 p-8 text-center relative overflow-hidden transition-all duration-500" id="headerBox">
<div class="absolute inset-0 opacity-10 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')]"></div>
<div class="relative z-10 flex flex-col items-center">
<div class="w-16 h-16 bg-white rounded-2xl flex items-center justify-center shadow-lg mb-4 text-blue-600 transition-all duration-300" id="headerIconBox">
<i data-lucide="smartphone" id="headerIcon" class="w-8 h-8"></i>
</div>
<h1 class="text-2xl font-black text-white tracking-tight" id="headerTitle">Bienvenido</h1>
<p class="text-blue-100 text-sm mt-1 font-medium" id="headerSubtitle">Introduce tu móvil para empezar</p>
</div>
</div>
<div class="p-6 md:p-8 relative min-h-[300px]">
<div id="step1" class="fade-in space-y-6">
<div>
<label class="text-[10px] font-bold text-slate-500 uppercase ml-1">Teléfono Móvil</label>
<input type="tel" id="cliPhone" placeholder="Ej: 600123456" class="w-full mt-1 bg-slate-50 border-2 border-slate-100 px-4 py-4 rounded-xl text-lg text-center tracking-widest font-black focus:border-blue-500 focus:bg-white 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-4 rounded-xl shadow-xl shadow-blue-200 transition-all flex items-center justify-center gap-2 active:scale-95 uppercase tracking-wider text-sm">
<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 mb-4">
<p class="text-sm text-slate-500 font-medium">Hemos enviado un WhatsApp al <strong id="displayPhone" class="text-slate-800"></strong> con un código de 4 cifras.</p>
</div>
<div>
<label class="text-[10px] font-bold text-slate-500 uppercase ml-1">Código de Seguridad</label>
<input type="number" id="cliCode" placeholder="· · · ·" maxlength="4" class="w-full mt-1 bg-slate-50 border-2 border-slate-100 px-4 py-4 rounded-xl text-3xl text-center tracking-[1em] 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-4 rounded-xl shadow-xl shadow-slate-200 transition-all flex items-center justify-center gap-2 active:scale-95 uppercase tracking-wider text-sm">
<span>Verificar Acceso</span> <i data-lucide="check-circle" class="w-4 h-4"></i>
</button>
<button type="button" onclick="goToStep(1)" class="w-full text-slate-400 text-xs font-bold uppercase tracking-wider mt-2 hover:text-slate-600">Cambiar número</button>
</div>
<form id="step3" class="fade-in space-y-6 hidden" onsubmit="submitFinalRequest(event)">
<div id="welcomeBackBox" class="hidden bg-emerald-50 border border-emerald-100 rounded-xl p-4 text-center">
<span class="text-xs font-black text-emerald-600 uppercase tracking-widest">¡Hola de nuevo!</span>
<p class="text-lg font-black text-emerald-800 mt-1" id="clientGreeting">--</p>
</div>
<div id="boxName">
<label class="text-[10px] font-bold text-slate-500 uppercase ml-1">Tu Nombre Completo *</label>
<input type="text" id="cliName" placeholder="Ej: Juan Pérez" class="w-full mt-1 bg-slate-50 border-2 border-slate-100 px-4 py-3 rounded-xl text-sm font-bold focus:border-blue-500 outline-none">
</div>
<div id="boxAddress">
<label class="text-[10px] font-bold text-slate-500 uppercase ml-1">Dirección del Servicio *</label>
<select id="cliAddressSelect" onchange="toggleNewAddressInput()" class="hidden w-full mt-1 mb-2 bg-slate-50 border-2 border-slate-100 px-4 py-3 rounded-xl 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-1 bg-slate-50 border-2 border-slate-100 px-4 py-3 rounded-xl text-sm font-bold focus:border-blue-500 outline-none">
</div>
<div>
<label class="text-[10px] font-bold text-slate-500 uppercase ml-1">¿Qué profesional necesitas? *</label>
<select id="cliGuild" required class="w-full mt-1 bg-slate-50 border-2 border-slate-100 px-4 py-3 rounded-xl text-sm font-bold text-slate-700 focus:border-blue-500 outline-none cursor-pointer">
<option value="">Selecciona una especialidad...</option>
<option value="1">Electricidad</option>
<option value="2">Fontanería</option>
<option value="3">Persianas</option>
<option value="4">Cerrajería</option>
</select>
</div>
<div>
<label class="text-[10px] font-bold text-slate-500 uppercase ml-1">Describe el problema *</label>
<textarea id="cliDesc" required rows="2" placeholder="Explícanos brevemente qué ocurre..." class="w-full mt-1 bg-slate-50 border-2 border-slate-100 px-4 py-3 rounded-xl text-sm font-medium focus:border-blue-500 outline-none resize-none"></textarea>
</div>
<div class="bg-red-50 border-2 border-red-100 rounded-2xl p-4">
<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-1.5"><i data-lucide="flame" class="w-4 h-4"></i> ¿Es una Urgencia?</span>
<span class="text-[10px] font-bold text-red-500 uppercase mt-0.5">Asistencia Inmediata</span>
</div>
<div class="relative inline-block w-12 mr-2 align-middle select-none">
<input type="checkbox" id="cliUrgent" onchange="handleUrgency(this)" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
<label for="cliUrgent" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
</div>
<button type="submit" id="btnSubmitFinal" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-black py-4 rounded-xl shadow-xl shadow-blue-200 transition-all flex items-center justify-center gap-2 active:scale-95 uppercase tracking-wider text-sm mt-4">
<span>Buscar Técnico</span> <i data-lucide="search" class="w-4 h-4"></i>
</button>
</form>
</div>
</div>
<div id="urgentModal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl p-6 md:p-8 max-w-sm w-full shadow-2xl text-center fade-in">
<div class="w-16 h-16 bg-red-100 text-red-600 rounded-full flex items-center justify-center mx-auto mb-4 animate-pulse">
<i data-lucide="alert-triangle" class="w-8 h-8"></i>
</div>
<h3 class="text-xl font-black text-slate-800 mb-2">Servicio de Urgencia</h3>
<p class="text-sm text-slate-600 mb-6 font-medium">
Los servicios de urgencia <strong>no requieren cita previa</strong>. Un técnico acudirá a tu domicilio a la mayor brevedad posible.<br><br>
Ten en cuenta que esta modalidad conlleva un <strong>coste adicional de tarifa de urgencia</strong>.
</p>
<div class="flex gap-3">
<button type="button" onclick="cancelUrgency()" class="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold py-3 rounded-xl transition-all">Cancelar</button>
<button type="button" onclick="acceptUrgency()" class="flex-1 bg-red-600 hover:bg-red-700 text-white font-bold py-3 rounded-xl shadow-lg shadow-red-200 transition-all">Acepto</button>
</div>
</div>
</div>
<div id="successScreen" class="fixed inset-0 bg-white z-[100] hidden flex-col items-center justify-center p-6 text-center">
<div class="w-24 h-24 bg-emerald-100 text-emerald-500 rounded-full flex items-center justify-center mb-6">
<i data-lucide="zap" class="w-12 h-12"></i>
</div>
<h2 class="text-3xl font-black text-slate-800 mb-2">¡Técnico Avisado!</h2>
<p class="text-slate-500 font-medium max-w-sm">Tu solicitud de urgencia ha entrado en nuestra bolsa prioritaria. El primer operario disponible aceptará tu aviso y se pondrá en camino de inmediato.</p>
</div>
<script>
lucide.createIcons();
// 🔧 CONFIGURACIÓN
const API_URL = 'http://127.0.0.1:3000'; // Pon aquí tu dominio real
const OWNER_ID = 1; // Tu ID de empresa en la BBDD
// Estado del cliente
let currentClient = null;
// --- NAVEGACIÓN ENTRE PASOS ---
function goToStep(step) {
document.getElementById('step1').classList.add('hidden');
document.getElementById('step2').classList.add('hidden');
document.getElementById('step3').classList.add('hidden');
const title = document.getElementById('headerTitle');
const subtitle = document.getElementById('headerSubtitle');
const iconBox = document.getElementById('headerIconBox');
const icon = document.getElementById('headerIcon');
const headerBox = document.getElementById('headerBox');
if (step === 1) {
document.getElementById('step1').classList.remove('hidden');
title.innerText = "Bienvenido"; subtitle.innerText = "Introduce tu móvil para empezar";
headerBox.className = "bg-blue-600 p-8 text-center relative overflow-hidden transition-all duration-500";
iconBox.className = "w-16 h-16 bg-white rounded-2xl flex items-center justify-center shadow-lg mb-4 text-blue-600 transition-all duration-300";
icon.setAttribute('data-lucide', 'smartphone');
} else if (step === 2) {
document.getElementById('step2').classList.remove('hidden');
title.innerText = "Verificación"; subtitle.innerText = "Introduce el código recibido";
headerBox.className = "bg-slate-800 p-8 text-center relative overflow-hidden transition-all duration-500";
iconBox.className = "w-16 h-16 bg-slate-700 rounded-2xl flex items-center justify-center shadow-lg mb-4 text-white transition-all duration-300";
icon.setAttribute('data-lucide', 'lock');
} else if (step === 3) {
document.getElementById('step3').classList.remove('hidden');
title.innerText = "¿En qué te ayudamos?"; subtitle.innerText = "Detalles del servicio";
headerBox.className = "bg-emerald-600 p-8 text-center relative overflow-hidden transition-all duration-500";
iconBox.className = "w-16 h-16 bg-white rounded-2xl flex items-center justify-center shadow-lg mb-4 text-emerald-600 transition-all duration-300";
icon.setAttribute('data-lucide', 'wrench');
}
lucide.createIcons();
}
// --- PASO 1: PEDIR OTP ---
async function requestOTP() {
const phone = document.getElementById('cliPhone').value;
if (phone.length < 9) return alert("Introduce un teléfono válido.");
const btn = document.getElementById('btnStep1');
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Enviando...';
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 })
});
const data = await res.json();
if (data.ok) {
document.getElementById('displayPhone').innerText = phone;
goToStep(2);
} else {
alert("Error al enviar el código.");
}
} catch (e) {
alert("Error de conexión.");
} finally {
btn.innerHTML = '<span>Recibir Código</span> <i data-lucide="message-circle" class="w-4 h-4"></i>';
lucide.createIcons();
}
}
// --- PASO 2: VERIFICAR OTP ---
async function verifyOTP() {
const phone = document.getElementById('cliPhone').value;
const code = document.getElementById('cliCode').value;
if (code.length !== 4) return alert("El código debe tener 4 números.");
const btn = document.getElementById('btnStep2');
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Comprobando...';
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) {
prepareStep3(data.exists ? data.client : null);
goToStep(3);
} else {
alert("❌ " + (data.error || "Código incorrecto."));
}
} catch (e) {
alert("Error de conexión.");
} finally {
btn.innerHTML = '<span>Verificar Acceso</span> <i data-lucide="check-circle" class="w-4 h-4"></i>';
lucide.createIcons();
}
}
// --- ADAPTAR UI DEPENDIENDO DE SI EXISTE ---
function prepareStep3(clientData) {
currentClient = clientData;
const nameBox = document.getElementById('boxName');
const welcomeBox = document.getElementById('welcomeBackBox');
const selectAddr = document.getElementById('cliAddressSelect');
const inputAddr = document.getElementById('cliAddressInput');
if (clientData) {
// Ya lo conocemos
welcomeBox.classList.remove('hidden');
document.getElementById('clientGreeting').innerText = clientData.full_name;
nameBox.classList.add('hidden'); // Ocultamos pedir nombre
document.getElementById('cliName').value = clientData.full_name; // Lo rellenamos oculto
// Direcciones
let addrs = [];
try { addrs = typeof clientData.addresses === 'string' ? JSON.parse(clientData.addresses) : clientData.addresses; } catch(e) {}
if (addrs && addrs.length > 0) {
selectAddr.classList.remove('hidden');
inputAddr.classList.add('hidden');
selectAddr.innerHTML = '';
addrs.forEach(a => selectAddr.innerHTML += `<option value="${a}">${a}</option>`);
selectAddr.innerHTML += `<option value="NEW"> Añadir nueva dirección...</option>`;
inputAddr.value = addrs[0]; // Por defecto la primera
}
} else {
// Cliente Nuevo
welcomeBox.classList.add('hidden');
nameBox.classList.remove('hidden');
selectAddr.classList.add('hidden');
inputAddr.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;
}
}
// --- LÓGICA URGENCIA ---
const urgentToggle = document.getElementById('cliUrgent');
const urgentModal = document.getElementById('urgentModal');
function handleUrgency(checkbox) { if (checkbox.checked) urgentModal.classList.remove('hidden'); }
function cancelUrgency() { urgentToggle.checked = false; urgentModal.classList.add('hidden'); }
function acceptUrgency() { urgentModal.classList.add('hidden'); }
// --- ENVÍO FINAL ---
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> Procesando...'; lucide.createIcons();
// Averiguar qué dirección ha elegido
let finalAddress = document.getElementById('cliAddressInput').value;
if (document.getElementById('cliAddressSelect').value !== "NEW" && !document.getElementById('cliAddressSelect').classList.contains('hidden')) {
finalAddress = document.getElementById('cliAddressSelect').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: urgentToggle.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: ' + (data.error || 'No se pudo crear la solicitud.'));
btn.disabled = false; btn.innerHTML = '<span>Buscar Técnico</span> <i data-lucide="search" class="w-4 h-4"></i>'; lucide.createIcons();
}
} catch (err) {
alert('❌ Error de conexión.');
btn.disabled = false; btn.innerHTML = '<span>Buscar Técnico</span> <i data-lucide="search" class="w-4 h-4"></i>'; lucide.createIcons();
}
}
</script>
</body>
</html>