Files
Portal/crear-cita.html
2026-03-14 21:49:44 +00:00

373 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); } }
/* Fix para el Toggle Switch - Movimiento suave */
.peer:checked ~ .peer-checked-bg { background-color: #ef4444; }
.peer:checked ~ .peer-checked-circle { transform: translateX(100%); border-color: white; }
</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">Hola, <br><span id="clientFirstName" class="text-blue-600"></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">
<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 gestionar tu asistencia</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">Código de Seguridad</h3>
<p class="text-sm text-slate-500 font-medium px-4">Introduce las 4 cifras enviadas 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 de teléfono</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, CP y Ciudad" 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">Especialidad Requerida *</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 profesionales...</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">Descripción de la Avería *</label>
<textarea id="cliDesc" required rows="3" placeholder="¿Qué ocurre? Ejemplo: Gotea el termo, no hay luz en la cocina..." 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 Urgente?
</span>
<span class="text-[10px] font-bold text-red-400 uppercase mt-0.5 tracking-wider">Atención en < 3 horas</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-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-6">
<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">
Usar otro 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">Atención Urgente</h3>
<p class="text-sm text-slate-500 mb-8 leading-relaxed font-medium">
Los servicios urgentes se atienden de forma prioritaria. <br><br>
<span class="text-red-600 font-bold">Esta modalidad conlleva un recargo por desplazamiento inmediato.</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">Entendido, acepto</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">No, es normal</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">Volver al inicio</button>
</div>
<script>
lucide.createIcons();
// 🔧 CONFIGURACIÓN GLOBAL
const API_URL = 'https://integrarepara-api.integrarepara.es';
const urlParams = new URLSearchParams(window.location.search);
const OWNER_ID = parseInt(urlParams.get('c')) || 1; // ID de empresa por URL
let currentClient = null;
// 🔄 INICIO AUTOMÁTICO (Cookie/Session de 10 días)
document.addEventListener("DOMContentLoaded", () => {
loadCompanyData();
const sesionGuardada = localStorage.getItem('clienteSesion');
if (sesionGuardada) {
try {
const datos = JSON.parse(sesionGuardada);
// Comprobamos si no ha caducado (10 días)
if (Date.now() < datos.expires) {
console.log("Sesión activa recuperada...");
document.getElementById('cliPhone').value = datos.phone;
prepareStep3(datos.client);
goToStep(3);
} else {
localStorage.removeItem('clienteSesion');
}
} catch (e) { localStorage.removeItem('clienteSesion'); }
}
});
// 🏢 CARGAR DATOS DE LA EMPRESA (LOGO, NOMBRE Y GREMIOS DINÁMICOS)
async function loadCompanyData() {
try {
const res = await fetch(`${API_URL}/public/company/${OWNER_ID}/guilds`);
const data = await res.json();
if (data.ok) {
// Nombre e Imagen
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');
}
// Gremios del desplegable
const selectGremio = document.getElementById('cliGuild');
selectGremio.innerHTML = '<option value="">Selecciona un profesional...</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 configuración de empresa", e);
document.getElementById('companyNameDisplay').innerText = "Marsalva Servicios";
}
}
// 🚪 LOGOUT
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();
}
// 📲 ENVIAR OTP
async function requestOTP() {
const phone = document.getElementById('cliPhone').value;
if (phone.length < 9) return alert("Por favor, introduce un móvil 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 al conectar con el servidor."); }
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) {
// Guardar sesión (10 días)
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 de conexión."); }
finally { btn.disabled = false; btn.innerHTML = '<span>Verificar Acceso</span>'; lucide.createIcons(); }
}
// 📝 PREPARAR FORMULARIO FINAL
function prepareStep3(clientData) {
currentClient = clientData;
const welcomeBox = document.getElementById('welcomeBackBox');
if (clientData && clientData.full_name) {
welcomeBox.classList.remove('hidden');
// Nombre en mayúsculas como en la foto
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;
}
}
// 🚨 GESTIÓN 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'); }
// 📤 ENVÍO DEFINITIVO
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> Enviando aviso...';
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') {
// Si no es urgente, le mandamos a elegir fecha
window.location.href = data.redirectUrl;
}
} else { alert("Error: " + data.error); }
} catch (err) { alert('Error de conexión'); }
finally { btn.disabled = false; btn.innerHTML = '<span>Solicitar Técnico Ahora</span>'; lucide.createIcons(); }
}
</script>
</body>
</html>