Kaynağa Gözat

pin recovery update

latapp 9 ay önce
ebeveyn
işleme
c4426602c6

+ 188 - 30
config/mails.py

@@ -405,7 +405,6 @@ PRINTER_DISCONNECTED_MAIL = {
 </html>
 """
 }
-
 PIN_RECOVERY_MAIL = {
     "subject": "Recuperación de PIN | Pedidos Express",
     "body": """
@@ -414,7 +413,7 @@ PIN_RECOVERY_MAIL = {
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Recupera tu PIN </title>
+    <title>Recupera tu PIN</title>
 </head>
 <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f3f4f6;">
     <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f3f4f6; padding: 20px;">
@@ -439,7 +438,20 @@ PIN_RECOVERY_MAIL = {
                                 <tr>
                                     <td style="text-align: center; margin-bottom: 32px;">
                                         <h2 style="color: #101419; font-size: 24px; margin: 0 0 12px 0; font-weight: bold;">¿Olvidaste tu PIN?</h2>
-                                        <p style="color: #6b7280; font-size: 16px; line-height: 1.6; margin: 0;">No te preocupes, puedes crear un nuevo PIN de acceso a {app_name}.</p>
+                                        <p style="color: #6b7280; font-size: 16px; line-height: 1.6; margin: 0;">No te preocupes, usa el siguiente código para crear un nuevo PIN de acceso a {app_name}.</p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- Security Code Section -->
+                            <table width="100%" cellpadding="0" cellspacing="0">
+                                <tr>
+                                    <td style="background: linear-gradient(135deg, #101419 0%, #37404a 100%); border-radius: 12px; padding: 32px; text-align: center; margin: 32px 0; color: white;">
+                                        <p style="font-size: 18px; margin: 0 0 16px 0; opacity: 0.95;">Tu código de verificación es:</p>
+                                        <div style="font-size: 48px; font-weight: bold; letter-spacing: 8px; margin: 24px 0; border: 3px solid white; border-radius: 12px; padding: 24px; display: inline-block; min-width: 280px; background-color: rgba(255,255,255,0.1); font-family: 'Courier New', monospace;">
+                                            {verification_code}
+                                        </div>
+                                        <p style="font-size: 16px; margin: 16px 0 0 0; opacity: 0.9;">Ingresa este código en la aplicación para continuar</p>
                                     </td>
                                 </tr>
                             </table>
@@ -452,39 +464,157 @@ PIN_RECOVERY_MAIL = {
                                         <ul style="list-style: none; padding: 0; margin: 0;">
                                             <li style="color: #374151; font-size: 14px; line-height: 1.5; margin-bottom: 8px; padding-left: 20px; position: relative;">
                                                 <span style="color: #10b981; font-weight: bold; position: absolute; left: 0;">✓</span>
-                                                Este enlace es válido por 24 horas
+                                                Este código es válido por 15 minutos
                                             </li>
                                             <li style="color: #374151; font-size: 14px; line-height: 1.5; margin-bottom: 8px; padding-left: 20px; position: relative;">
                                                 <span style="color: #10b981; font-weight: bold; position: absolute; left: 0;">✓</span>
-                                                Podrás elegir tu nuevo PIN
+                                                Solo puedes usarlo una vez
                                             </li>
                                             <li style="color: #374151; font-size: 14px; line-height: 1.5; margin-bottom: 8px; padding-left: 20px; position: relative;">
                                                 <span style="color: #10b981; font-weight: bold; position: absolute; left: 0;">✓</span>
-                                                Si no solicitaste este cambio, ignora este correo
+                                                Si no solicitaste este código, ignora este correo
                                             </li>
                                         </ul>
                                     </td>
                                 </tr>
                             </table>
                             
-                            <!-- Reset PIN Section -->
+                            <!-- Next Steps -->
+                            <table width="100%" cellpadding="0" cellspacing="0" style="margin: 32px 0;">
+                                <tr>
+                                    <td style="background-color: #f0f9ff; border-radius: 8px; padding: 24px; border-left: 4px solid #3b82f6;">
+                                        <h3 style="color: #101419; font-size: 16px; font-weight: 600; margin: 0 0 12px 0;">¿Cómo usar el código?</h3>
+                                        <p style="color: #374151; font-size: 14px; line-height: 1.5; margin: 0;">
+                                            1. Abre la aplicación {app_name}<br>
+                                            2. Ve a la opción "Recuperar PIN"<br>
+                                            3. Ingresa el código de 6 dígitos<br>
+                                            4. Crea tu nuevo PIN<br>
+                                            5. ¡Disfruta de todos nuestros beneficios!
+                                        </p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- Warning -->
+                            <table width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;">
+                                <tr>
+                                    <td style="background-color: #fef2f2; border-radius: 8px; padding: 20px; border-left: 4px solid #ef4444;">
+                                        <p style="color: #991b1b; font-size: 14px; font-weight: 600; margin: 0 0 8px 0;">⚠️ Importante:</p>
+                                        <p style="color: #7f1d1d; font-size: 14px; line-height: 1.5; margin: 0;">
+                                            No compartas este código con nadie. Nuestro equipo nunca te pedirá este código por teléfono o email.
+                                        </p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- Website Section -->
                             <table width="100%" cellpadding="0" cellspacing="0">
                                 <tr>
-                                    <td style="background: linear-gradient(135deg, #101419 0%, #37404a 100%); border-radius: 12px; padding: 32px; text-align: center; margin: 32px 0; color: white;">
-                                        <p style="font-size: 18px; margin: 0 0 16px 0; opacity: 0.95;">Haz clic en el botón para crear tu nuevo PIN</p>
-                                        <div style="font-size: 24px; font-weight: bold; margin: 16px 0; border: 3px solid white; border-radius: 8px; padding: 16px; display: inline-block; min-width: 200px; background-color: rgba(255,255,255,0.1);">
-                                            🔐 Restablecer PIN
+                                    <td style="text-align: center; margin: 32px 0; padding: 24px; background-color: #f9fafb; border-radius: 8px;">
+                                        <p style="color: #6b7280; margin: 0 0 12px 0;">Visita nuestra plataforma:</p>
+                                        <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>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                        </td>
+                    </tr>
+                    
+                    <!-- Footer -->
+                    <tr>
+                        <td style="background-color: #f9fafb; padding: 24px 32px; text-align: center; border-top: 1px solid #e5e7eb;">
+                            <p style="color: #6b7280; font-size: 14px; margin: 0; line-height: 1.5;">
+                                <strong>Express Klein</strong> - Tu aplicación de pedidos favorita<br>
+                                Este código de verificación expira en 15 minutos.<br>
+                                Si no solicitaste este código, puedes ignorar este correo.<br>
+                                © 2025 Express Klein. Todos los derechos reservados.
+                            </p>
+                        </td>
+                    </tr>
+                    
+                </table>
+            </td>
+        </tr>
+    </table>
+</body>
+</html>
+    """
+}
+PIN_SUCCESSFULLY = {
+    "subject": "Tu PIN ha sido cambiado exitosamente",
+    "body": """
+        <!DOCTYPE html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>PIN Restablecido Exitosamente</title>
+</head>
+<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f3f4f6;">
+    <table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f3f4f6; padding: 20px;">
+        <tr>
+            <td align="center">
+                <table width="600" cellpadding="0" cellspacing="0" style="max-width: 600px; background-color: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
+                    
+                    <!-- Header -->
+                    <tr>
+                        <td style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; padding: 32px; text-align: center;">
+                            <div style="font-size: 48px; margin-bottom: 16px;">✅</div>
+                            <h1 style="margin: 0; font-size: 28px; font-weight: bold;">¡Hola {name}!</h1>
+                            <p style="margin: 8px 0 0 0; font-size: 16px; opacity: 0.9;">PIN Restablecido Exitosamente</p>
+                        </td>
+                    </tr>
+                    
+                    <!-- Content -->
+                    <tr>
+                        <td style="padding: 32px;">
+                            
+                            <!-- Success Message -->
+                            <table width="100%" cellpadding="0" cellspacing="0">
+                                <tr>
+                                    <td style="text-align: center; margin-bottom: 32px;">
+                                        <h2 style="color: #101419; font-size: 24px; margin: 0 0 12px 0; font-weight: bold;">🎉 ¡Perfecto!</h2>
+                                        <p style="color: #6b7280; font-size: 16px; line-height: 1.6; margin: 0;">Tu PIN de acceso a {app_name} ha sido actualizado correctamente.</p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- Confirmation Details -->
+                            <table width="100%" cellpadding="0" cellspacing="0">
+                                <tr>
+                                    <td style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); border-radius: 12px; padding: 32px; text-align: center; margin: 32px 0; color: white;">
+                                        <div style="font-size: 32px; margin-bottom: 16px;">🔐</div>
+                                        <p style="font-size: 18px; margin: 0 0 16px 0; opacity: 0.95;">Tu nuevo PIN está listo</p>
+                                        <div style="font-size: 16px; margin: 16px 0; border: 3px solid white; border-radius: 8px; padding: 20px; background-color: rgba(255,255,255,0.1);">
+                                            <strong>Fecha de cambio:</strong> {date}<br>
+                                            <strong>Hora:</strong> {time}
                                         </div>
-                                        <p style="font-size: 16px; margin: 16px 0 0 0; opacity: 0.9;">Podrás elegir un nuevo PIN de tu preferencia</p>
-                                        <table width="100%" cellpadding="0" cellspacing="0" style="margin-top: 20px;">
-                                            <tr>
-                                                <td align="center">
-                                                    <a href="{reset_pin_url}" style="background-color: white; color: #101419; padding: 16px 32px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; display: inline-block;">
-                                                        Crear Nuevo PIN
-                                                    </a>
-                                                </td>
-                                            </tr>
-                                        </table>
+                                        <p style="font-size: 16px; margin: 16px 0 0 0; opacity: 0.9;">Ya puedes acceder a tu cuenta con tu nuevo PIN</p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- Security Status -->
+                            <table width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;">
+                                <tr>
+                                    <td style="background-color: #f0fdf4; border-radius: 8px; padding: 24px; border-left: 4px solid #10b981;">
+                                        <h3 style="color: #101419; font-size: 18px; font-weight: 600; margin: 0 0 16px 0; text-align: center;">✅ Estado de Seguridad</h3>
+                                        <ul style="list-style: none; padding: 0; margin: 0;">
+                                            <li style="color: #166534; font-size: 14px; line-height: 1.5; margin-bottom: 8px; padding-left: 20px; position: relative;">
+                                                <span style="color: #10b981; font-weight: bold; position: absolute; left: 0;">✓</span>
+                                                PIN actualizado exitosamente
+                                            </li>
+                                            <li style="color: #166534; font-size: 14px; line-height: 1.5; margin-bottom: 8px; padding-left: 20px; position: relative;">
+                                                <span style="color: #10b981; font-weight: bold; position: absolute; left: 0;">✓</span>
+                                                Tu cuenta está completamente segura
+                                            </li>
+                                            <li style="color: #166534; font-size: 14px; line-height: 1.5; margin-bottom: 8px; padding-left: 20px; position: relative;">
+                                                <span style="color: #10b981; font-weight: bold; position: absolute; left: 0;">✓</span>
+                                                Acceso restaurado correctamente
+                                            </li>
+                                        </ul>
                                     </td>
                                 </tr>
                             </table>
@@ -493,25 +623,53 @@ PIN_RECOVERY_MAIL = {
                             <table width="100%" cellpadding="0" cellspacing="0" style="margin: 32px 0;">
                                 <tr>
                                     <td style="background-color: #f0f9ff; border-radius: 8px; padding: 24px; border-left: 4px solid #3b82f6;">
-                                        <h3 style="color: #101419; font-size: 16px; font-weight: 600; margin: 0 0 12px 0;">¿Qué sigue?</h3>
+                                        <h3 style="color: #101419; font-size: 16px; font-weight: 600; margin: 0 0 12px 0;">🚀 ¿Qué puedes hacer ahora?</h3>
                                         <p style="color: #374151; font-size: 14px; line-height: 1.5; margin: 0;">
-                                            1. Haz clic en "Crear Nuevo PIN"<br>
-                                            2. Elige un PIN fácil de recordar pero seguro<br>
-                                            3. Ingresa a {app_name} con tu nuevo PIN<br>
-                                            4. ¡Disfruta de todos nuestros beneficios!
+                                            1. Abre la aplicación {app_name}<br>
+                                            2. Ingresa con tu nuevo PIN<br>
+                                            3. Explora todas las funciones disponibles<br>
+                                            4. Realiza tus pedidos favoritos<br>
+                                            5. ¡Disfruta de la experiencia completa!
                                         </p>
                                     </td>
                                 </tr>
                             </table>
                             
-                            <!-- Website Section -->
+                            <!-- Security Tips -->
+                            <table width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;">
+                                <tr>
+                                    <td style="background-color: #fffbeb; border-radius: 8px; padding: 20px; border-left: 4px solid #f59e0b;">
+                                        <p style="color: #92400e; font-size: 14px; font-weight: 600; margin: 0 0 8px 0;">💡 Consejos de Seguridad:</p>
+                                        <p style="color: #a16207; font-size: 14px; line-height: 1.5; margin: 0;">
+                                            • Recuerda tu nuevo PIN y no lo compartas con nadie<br>
+                                            • Si tienes problemas para acceder, contáctanos<br>
+                                            • Mantén tu aplicación siempre actualizada
+                                        </p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- Not You Section -->
+                            <table width="100%" cellpadding="0" cellspacing="0" style="margin: 24px 0;">
+                                <tr>
+                                    <td style="background-color: #fef2f2; border-radius: 8px; padding: 20px; border-left: 4px solid #ef4444;">
+                                        <p style="color: #991b1b; font-size: 14px; font-weight: 600; margin: 0 0 8px 0;">⚠️ ¿No fuiste tú?</p>
+                                        <p style="color: #7f1d1d; font-size: 14px; line-height: 1.5; margin: 0;">
+                                            Si no realizaste este cambio, contacta inmediatamente a nuestro equipo de soporte. Tu seguridad es nuestra prioridad.
+                                        </p>
+                                    </td>
+                                </tr>
+                            </table>
+                            
+                            <!-- App Download Section -->
                             <table width="100%" cellpadding="0" cellspacing="0">
                                 <tr>
                                     <td style="text-align: center; margin: 32px 0; padding: 24px; background-color: #f9fafb; border-radius: 8px;">
-                                        <p style="color: #6b7280; margin: 0 0 12px 0;">Visita nuestra plataforma:</p>
+                                        <p style="color: #6b7280; margin: 0 0 12px 0;">Accede desde nuestra plataforma:</p>
                                         <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>
@@ -524,8 +682,8 @@ PIN_RECOVERY_MAIL = {
                         <td style="background-color: #f9fafb; padding: 24px 32px; text-align: center; border-top: 1px solid #e5e7eb;">
                             <p style="color: #6b7280; font-size: 14px; margin: 0; line-height: 1.5;">
                                 <strong>Express Klein</strong> - Tu aplicación de pedidos favorita<br>
-                                Este enlace de restablecimiento expira en 24 horas.<br>
-                                Si no solicitaste este cambio, puedes ignorar este correo.<br>
+                                Este correo confirma que tu PIN fue actualizado exitosamente.<br>
+                                Si tienes alguna pregunta, no dudes en contactarnos.<br>
                                 © 2025 Express Klein. Todos los derechos reservados.
                             </p>
                         </td>

+ 10 - 2
models/user.py

@@ -1,3 +1,4 @@
+import email
 from typing import Optional
 from pydantic import BaseModel, Field
 
@@ -27,10 +28,17 @@ class User(BaseModel):
     permissions: Optional[int] = 0  # Default to normal user
     reward_progress:int
 
-
+class UserMail(BaseModel):
+    email: str
 class LoginRequest(BaseModel):
     email: str
     pin: str = Field(min_length=4, max_length=4, description="4-digit PIN for user authentication")
 
 class PinRecoveryRequest(BaseModel):
-    email: str
+    email: str
+    token: Optional[str] = None
+    new_pin: Optional[str] = Field(min_length=4, max_length=4, description="4-digit PIN for user authentication")
+
+class PinRecoveryValidateRequest(BaseModel):
+    email: str
+    code: int

+ 2 - 1
public/main/index.html

@@ -316,7 +316,8 @@
                required />
       </div>
     </div>
-    <span id="ErrorLogin" class="hidden text-red-500 text-sm">Todo cago, perdon</span>
+    <span id="ErrorLogin" class="hidden text-red-500 text-sm"></span>
+    <a id="recoveryPIN" href="/recovery" class="block w-full bg-gray-200 text-center text-black py-3 rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-[#101419]" >Olvide mi PIN</a>
     <button id="sessionAcceptBtn"
             type="submit"
             class="w-full bg-[#101419] hover:bg-[#37404a] text-white py-3 rounded-lg font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-[#101419]">

+ 2 - 26
public/main/js/app.js

@@ -127,6 +127,7 @@ function initializeLoginModal() {
             document.querySelector("#emailInput").setAttribute("required", "");
             document.querySelector("#pinInput").setAttribute("required", "");
             logoutBtn.classList.add("hidden");
+            document.querySelector("#recoveryPIN").classList.remove("hidden");
         });
     }
 
@@ -186,33 +187,7 @@ function setupSearchListener() {
 //#endregion
 //#region ===== Utilidad =====
 
-  /**
-   * Ordena resultados por relevancia
-   */
-function sortByRelevance(items) {
-    console.log("Sorting results by relevance...", items);
-    const sortedResults = items
-      .sort((a, b) => {
-        // Primero por score (descendente)
-        if (a.score !== b.score) {
-          return b.score - a.score;
-        }
-        
-        // Luego por distancia (ascendente)
-        if (a.distance !== b.distance) {
-          return a.distance - b.distance;
-        }
-        
-        // Finalmente por longitud del texto (ascendente)
-        const aText = typeof a.item === 'string' ? a.item : JSON.stringify(a.item);
-        const bText = typeof b.item === 'string' ? b.item : JSON.stringify(b.item);
-        return aText.length - bText.length;
-      })
-      .map(result => result.item);
 
-      console.log("Sorted results: ",sortedResults);
-      return sortedResults;
-  }
 
 function setCookie(name, value, days) {
     const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
@@ -259,6 +234,7 @@ async function checkCache() {
             pinInputContainer.classList.add("hidden");
             pinInput.removeAttribute("required");
             document.querySelector("#logoutBtn").classList.remove("hidden");
+            document.querySelector("#recoveryPIN").classList.add("hidden");
             cacheMode = true;     
             return true
         }

+ 110 - 42
public/pin_forgot.html

@@ -250,7 +250,7 @@
     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');
@@ -390,6 +390,43 @@
       }, 1000);
     }
 
+    async function sendMail(){
+
+        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.');
+            codeInput.focus();
+            codeInput.select();
+            return;
+          default:
+            return;
+        }
+        
+        codeSubmitBtn.disabled = false;
+        codeSubmitBtn.textContent = 'Verificar código';
+    }
+
     function stopResendTimer() {
       if (resendTimer) {
         clearInterval(resendTimer);
@@ -401,7 +438,7 @@
     }
 
     // FASE 1: Envío de correo
-    emailForm.addEventListener('submit', function(e) {
+    emailForm.addEventListener('submit', async function(e) {
       e.preventDefault();
       
       const email = emailInput.value.trim();
@@ -418,38 +455,41 @@
         return;
       }
       
-      // Simular envío de código
       emailSubmitBtn.disabled = true;
       emailSubmitBtn.textContent = 'Enviando código...';
       
-      setTimeout(() => {
         userEmail = email;
-        verificationCode = generateVerificationCode();
         emailDisplay.textContent = email;
-        
+        const body = JSON.stringify({ email })
+        console.log(body)
         // En una aplicación real, aquí enviarías el código por email
-        fetch('/', {
+        const emailResponse = await fetch('/recovery', {
           method: 'POST',
           headers: {
             'Content-Type': 'application/json'
           },
-          body: JSON.stringify({ email })
+          body: body
         });
 
+        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
-        }, 1500);
+        }, 400);
         
         emailSubmitBtn.disabled = false;
         emailSubmitBtn.textContent = 'Enviar código';
-      }, 2000);
     });
 
     // FASE 2: Verificación de código
-    codeForm.addEventListener('submit', function(e) {
+    codeForm.addEventListener('submit',async function(e) {
       e.preventDefault();
       
       const code = codeInput.value.trim();
@@ -469,27 +509,45 @@
       codeSubmitBtn.disabled = true;
       codeSubmitBtn.textContent = 'Verificando...';
       
-      setTimeout(() => {
-        // En una aplicación real, verificarías el código con el servidor
-        if (code === verificationCode) {
-          showSuccess('Código verificado correctamente');
-          
-          setTimeout(() => {
-            showStep(3);
-          }, 1500);
-        } else {
-          showError('Código incorrecto. Por favor verifica e intenta nuevamente.');
-          codeInput.focus();
-          codeInput.select();
-        }
-        
-        codeSubmitBtn.disabled = false;
-        codeSubmitBtn.textContent = 'Verificar código';
-      }, 1500);
+
+   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.');
+       codeInput.focus();
+       codeInput.select();
+       return;
+     default:
+       return;
+   }
+
+   codeSubmitBtn.disabled = false;
+   codeSubmitBtn.textContent = 'Verificar código';
+
     });
 
     // FASE 3: Crear nuevo PIN
-    pinForm.addEventListener('submit', function(e) {
+    pinForm.addEventListener('submit', async function(e) {
       e.preventDefault();
       
       const newPin = newPinInput.value.trim();
@@ -532,18 +590,29 @@
       
       pinSubmitBtn.disabled = true;
       pinSubmitBtn.textContent = 'Estableciendo PIN...';
-      
-      setTimeout(() => {
-        // En una aplicación real, aquí guardarías el nuevo PIN
-        showSuccess('¡PIN establecido correctamente! Ya puedes acceder a tu cuenta.');
-        
-        setTimeout(() => {
+
+
+        const changePinResponse = await fetch('/api/users/pin-recovery', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+            'Authorization': 'Bearer ' + changeToken
+          },
+          body: JSON.stringify({email:userEmail, token: changeToken, new_pin: newPin})
+        });
+        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 = '/login';
-        }, 2000);
+          window.location.href = '/';
+        }, 500);
+        } else {
+          showError('Error al establecer el PIN. Por favor intenta nuevamente.');
+        }
+
+        
         
-      }, 2000);
     });
 
     // Eventos adicionales
@@ -554,10 +623,9 @@
 
     resendCodeBtn.addEventListener('click', function() {
       if (resendCodeBtn.disabled) return; // Prevenir clics múltiples
-      
-      verificationCode = generateVerificationCode();
-      console.log('Nuevo código de verificación:', verificationCode);
-      showSuccess('Código reenviado a ' + userEmail);
+
+      sendMail();
+
       startResendTimer(); // Reiniciar el timer
     });
 

+ 50 - 4
routes/users.py

@@ -1,7 +1,10 @@
+from datetime import datetime
 import json
 from logging import getLogger
+import re
 from uuid import uuid4
 
+from models import user
 import redis
 from cryptography.fernet import Fernet
 from fastapi import APIRouter, Depends, Request
@@ -10,10 +13,10 @@ from fastapi.exceptions import HTTPException
 
 from auth.security import generate_token
 from auth.security import get_current_user
-from config.mails import REGISTER_MAIL
+from config.mails import REGISTER_MAIL, PIN_RECOVERY_MAIL, PIN_SUCCESSFULLY
 from config.messages import ErrorResponse, SuccessResponse, UserResponse
 from config.settings import APPNAME, PIN_KEY
-from models.user import LoginRequest, PinRecoveryRequest, PinUserRequest, RegisterUserRequest, User, User, UserIDRequest, UserRewardRequest
+from models.user import LoginRequest, PinRecoveryRequest, PinRecoveryValidateRequest, PinUserRequest, RegisterUserRequest, User, UserIDRequest, UserMail, UserRewardRequest
 from services.data_service import BlacklistDataService, UserDataService
 from services.email_service import get_email_sender
 from services.print_service import print_ticket
@@ -253,6 +256,27 @@ async def delete_user(request: UserIDRequest):
     else:
         return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND})
 
+@user_router.post("/pin-recovery")
+async def change_pin(request: PinRecoveryRequest):
+    """Change a user's PIN"""
+    user = user_data_service.get_by_email(request.email)
+    if not user:
+        return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND.format(user_id=request.email)})
+
+    real_token = recovery_service.get_token(user.id)
+    if real_token and real_token != request.token:
+        return JSONResponse(status_code=400, content={"message": "Invalid token"})
+    logger.info(f"Pin change, to {request.new_pin} for user {user.email}")
+    user_data_service.update(user_id=user.id, pin_hash=request.new_pin)
+    sender = get_email_sender()
+    sender.send_email(
+        to=[user.email],
+        subject=PIN_SUCCESSFULLY["subject"],
+        body=PIN_SUCCESSFULLY["body"].format(app_name=APPNAME, date=datetime.now().strftime("%Y-%m-%d"), time=datetime.now().strftime("%H:%M:%S"), name=user.name)
+    )
+
+    return JSONResponse(status_code=200, content={"message": "Recovery email sent"})
+
 @user_router.post("/reward")
 async def reward_user(request: UserRewardRequest, user: User = Depends(get_current_user)):
     """Reward a user with 1 free beer"""
@@ -311,7 +335,7 @@ async def pin_forgot_get():
     )
 
 @recovery_pin_router.post("/")
-async def pin_forgot_post(request: PinRecoveryRequest, http_request: Request):
+async def pin_forgot_post(request: UserMail):
     """Handle the PIN forgot form submission"""
 
     user = user_data_service.get_by_email(request.email)
@@ -319,5 +343,27 @@ async def pin_forgot_post(request: PinRecoveryRequest, http_request: Request):
         return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND.format(user_id=request.email)})
 
     recovery_key = recovery_service.generate_recovery_key(user.id)
+    sender = get_email_sender()
+    sender.send_email(
+        to=[user.email],
+        subject=PIN_RECOVERY_MAIL["subject"],
+        body=PIN_RECOVERY_MAIL["body"].format(app_name=APPNAME, verification_code=recovery_key,name=user.name)
+    )
     # Send recovery_key to user's email
-    return JSONResponse(status_code=200, content={"message": SuccessResponse.RECOVERY_EMAIL_SENT})
+    return JSONResponse(status_code=200, content={"message": SuccessResponse.RECOVERY_EMAIL_SENT})
+
+@recovery_pin_router.post("/validate")
+async def pin_forgot_validate(request: PinRecoveryValidateRequest):
+    """Validate the PIN recovery code"""    
+    user = user_data_service.get_by_email(request.email)
+    if not user:
+        return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND.format(user_id=request.email)})
+    recovery_data = recovery_service.get_recovery_data(user.id)
+    logger.info(f"Recovery data for {request.email}: {recovery_data}|{request.code}")
+    if recovery_data.code == -1:
+        return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND.format(user_id=request.email)})
+    if recovery_data.code != request.code:
+        return JSONResponse(status_code=400, content={"message": "Invalid recovery code"})
+    token = uuid4().hex
+    recovery_service.add_token(user.id, token)
+    return JSONResponse(status_code=200, content={"message": "Recovery code validated successfully", "token": token})

+ 2 - 0
services/data_service.py

@@ -220,6 +220,8 @@ class UserDataService(BaseDataService):
     def login(self, email: str, pin_hashed: str) -> Optional[User]:
         """Login user by email and pin hash"""
         user = self.get_by_email(email)
+        if user:
+            logger.info(f"comparing {fernet.decrypt(user.pin_hash.encode()).decode()} with {pin_hashed}")
         if user and fernet.decrypt(user.pin_hash.encode()).decode() == pin_hashed:
             logger.info(f"User {user.email} logged in successfully.")
             return user

+ 21 - 10
services/recovery_service.py

@@ -3,21 +3,32 @@ import redis
 from pydantic import BaseModel
 import random
 class RedisRecoveryData(BaseModel):
-    user_id: int
+    code: int
 
-RECOVERY_REDIS_KEY = "pin_recovery:{pin_code}"
+RECOVERY_REDIS_KEY = "pin_recovery_{user_id}"
 
-def generate_recovery_key(user_id) -> str:
+def generate_recovery_key(user_id:int):
     pin_code = str(random.randint(100000, 999999))
     redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
-    redis_client.set(RECOVERY_REDIS_KEY.format(pin_code=pin_code), json.dumps({"user_id": user_id}))
-    redis_client.expire(RECOVERY_REDIS_KEY.format(pin_code=pin_code), 1800)  # Expire in 30 minutes
-    return RECOVERY_REDIS_KEY.format(pin_code=pin_code)
+    redis_client.set(RECOVERY_REDIS_KEY.format(user_id=user_id), json.dumps({"code": pin_code}))
+    redis_client.expire(RECOVERY_REDIS_KEY.format(user_id=user_id), 900)  # Expire in 15 minutes
+    return pin_code
 
-def get_recovery_data(pin_code: str) -> RedisRecoveryData:
-    redis_client = redis.Redis(host='localhost', port=6379, db=0)
-    data = redis_client.get(RECOVERY_REDIS_KEY.format(pin_code=pin_code))
+def add_token(user_id, token):
+    redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
+    redis_client.delete(RECOVERY_REDIS_KEY.format(user_id=user_id))
+    redis_client.set(RECOVERY_REDIS_KEY.format(user_id=user_id), json.dumps({"token": token}))
+
+def get_token(user_id):
+    redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
+    data = redis_client.get(RECOVERY_REDIS_KEY.format(user_id=user_id))
+    json_data = json.loads(str(data)) if data else None
+    return json_data.get("token") if json_data else None
+
+def get_recovery_data(user_id: int) -> RedisRecoveryData:
+    redis_client = redis.Redis(host='localhost', port=6379, db=0,decode_responses=True)
+    data = redis_client.get(RECOVERY_REDIS_KEY.format(user_id=user_id))
     json_data = json.loads(str(data)) if data else None
     if json_data:
         return RedisRecoveryData(**json_data)
-    return RedisRecoveryData(user_id=-1)
+    return RedisRecoveryData(code=-1)