Salieron al pasar el lint por todo el proyecto. De 47 a 0 (quedan 15 avisos:
<img> vs <Image /> y el <link> del tema, los dos deliberados).
- Footer y Video (42 de los 47, que en realidad eran 7 enlaces: el plugin repite
cada uno 6 veces): usaban `<a href="/terms-and-conditions">` SIN el idioma
delante. No estaba roto de milagro: el middleware lo salvaba mirando la cookie
NEXT_LOCALE. Pero cada clic se comía un 307 y recargaba la página entera en
vez de navegar en cliente, y el idioma lo decidía la cookie en vez de la URL
en la que estás. Ahora van con el `Link` de i18n, que ya existía.
- Turnstile: escribía una ref (`cb.current = onVerify`) DURANTE el render. Es el
patrón de "callback fresca", pero no está permitido: React puede descartar ese
render y dejarla mal. Pasa a un efecto.
- ActivateClient y ConfirmClient: ponían el estado de error con setState dentro
del efecto cuando NO hay hash. Eso se sabe ya al renderizar (es un prop), así
que se deriva del estado inicial y se ahorra un render.
- BattlepayList: `window.location.href = …` -> `.assign()`, igual que en la
tienda.
- CookieConsent: aquí el efecto es correcto y la regla no aplica, así que se
silencia explicando por qué: el consentimiento vive en una cookie del
navegador, en el servidor no existe, y leerlo al renderizar rompería la
hidratación (le saldría el banner a quien ya había decidido).
Verificado: desde /es/ el pie enlaza a /es/terms-and-conditions y desde /en/ a
/en/terms-and-conditions; portada, cookies, tienda y mi-cuenta siguen dando 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Turnstile: quitar clases Tailwind del wrapper (my-3/flex/justify-center)
→ estilo inline neutral centrado, como la web Django.
- login/register/recover: el boton ya NO arranca deshabilitado (gris) por
falta de captcha; se ve activo como en Django. El captcha lo sigue
validando el servidor (devuelve captchaFailed). Se quita el SITE_KEY
cliente ya innecesario.
- reset-password: pagina que habia quedado sin migrar (usaba nw-btn/
nw-input/mx-auto/text-amber) → PageShell + middle-center-table + inputs
planos + recover-button + alert-message, igual que el resto del flujo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- lib/turnstile.ts: verifyTurnstile(token, ip) contra siteverify (fail-closed).
- components/Turnstile.tsx: widget cliente (carga el script de Cloudflare, callback
con el token). Site key por NEXT_PUBLIC_TURNSTILE_SITE_KEY; secreto server-only.
- LoginForm/RegisterForm: widget + token en el POST; submit deshabilitado hasta
resolver el captcha (si hay site key).
- Routes login/register: verifican el token antes de continuar (error captchaFailed).
- Catálogos: captchaFailed.
Verificado: POST sin token -> captchaFailed en login y registro (enforcement server).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>