Просмотр исходного кода

Remove unnecessary text from PIN recovery email template and streamline HTML structure

Erwin Jacimino 6 месяцев назад
Родитель
Сommit
ac18512c33
2 измененных файлов с 298 добавлено и 560 удалено
  1. 0 1
      config/mails.py
  2. 298 559
      public/pin_forgot.html

+ 0 - 1
config/mails.py

@@ -652,7 +652,6 @@ PIN_SUCCESSFULLY = {
                                         <a href="https://expressklein.store" style="color: #101419; font-size: 20px; font-weight: 600; text-decoration: none; border-bottom: 2px solid #101419; padding-bottom: 4px;">
                                             expressklein.store
                                         </a>
-                                        <p style="color: #6b7280; font-size: 14px; margin: 16px 0 0 0;">O descarga nuestra app móvil</p>
                                     </td>
                                 </tr>
                             </table>

+ 298 - 559
public/pin_forgot.html

@@ -5,660 +5,399 @@
   <title>Recuperar PIN - Biergarten Klein</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   
-  <!-- Meta tags para evitar cache -->
   <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
   <meta http-equiv="Pragma" content="no-cache">
   <meta http-equiv="Expires" content="0">
   
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-  <link rel="stylesheet" as="style" onload="this.rel='stylesheet'"
-        href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans:wght@400;500;700;900&family=Spline+Sans:wght@400;500;700">
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?display=swap&family=Noto+Sans:wght@400;500;700;900&family=Spline+Sans:wght@400;500;700">
   <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
-   <script>
-        tailwind.config = {
-            theme: {
-                extend: {
-                    colors: {
-                        'custom-dark': '#101419',
-                        'custom-dark-hover': '#37404a',
-                        'gray-50': '#f9fafb',
-                        'gray-100': '#f3f4f6',
-                    }
-                }
-            }
+  <script>
+    tailwind.config = {
+      theme: {
+        extend: {
+          colors: {
+            'custom-dark': '#101419',
+            'custom-dark-hover': '#37404a',
+            'gray-50': '#f9fafb',
+            'gray-100': '#f3f4f6',
+          }
         }
-    </script>
+      }
+    }
+  </script>
 </head>
 
-<body class="min-h-screen bg-gray-50 flex items-center justify-center p-4" style='font-family:"Spline Sans","Noto Sans",sans-serif;'>
+<body class="min-h-screen bg-gray-50 flex items-center justify-center p-4 font-sans" style='font-family:"Spline Sans","Noto Sans",sans-serif;'>
   
   <div class="w-full max-w-md">
-    <!-- Header -->
     <div class="text-center mb-8">
       <div class="inline-flex items-center justify-center w-16 h-16 bg-[#101419] rounded-full mb-4">
         <svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
           <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
         </svg>
       </div>
-      <h1 class="text-[26px] font-bold text-[#101419] tracking-tight mb-2">
-        ¿Olvidaste tu PIN?
-      </h1>
-      <p class="text-[#58728d] text-sm leading-relaxed">
-        No te preocupes, te ayudamos a recuperar el acceso a tu cuenta
-      </p>
+      <h1 class="text-[26px] font-bold text-[#101419] tracking-tight mb-2">¿Olvidaste tu PIN?</h1>
+      <p class="text-[#58728d] text-sm leading-relaxed">No te preocupes, te ayudamos a recuperar el acceso a tu cuenta</p>
     </div>
 
-    <!-- Indicador de pasos -->
     <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6">
       <div class="space-y-4">
         <div class="flex items-start space-x-3" id="step1Indicator">
-          <div class="flex-shrink-0 w-6 h-6 bg-[#101419] text-white rounded-full flex items-center justify-center text-xs font-medium">1</div>
+          <div class="step-circle flex-shrink-0 w-6 h-6 bg-[#101419] text-white rounded-full flex items-center justify-center text-xs font-medium">1</div>
           <div class="text-sm">
-            <p class="font-medium text-[#101419]">Ingresa tu correo</p>
-            <p class="text-[#58728d] mt-1">Te enviaremos un código de verificación</p>
+            <p class="step-title font-medium text-[#101419]">Ingresa tu correo</p>
+            <p class="step-desc text-[#58728d] mt-1">Te enviaremos un código</p>
           </div>
         </div>
         <div class="flex items-start space-x-3" id="step2Indicator">
-          <div class="flex-shrink-0 w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">2</div>
+          <div class="step-circle flex-shrink-0 w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">2</div>
           <div class="text-sm">
-            <p class="font-medium text-gray-400">Código de verificación</p>
-            <p class="text-gray-400 mt-1">Ingresa el código de 6 dígitos</p>
+            <p class="step-title font-medium text-gray-400">Código de verificación</p>
+            <p class="step-desc text-gray-400 mt-1">Ingresa el código de 6 dígitos</p>
           </div>
         </div>
         <div class="flex items-start space-x-3" id="step3Indicator">
-          <div class="flex-shrink-0 w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">3</div>
+          <div class="step-circle flex-shrink-0 w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium">3</div>
           <div class="text-sm">
-            <p class="font-medium text-gray-400">Nuevo PIN</p>
-            <p class="text-gray-400 mt-1">Crea tu nuevo PIN de 4 dígitos</p>
+            <p class="step-title font-medium text-gray-400">Nuevo PIN</p>
+            <p class="step-desc text-gray-400 mt-1">Crea tu nuevo PIN</p>
           </div>
         </div>
       </div>
     </div>
 
-    <!-- FASE 1: Ingreso de correo -->
     <form id="emailForm" class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 space-y-6">
       <div class="text-center">
-        <h2 class="text-[19px] font-bold text-[#101419] mb-2">Ingresa tu correo electrónico</h2>
-        <p class="text-sm text-[#58728d]">
-          Te enviaremos un código de verificación a tu correo
-        </p>
+        <h2 class="text-[19px] font-bold text-[#101419] mb-2">Ingresa tu correo</h2>
+        <p class="text-sm text-[#58728d]">Te enviaremos un código de verificación</p>
       </div>
-
-      <div class="space-y-4">
-        <div>
-          <label for="emailInput" class="block text-sm font-medium text-[#101419] mb-2">
-            Correo electrónico 
-          </label>
-          <input 
-            id="emailInput"
-            name="email"
-            type="email"
-            class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all"
-            placeholder="tu@email.com"
-            required
-          />
-        </div>
+      <div>
+        <label for="emailInput" class="block text-sm font-medium text-[#101419] mb-2">Correo electrónico</label>
+        <input id="emailInput" name="email" type="email" class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] outline-none" placeholder="tu@email.com" required />
       </div>
-
       <div class="space-y-3">
-        <button 
-          id="emailSubmitBtn"
-          type="submit"
-          class="w-full bg-[#101419] hover:bg-[#37404a] disabled:opacity-50 disabled:cursor-not-allowed text-white py-3 rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-[#101419]"
-        >
-          Enviar código
-        </button>
-        
-        <a 
-          href="/"
-          class="block w-full text-center border border-gray-300 hover:border-[#101419] text-[#101419] py-3 rounded-lg font-medium transition-colors duration-200"
-        >
-          Volver al inicio
-        </a>
+        <button id="emailSubmitBtn" type="submit" class="w-full bg-[#101419] hover:bg-[#37404a] text-white py-3 rounded-lg font-medium disabled:opacity-50 transition-colors">Enviar código</button>
+        <a href="/" class="block w-full text-center border border-gray-300 text-[#101419] py-3 rounded-lg font-medium hover:border-[#101419] transition-colors">Volver al inicio</a>
       </div>
     </form>
 
-    <!-- FASE 2: Verificación de código -->
     <form id="codeForm" class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 space-y-6 hidden">
       <div class="text-center">
         <h2 class="text-[19px] font-bold text-[#101419] mb-2">Código de verificación</h2>
-        <p class="text-sm text-[#58728d]">
-          Ingresa el código de 6 dígitos que enviamos a <span id="emailDisplay" class="font-medium"></span>
-        </p>
+        <p class="text-sm text-[#58728d]">Ingresa el código enviado a <span id="emailDisplay" class="font-medium"></span></p>
       </div>
-
-      <div class="space-y-4">
-        <div>
-          <label for="codeInput" class="block text-sm font-medium text-[#101419] mb-2">
-            Código de verificación
-          </label>
-          <input 
-            id="codeInput"
-            name="code"
-            type="text"
-            maxlength="6"
-            class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all text-center text-2xl tracking-widest"
-            placeholder="000000"
-            required
-          />
-        </div>
-
-        <div class="text-center">
-          <button 
-            type="button"
-            id="resendCodeBtn"
-            class="text-sm text-[#58728d] hover:text-[#101419] transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:text-[#58728d]"
-          >
-            ¿No recibiste el código? <span class="font-medium" id="resendText">Reenviar</span>
-          </button>
-        </div>
+      <div>
+        <label for="codeInput" class="block text-sm font-medium text-[#101419] mb-2">Código (6 dígitos)</label>
+        <input id="codeInput" name="code" type="text" maxlength="6" inputmode="numeric" class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] outline-none text-center text-2xl tracking-widest" placeholder="000000" required />
       </div>
-
-      <div class="space-y-3">
-        <button 
-          id="codeSubmitBtn"
-          type="submit"
-          class="w-full bg-[#101419] hover:bg-[#37404a] disabled:opacity-50 disabled:cursor-not-allowed text-white py-3 rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-[#101419]"
-        >
-          Verificar código
-        </button>
-        
-        <button 
-          type="button"
-          id="backToEmailBtn"
-          class="block w-full text-center border border-gray-300 hover:border-[#101419] text-[#101419] py-3 rounded-lg font-medium transition-colors duration-200"
-        >
-          Cambiar correo
+      <div class="text-center">
+        <button type="button" id="resendCodeBtn" class="text-sm text-[#58728d] hover:text-[#101419] disabled:opacity-50 transition-colors">
+          ¿No recibiste el código? <span class="font-medium" id="resendText">Reenviar</span>
         </button>
       </div>
+      <div class="space-y-3">
+        <button id="codeSubmitBtn" type="submit" class="w-full bg-[#101419] hover:bg-[#37404a] text-white py-3 rounded-lg font-medium disabled:opacity-50 transition-colors">Verificar código</button>
+        <button type="button" id="backToEmailBtn" class="block w-full text-center border border-gray-300 text-[#101419] py-3 rounded-lg font-medium hover:border-[#101419] transition-colors">Cambiar correo</button>
+      </div>
     </form>
 
-    <!-- FASE 3: Crear nuevo PIN -->
     <form id="pinForm" class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 space-y-6 hidden">
       <div class="text-center">
         <h2 class="text-[19px] font-bold text-[#101419] mb-2">Crea tu nuevo PIN</h2>
-        <p class="text-sm text-[#58728d]">
-          Ingresa un PIN de 4 dígitos que sea fácil de recordar para ti
-        </p>
+        <p class="text-sm text-[#58728d]">Ingresa un PIN de 4 dígitos</p>
       </div>
-
       <div class="space-y-4">
         <div>
-          <label for="newPinInput" class="block text-sm font-medium text-[#101419] mb-2">
-            Nuevo PIN
-          </label>
-          <input 
-            id="newPinInput"
-            name="newPin"
-            type="password"
-            maxlength="4"
-            class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all text-center text-2xl tracking-widest"
-            placeholder="••••"
-            required
-          />
+          <label for="newPinInput" class="block text-sm font-medium text-[#101419] mb-2">Nuevo PIN</label>
+          <input id="newPinInput" name="newPin" type="password" maxlength="4" inputmode="numeric" class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] outline-none text-center text-2xl tracking-widest" placeholder="••••" required />
         </div>
-
         <div>
-          <label for="confirmPinInput" class="block text-sm font-medium text-[#101419] mb-2">
-            Confirmar PIN
-          </label>
-          <input 
-            id="confirmPinInput"
-            name="confirmPin"
-            type="password"
-            maxlength="4"
-            class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] focus:border-transparent outline-none transition-all text-center text-2xl tracking-widest"
-            placeholder="••••"
-            required
-          />
+          <label for="confirmPinInput" class="block text-sm font-medium text-[#101419] mb-2">Confirmar PIN</label>
+          <input id="confirmPinInput" name="confirmPin" type="password" maxlength="4" inputmode="numeric" class="w-full border border-gray-300 px-4 py-3 rounded-lg focus:ring-2 focus:ring-[#101419] outline-none text-center text-2xl tracking-widest" placeholder="••••" required />
         </div>
-
-        <div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-sm">
-          <div class="flex items-start space-x-2">
-            <svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
-              <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
-            </svg>
-            <p>Recuerda elegir un PIN que sea seguro pero fácil de recordar. Evita fechas obvias como tu cumpleaños.</p>
-          </div>
+        <div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-sm flex items-start space-x-2">
+          <svg class="w-4 h-4 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg>
+          <p>Elige un PIN seguro pero fácil de recordar.</p>
         </div>
       </div>
-
-      <div class="space-y-3">
-        <button 
-          id="pinSubmitBtn"
-          type="submit"
-          class="w-full bg-[#101419] hover:bg-[#37404a] disabled:opacity-50 disabled:cursor-not-allowed text-white py-3 rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-[#101419]"
-        >
-          Establecer nuevo PIN
-        </button>
-      </div>
+      <button id="pinSubmitBtn" type="submit" class="w-full bg-[#101419] hover:bg-[#37404a] text-white py-3 rounded-lg font-medium disabled:opacity-50 transition-colors">Establecer PIN</button>
     </form>
 
-    <!-- Mensaje de error global -->
-    <div id="errorMessage" class="hidden bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm mt-4">
-    </div>
-
-    <!-- Mensaje de éxito -->
-    <div id="successMessage" class="hidden bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm mt-4">
-    </div>
+    <div id="errorMessage" class="hidden bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm mt-4 text-center"></div>
+    <div id="successMessage" class="hidden bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm mt-4 text-center"></div>
   </div>
 
   <script>
-    // Variables globales
-    let currentStep = 1;
-    let userEmail = '';
-    let verificationCode = '';
-    let resendTimer = null;
-    let resendCountdown = 0;
-    let changeToken = '';
-    // Referencias a elementos del DOM
-    const emailForm = document.getElementById('emailForm');
-    const codeForm = document.getElementById('codeForm');
-    const pinForm = document.getElementById('pinForm');
-    
-    const emailInput = document.getElementById('emailInput');
-    const codeInput = document.getElementById('codeInput');
-    const newPinInput = document.getElementById('newPinInput');
-    const confirmPinInput = document.getElementById('confirmPinInput');
-    
-    const emailSubmitBtn = document.getElementById('emailSubmitBtn');
-    const codeSubmitBtn = document.getElementById('codeSubmitBtn');
-    const pinSubmitBtn = document.getElementById('pinSubmitBtn');
-    
-    const backToEmailBtn = document.getElementById('backToEmailBtn');
-    const resendCodeBtn = document.getElementById('resendCodeBtn');
-    const resendText = document.getElementById('resendText');
-    
-    const errorMessage = document.getElementById('errorMessage');
-    const successMessage = document.getElementById('successMessage');
-    const emailDisplay = document.getElementById('emailDisplay');
-
-    // Indicadores de pasos
-    const step1Indicator = document.getElementById('step1Indicator');
-    const step2Indicator = document.getElementById('step2Indicator');
-    const step3Indicator = document.getElementById('step3Indicator');
-
-    // Inicialización
-    emailInput.focus();
-
-    // Funciones de utilidad
-    function showError(message) {
-      errorMessage.textContent = message;
-      errorMessage.classList.remove('hidden');
-      successMessage.classList.add('hidden');
-    }
-
-    function showSuccess(message) {
-      successMessage.textContent = message;
-      successMessage.classList.remove('hidden');
-      errorMessage.classList.add('hidden');
-    }
-
-    function hideMessages() {
-      errorMessage.classList.add('hidden');
-      successMessage.classList.add('hidden');
-    }
-
-    function updateStepIndicators() {
-      // Resetear todos los indicadores
-      [step1Indicator, step2Indicator, step3Indicator].forEach((step, index) => {
-        const circle = step.querySelector('.w-6');
-        const texts = step.querySelectorAll('p');
+    // --- ESTADO GLOBAL ---
+    const state = {
+      step: 1,
+      email: '',
+      token: '',
+      resendTimer: null,
+      resendCountdown: 0
+    };
+
+    // --- REFERENCIAS DOM ---
+    const dom = {
+      forms: {
+        email: document.getElementById('emailForm'),
+        code: document.getElementById('codeForm'),
+        pin: document.getElementById('pinForm')
+      },
+      inputs: {
+        email: document.getElementById('emailInput'),
+        code: document.getElementById('codeInput'),
+        newPin: document.getElementById('newPinInput'),
+        confirmPin: document.getElementById('confirmPinInput')
+      },
+      buttons: {
+        email: document.getElementById('emailSubmitBtn'),
+        code: document.getElementById('codeSubmitBtn'),
+        pin: document.getElementById('pinSubmitBtn'),
+        resend: document.getElementById('resendCodeBtn'),
+        resendText: document.getElementById('resendText'),
+        back: document.getElementById('backToEmailBtn')
+      },
+      messages: {
+        error: document.getElementById('errorMessage'),
+        success: document.getElementById('successMessage'),
+        emailDisplay: document.getElementById('emailDisplay')
+      },
+      indicators: [
+        document.getElementById('step1Indicator'),
+        document.getElementById('step2Indicator'),
+        document.getElementById('step3Indicator')
+      ]
+    };
+
+    // --- UTILIDADES DE UI ---
+    const ui = {
+      showError: (msg) => {
+        dom.messages.error.textContent = msg;
+        dom.messages.error.classList.remove('hidden');
+        dom.messages.success.classList.add('hidden');
+      },
+      showSuccess: (msg) => {
+        dom.messages.success.textContent = msg;
+        dom.messages.success.classList.remove('hidden');
+        dom.messages.error.classList.add('hidden');
+      },
+      hideMessages: () => {
+        dom.messages.error.classList.add('hidden');
+        dom.messages.success.classList.add('hidden');
+      },
+      setLoading: (btn, isLoading, text) => {
+        btn.disabled = isLoading;
+        btn.textContent = text;
+      },
+      updateStepIndicators: () => {
+        dom.indicators.forEach((el, index) => {
+          const circle = el.querySelector('.step-circle');
+          const title = el.querySelector('.step-title');
+          const desc = el.querySelector('.step-desc');
+          const stepNum = index + 1;
+
+          // Reset clases base
+          circle.className = 'step-circle flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium transition-colors';
+          
+          if (stepNum < state.step) { // Completado
+            circle.classList.add('bg-green-500', 'text-white');
+            circle.innerHTML = '✓';
+            title.className = 'step-title font-medium text-green-600';
+            desc.className = 'step-desc mt-1 text-green-600';
+          } else if (stepNum === state.step) { // Actual
+            circle.classList.add('bg-[#101419]', 'text-white');
+            circle.textContent = stepNum;
+            title.className = 'step-title font-medium text-[#101419]';
+            desc.className = 'step-desc mt-1 text-[#58728d]';
+          } else { // Pendiente
+            circle.classList.add('bg-gray-300', 'text-gray-600');
+            circle.textContent = stepNum;
+            title.className = 'step-title font-medium text-gray-400';
+            desc.className = 'step-desc mt-1 text-gray-400';
+          }
+        });
+      },
+      setStep: (step) => {
+        state.step = step;
+        // Ocultar todos
+        Object.values(dom.forms).forEach(f => f.classList.add('hidden'));
         
-        if (index + 1 < currentStep) {
-          // Paso completado
-          circle.className = 'flex-shrink-0 w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-medium';
-          circle.innerHTML = '✓';
-          texts.forEach(text => {
-            text.className = text.className.replace('text-gray-400', 'text-green-600');
-            text.className = text.className.replace('text-[#58728d]', 'text-green-600');
-          });
-        } else if (index + 1 === currentStep) {
-          // Paso actual
-          circle.className = 'flex-shrink-0 w-6 h-6 bg-[#101419] text-white rounded-full flex items-center justify-center text-xs font-medium';
-          circle.textContent = index + 1;
-          texts.forEach(text => {
-            text.className = text.className.replace('text-gray-400', 'text-[#101419]');
-            if (text.className.includes('font-medium')) {
-              text.className = text.className.replace('text-gray-400', 'text-[#101419]');
-            } else {
-              text.className = text.className.replace('text-gray-400', 'text-[#58728d]');
-            }
-          });
-        } else {
-          // Paso pendiente
-          circle.className = 'flex-shrink-0 w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-medium';
-          circle.textContent = index + 1;
-          texts.forEach(text => {
-            text.className = text.className.replace('text-[#101419]', 'text-gray-400');
-            text.className = text.className.replace('text-[#58728d]', 'text-gray-400');
-            text.className = text.className.replace('text-green-600', 'text-gray-400');
-          });
-        }
-      });
-    }
+        // Gestionar temporizador
+        if (step !== 2) actions.stopResendTimer();
+        if (step === 2) actions.startResendTimer();
 
-    function showStep(step) {
-      // Ocultar todos los formularios
-      emailForm.classList.add('hidden');
-      codeForm.classList.add('hidden');
-      pinForm.classList.add('hidden');
-      
-      // Detener timer si cambia de paso
-      if (step !== 2) {
-        stopResendTimer();
-      }
-      
-      // Mostrar el formulario correspondiente
-      switch(step) {
-        case 1:
-          emailForm.classList.remove('hidden');
-          emailInput.focus();
-          break;
-        case 2:
-          codeForm.classList.remove('hidden');
-          codeInput.focus();
-          break;
-        case 3:
-          pinForm.classList.remove('hidden');
-          newPinInput.focus();
-          break;
-      }
-      
-      currentStep = step;
-      updateStepIndicators();
-      hideMessages();
-    }
+        // Mostrar actual
+        if (step === 1) { dom.forms.email.classList.remove('hidden'); dom.inputs.email.focus(); }
+        if (step === 2) { dom.forms.code.classList.remove('hidden'); dom.inputs.code.focus(); }
+        if (step === 3) { dom.forms.pin.classList.remove('hidden'); dom.inputs.newPin.focus(); }
 
-    function generateVerificationCode() {
-      return Math.floor(100000 + Math.random() * 900000).toString();
-    }
-
-    function startResendTimer() {
-      resendCountdown = 60; // 1 minuto
-      resendCodeBtn.disabled = true;
-      
-      resendTimer = setInterval(() => {
-        if (resendCountdown > 0) {
-          resendText.textContent = `Reenviar (${resendCountdown}s)`;
-          resendCountdown--;
-        } else {
-          clearInterval(resendTimer);
-          resendCodeBtn.disabled = false;
-          resendText.textContent = 'Reenviar';
-        }
-      }, 1000);
-    }
-
-    async function sendMail(){
+        ui.updateStepIndicators();
+        ui.hideMessages();
+      }
+    };
 
-        const codeVerify = await fetch('/recovery/validate', {
+    // --- LOGICA DE NEGOCIO (API) ---
+    const api = {
+      sendCode: async (email) => {
+        const res = await fetch('/recovery', {
           method: 'POST',
-          headers: {
-            'Content-Type': 'application/json'
-          },
-          body: JSON.stringify({ email: userEmail, code })
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ email })
         });
-
-        switch (codeVerify.status) {
-          case 200:
-            showSuccess('Código verificado correctamente');
-            setTimeout(async () => {
-              const responseJSON = await codeVerify.json();
-              changeToken = responseJSON.data.token;
-              showStep(3);
-            }, 1000);
-            break;
-          case 404:
-            showError('Usuario no encontrado. Por favor verifica e intenta nuevamente.');
-            codeInput.focus();
-            codeInput.select();
-            return;
-          case 400:
-            showError('Código incorrecto. Por favor verifica e intenta nuevamente.');
-            codeInput.focus();
-            codeInput.select();
-            return;
-          default:
-            return;
-        }
-        
-        codeSubmitBtn.disabled = false;
-        codeSubmitBtn.textContent = 'Verificar código';
-    }
-
-    function stopResendTimer() {
-      if (resendTimer) {
-        clearInterval(resendTimer);
-        resendTimer = null;
-      }
-      resendCountdown = 0;
-      resendCodeBtn.disabled = false;
-      resendText.textContent = 'Reenviar';
-    }
-
-    // FASE 1: Envío de correo
-    emailForm.addEventListener('submit', async function(e) {
-      e.preventDefault();
-      
-      const email = emailInput.value.trim();
-      
-      if (!email) {
-        showError('Por favor ingresa tu correo electrónico');
-        emailInput.focus();
-        return;
-      }
-      
-      if (!isValidEmail(email)) {
-        showError('Por favor ingresa un correo electrónico válido');
-        emailInput.focus();
-        return;
-      }
-      
-      emailSubmitBtn.disabled = true;
-      emailSubmitBtn.textContent = 'Enviando código...';
-      
-        userEmail = email;
-        emailDisplay.textContent = email;
-        const body = JSON.stringify({ email })
-        // En una aplicación real, aquí enviarías el código por email
-        const emailResponse = await fetch('/recovery', {
+        if (!res.ok) throw new Error((await res.json()).message || 'Error al enviar código');
+        return res.json();
+      },
+      validateCode: async (email, code) => {
+        const res = await fetch('/recovery/validate', {
           method: 'POST',
-          headers: {
-            'Content-Type': 'application/json'
-          },
-          body: body
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ email, code })
         });
-
-        if (!emailResponse.ok) {
-          showError(emailResponse.message || 'Error al enviar el código. Por favor intenta nuevamente.');
-          emailInput.focus();
-          return;
-        }
-
-        showSuccess(`Código enviado a ${email}`);
-        
-        setTimeout(() => {
-          showStep(2);
-          startResendTimer(); // Iniciar el timer cuando se muestra la fase 2
-        }, 400);
-        
-        emailSubmitBtn.disabled = false;
-        emailSubmitBtn.textContent = 'Enviar código';
-    });
-
-    // FASE 2: Verificación de código
-    codeForm.addEventListener('submit',async function(e) {
-      e.preventDefault();
-      
-      const code = codeInput.value.trim();
-      
-      if (!code || code.length !== 6) {
-        showError('Por favor ingresa el código de 6 dígitos');
-        codeInput.focus();
-        return;
-      }
-      
-      if (!/^\d{6}$/.test(code)) {
-        showError('El código debe contener solo números');
-        codeInput.focus();
-        return;
-      }
-      
-      codeSubmitBtn.disabled = true;
-      codeSubmitBtn.textContent = 'Verificando...';
-      
-
-   const codeVerify = await fetch('/recovery/validate', {
-     method: 'POST',
-     headers: {
-       'Content-Type': 'application/json'
-     },
-     body: JSON.stringify({ email: userEmail, code })
-   });
-
-   switch (codeVerify.status) {
-     case 200:
-       showSuccess('Código verificado correctamente');
-       setTimeout(async () => {
-         data = await codeVerify.json();
-         changeToken = data.token;
-         showStep(3);
-       }, 1000);
-       break;
-     case 404:
-       showError('Usuario no encontrado. Por favor verifica e intenta nuevamente.');
-       codeInput.focus();
-       codeInput.select();
-       return;
-     case 400:
-       showError('Código incorrecto. Por favor verifica e intenta nuevamente.');
-       codeSubmitBtn.disabled = false;
-       codeSubmitBtn.textContent = 'Verificar código';
-       codeInput.focus();
-       codeInput.select();
-       return;
-     default:
-       return;
-   }
-
-   codeSubmitBtn.disabled = false;
-   codeSubmitBtn.textContent = 'Verificar código';
-
-    });
-
-    // FASE 3: Crear nuevo PIN
-    pinForm.addEventListener('submit', async function(e) {
-      e.preventDefault();
-      
-      const newPin = newPinInput.value.trim();
-      const confirmPin = confirmPinInput.value.trim();
-      
-      if (!newPin || newPin.length !== 4) {
-        showError('El PIN debe tener 4 dígitos');
-        newPinInput.focus();
-        return;
-      }
-      
-      if (!/^\d{4}$/.test(newPin)) {
-        showError('El PIN debe contener solo números');
-        newPinInput.focus();
-        return;
-      }
-      
-      if (!confirmPin || confirmPin.length !== 4) {
-        showError('Por favor confirma tu PIN');
-        confirmPinInput.focus();
-        return;
-      }
-      
-      if (newPin !== confirmPin) {
-        showError('Los PINs no coinciden. Por favor verifica e intenta nuevamente.');
-        confirmPinInput.focus();
-        confirmPinInput.select();
-        return;
-      }
-      
-      
-      pinSubmitBtn.disabled = true;
-      pinSubmitBtn.textContent = 'Estableciendo PIN...';
-
-
-        const changePinResponse = await fetch('/api/users/pin-recovery', {
+        const data = await res.json();
+        if (res.status === 404) throw new Error('Usuario no encontrado');
+        if (res.status === 400) throw new Error('Código incorrecto');
+        if (!res.ok) throw new Error(data.message || 'Error de validación');
+        return data; // Debe contener { data: { token: '...' } }
+      },
+      setPin: async (token, new_pin) => {
+        const res = await fetch('/api/users/pin-recovery', {
           method: 'POST',
-          headers: {
+          headers: { 
             'Content-Type': 'application/json',
-            'Authorization': 'Bearer ' + changeToken
+            'Authorization': 'Bearer ' + token 
           },
-          body: JSON.stringify({email:userEmail, token: changeToken, new_pin: newPin})
+          body: JSON.stringify({ email: state.email, token, new_pin })
         });
-        if (changePinResponse.ok) {
-          showSuccess('¡PIN establecido correctamente! Ya puedes acceder a tu cuenta.');
-          setTimeout(() => {
-          // Redirigir al login o página principal
-          alert('PIN establecido correctamente. Serás redirigido al inicio de sesión.');
-          window.location.href = '/';
-        }, 500);
-        } else {
-          showError('Error al establecer el PIN. Por favor intenta nuevamente.');
+        if (!res.ok) throw new Error('Error al establecer el PIN');
+        return res.json();
+      }
+    };
+
+    // --- ACCIONES ---
+    const actions = {
+      requestEmailCode: async () => {
+        const email = dom.inputs.email.value.trim();
+        if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
+          return ui.showError('Ingresa un correo válido');
         }
 
+        ui.setLoading(dom.buttons.email, true, 'Enviando...');
         
-        
-    });
+        try {
+          await api.sendCode(email);
+          state.email = email;
+          dom.messages.emailDisplay.textContent = email;
+          ui.showSuccess(`Código enviado a ${email}`);
+          setTimeout(() => ui.setStep(2), 500);
+        } catch (err) {
+          ui.showError(err.message);
+        } finally {
+          ui.setLoading(dom.buttons.email, false, 'Enviar código');
+        }
+      },
+
+      submitVerificationCode: async () => {
+        const code = dom.inputs.code.value.trim();
+        if (!code || code.length !== 6) return ui.showError('Ingresa el código de 6 dígitos');
+
+        ui.setLoading(dom.buttons.code, true, 'Verificando...');
+
+        try {
+          const response = await api.validateCode(state.email, code);
+          // Ajuste robusto: a veces la API devuelve data.token o data.data.token
+          state.token = response.token || (response.data && response.data.token);
+          
+          if (!state.token) throw new Error('Token de seguridad no recibido');
+
+          ui.showSuccess('Código verificado');
+          setTimeout(() => ui.setStep(3), 500);
+        } catch (err) {
+          ui.showError(err.message);
+          dom.inputs.code.select();
+        } finally {
+          ui.setLoading(dom.buttons.code, false, 'Verificar código');
+        }
+      },
 
-    // Eventos adicionales
-    backToEmailBtn.addEventListener('click', function() {
-      stopResendTimer(); // Detener el timer si regresa al paso 1
-      showStep(1);
-    });
+      submitNewPin: async () => {
+        const pin = dom.inputs.newPin.value;
+        const confirm = dom.inputs.confirmPin.value;
 
-    resendCodeBtn.addEventListener('click', function() {
-      if (resendCodeBtn.disabled) return; // Prevenir clics múltiples
+        if (pin.length !== 4) return ui.showError('El PIN debe tener 4 dígitos');
+        if (pin !== confirm) return ui.showError('Los PINs no coinciden');
 
-      sendMail();
+        ui.setLoading(dom.buttons.pin, true, 'Guardando...');
 
-      startResendTimer(); // Reiniciar el timer
-    });
+        try {
+          await api.setPin(state.token, pin);
+          ui.showSuccess('¡PIN actualizado! Redirigiendo...');
+          setTimeout(() => window.location.href = '/', 1500);
+        } catch (err) {
+          ui.showError(err.message);
+          ui.setLoading(dom.buttons.pin, false, 'Establecer PIN');
+        }
+      },
 
-    // Formatear inputs de PIN para que solo acepten números
-    [newPinInput, confirmPinInput].forEach(input => {
-      input.addEventListener('input', function(e) {
-        e.target.value = e.target.value.replace(/\D/g, '').slice(0, 4);
-        hideMessages();
-      });
-    });
+      startResendTimer: () => {
+        state.resendCountdown = 60;
+        dom.buttons.resend.disabled = true;
+        actions.updateTimerText();
+        
+        state.resendTimer = setInterval(() => {
+          state.resendCountdown--;
+          actions.updateTimerText();
+          if (state.resendCountdown <= 0) actions.stopResendTimer();
+        }, 1000);
+      },
+
+      stopResendTimer: () => {
+        if (state.resendTimer) clearInterval(state.resendTimer);
+        state.resendTimer = null;
+        state.resendCountdown = 0;
+        dom.buttons.resend.disabled = false;
+        dom.buttons.resendText.textContent = 'Reenviar';
+      },
+
+      updateTimerText: () => {
+        if (state.resendCountdown > 0) {
+          dom.buttons.resendText.textContent = `Reenviar (${state.resendCountdown}s)`;
+        }
+      }
+    };
 
-    // Formatear input de código para que solo acepte números
-    codeInput.addEventListener('input', function(e) {
-      e.target.value = e.target.value.replace(/\D/g, '').slice(0, 6);
-      hideMessages();
+    // --- EVENT LISTENERS ---
+    
+    // Forms Submits
+    dom.forms.email.addEventListener('submit', (e) => { e.preventDefault(); actions.requestEmailCode(); });
+    dom.forms.code.addEventListener('submit', (e) => { e.preventDefault(); actions.submitVerificationCode(); });
+    dom.forms.pin.addEventListener('submit', (e) => { e.preventDefault(); actions.submitNewPin(); });
+
+    // Buttons
+    dom.buttons.back.addEventListener('click', () => ui.setStep(1));
+    dom.buttons.resend.addEventListener('click', () => {
+      if (!dom.buttons.resend.disabled) {
+        actions.stopResendTimer(); // Resetear timer actual si existe
+        actions.requestEmailCode(); // Reutilizar lógica de envío
+        // requestEmailCode maneja el timer al pasar al step 2, 
+        // pero como ya estamos en step 2, forzamos el inicio del timer:
+        actions.startResendTimer(); 
+      }
     });
 
-    // Ocultar mensajes cuando el usuario escriba
-    [emailInput, codeInput, newPinInput, confirmPinInput].forEach(input => {
-      input.addEventListener('input', hideMessages);
+    // Inputs Formatting
+    [dom.inputs.code, dom.inputs.newPin, dom.inputs.confirmPin].forEach(input => {
+      input.addEventListener('input', (e) => {
+        e.target.value = e.target.value.replace(/\D/g, ''); // Solo números
+        ui.hideMessages();
+      });
     });
+    dom.inputs.email.addEventListener('input', ui.hideMessages);
 
-    // Validar email
-    function isValidEmail(email) {
-      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-      return emailRegex.test(email);
-    }
-
-    // Navegación con Enter entre campos de PIN
-    newPinInput.addEventListener('keypress', function(e) {
-      if (e.key === 'Enter' && this.value.length === 4) {
-        confirmPinInput.focus();
-      }
-    });
+    // Inicialización
+    dom.inputs.email.focus();
 
-    confirmPinInput.addEventListener('keypress', function(e) {
-      if (e.key === 'Enter' && this.value.length === 4) {
-        pinForm.dispatchEvent(new Event('submit'));
-      }
-    });
   </script>
-
 </body>
 </html>