440 lines
22 KiB
HTML
440 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>IntegraRepara - Gestión Inteligente</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
/* Animaciones suaves */
|
|
.fade-in { animation: fadeIn 0.5s ease-in-out; }
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
.hidden { display: none; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 font-sans text-gray-800">
|
|
|
|
<nav class="bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center shadow-sm">
|
|
<div class="flex items-center gap-2">
|
|
<div class="bg-blue-600 text-white p-2 rounded-lg font-bold">IR</div>
|
|
<span class="text-xl font-bold tracking-tight text-gray-900">IntegraRepara</span>
|
|
</div>
|
|
<div>
|
|
<button onclick="showLogin()" class="text-gray-600 hover:text-blue-600 font-medium transition-colors">Iniciar Sesión</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="min-h-[calc(100vh-80px)] flex items-center justify-center p-4">
|
|
|
|
<div class="max-w-4xl w-full bg-white rounded-2xl shadow-xl overflow-hidden flex flex-col md:flex-row fade-in">
|
|
|
|
<div class="md:w-1/2 bg-slate-900 text-white p-8 flex flex-col justify-center relative overflow-hidden">
|
|
<div class="absolute top-0 left-0 w-full h-full bg-gradient-to-br from-blue-600 to-slate-900 opacity-50"></div>
|
|
<div class="relative z-10">
|
|
<h2 class="text-3xl font-bold mb-4">Gestiona tu negocio de reparaciones</h2>
|
|
<p class="text-slate-300 mb-6">Automatiza citas, gestiona clientes y notifica por WhatsApp automáticamente. Todo en un solo lugar.</p>
|
|
<div class="flex gap-2 text-sm text-blue-200">
|
|
<span class="flex items-center gap-1"><i data-lucide="check-circle" class="w-4 h-4"></i> WhatsApp API</span>
|
|
<span class="flex items-center gap-1"><i data-lucide="check-circle" class="w-4 h-4"></i> Facturación</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md:w-1/2 p-8 md:p-12 relative">
|
|
|
|
<div id="loginForm" class="space-y-6">
|
|
<h2 class="text-2xl font-bold text-gray-800">Bienvenido de nuevo</h2>
|
|
<p class="text-gray-500 text-sm">Introduce tus datos para acceder al panel.</p>
|
|
|
|
<form onsubmit="handleLogin(event)" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
|
<input type="email" id="loginEmail" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Contraseña</label>
|
|
<input type="password" id="loginPass" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all">
|
|
<div class="text-right mt-1">
|
|
<button type="button" onclick="showForgotPassword()" class="text-xs text-blue-600 hover:underline">¿Olvidaste tu contraseña?</button>
|
|
</div>
|
|
</div>
|
|
<button type="submit" id="btnLogin" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 rounded-lg transition-all shadow-lg shadow-blue-500/30">
|
|
Entrar
|
|
</button>
|
|
</form>
|
|
|
|
<div class="text-center mt-4">
|
|
<p class="text-sm text-gray-600">¿No tienes cuenta? <button onclick="showRegister()" class="text-blue-600 font-bold hover:underline">Regístrate gratis</button></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="registerForm" class="hidden space-y-5">
|
|
<h2 class="text-2xl font-bold text-gray-800">Crear Cuenta</h2>
|
|
<p class="text-gray-500 text-sm">Prueba IntegraRepara gratis hoy mismo.</p>
|
|
|
|
<form onsubmit="handleRegister(event)" class="space-y-3">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="text-xs font-bold text-gray-600">Nombre Completo</label>
|
|
<input type="text" id="regName" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
</div>
|
|
<div>
|
|
<label class="text-xs font-bold text-gray-600">DNI / CIF</label>
|
|
<input type="text" id="regDni" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-xs font-bold text-gray-600">Teléfono (WhatsApp)</label>
|
|
<input type="tel" id="regPhone" placeholder="+34 600..." required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
<p class="text-xs text-gray-400 mt-1">Te enviaremos un código de verificación.</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-xs font-bold text-gray-600">Email</label>
|
|
<input type="email" id="regEmail" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-xs font-bold text-gray-600">Dirección</label>
|
|
<input type="text" id="regAddress" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-xs font-bold text-gray-600">Contraseña</label>
|
|
<input type="password" id="regPass" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
|
</div>
|
|
|
|
<button type="submit" id="btnRegister" class="w-full bg-slate-900 hover:bg-slate-800 text-white font-bold py-3 rounded-lg transition-all">
|
|
Registrarse y Enviar Código
|
|
</button>
|
|
</form>
|
|
|
|
<div class="text-center mt-2">
|
|
<p class="text-sm text-gray-600">¿Ya tienes cuenta? <button onclick="showLogin()" class="text-blue-600 font-bold hover:underline">Entrar</button></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="verifyForm" class="hidden space-y-6 text-center fade-in">
|
|
<div class="mx-auto w-16 h-16 bg-green-100 rounded-full flex items-center justify-center text-green-600 mb-4">
|
|
<i data-lucide="message-square" class="w-8 h-8"></i>
|
|
</div>
|
|
<h2 class="text-2xl font-bold text-gray-800">Verifica tu WhatsApp</h2>
|
|
<p class="text-gray-500 text-sm">Hemos enviado un código de 6 dígitos a tu teléfono.</p>
|
|
|
|
<form onsubmit="handleVerify(event)" class="space-y-4 max-w-xs mx-auto">
|
|
<input type="text" id="verifyCode" placeholder="123456" maxlength="6" class="w-full text-center text-3xl tracking-widest px-4 py-3 border-2 border-gray-300 rounded-xl focus:border-green-500 focus:ring-green-500 outline-none transition-all font-mono">
|
|
|
|
<button type="submit" id="btnVerify" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 rounded-lg transition-all shadow-lg shadow-green-500/30">
|
|
Verificar y Entrar
|
|
</button>
|
|
</form>
|
|
<button onclick="showRegister()" class="text-sm text-gray-400 hover:text-gray-600 mt-4">Volver / Corregir número</button>
|
|
</div>
|
|
|
|
<div id="forgotPasswordForm" class="hidden space-y-6 fade-in">
|
|
<div class="flex items-center gap-2 mb-4">
|
|
<button onclick="showLogin()" class="text-gray-500 hover:text-gray-700"><i data-lucide="arrow-left" class="w-5 h-5"></i></button>
|
|
<h2 class="text-2xl font-bold text-gray-800">Recuperar Acceso</h2>
|
|
</div>
|
|
|
|
<div id="forgotStep1">
|
|
<p class="text-gray-500 text-sm mb-4">Introduce tu DNI y Teléfono para verificar tu identidad.</p>
|
|
<form onsubmit="handleForgotRequest(event)" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">DNI / CIF</label>
|
|
<input type="text" id="forgotDni" required class="w-full px-4 py-2 border border-gray-300 rounded-lg outline-none focus:border-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Teléfono</label>
|
|
<input type="tel" id="forgotPhone" required class="w-full px-4 py-2 border border-gray-300 rounded-lg outline-none focus:border-blue-500">
|
|
</div>
|
|
<button type="submit" id="btnForgotRequest" class="w-full bg-slate-900 text-white font-bold py-3 rounded-lg hover:bg-slate-800 transition-all">
|
|
Enviar Código de Recuperación
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="forgotStep2" class="hidden">
|
|
<p class="text-gray-500 text-sm mb-4">Introduce el código recibido y tu nueva contraseña.</p>
|
|
<form onsubmit="handleResetConfirm(event)" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Código WhatsApp</label>
|
|
<input type="text" id="forgotCode" required placeholder="123456" class="w-full text-center tracking-widest px-4 py-2 border border-gray-300 rounded-lg outline-none focus:border-green-500 font-mono text-xl">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Nueva Contraseña</label>
|
|
<input type="password" id="forgotNewPass" required class="w-full px-4 py-2 border border-gray-300 rounded-lg outline-none focus:border-blue-500">
|
|
</div>
|
|
<button type="submit" id="btnResetConfirm" class="w-full bg-green-600 text-white font-bold py-3 rounded-lg hover:bg-green-700 transition-all">
|
|
Cambiar Contraseña
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast" class="fixed bottom-5 right-5 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-2xl transform translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center gap-3">
|
|
<span id="toastMsg">Mensaje</span>
|
|
</div>
|
|
|
|
<script>
|
|
// ==========================================
|
|
// CONFIGURACIÓN (URL DE TU API)
|
|
// ==========================================
|
|
const API_URL = "https://integrarepara-api.integrarepara.es";
|
|
|
|
// Inicializar Iconos
|
|
lucide.createIcons();
|
|
|
|
// Verificar si ya está logueado
|
|
if (localStorage.getItem('token')) {
|
|
window.location.href = "panel.html";
|
|
}
|
|
|
|
// Variables de estado
|
|
let tempPhone = "";
|
|
let forgotPhoneTemp = "";
|
|
|
|
// UI Helpers
|
|
function hideAll() {
|
|
// AÑADIDO: Ocultamos también el formulario de recuperar
|
|
['loginForm', 'registerForm', 'verifyForm', 'forgotPasswordForm'].forEach(id => document.getElementById(id).classList.add('hidden'));
|
|
}
|
|
|
|
function showRegister() {
|
|
hideAll();
|
|
document.getElementById('registerForm').classList.remove('hidden');
|
|
document.getElementById('registerForm').classList.add('fade-in');
|
|
}
|
|
|
|
function showLogin() {
|
|
hideAll();
|
|
document.getElementById('loginForm').classList.remove('hidden');
|
|
document.getElementById('loginForm').classList.add('fade-in');
|
|
}
|
|
|
|
function showVerify() {
|
|
hideAll();
|
|
document.getElementById('verifyForm').classList.remove('hidden');
|
|
}
|
|
|
|
// AÑADIDO: Mostrar formulario de recuperación
|
|
function showForgotPassword() {
|
|
hideAll();
|
|
document.getElementById('forgotPasswordForm').classList.remove('hidden');
|
|
document.getElementById('forgotStep1').classList.remove('hidden');
|
|
document.getElementById('forgotStep2').classList.add('hidden');
|
|
}
|
|
|
|
function showToast(msg, isError = false) {
|
|
const toast = document.getElementById('toast');
|
|
const toastMsg = document.getElementById('toastMsg');
|
|
toast.className = `fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-2xl transform transition-all duration-300 z-50 flex items-center gap-3 ${isError ? 'bg-red-600' : 'bg-slate-800'} text-white`;
|
|
toastMsg.innerText = msg;
|
|
toast.classList.remove('translate-y-20', 'opacity-0');
|
|
setTimeout(() => {
|
|
toast.classList.add('translate-y-20', 'opacity-0');
|
|
}, 4000);
|
|
}
|
|
|
|
// ==========================================
|
|
// LÓGICA DE REGISTRO
|
|
// ==========================================
|
|
async function handleRegister(e) {
|
|
e.preventDefault();
|
|
const btn = document.getElementById('btnRegister');
|
|
btn.disabled = true;
|
|
btn.innerText = "Procesando...";
|
|
|
|
const data = {
|
|
fullName: document.getElementById('regName').value,
|
|
dni: document.getElementById('regDni').value,
|
|
phone: document.getElementById('regPhone').value,
|
|
email: document.getElementById('regEmail').value,
|
|
address: document.getElementById('regAddress').value,
|
|
password: document.getElementById('regPass').value
|
|
};
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
const json = await res.json();
|
|
|
|
if (res.ok && json.ok) {
|
|
tempPhone = json.phone; // Guardar teléfono normalizado
|
|
showToast("✅ Código enviado por WhatsApp");
|
|
showVerify();
|
|
} else {
|
|
showToast("❌ " + (json.error || "Error desconocido"), true);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
showToast("❌ Error de conexión con el servidor", true);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerText = "Registrarse y Enviar Código";
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// LÓGICA DE VERIFICACIÓN
|
|
// ==========================================
|
|
async function handleVerify(e) {
|
|
e.preventDefault();
|
|
const btn = document.getElementById('btnVerify');
|
|
btn.disabled = true;
|
|
btn.innerText = "Verificando...";
|
|
|
|
const code = document.getElementById('verifyCode').value;
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/auth/verify`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ phone: tempPhone, code: code })
|
|
});
|
|
const json = await res.json();
|
|
|
|
if (res.ok && json.ok) {
|
|
showToast("🎉 ¡Cuenta verificada!");
|
|
localStorage.setItem('token', json.token);
|
|
// REDIRECCIÓN AL PANEL
|
|
setTimeout(() => { window.location.href = "panel.html"; }, 1000);
|
|
} else {
|
|
showToast("❌ " + (json.error || "Código incorrecto"), true);
|
|
}
|
|
} catch (error) {
|
|
showToast("❌ Error verificando código", true);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerText = "Verificar y Entrar";
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// LÓGICA DE LOGIN
|
|
// ==========================================
|
|
async function handleLogin(e) {
|
|
e.preventDefault();
|
|
const btn = document.getElementById('btnLogin');
|
|
btn.disabled = true;
|
|
btn.innerText = "Entrando...";
|
|
|
|
const email = document.getElementById('loginEmail').value;
|
|
const password = document.getElementById('loginPass').value;
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password })
|
|
});
|
|
const json = await res.json();
|
|
|
|
if (res.ok && json.ok) {
|
|
showToast("👋 ¡Hola de nuevo!");
|
|
|
|
// Guardamos el token y los datos del usuario para usarlos en la app
|
|
localStorage.setItem('token', json.token);
|
|
localStorage.setItem('role', json.role);
|
|
localStorage.setItem('userName', json.name);
|
|
|
|
// REDIRECCIÓN INTELIGENTE SEGÚN EL ROL
|
|
setTimeout(() => {
|
|
if (json.role === 'operario') {
|
|
window.location.href = "operario.html";
|
|
} else {
|
|
window.location.href = "panel.html";
|
|
}
|
|
}, 1000);
|
|
|
|
} else {
|
|
showToast("❌ " + (json.error || "Credenciales incorrectas"), true);
|
|
}
|
|
} catch (error) {
|
|
showToast("❌ Error de conexión", true);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerText = "Entrar";
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// AÑADIDO: LÓGICA DE RECUPERAR CONTRASEÑA
|
|
// ==========================================
|
|
|
|
async function handleForgotRequest(e) {
|
|
e.preventDefault();
|
|
const btn = document.getElementById('btnForgotRequest');
|
|
btn.disabled = true;
|
|
btn.innerText = "Enviando...";
|
|
|
|
const dni = document.getElementById('forgotDni').value;
|
|
const phone = document.getElementById('forgotPhone').value;
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/auth/forgot-password`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({ dni, phone })
|
|
});
|
|
const json = await res.json();
|
|
|
|
if (res.ok && json.ok) {
|
|
forgotPhoneTemp = document.getElementById('forgotPhone').value;
|
|
showToast("✅ Código enviado a tu WhatsApp");
|
|
document.getElementById('forgotStep1').classList.add('hidden');
|
|
document.getElementById('forgotStep2').classList.remove('hidden');
|
|
} else {
|
|
showToast("❌ " + (json.error || "Datos no encontrados"), true);
|
|
}
|
|
} catch(e) {
|
|
showToast("Error de conexión", true);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerText = "Enviar Código de Recuperación";
|
|
}
|
|
}
|
|
|
|
async function handleResetConfirm(e) {
|
|
e.preventDefault();
|
|
const btn = document.getElementById('btnResetConfirm');
|
|
btn.disabled = true;
|
|
btn.innerText = "Actualizando...";
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/auth/reset-password`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
phone: forgotPhoneTemp,
|
|
code: document.getElementById('forgotCode').value,
|
|
newPassword: document.getElementById('forgotNewPass').value
|
|
})
|
|
});
|
|
const json = await res.json();
|
|
|
|
if (res.ok && json.ok) {
|
|
showToast("🎉 Contraseña cambiada correctamente");
|
|
setTimeout(() => { showLogin(); }, 2000);
|
|
} else {
|
|
showToast("❌ " + (json.error || "Error"), true);
|
|
}
|
|
} catch(e) {
|
|
showToast("Error de conexión", true);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerText = "Cambiar Contraseña";
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |