Recuperar contraseña
Exportar contenido
Restablece tu contraseña cuando no puedes acceder a tu cuenta. Puedes recibir el enlace en tu correo principal o en el de recuperación si lo configuraste.
- En la pantalla de login, clic en “¿Olvidaste tu contraseña?”.
- Introduce el correo electrónico de tu cuenta.
- Elige dónde recibir el enlace:
- Correo principal — El que usas para iniciar sesión
- Correo de recuperación — Si configuraste uno alternativo
- Clic en “Enviar enlace” — recibirás un email con un enlace.
- Abre el email y clic en el enlace — te redirige a la pantalla de cambio.
- Introduce tu nueva contraseña (2 veces para confirmar).
- Clic en “Cambiar contraseña” — contraseña actualizada.
| Campo | Obligatorio | Descripción |
|---|---|---|
| Email de cuenta | Si | El correo con el que te registraste |
| Nueva contraseña | Si | Mínimo 8 caracteres |
| Confirmar contraseña | Si | Debe coincidir con la nueva |
Validaciones de contraseña
Sección titulada «Validaciones de contraseña»| Regla | Descripción |
|---|---|
| Longitud mínima | 8 caracteres |
| Coincidencia | Ambos campos deben ser iguales |
Como se que lo hice bien
Sección titulada «Como se que lo hice bien»- Email recibido con asunto: “Recuperación de contraseña - Factux”
- Toast de confirmación: “Contraseña actualizada correctamente”
- Puedes iniciar sesión con la nueva contraseña
Errores comunes
Sección titulada «Errores comunes»| Mensaje | Causa | Solución |
|---|---|---|
| ”No se encontró una cuenta con este correo” | Email no registrado | Verifica el email o regístrate |
| ”El enlace ha expirado” | Token usado o más de 24h | Solicita un nuevo enlace |
| ”Las contraseñas no coinciden” | Campos diferentes | Escribe la misma contraseña en ambos |
| ”Error al enviar el correo” | Problema de servidor | Reintenta en unos minutos |
ALERTA: El enlace de recuperación es válido por 24 horas y solo puede usarse una vez.
Detalles técnicos — Recuperar contraseña
Sección titulada «Detalles técnicos — Recuperar contraseña»Flujo de recuperación completo
Edge Function: send-password-reset-email
Ubicación: /supabase/functions/send-password-reset-email/index.ts
Proceso paso a paso:
// 1. Usuario solicita resetinterface ResetPasswordRequest { email: string; sendToRecoveryEmail?: boolean;}
// 2. Buscar usuarioconst { data: users } = await supabaseClient .rpc('get_user_by_email', { user_email: email.toLowerCase() });
// 3. Obtener recovery_email si se solicitaconst { data: profile } = await supabaseClient .from("profiles") .select("recovery_email") .eq("user_id", user.id) .maybeSingle();
// 4. Determinar email destinolet targetEmail = email.toLowerCase();if (sendToRecoveryEmail && profile?.recovery_email) { targetEmail = profile.recovery_email;}
// 5. Generar token seguroconst token = crypto.randomUUID();const expiresAt = new Date();expiresAt.setHours(expiresAt.getHours() + 24);
// 6. Guardar token en base de datosawait supabaseClient .from("password_reset_tokens") .insert({ user_id: user.id, email: email.toLowerCase(), token, expires_at: expiresAt.toISOString(), });
// 7. Crear URL de resetconst resetUrl = `${APP_URL}/reset-password?token=${token}`;
// 8. Enviar email via SendGridawait fetch("https://api.sendgrid.com/v3/mail/send", { method: "POST", headers: { Authorization: `Bearer ${SENDGRID_API_KEY}`, }, body: JSON.stringify({ template_id: "d-b937c4c6573a49c3bd745df56b5cb2e6", personalizations: [{ to: [{ email: targetEmail }], dynamic_template_data: { reset_password_url: resetUrl, user_email: email.toLowerCase(), expiration_hours: "24" } }] }),});Edge Function: update-password-with-token
Ubicación: /supabase/functions/update-password-with-token/index.ts
Proceso de cambio:
// 1. Validar tokenconst { data: tokenData } = await supabaseClient .from("password_reset_tokens") .select("*") .eq("token", token) .is("used_at", null) .gt("expires_at", new Date().toISOString()) .single();
// 2. Actualizar contraseñaawait supabaseClient.auth.admin.updateUserById( tokenData.user_id, { password: newPassword });
// 3. Marcar token como usadoawait supabaseClient .from("password_reset_tokens") .update({ used_at: new Date().toISOString() }) .eq("id", tokenData.id);Tabla de tokens:
| Columna | Tipo | Descripción |
|---|---|---|
| id | UUID | ID del token |
| user_id | UUID | Usuario asociado |
| TEXT | Email para referencia | |
| token | TEXT | UUID único del token |
| expires_at | TIMESTAMP | Expiración (created_at + 24h) |
| used_at | TIMESTAMP | Cuándo se usó (NULL si no usado) |
| created_at | TIMESTAMP | Cuándo se creó |
Constraints:
CONSTRAINT valid_expiry CHECK (expires_at > created_at)Limpieza automática:
-- Función que ejecuta cron para limpiar tokens viejosCREATE OR REPLACE FUNCTION cleanup_expired_password_tokens()RETURNS INTEGER AS $$DECLARE deleted_count INTEGER;BEGIN DELETE FROM public.password_reset_tokens WHERE expires_at < NOW() OR used_at IS NOT NULL AND used_at < NOW() - INTERVAL '7 days';
GET DIAGNOSTICS deleted_count = ROW_COUNT; RETURN deleted_count;END;$$ LANGUAGE plpgsql SECURITY DEFINER;Configuración SendGrid:
| Variable de entorno | Valor |
|---|---|
| SENDGRID_API_KEY | API key de SendGrid |
| SENDGRID_FROM_EMAIL | noreply@facturik.com |
| SENDGRID_TEMPLATE_ID | d-b937c4c6573a49c3bd745df56b5cb2e6 |
| APP_URL | https://clientes.facturik.com |
Query de diagnóstico:
-- Tokens activos pendientes de usoSELECT prt.*, u.email as user_emailFROM password_reset_tokens prtJOIN auth.users u ON prt.user_id = u.idWHERE prt.expires_at > NOW() AND prt.used_at IS NULLORDER BY prt.created_at DESC;
-- Historial de resets de un usuarioSELECT * FROM password_reset_tokensWHERE user_id = '<user_id>'ORDER BY created_at DESCLIMIT 10;
-- Tokens expirados sin usar (posible problema)SELECT COUNT(*) FROM password_reset_tokensWHERE expires_at < NOW() AND used_at IS NULL;