Problemas encontrados y soluciones aplicadas
1. FIDO_SERVER_ID fijo para producción nunca funcionaba en local
Archivo: reymota/settings.py:174-178
El RP ID de WebAuthn debe coincidir con el dominio del navegador. "nc.reymota.es" no funciona en localhost. Ahora es condicional:
if LOCAL:
    FIDO_SERVER_ID="localhost"
else:
    FIDO_SERVER_ID="nc.reymota.es"
Además cambié FIDO_SERVER_NAME de "TestApp" a "ReyMota.es".
2. passkeys.js incluido DOS VECES en el login
Archivo: templates/registration/login.html
Estaba dos veces: línea 66 (dentro del <form>, sin allow_password) y línea 92 (al final). Eliminé la del interior del formulario.
3. CRÍTICO: LoginView de Django + AuthenticationForm no pueden procesar passkeys
Archivos: reymotausers/auth.py, accounts/urls.py
El problema de raíz: Django's AuthenticationForm requiere username + password para llamar a authenticate(). El login con passkey envía el formulario sin password, así que authenticate() nunca se ejecutaba y el backend PasskeyModelBackend jamás procesaba el passkey.
Solución: Reemplacé el LoginView de Django por una vista personalizada que:
- Si el campo passkeys tiene datos → llama a authenticate(request) directamente → el backend procesa la aserción WebAuthn
- Si no → usa AuthenticationForm normal para login con email+password
Flujo que ahora funciona:
Botón "Login by Passkeys"
  → JS llama a GET /passkeys/auth/begin/ (guarda fido2_state en sesión)
  → navigator.credentials.get() → assertion
  → submit del form con passkeys=<JSON>
  → loginView detecta passkeys → authenticate(request)
  → PasskeyModelBackend → auth_complete() → login()
  → redirect a principal
