Commit Graph

54 Commits

Author SHA1 Message Date
Inna 4e089b5ebb Añadir ruta /security-2falogin (verificación en 2 pasos / TOTP)
Activación de 2FA (TOTP) para el login del juego, guardando el secreto en
acore_auth.account.totp_secret tal como lo lee el bnetserver 3.4.3.

- lib/two-factor.ts: TOTP (HMAC-SHA1, 30s, 6 dígitos, ±1) idéntico al core
  (verificado contra los vectores RFC 6238), Base32, otpauth, y codificado
  del secreto: crudo por defecto o AES-128-GCM (ciphertext‖IV12‖tag12) si se
  define TOTP_MASTER_SECRET (mismo hex que TOTPMasterSecret del servidor).
- Flujo seguro sin bloqueos: el secreto se genera y queda PENDIENTE en la
  sesión; sólo se escribe en la cuenta tras verificar un código válido.
- /api/account/2fa: activar (password + token de seguridad), verificar y
  desactivar (con código actual). QR generado en el servidor (el secreto no
  sale a terceros).
- Página + componente fieles al markup (preview, pasos, copiar clave, ojos).

Verificado E2E: activar -> QR/secreto -> verificar -> totp_secret = 20 bytes
(igual al secreto escaneado) -> desactivar -> NULL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 21:37:13 +00:00
Inna 89f6e5689f d-points: dejar sólo Stripe y SumUp con compra de PD funcional
Elimina las pestañas de PayPal/dLocal/Crypto/Skrill y por país; deja
Información + Stripe + SumUp (las dos únicas pasarelas que se usarán).

- Stripe: totalmente funcional reutilizando createCheckoutSession + webhook
  + página de éxito. Nueva ruta /api/dpoints/checkout crea la sesión con
  importe elegido por el usuario (1 unidad = 100 PD).
- SumUp: integración real (lib/sumup.ts) mediante checkout hospedado,
  habilitada al definir SUMUP_API_KEY/SUMUP_MERCHANT_CODE; si no, avisa.
- Nuevo servicio de entrega `dpoints` (lib/paid-services.ts) que acredita
  PD en home_api_points; entrega idempotente vía reclamo atómico.
- Página /d-points-success entrega ambos proveedores (session_id/ref).
- Iconos SVG generados para cada pasarela (stripe/sumup, marca + wordmark).
- Moneda configurable con DP_CURRENCY (por defecto EUR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 17:14:29 +00:00
Inna 44fcedbe62 Añadir ruta /d-points (Adquirir PD) con pasarelas multi-método
Reproduce fielmente el panel de compra de PD de UltimoWoW como componente
cliente con pestañas (Información, PayPal, dLocal Go, Crypto, Skrill y
métodos alternativos por país: Argentina, Chile, Colombia, Perú).

- Página servidor con login requerido (redirige a /login y /select-account
  como battlepay); inyecta username de la cuenta de juego e IP reales.
- Pestañas, tabla de rangos con mostrar/ocultar, y calculadora ARS
  (dolarapi) migradas a estado/efectos de React.
- Formularios con casilla de T&C que habilita el botón; PayPal usa
  PAYPAL_BUSINESS_EMAIL por entorno; dLocal/NOWPayments muestran aviso
  hasta configurar su backend (no se fabrican credenciales/gateways).
- Banderas por país y logo de NOWPayments añadido a nw-logos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:59:53 +00:00
Inna c3e57ff25e web-next: /recruit según el markup (Información + Panel RAF con recompensas)
- Reescrita la página: box «Información» con la guía completa de Recluta a un
  amigo (marca → nombre del reino) + box «Panel de Recluta a un amigo».
- Nuevo RecruitPanel: estadísticas (cuenta reclutada, amigos reclutados, amigos
  al nivel 80, recompensas reclamadas X/6) + tabla de recompensas (objetivo,
  objeto con icono y color de calidad, estado) con botón Reclamar para las
  disponibles + sección «Recompensas disponibles». Sustituye a RecruitClaim.
- lib/recruit-claim: getRecruitStats (isRecruited + recruitedCount).
- Colores de calidad de objeto .q0-.q5 en globals.css.
- Sembradas las 6 recompensas en home_recruitreward (seed en sql/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 16:14:10 +00:00
Inna a7fb4f7943 web-next: fix layout del formulario de añadir cuenta (mensaje abajo)
El <form> dentro del <p> «Nombre de la cuenta» era HTML inválido y el mensaje
salía inline (en inglés «Wrong password» partía en «Add Wrong / Password»).
Se quita el form: input+botón (onClick, Enter) en un span-bloque .add-account-box
debajo del nombre, y el mensaje en .add-account-msg (display:block) en su propia
línea. Igual en ES y EN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:54:50 +00:00
Inna c2e35d33ea web-next: botón «+» para crear cuentas de juego (WOW2, WOW3…)
Junto al «Nombre de la cuenta» hay un botón «+» que pide la contraseña y crea
una nueva cuenta de juego bajo la Battle.net. Ruta /api/account/add-game-account:
verifica la contraseña (authenticate contra battlenet_accounts), calcula el
siguiente battlenet_index (máx 8) y crea la fila `account` con SRP6 Grunt
derivado de la contraseña (gameMakeRegistration) — igual que la activación, así
la cuenta sirve para entrar al juego. Recarga completa al crear/cambiar.
GameAccountSwitcher siempre presente (select si >1, texto si 1) + «+».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:50:18 +00:00
Inna e62ab94054 web-next: selector de cuenta INLINE en «Nombre de la cuenta» + fix iconos al cambiar
- El desplegable WOW1/WOW2… ya no es un box aparte: va dentro del campo «Nombre
  de la cuenta» (select compacto .account-select). Con una sola cuenta, texto plano.
- FIX iconos: el cambio de cuenta hacía router.refresh() (navegación suave) que en
  algunos navegadores dejaba caer las hojas de estilo/FA y no se recuperaban. Ahora
  hace window.location.reload() (recarga completa) → todo se recarga bien.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:39:46 +00:00
Inna e1983eee9e web-next: selector de cuenta de juego (WOW1/WOW2…) en /account
Si la Battle.net tiene más de una cuenta de juego, /account muestra un
desplegable «Cargar datos de la cuenta» (WOW1, WOW2…). Al cambiar, fija la
cuenta activa en la sesión vía /api/auth/select-account y refresca → toda la
página (datos, personajes, servicios, compras) opera sobre la cuenta elegida
(la sesión.accountId manda). Con una sola cuenta no aparece (como hasta ahora).
components/GameAccountSwitcher.tsx (client) + getGameAccounts en la page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:28:09 +00:00
Inna 24482eaea0 web-next: «Cambiar correo» usa el mismo icono que «Cambiar contraseña»
En «Opciones de cuenta», change-email pasa de email-icon a password-icon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:09:47 +00:00
Inna eb030ef966 web-next: «Opciones de cuenta» reordenado según el markup
accountTools en el orden del markup: cambiar contraseña, 2FA (email-icon,
label purple-info), transferir PD, comercio de PD, código promoción, token,
recluta, renombrar hermandad, votar, adquirir PD. «Cambiar correo» (que
tenemos y no está en la lista) al final; panel admin el último (solo admin).
Tool.labelClass para el morado del 2FA. Iconos nuevos del sprite: trade-pd-icon
(-299 -928) y color .purple-info (#b172ff) añadidos al tema. Claves i18n nuevas
(svc2FA/TransferDP/TradePD/Promo/RenameGuild/DPoints). Rutas nuevas → 404 aún.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:04:46 +00:00
Inna 38b4989268 web-next: panel «Opciones de personaje» completo y en orden + sprite de iconos nuevo
- characterTools reordenado y completado según el markup del usuario (14 items):
  ...gold, Rastreador de misiones (/quest), transfer, Recuperar personaje
  (/restore-character), Recuperar ítems (/restore-items), Tienda (/store),
  Enviar regalo (/send-gift). Se retira battlepay del panel (queda en cabecera).
  Claves i18n nuevas (svcQuest/RestoreChar/RestoreItems/StoreItems/SendGift).
- Sprite de iconos actualizado a account-icons.png (de uw-ryu) que incluye el
  icono de regalo; CSS: .acc-icon → .png, send-gift-icon (-424 -553) y promo-icon
  reubicado (-174 -928), verificado que las 25 posiciones restantes coinciden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:19:54 +00:00
Inna efbc7ec623 web-next: panel HISTORIALES en /account (debajo de Opciones de personaje)
Tercer panel plegable «HISTORIALES» (id account-history) con 4 herramientas:
Historial de PD y PV, transacciones, sanciones y seguridad (points-history/
trans-history/ban-history/security-history; iconos pointsh/dnth/banh/securityh).
Arranca CERRADO como los otros dos (el usuario lo abre al click). Prop
defaultOpen añadido a Panel (no usado aquí). Claves i18n en Account (es/en).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:01:31 +00:00
Inna 7df9cc408a web-next: footer usa el nombre del reino (realmlist) en copyright y diseño
Las dos líneas del pie (© Copyright NovaWoW™ y Diseño: "NovaWoW" Por Inna
Hoover Brown) ahora usan el nombre del reino de realmlist (getRealmName).
Footer pasa a async server component.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 13:37:13 +00:00
Inna 1fc235c24f web-next: usar el nombre del reino (realmlist) en menú, estado y título
- Menú de navegación: el desplegable «NovaWoW» ahora muestra el nombre del
  reino de realmlist (Header pasa realmName a SiteHeader).
- Home: el título «NovaWoW WOTLK 3.4.3» y el nombre del recuadro de estado
  (antes home_serverselection.name) usan el nombre del reino de realmlist.
Ahora el reino es «Trinity». El logo (alt) y el texto de intro se mantienen
como marca.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 13:34:51 +00:00
Inna e448f34305 web-next: enlace CHANGELOG de la cabecera → /changelogs
Apuntaba a un tema del foro externo (foro.novawow.com); ahora va a la página
interna /changelogs (API de Gitea).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 13:19:39 +00:00
Inna 0affbb2ced web-next: top de logros por PUNTOS reales + fix logro Nivel 80
- «Top de logros» ahora suma los PUNTOS de logros (no el nº): JOIN a
  acore_world.achievement_points (id→points), poblada desde el DB2 Achievement
  del build 3.4.3.54261 (wago.tools). Columna «Puntos de logros». Seed en
  sql/achievement_points.sql (1912 logros).
- Fix: el logro «Nivel 80» es el id 13 (logros de nivel WotLK van de +10:
  6=Nv10,7=Nv20…12=Nv70,13=Nv80), no el 20. «Primeros del reino» ahora se
  puebla correctamente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 13:10:55 +00:00
Inna 5d724afb8d web-next: página de jugadores /<slug>-players con datos reales de la BD
Extiende la ruta dinámica [realm] para servir también /<slug>-players (slug
del reino en realmlist). PlayersBoard (server) consulta acore_characters:
- Personajes creados por clase (conteo por clase y facción) + totales.
- Top de muertes con honor (characters.totalKills/todayKills, LIMIT 10).
- Top de logros (nº de logros por personaje vía character_achievement;
  los «puntos» no existen en la BD del servidor, vienen del DBC del cliente).
- Primeros del reino - Nivel 80 (primer personaje por fecha del logro 20 =
  Nivel 80, overall/por clase/por raza; vacío si aún no hay nivel 80).
Imágenes reales del tema (nw-classes/nw-races/nw-icons). lib/players.ts con
los mapas de clase/raza/facción. RealmInfo extraído a su componente. Enlace
JUGADORES de la cabecera ahora dinámico (/<slug>-players), como REINO.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 12:53:50 +00:00
Inna cae1c224b0 web-next: enlace REINO de la cabecera dinámico (/<slug>-realm)
El menú de la cabecera enlazaba a /novawow-realm fijo. Ahora Header (server)
calcula el slug desde realmlist (getRealmName + realmSlug) y lo pasa a
SiteHeader como realmHref → el enlace REINO apunta a /trinity-realm (o al
reino que haya). Acompaña a la ruta dinámica [realm].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 12:41:44 +00:00
Inna 5da124cf28 web-next: página /download-client (descarga del cliente por torrent)
Ruta con el markup del tema + lógica interactiva (componente cliente
ClientDownload): selector de idioma (esES/esMX/enUS/enGB) que muestra el
selector de SO y la descripción del idioma; al elegir SO «Mac OS Catalina»
aparece la guía de CrossOver; el botón DESCARGAR abre el magnet link
correspondiente (btih por idioma + trackers comunes) o muestra error si
falta elegir. Clases del tema (game-download-select/g-download-button).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 12:20:37 +00:00
Inna 57e91480d1 web-next: «Personalizar» abre panel granular de cookies (uw-cookie-details)
El botón Personalizar del banner ahora despliega un panel con checkboxes por
categoría (Necesarias fija+desactivada, Analíticas, Marketing) y «Guardar
preferencias», tal como el markup del usuario. Al guardar, escribe la cookie
uw_cookie_consent con la selección granular y sincroniza con Zaraz. Categorías
alineadas a las del panel (Necesarias/Analíticas/Marketing); ConsentStatus y
acceptAll/rejectAll usan las mismas. Estilos del panel en globals.css.

Verificado: Personalizar abre el panel, marcar Analíticas + Guardar deja la
cookie {necessary,analytics:true,marketing:false} y /cookies lo refleja.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 12:02:49 +00:00
Inna 4ebe3c34d3 web-next: banner de consentimiento de cookies + estado de la API (Zaraz)
- Banner real `uw-cookie-content` (Rechazar/Personalizar/Aceptar todas) fijo
  abajo y site-wide (montado en el layout, componente CookieBanner). Aparece
  en la primera visita, guarda la decisión en la cookie uw_cookie_consent (1
  año) y se sincroniza con Cloudflare Zaraz si está presente
  (zaraz.consent.setAll / .modal). Estilos en globals.css.
- «Personalizar» abre el modal granular de Zaraz (o /cookies si no hay Zaraz).
- window.UWCookies {showBanner, revokeConsent, acceptAll, rejectAll}.
- Página /cookies: panel «Tu estado de consentimiento» (ConsentStatus) que
  muestra los datos EN VIVO de la API de Zaraz (zaraz.consent.getAll()) por
  propósito; si Zaraz no está, cae a la preferencia guardada por categoría.

Verificado con navegador: el banner aparece, «Aceptar todas» lo oculta y
escribe la cookie, y el panel de /cookies refleja el estado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 11:58:54 +00:00
Inna e289a87d1f web-next: página /cookies (Declaración de Cookies) + consentimiento Zaraz
Crea la ruta de Declaración de Cookies con el markup del tema y las 5 tablas
de cookies (uw-cookie-table, estilizadas en globals.css) + estilos de listas
y botones (btn-uw). Los enlaces internos .php se cablean a las rutas Next
(/privacy-policy, /contact-us).

Sistema de consentimiento = Cloudflare Zaraz (elección del usuario):
components/CookieConsent.tsx expone window.UWCookies.showBanner()
(zaraz.consent.modal=true) y revokeConsent() (zaraz.consent.setAll(false)),
que Cloudflare activa cuando el dominio pasa por su proxy con Zaraz Consent;
degrada con aviso si Zaraz no está presente. El footer ya enlazaba aquí.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 11:49:16 +00:00
Inna d6acba4ad5 web-next: mover «Panel de administración» dentro de «Opciones de cuenta»
Antes era un tool-button suelto a ancho completo (podía desbordarse). Ahora
es una celda más de la rejilla de 2 columnas del panel «Opciones de cuenta»
(solo admins), con el mismo contorno/ancho que el resto (Token de seguridad,
etc.). Utilidades y Herramientas se mantiene igual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 11:09:10 +00:00
Inna 6337de73ad web-next: /account idéntico al my-account de Django
La página de cuenta no se parecía a la de Django (oro descuadrado, info como
tabla, herramientas en grupos abiertos). Reescrita para replicar el
partials/my-account.html + isla AccountDashboard de Django:

- Información en DOS fieldsets (.account-fieldset): «Datos básicos» (cuentas,
  correos, fecha de registro, IPs) y «Estado de la cuenta» (insignia de rango
  nw-rank + PD/PV/créditos/token + baneo + reclutamiento). lib/account.ts
  ampliado para traer reg_mail/email/joindate/last_ip/last_attempt_ip, estado
  de baneo (account_banned) y nº de reclutados.
- Personajes en char-box con el markup exacto de Django: retrato flotante +
  oro EN LÍNEA (monedas width=10). Antes las monedas salían a tamaño nativo
  (descuadre) y apiladas.
- Herramientas en paneles plegables (acordeón) «OPCIONES DE CUENTA / OPCIONES
  DE PERSONAJE» como Django (componente cliente AccountTools), con los
  servicios ya implementados en Next y las etiquetas/descripciones exactas.
- globals.css: revertir otro reset del preflight de Tailwind — img{display:
  block} apilaba verticalmente los iconos en línea (monedas); el tema los usa
  inline como la web Django. Volvemos a img{display:inline}.

Verificado con Playwright (login real con cuenta de prueba + captura): info,
personajes con oro en línea y paneles que expanden con sus iconos; home sin
regresión por el cambio de img.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 10:58:36 +00:00
Inna 51203c7eee web-next: paridad de los campos de login con el tema Django
- 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>
2026-07-13 10:08:14 +00:00
Inna cc158d3819 web-next: migrar toda la UI al tema real nw-ryu
Reescritura completa del frontend Next.js del sistema visual Tailwind
"simulado" al tema Django original (nw-ryu), para paridad pixel con la
web actual antes del cutover.

- Tema real: copia de static/nw-themes/nw-ryu + favicons a public/, el
  layout carga el novavow-style.css real de ultimo (gana la cascada sobre
  Tailwind) + Font Awesome.
- Shell replicando los partials Django: SiteHeader, Video, Social, Footer,
  ServerClock; home con estructura real (main-page/middle-content/...).
- Helpers reutilizables: PageShell (main-page > middle-content > body-content
  > title-content) y ServiceBox (title-box-content + back-to-account).
- Paginas migradas a clases reales del tema (fieldset/tool-button/char-box/
  item-box/info-box-light/max-center-table/alert-message/botones reales),
  eliminando el markup Tailwind (.nw-btn/.nw-card/.nw-input):
  auth (login/register/recover/reset/select-account/activate),
  cuenta + servicios de personaje (revive/unstuck/rename/customize/
  change-race/change-faction/level-up/gold/transfer + pago Stripe),
  ajustes (change-password/change-email/security-token),
  comunidad (vote-points/recruit/battlepay), foro completo, y
  admin (indice + 7 secciones + los Admin*Manager).
- Se conserva el bilingue (next-intl); claves nuevas en messages/es|en.json.

Verificado: typecheck + build OK; rutas protegidas 307->login; sin
MISSING_MESSAGE; cero Tailwind residual (solo .nw-tool-btn/.nw-page,
clases propias en globals.css).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 09:54:58 +00:00
Inna 8bb18838ff Admin: gestión de foros (categorías y foros)
- lib/admin-forum.ts: listCategoriesWithForums, createCategory/deleteCategory
  (solo si vacía), createForum/deleteForum (borrado lógico), setForumVisibility.
- API: /api/admin/forum(+/[id] DELETE/PATCH visibilidad) y
  /api/admin/forum/category(+/[id]) (403 sin admin).
- UI: /admin/forum (AdminForumManager) — alta de categorías, alta/borrado de foros
  por categoría, mostrar/ocultar; tarjeta "Gestionar foros" en el índice del admin.
- i18n es/en (Admin): 13 claves nuevas.

Verificado: build OK, /admin/forum 307->login, APIs 403 sin admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:49:52 +00:00
Inna f721aac7e5 Tienda Battlepay: pago de órdenes pendientes vía Stripe
Reutiliza la infraestructura Stripe (sin depender de SumUp/credenciales externas):
- lib/battlepay.ts: getPendingOrders/getPendingOrder/markOrderPaid sobre
  acore_auth.battlepay_orders (PENDING->PAID, el mod-battlepay entrega en juego).
- fulfill.ts: rama battlepay (metadata.battlepay_reference -> markOrderPaid),
  idempotente vía el claim atómico compartido con el webhook.
- API POST /api/battlepay/pay (valida orden PENDING de la cuenta, crea checkout).
- UI: /battlepay (lista + pagar) y /battlepay-success (entrega compartida),
  enlace "Tienda" en la cabecera para usuarios logueados.
- i18n es/en: namespace Battlepay + Nav.store.

Verificado: build OK, /battlepay 307->login, API 401 sin sesión, success 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:12:12 +00:00
Inna 8a3ba8ff40 Foro: restaurar posts y temas borrados (moderadores)
- lib/forum.ts: getTopic/getPosts/countPosts aceptan includeDeleted (mods ven lo
  borrado); Post.deleted y TopicFull.deleted.
- API: PUT /api/forum/post restaura post (mod-only); moderate action 'restore'
  restaura tema (usa includeDeleted al leer el tema).
- UI: posts borrados atenuados con badge y botón Restaurar (PostActions);
  TopicModBar muestra banner + Restaurar tema cuando el tema está borrado.
- i18n es/en: Forum.restore, restoreTopic, deletedMark.

Verificado: build OK, PUT post y moderate restore 401 sin sesión, /forum 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:07:18 +00:00
Inna 72fdead9f3 Foro: editor rico ligero (barra de formato) en respuestas, temas y edición
- components/RichTextArea.tsx: barra de formato (negrita, cursiva, subrayado, tachado,
  enlace, cita, lista, código) que envuelve la selección del textarea en HTML;
  el HTML se sanea en el servidor con cleanPostHtml. Sin dependencias pesadas
  (evita CKEditor y no rompe el pipeline de saneado).
- Cableado en ReplyForm, NewTopicForm y PostActions (edición).
- i18n es/en: namespace Editor.

Verificado: build OK, /forum y home 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:03:38 +00:00
Inna 7c1cd195ff Foro: mover tema entre foros (moderadores)
- lib/forum-write.ts: moveTopic (forum_id + moved=1). lib/forum.ts: listForumsForMove.
- API /api/forum/moderate: acción 'move' con forumId, validando foro destino visible
  y distinto del actual.
- TopicModBar: selector «Mover a» con los foros visibles (excl. el actual); tras mover,
  redirige al foro destino.
- i18n es/en: Forum.moveTopic.

Verificado: build OK, move 401 sin sesión, /forum 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:01:48 +00:00
Inna 7534b9cbbb Reclutamiento: página de usuario para reclamar recompensas (recruit-a-friend)
Portado de home/views/recruit.py:
- lib/recruit-claim.ts: getRecruitRewards (con marca de reclamadas),
  getRecruitedLevel80Count, y claimRecruitReward con las mismas validaciones que
  Django (recompensa existe, no reclamada, amigos reclutados a nivel 80 suficientes,
  anti-abuso por IP compartida), entrega por SOAP `.send items` (éxito si "Mail sent to")
  y registro en home_claimedreward. Extra de seguridad: valida que el personaje sea de la cuenta.
- API POST /api/recruit/claim (401 sin sesión).
- UI /recruit (pública): rejilla de recompensas con estado reclamada/elegible/bloqueada,
  selector de personaje y contador de amigos a nivel 80. Enlace en el footer.
- i18n es/en: namespace RecruitClaim + Nav.recruit.

Verificado: build OK, /recruit 200, claim 401 sin sesión.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:45:29 +00:00
Inna c6c56c3d95 Admin: recompensas de reclutamiento (recruit-a-friend)
- lib/admin-recruit.ts: list/create/delete sobre home_recruitreward
  (required_friends, reward_name, item_id, item_quantity, item_link, icon_class).
- API: POST /api/admin/recruit, DELETE /api/admin/recruit/[id] (403 sin admin).
- UI: /admin/recruit con AdminRecruitManager (alta ordenada por amigos requeridos),
  enlace desde el índice del admin.
- i18n es/en (Admin): 10 claves nuevas.

Verificado: build OK, /admin/recruit 307→login, APIs 403 sin admin,
columnas confirmadas en la migración 0001_initial.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:41:31 +00:00
Inna d382adcd59 Admin: gestión de usuarios (buscar, banear/desbanear)
- lib/admin-users.ts: searchAccounts (acore_auth.account + account_banned + gmlevel),
  banAccount/unbanAccount por BD (autoritativo) con kick best-effort por SOAP.
  Baneo días<=0 = permanente (unbandate=bandate, convención AzerothCore); desactiva
  baneo activo previo antes de insertar el nuevo.
- API: GET /api/admin/users?q=, POST {accountId, action:ban|unban, reason, days} (403 sin admin).
- UI: /admin/users con AdminUsersManager (buscar por usuario/email, badges GM/online/baneado,
  banear con motivo+días o desbanear), enlace desde el índice del admin.
- i18n es/en (Admin): 14 claves nuevas.

Verificado: build OK, /admin/users 307→login, APIs 403 sin admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:39:08 +00:00
Inna d9e6ff1496 Admin: gestión de opciones de oro (home_goldprice)
- lib/admin-gold.ts: list/create/delete sobre home_goldprice (id, gold_amount, price).
- API: POST /api/admin/gold, DELETE /api/admin/gold/[id] (403 sin admin).
- UI: /admin/gold con AdminGoldManager (alta ordenada por cantidad + borrado),
  enlace desde el índice del admin.
- i18n es/en (Admin): manageGold, gold, goldAmount, goldPrice, goldUnit, noGold.

Verificado: build OK, /admin/gold 307→login sin sesión, APIs 403 sin admin,
columnas confirmadas en la migración Django.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:35:13 +00:00
Inna 36a4354ff7 Foro: paginación de temas (20/pág) y de posts (15/pág)
- lib/forum.ts: countTopics/countPosts + offset/limit en getTopics/getPosts
  (mismos límites que Django: FORUM_TOPICS_PER_PAGE=20, FORUM_POSTS_PER_PAGE=15).
- components/Pagination.tsx: paginación reutilizable (ventana ±2, primera/última,
  prev/next) con enlaces ?page=N; no renderiza si solo hay una página.
- Foro y tema aceptan ?page=N (clamp al rango válido).
- El formulario de respuesta solo aparece en la última página; enlace a ella si no.
- i18n: namespace Common (prev/next/page) + replyOnLastPage.

Build OK. Sin datos de foro en acore_web (estado vacío correcto).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:32:16 +00:00
Inna 1de1002938 Foro: edición/borrado de posts, moderación de temas y búsqueda
Portado desde forum/db.py + forum/permissions.py de Django:
- lib/forum-perm.ts: forumIsModerator (gmlevel >= FORUM_MOD_GMLEVEL) y
  canEditPost (autor o moderador).
- Edición y borrado lógico de posts (autor o mod), respetando tema bloqueado,
  con validación de longitud mínima (plainLength).
- Moderación de temas (solo mod): bloquear/desbloquear, fijar/no fijar, borrar.
- Búsqueda de temas por título/contenido (searchTopics/countSearchTopics)
  con página /forum/search y buscador en el índice.
- API: PATCH/DELETE /api/forum/post, POST /api/forum/moderate (401/403 correctos).
- UI: PostActions, TopicModBar, ForumSearchBox; marca "editado" y "[Fijado]".
- i18n: 22 claves nuevas en es/en.

Verificado: build OK, /forum y /forum/search 200, APIs 401 sin sesión.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:28:39 +00:00
Inna 13636d097a Footer: fila de redes sociales con iconos SVG e URLs reales de NovaWoW
- Discord (discord.gg/novawow), YouTube (canal oficial), Facebook, Instagram.
- Iconos SVG inline (sin PNGs pesados), adaptables al tema, hover dorado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:20:43 +00:00
Inna 6e7a9634d1 Marca real: logos NovaWoW en cabecera/hero + footer con enlaces
- Copia los logos del tema original (nw-logo -> public/brand/logo.webp,
  nw-long-logo -> logo-long.webp).
- Header: logo + nombre. Home hero: logo largo con glow dorado.
- Footer: logo + copyright + enlaces (inicio/foros/votar/registro), i18n.

Verificado: logos se sirven (200), home 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:15:56 +00:00
Inna 3e47a3d240 Aplica el sistema de diseño (nw-input/nw-btn/nw-card) a todas las páginas
Reemplazo masivo de los estilos sueltos repetidos por las clases del sistema visual
en ~20 componentes/páginas (auth, cuenta, servicios de personaje, foro, admin, voto):
- inputs -> .nw-input, botones primarios -> .nw-btn, tarjetas -> .nw-card.
Cohesión visual completa con la home y la cabecera.

Verificado: build OK, páginas clave 200/redirect correctos, clases aplicadas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:13:22 +00:00
Inna c9ba6c35e3 Diseño visual: tema NovaWoW (tokens Tailwind), hero en la home, cabecera pulida
- globals.css: tokens @theme (nw-bg/panel/border/gold/text/muted), fondo con
  degradado + brillo radial, y componentes reutilizables (.nw-btn, .nw-btn-ghost,
  .nw-card, .nw-heading con oro degradado).
- Header: sticky con backdrop-blur y marca en oro degradado.
- Home: sección hero (nombre del server + tagline + CTAs Crear cuenta / Foros) y
  estado del servidor + noticias en tarjetas (nw-card). Textos hero en catálogos.

Verificado: build OK, home renderiza el hero/tarjetas, los tokens nw compilan en el
CSS. Sistema visual base listo para aplicar al resto de páginas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:09:58 +00:00
Inna f9609aad99 Página de votación de usuario (/vote-points) + enlace en cabecera
- lib/vote.ts: getVoteSites (home_votesite), registerVote (cooldown 12h30 vía
  home_votelog, acredita PV en home_api_points, registra el voto).
- Route /api/vote (POST {url}, guard sesión). Página /vote-points (VotePanel:
  abre el sitio + registra el voto, muestra PV/cooldown). Enlace "Votar" en cabecera.
- Cierra el ciclo con el admin de sitios de voto.

Verificado: /vote-points 200, POST 401 sin sesión; lógica probada: voto -> +PV,
segundo voto -> cooldown 12h29m.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:06:57 +00:00
Inna 934bee2aec Admin: gestión de sitios de voto (home_votesite)
- lib/admin-votes.ts: listVoteSites / createVoteSite / deleteVoteSite.
- Routes /api/admin/votes (POST) y /api/admin/votes/[id] (DELETE), guard isAdmin.
- Página /admin/votes (AdminVotesManager: crear/listar/borrar) + enlace en dashboard.
- Conecta con la página de votación (/vote-points) ya existente.

Verificado: guardas (307/403), CRUD del write layer OK (con env cargado).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:03:06 +00:00
Inna 0f71e1f4d9 Admin: gestión de precios de los servicios de personaje
- lib/admin-prices.ts: PRICE_TABLES (rename/customize/change-race/change-faction/
  level-up/transfer -> home_*price), getAllPrices, setPrice (fila única: DELETE+INSERT).
- Route /api/admin/prices (POST, guard isAdmin). Página /admin/prices con
  AdminPricesManager (editar cada precio). Enlace en el dashboard admin.
- Catálogo Admin (precios + nombres de servicio).

Verificado: /admin/prices redirige sin permiso, API 403; setPrice escribe en BD
correctamente (probado con env cargado). Pendiente admin: sitios de voto, gold options,
moderación del foro.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:59:45 +00:00
Inna d5b00a86dc Panel admin propio: gestión de noticias
- lib/admin.ts: isAdmin(session) por allowlist ADMIN_EMAILS (normalizada) o gmlevel
  de AzerothCore (account_access, resiliente si no existe). ADMIN_EMAILS/ADMIN_GMLEVEL
  en .env.local.
- lib/admin-news.ts: listNews / createNews / deleteNews (home_noticia).
- Routes /api/admin/news (POST, guard isAdmin) y /api/admin/news/[id] (DELETE).
- Páginas /admin (dashboard) y /admin/news (protegidas: no-admin -> redirect).
  AdminNewsManager (cliente: crear + listar + borrar).

Verificado: /admin sin permiso redirige, APIs 403; el pipeline crear noticia -> se
renderiza en la home (SSR) funciona. Turnstile bloquea el login por curl (correcto).
Pendiente admin: precios de servicios, foros, sitios de voto, usuarios.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:55:34 +00:00
Inna 239392b876 Foro (escritura): crear tema y responder, con saneado HTML
- lib/forum-sanitize.ts: cleanPostHtml con sanitize-html (misma allowlist que
  forum/sanitize.py de nh3: formato básico + a/img/tablas, rel nofollow, esquemas
  http/https/mailto). Verificado XSS-safe (script/onclick/javascript: eliminados).
- lib/forum-write.ts: createTopic (tema + primer post), createPost (respuesta +
  updated_at), forumIsPostable / topicIsReplyable.
- Routes /api/forum/topic y /api/forum/reply (guard de sesión; identidad = cuenta de
  juego: poster=username, poster_id=accountId). Componentes NewTopicForm y ReplyForm
  (clientes). Se muestran solo si hay sesión; el tema cerrado no admite respuesta.

Verificado: escritura 401 sin sesión, saneado correcto. Pendiente: editar/borrar,
moderación (fijar/cerrar/mover), búsqueda, editor enriquecido.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:49:53 +00:00
Inna e743d98777 Foro (lectura): índice, temas y mensajes en Next.js
- lib/forum.ts: getForumIndex (categorías + foros visibles con contadores y último
  tema, port de forum/db.py), getForum/getTopics, getTopic/getPosts (acore_web).
- Páginas app/[locale]/forum (índice), /forum/[forumId] (temas), /forum/topic/[topicId]
  (mensajes; render del HTML ya saneado por Django). Enlace "Foros" en la cabecera.
- Catálogo Forum + Nav.forum.

Verificado: /forum 200, foros/temas inexistentes 404, enlace en la cabecera.
Solo lectura; pendiente escribir (crear tema/responder + saneado nh3->TS) y moderación.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:45:39 +00:00
Inna cceab26999 Cambio de email con doble confirmación (completa el bloque de auth)
- lib/change-email.ts: requestEmailChange (valida pass/email/token, crea activación
  con old_email/old_email_hash, email al correo actual), confirmOldEmail (marca is_used,
  email al nuevo), confirmNewEmail (re-deriva verifier con el nuevo email + actualiza
  battlenet_accounts y account; borra la activación).
- components/ConfirmClient.tsx: cliente genérico que confirma un hash por POST al abrir.
- Routes /api/account/change-email, /confirm-old-email, /confirm-new-email.
- Páginas /change-email (protegida) y /confirm-old-email, /confirm-new-email (auto).
- Catálogos ChangeEmail, ConfirmOldEmail, ConfirmNewEmail.

Verificado: change-email protegida (401/redirect), confirmaciones con hash inválido
-> invalidLink sin crash. AUTH 100% reimplementado en Next.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:41:42 +00:00
Inna 548fca16e6 Añade gold y transfer: patrón de pago unificado con fulfill + metadata
- lib/stripe.ts: createCheckoutSession acepta metadata; claimPaidCheckout la
  recupera de la sesión Stripe y la devuelve.
- lib/paid-services.ts: config unificado con fulfill(character, meta) + extraFields.
  gold -> UPDATE characters money (BD directa, no SOAP), transfer -> SOAP
  .char changeaccount. Los 5 simples usan soapFulfill.
- checkout [service]: lee extraFields -> metadata, precio depende de metadata (gold),
  valida precio>0. service-success llama cfg.fulfill(name, metadata).
- prices.ts: getTransferPrice, getGoldOptions/goldPriceFor (home_goldprice).
- GoldForm (personaje + cantidad) y TransferForm (personaje + destino); páginas
  /gold y /transfer. Catálogos Paid.gold/transfer.

Verificado: /gold /transfer redirigen a login, checkout 401 sin sesión, home OK.
NOTA: transfer no valida aún el token de seguridad ni reglas AC (nivel>=55, DK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:32:12 +00:00
Inna 87f9b0b003 Generaliza los servicios de personaje de pago (5) con infra común
- lib/paid-services.ts: config PAID_SERVICES (rename/customize/change-race/
  change-faction/level-up) con comando SOAP, precio y productName por servicio.
- app/api/character/[service]/checkout: ruta de checkout GENÉRICA (valida servicio +
  personaje, crea la sesión Stripe con successUrl /service-success?service=...).
- app/[locale]/service-success: página de éxito ÚNICA que verifica el pago
  (claimPaidCheckout) y ejecuta el comando SOAP del servicio.
- components/ServicePageContent: contenido común (guardas + PaidServiceForm).
- 5 páginas mínimas /rename /customize /change-race /change-faction /level-up.
- Catálogo Paid (por servicio: title/pay/success) sustituye a Rename.

Verificado: 5 páginas redirigen a login, checkout 401 sin sesión, servicio inválido
404, service-success con pago inválido no entrega (muestra error). Pendiente: gold
(cantidad) y transfer (destino+token), variantes del patrón.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:25:57 +00:00