Commit Graph

50 Commits

Author SHA1 Message Date
Inna eaae16e33a web-next: change-race/faction/level-up/gold por SumUp + rastreador de misiones
- /change-race-character (3€), /change-faction-character (5€, valida condiciones:
  sin hermandad/correo/subastas/capitán-arena y oro <= cap por nivel),
  /level-up-character (10€), /gold-character (oro por correo SOAP). Cada uno borra
  su ruta antigua y actualiza el link del panel.
- SumUp: hook precheck en PaidServiceConfig (condiciones cambio de facción);
  columna home_stripelog.metadata para campos extra (gold_amount).
- /quest-character: rastreador de estado de misiones/cadenas (clase, Hijos de
  Hodir, misión por ID) en personaje offline; cooldowns por categoría, Turnstile,
  IDs de misión verificados en wotlkdb 3.3.5a. Tabla home_questsearchhistory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:39:28 +00:00
Inna f7a376bd7c web-next: rename-character/customize-character por SumUp + renombrado de rutas
- /rename-character y /customize-character: renombrar/personalizar pagados por SumUp
  (getRenamePrice/getCustomizePrice). SumUp ahora es service-aware: columna
  home_stripelog.service; createSumUpCheckout guarda service+character;
  fulfillSumUpCheckout despacha a la acción del servicio (.char rename/customi) o,
  sin service, acredita PD. [service]/checkout acepta provider:'sumup';
  PaidServiceForm acepta provider+confirmText; service-success maneja el return SumUp.
  Se eliminan las antiguas /rename y /customize (Stripe).
- /revive-character (comparte ReviveServiceContent); se elimina /revive.
- Renombres de ruta + todos sus enlaces:
  /account -> /my-account, /recruit -> /recruit-a-friend, /unstuck -> /unstuck-character.
  (/select-account y /api/account/* intactos.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:35:56 +00:00
Inna 6ca94c2803 web-next: selector de personaje coloreado + rediseño unstuck + fixes vote/account-info
- CharacterSelect: componente reutilizable que colorea las opciones por clase
  (class="priest big-font"…) y el <select> con la clase seleccionada. Usado en
  unstuck, revive, gold, transfer, trade-points, servicios de pago y recruit.
  getGameCharacters ahora devuelve classCss.
- /unstuck (+revive): diseño del original (info + NOTA, prompt, opciones coloreadas,
  botón "Desbloqueado"/"Revivido" con refresh).
- /vote-points: seed de los 4 sitios con logos y URLs (sql/seed_votesites.sql);
  botón voted-button para sitios en cooldown; etiqueta "Nota:" separada.
- account-info.png (imagen nueva) y CSS de los paneles de cuenta: se añade
  background-blend-mode: saturation y background-size en #account-settings y
  .account-fieldset para que la imagen salga como el original.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:55:43 +00:00
Inna b9803adeb9 web-next: rediseño de security-token, recover, vote-points + rename-guild
- /security-token: diseño completo del original (info + advertencia + NOTA 7 días),
  fecha en formato HH:MM:SS DD-MM-YYYY, botón "Token enviado", enlace a /recover.
- /recover: 4 opciones (contraseña por usuario, nombre de cuenta, token de seguridad
  y enlace de activación por correo); recuperación de token nueva; Turnstile por envío.
- /vote-points: caja de info con las 7 secciones Q&A + panel "Sitios de votación" con
  tarjetas inline-div (imagen, PV, último voto), botón refrescar; abre el sitio y acredita
  PV al volver. lib/vote.ts + getVoteSitesForAccount.
- /rename-guild: renombra una hermandad de la que la cuenta es Maestro por 1000 PD +
  token de seguridad; SOAP .guild rename con comillas; reembolsa los PD si SOAP falla.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:06:49 +00:00
Inna 66d056c04b web-next: herramientas de PD (transferir, comercio, códigos de promoción)
- /transfer-d-points: transferir PD a la cuenta dueña de un personaje
  (requiere token de seguridad; movimiento atómico con FOR UPDATE).
- /trade-points: comercio PD<->oro mediante códigos de 1 solo uso
  (contraseña + token + Turnstile; oro al creador por SOAP; expira 1h;
  lock de 5s tras canjear; rollback total si falla la entrega).
- /promo-code: canje de códigos por PD/PV (sensibles a mayúsculas/minúsculas,
  usos limitados, 1 canje por cuenta).
- /admin/promo: panel para crear / editar / activar / desactivar / borrar
  códigos de promoción.
- Tablas nuevas (sql/): home_tradecode, home_promocode, home_promoredemption.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 09:27:11 +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 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 e4bed7402b web-next: «Información» de /account con iconos, correos ocultos, PD|PV y 2FA
Los fieldsets Datos básicos/Estado de la cuenta ahora replican el markup nuevo:
iconos Font Awesome por campo (account-ficon), .separate account-info,
«Nombre de la cuenta», correos enmascarados (maskEmail), PD | PV en una línea
(dp-color/vp-color), Token, «2FA: Desactivado» (fa-lock-open red-info), Cuenta
baneada con enlace «Consultar historial» (/ban-history), reclutada y amigos.
Se quita Créditos Battlepay. Clases account-ficon/account-info al tema; claves
i18n accountName/twofaLabel/twofaOff/banHistoryLink.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:16:58 +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 4b78aa847a web-next: página 404 temática (not-found)
Añade una 404 con el markup del tema (main-page > middle-content >
body-content > title-content + body-box-content justified), dentro del
layout de [locale] (cabecera/vídeo/pie). Como el layout raíz es un segmento
dinámico ([locale]), se usa el patrón de next-intl: `[locale]/not-found.tsx`
+ catch-all `[locale]/[...rest]/page.tsx` que dispara notFound(). Bilingüe
(claves NotFound.title/message en es/en). Devuelve HTTP 404; las rutas
conocidas siguen 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 11:22:06 +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 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 1a45d48412 Admin: corregir columnas de account_access + enlace al panel en la cuenta
- Este build de AzerothCore usa account_access(AccountID, SecurityLevel), no el
  clásico (id, gmlevel). Se corrigen las 4 consultas (admin.ts, forum-perm.ts,
  forum.ts, admin-users.ts) -> isAdmin/forumIsModerator/gmlevel ahora funcionan.
- account page: enlace destacado al Panel de administración, visible solo si isAdmin.
- i18n es/en: Account.adminPanel.

Verificado: build OK; cuenta de prueba con SecurityLevel 3 (>= ADMIN_GMLEVEL) es admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:42:04 +00:00
Inna 95df2e029d Cuenta: panel de servicios (enlaces a todas las páginas)
La página de cuenta solo mostraba datos y personajes, sin acceso a los servicios.
Añade un panel con 4 grupos:
- Servicios de personaje: renombrar, personalizar, cambiar raza/facción, subir nivel,
  comprar oro, transferir.
- Utilidades: revivir, desatascar, token de seguridad.
- Ajustes de cuenta: cambiar contraseña, cambiar email.
- Comunidad: votar, reclutar, tienda (Battlepay).
i18n es/en en el namespace Account.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:35:39 +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 00c03d84e1 Foro: perfil de usuario
- lib/forum.ts: getForumProfile (post_count, topic_count, joindate, gmlevel->status
  admin/gm, y últimos 5 temas), portado de forum/views.py profile().
- Página /forum/user/[username] con contadores, antigüedad, rango y temas recientes.
- Los nombres de autor en la vista del tema enlazan al perfil.
- i18n es/en: Forum.memberSince, recentTopics, roleAdmin, roleGm.

Verificado: build OK, /forum/user 200.

Foro COMPLETO: índice, foro/tema, crear, responder, editar/borrar, moderación
(lock/pin/mover/borrar/restaurar), búsqueda, paginación, editor rico y perfiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:08:49 +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 389dac68c4 Stripe: webhook que entrega el servicio aunque el usuario no vuelva
- app/api/stripe/webhook: verifica firma (constructEventAsync) y en
  checkout.session.completed registra la IP de Stripe (como Django) y ENTREGA
  el servicio. Idempotente frente a la página de éxito.
- lib/stripe.ts: claimPaidCheckout ahora es ATÓMICO (UPDATE ... WHERE fulfilled=0
  + affectedRows) para evitar doble entrega entre webhook y página de éxito;
  nuevo recordStripeIp.
- lib/fulfill.ts: fulfillCheckoutSession compartido (reclama + resuelve servicio
  desde metadata.service + ejecuta). checkout guarda `service` en metadata.
- service-success: usa la entrega compartida y distingue entregado/ya-procesado/error.
- i18n: Paid.alreadyProcessed.

Requiere configurar STRIPE_WEBHOOK_SECRET en .env.local (endpoint apuntando a
/api/stripe/webhook, evento checkout.session.completed).

Verificado: build OK, webhook 400 sin firma / firma inválida.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:55:18 +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 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 1da7349373 Token de seguridad y cambio de contraseña (páginas protegidas)
- lib/security-token.ts: requestSecurityToken (token de 6 chars, cooldown 7 días,
  guarda en home_securitytoken, email) + checkSecurityToken.
- lib/change-password.ts: changePassword (valida token + contraseña actual con
  authenticate, re-deriva verifier SRP6 v2, actualiza battlenet_accounts).
- Routes /api/account/security-token y /change-password (guard; el cambio hace
  logout de la sesión). Páginas /security-token y /change-password (protegidas) con
  sus forms cliente e i18n (SecurityToken, ChangePassword).

Verificado: páginas redirigen a login sin sesión, APIs 401. Reutiliza
home_securitytoken de Django y la crypto validada.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:36:05 +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
Inna e7cff56954 Servicios de personaje de pago (Stripe): patrón + rename
- lib/stripe.ts: createCheckoutSession (crea sesión Stripe + StripeLog en
  home_stripelog, success_url con {CHECKOUT_SESSION_ID}), isSessionPaid,
  claimPaidCheckout (verifica pagado + no entregado, marca fulfilled -> evita
  re-entregas; port de require_paid_stripe).
- lib/prices.ts: precios desde home_*price (rename/customize/race/faction/level).
- components/PaidServiceForm.tsx: selector + botón de pago -> redirige a la URL de
  Checkout de Stripe (session.url, sin stripe.js).
- Rename: route /api/character/rename/checkout (guard + valida personaje + crea
  checkout), página /rename (PaidServiceForm) y /rename/success (verifica pago y
  ejecuta SOAP .char rename, i18n). STRIPE_* + NEXT_PUBLIC_STRIPE_PUBLIC_KEY en
  .env.local. Namespace Rename.

Verificado: checkout sin sesión 401, /rename redirige a login, /rename/success con
session_id inválido no entrega el servicio (muestra error). Patrón para los demás
servicios de pago (cambiar precio + comando SOAP).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:21:30 +00:00
Inna 96e996e9c2 Cliente SOAP + servicios de personaje SOAP (revive/unstuck) en Next.js
- lib/soap.ts: executeSoapCommand (port de ac_soap.py; POST SOAP + basic auth al
  worldserver, timeout 5s, null si inaccesible). AC_SOAP_* en .env.local.
- lib/characters.ts: getGameCharacters / characterIsOfflineAndOwned (acore_characters).
- lib/character-services.ts: soapCharacterAction genérico (valida propiedad+offline,
  cooldown 12h vía home_revive/unstuckhistory, SOAP, registra historial) ->
  reviveCharacter / unstuckCharacter.
- components/CharacterActionForm.tsx (cliente reutilizable: select + botón).
- Routes /api/character/revive|unstuck (guard de sesión) y páginas
  app/[locale]/revive|unstuck (SSR, guardas, i18n). Namespace Services.

Verificado: API sin sesión 401, páginas redirigen a login, cliente SOAP devuelve null
limpio sin worldserver. Patrón base para el resto de servicios de personaje.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:16:32 +00:00
Inna a0a64bc70a Dashboard de /account: info, puntos, créditos, token y personajes
- lib/account.ts: getAccountDashboard(session) lee puntos (home_api_points),
  créditos battlepay (battlenet_accounts), estado del token de seguridad
  (home_securitytoken) y personajes (acore_characters). Resiliente si las BD de
  AzerothCore no están pobladas (devuelve vacíos/0).
- app/[locale]/account: página protegida enriquecida (tarjetas de cuenta + puntos +
  rejilla de personajes con oro), i18n. Namespace Account ampliado.

Verificado: build OK, guarda sigue redirigiendo a login sin sesión.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:11:57 +00:00
Inna 3573b7eb40 Recuperación de cuenta en Next.js (3 flujos + reset), con Turnstile
- lib/recover.ts: requestRecovery(type,email) -> password (crea token en
  home_passwordreset + email), accountname (email con las cuentas), activation
  (reenvía enlace); respuesta genérica anti-enumeración. resetPassword(token,pass)
  re-deriva el verifier SRP6 v2 (bnetMakeRegistration) y actualiza battlenet_accounts,
  marca el token usado.
- Routes /api/auth/recover (Turnstile) y /api/auth/reset-password.
- Páginas app/[locale]/recover (RecoverForm: selector de tipo + email + Turnstile) y
  reset-password (ResetForm con token de la URL). Catálogos Recover, Reset.

Verificado: recover sin captcha -> captchaFailed; reset token inválido -> invalidLink;
páginas 200. Reutiliza home_passwordreset de Django. Crypto de reset ya validada.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 23:07:16 +00:00
Inna aa61f79a55 Cloudflare Turnstile en login y registro (Next.js)
- 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>
2026-07-12 23:04:22 +00:00
Inna 4a783d165f Layout visual: cabecera con nav/sesión/idioma + pie, tema Tailwind
- components/Header.tsx (server, lee sesión): marca, nav (Inicio; login/register o
  account/logout según sesión), LanguageSwitcher y LogoutButton.
- components/LanguageSwitcher.tsx (cliente): cambia de idioma conservando la ruta
  (next-intl usePathname/useRouter). components/LogoutButton.tsx (compartido).
- components/Footer.tsx. app/[locale]/layout.tsx envuelve children con Header/Footer
  (columna min-h-screen). Se unifica el LogoutButton (se borra el de account/).
- messages: namespace Nav.

Verificado: home ES/EN con cabecera+nav+idioma+pie; enlaces locale-aware (/en/login);
textos por idioma. Al leer sesión en el header, todas las rutas pasan a dinámicas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 22:57:00 +00:00
Inna f332fc39ca Registro + activación por email en Next.js (SRP6 + nodemailer)
- lib/mail.ts: transporte SMTP (nodemailer, mismas creds Gmail que Django).
- lib/register.ts: registerAccount (valida, comprueba email existente, crea fila en
  home_accountactivation reutilizando la tabla de Django, envía email de activación);
  activateAccount (crea battlenet_accounts con SRP6 v2 + account con SRP6 Grunt, como
  activate_account_view: expansion=2, battlenet_index=1; borra la activación).
- Route handlers /api/auth/register y /api/auth/activate.
- Páginas app/[locale]/register (RegisterForm cliente) y app/[locale]/activate-account
  (ActivateClient auto-POST al abrir el enlace). Textos en messages (Register, Activate).

Validado: validaciones (missingFields/passwordMismatch/invalidEmail/passwordTooLong);
ciclo completo activación->crea bnet+account->login OK con la cuenta creada
(needsSelection=false), password mala->invalidCredentials. Todo en TS, crypto validada.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 22:53:09 +00:00
Inna e00a439dd9 Cierra el flujo de login: logout, selector de cuenta y /account con guardas
- Route handlers: /api/auth/logout (destroy sesión), /api/auth/select-account
  (fija la cuenta de juego elegida, valida que pertenece a la bnet en sesión).
- app/[locale]/select-account: página SSR (guarda: sin bnetId -> login) + lista de
  cuentas de juego (SelectAccountForm cliente) -> /account.
- app/[locale]/account: página protegida (sin bnetId -> login; sin username ->
  select-account) con datos de sesión + LogoutButton.
- Textos en messages (Account, SelectAccount).

Verificado: guardas locale-aware (ES /account -> /login, EN /en/account -> /en/login,
307), logout OK, select-account sin sesión 401. Redirect i18n con next-intl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 22:46:42 +00:00
Inna 60c2e44afd Login end-to-end en Next.js: iron-session + auth SRP6 + página i18n
- lib/session.ts: sesión cifrada httpOnly con iron-session (SESSION_SECRET en
  .env.local). SessionData {bnetId, bnetEmail, username, accountId}.
- lib/auth.ts: authenticate(email,password) verifica contra battlenet_accounts con
  bnetVerify (SRP6 v2); getGameAccounts(bnetId). Resiliente si acore no está.
- app/api/auth/login/route.ts: POST -> autentica, fija sesión, resuelve cuentas de
  juego (1 -> auto; 0/varias -> needsSelection). Devuelve JSON.
- app/[locale]/login: página SSR + LoginForm (cliente, next-intl, router i18n).
  Textos en messages/*.json (namespace Login).
- tsconfig target ES2020 (literales BigInt de bnet.ts).

Validado end-to-end: con una cuenta creada por bnet.py (como haría AzerothCore),
el login TS la verifica OK y emite la cookie de sesión; password incorrecta ->
invalidCredentials; sin campos -> missingFields. Página en ES y EN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 22:42:23 +00:00
Inna 032bd0020b i18n con next-intl: raíz multilenguaje (es por defecto, /en) + textos externalizados
- next-intl 4 (compatible Next 16): routing (es default en RAÍZ sin prefijo, en con
  /en, localePrefix 'as-needed'), middleware, request config, navigation helpers.
- Estructura app/[locale]/ (layout con <html lang>, NextIntlClientProvider, metadata
  SEO por idioma vía generateMetadata; page home con getTranslations).
- Catálogos messages/es.json y en.json: TODOS los textos de la UI (nada hardcodeado
  en los .tsx, se usan con t()).

Verificado: / -> español (lang=es, título ES), /en -> inglés (lang=en, título EN),
datos SSR desde MySQL. Añadir idioma = locale en routing + messages/<loc>.json.

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