Se añade la integración del script de Monetag (MultiTag: popunder + push + in-page
push + vignette) como alternativa a AdSense para monetizar sin depender de que
Google apruebe el sitio.
Tres interruptores (ver components/Monetag.tsx):
1. `MONETAG_SRC` + `MONETAG_ZONE` en el .env (no versionadas, documentadas en el
README). Cualquiera de las dos vacía = no se carga nada. Se leen en el servidor
y se pasan como props, así que encender/apagar es tocar el .env + reiniciar,
SIN rebuild. Ahora mismo van vacías: la integración queda instalada pero inerte
hasta que se pegue la zona del panel de Monetag.
2. El filtro común de publicidad, extraído a lib/ads.ts (`shouldServeAds`): fuera
para admins y para quien sea rango 2+ o haya pagado alguna vez. Antes esta lógica
vivía dentro de AdSense.tsx; ahora la comparten la etiqueta de AdSense y el
script de Monetag desde un único sitio, para que no se puedan desincronizar.
3. El consentimiento «Marketing» del banner, en components/MonetagScript.tsx
(cliente). A diferencia de la etiqueta de AdSense, MultiTag escribe cookies y
pide permisos, así que NO puede cargarse sin aceptar la categoría. Mismo patrón
que Analytics.tsx: useConsent reacciona en caliente, arranca en false.
El push de MultiTag necesitará además un sw.js servido desde la raíz del dominio
(public/sw.js, descargado del panel). Sin él, el resto de formatos funcionan igual.
Verificado: con las variables vacías la web queda idéntica (ni script de Monetag ni
cambios) y la etiqueta de AdSense sigue intacta. Con una zona de prueba, el filtro
de servidor deja pasar a anónimos y rango 1 sin pagos, y corta a los admins. Sin
consentimiento no se inyecta ningún <script> (la zona solo viaja como prop inerte
en el payload de React).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Se añade `<meta name="google-adsense-account">` al <head> de todas las páginas, que
es lo que pide Google para verificar la propiedad del sitio. OJO: esto NO muestra
anuncios ni carga adsbygoogle.js todavía; solo permite que Google confirme que el
dominio es nuestro y arranque la revisión.
TRES interruptores (ver components/AdSense.tsx):
1. `ADSENSE_CLIENT_ID` en el .env (no versionado). Vacío o sin poner = no se emite
nada. Documentada en el README. NO lleva prefijo NEXT_PUBLIC_ a propósito: la
etiqueta se pinta en el servidor, así que no hace falta exponerla al bundle ni
rebuildear para activarla/desactivarla, al revés que NEXT_PUBLIC_GA_ID.
2. Admin (ADMIN_EMAILS o gmlevel de AzerothCore): sin etiqueta, para que la
navegación del staff no cuente como tráfico con anuncios.
3. Rango de la cuenta (ns-ranks) Y pagos: solo ve anuncios el rango 1 (Newbie, el
de por defecto) que ADEMÁS nunca ha pagado.
Los dos cortes del punto 3 son distintos a propósito y no basta con mirar el rango:
el mínimo de SumUp son 0,20 €, que a 100 PD/unidad son 20 PD, y eso sigue siendo
rango 1. Mirando solo el rango, quien pagara el mínimo seguiría viendo anuncios,
que es justo lo contrario de lo que queremos.
Para no duplicar el SQL, las consultas de saldo y de total donado salen de
getAccountDashboard a dos helpers (`getPoints`, `getDonatedPD`) que reutiliza el
nuevo `getAccountRankInfo`, con las dos consultas mínimas en vez del panel entero
(personajes, baneos, correo…). El criterio de rango es el mismo que ya usaba
/my-account: el máximo entre lo donado y el saldo actual.
Verificado contra la web real: anónimo y rango 1 sin pagos SÍ reciben la etiqueta;
rango 1 con 99 PD también; 100 PD de saldo (rango 2) no; rango 1 tras pagar 0,20 €
tampoco; admin tampoco; y con la variable vacía no la recibe nadie. Los dos últimos
casos no existían en la BD, así que se probaron con datos temporales en la cuenta
14 (fila en stripelog y en api_points), ya borrados. /my-account sigue pintando el
rango correcto tras mover las consultas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Se añade GA4 (gtag.js) con el componente oficial `@next/third-parties`, que es lo
que recomienda el doc de Next 16 y carga el mismo gtag pero tras la hidratación.
DOS interruptores:
1. `NEXT_PUBLIC_GA_ID` en el .env (no versionado). Vacío o sin poner = no se carga
nada. Documentada en el README.
2. El consentimiento del visitante. El sitio YA tenía un banner con la categoría
«Analíticas» que se puede rechazar: soltar gtag en el <head> lo habría
convertido en mentira, y con visitantes en la UE, en un problema legal.
Para lo segundo se expone `useConsent(categoría)` desde CookieConsent, que además
reacciona en caliente: `writeConsent`/`revokeConsent` ya disparaban el evento
`ns:consentchange`, así que la analítica entra en cuanto se acepta y desaparece
si se revoca, sin recargar. Arranca en `false` a propósito: hasta confirmar el
consentimiento no se carga nada.
Verificado en el build: sin consentimiento no hay ni rastro de googletagmanager
en el HTML, y la condición compilada es `id && consent ? <GoogleAnalytics/> : null`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Antes: <link href="/ns-themes/.../nightspire-style.css?v=2"> en el <head>. Una
peticion aparte, sin minificar y con cache invalidada a mano con ?v=2.
Ahora: app/theme.css, importado al FINAL de globals.css. Next lo compila,
minifica y le pone hash de contenido; se sirve con cache immutable y hay una
hoja de estilo menos (4 -> 3).
⚠ El @import va el ULTIMO y no es cosmético: el tema se diseñó contra los
defaults del navegador y el preflight de Tailwind los pisa (box-sizing,
alturas). Antes ganaba la cascada por cargarse el último en el <head>; ahora
gana por ser el último import. Si sube de sitio, el diseño se rompe.
Las url() pasan a ABSOLUTAS (/ns-themes/ns-ryu/...). Los assets siguen en
public/, y así el compilador no intenta resolverlos: el @font-face declara
.eot/.otf/.ttf que NO existen (solo hay .woff) y con rutas relativas el build
fallaría.
Verificado: los 6 páginas dan exactamente el mismo alto que antes (8159, 1356,
2645, 1356, 2221, 2349 px) con la misma fuente y colores, y el input del login
sigue en box-sizing:content-box — que es justo donde el preflight rompía. Ojo:
el md5 de las capturas NO sirve para comparar, porque la cabecera lleva un vídeo
y cada captura pilla un fotograma distinto.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renombrado completo de los prefijos heredados: 19 carpetas, 11 ficheros y 318
referencias en 35 ficheros de código, más las clases y las rutas url() de dentro
del CSS del tema. Se usa git mv para conservar el historial.
También fuera del código, que un sed no ve:
- BD: votesite.image_url (4 filas) apuntaba a /nw-themes/...
- Ficheros con la marca vieja en el NOMBRE: novawow-maintenance.webp ->
nightspire-maintenance.webp (lo usa la página de mantenimiento) y
store_novawow_response.js.
⚠ Alias en Caddy /nw-themes/* -> /ns-themes/*: los correos ENVIADOS antes del
rebranding llevan esas rutas escritas y están en las bandejas de los usuarios.
Sin el alias, sus imágenes se romperían. Verificado que las rutas viejas siguen
sirviendo 200.
Nota: ns-js/ y ns-js-handlers/ son CÓDIGO MUERTO (manejadores jQuery del portal
Django, que ya se borró). Se midió en el navegador: la web no pide ni un solo JS
del tema. Se renombran igualmente por consistencia, pero son candidatos a
borrarse.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Marca, dominios y correos: UltimoWoW/Nova WoW -> NightSpire, cualquier URL de
esos dominios -> https://www.nightspire.gg y los correos -> consultas@nightspire.gg.
98 sustituciones en 16 ficheros de textos de usuario (messages/, app/, components/).
Assets: logo largo de cabecera y vídeo sustituidos por los de NightSpire (el logo
nuevo es 214x28, igual que el viejo, así que no hace falta tocar el CSS).
Redes: apuntan a las cuentas de NightSpire, tanto en el pie de la web como en el
de los correos (que seguían enlazando a las de NovaWoW desde el porte del diseño).
Cookie de sesión novawow_session -> nightspire_session. Cierra la sesión de todos
los conectados una vez; se asume ahora, recién hecho el cutover.
Ficheros del tema renombrados: novawow-style.css -> nightspire-style.css y
novawow-main-logo-transparent.webp -> nightspire-main-logo-transparent.webp, para
que la marca vieja no quede ni en las rutas.
El foro externo (foro.ultimowow.com) pasa a ser el nuestro: <Link> a /forum, que
respeta el idioma y no abre pestaña nueva.
NO se tocan y es a propósito:
- Los comentarios que nombran AzerothCore: describen el comportamiento REAL del
core (límite de 12 ítems por correo, convención de baneos, SOAP...). Cambiarlos
a NightSpire los volvería falsos. Además AzerothCore no aparece en la web: las
14 menciones son todas comentarios de código.
- El regex de brandify() sigue buscando los nombres VIEJOS: son los que hay
escritos en las noticias de la BD.
- lib/changelog.ts REPO='Inna/NovaWoW': es la ruta real del repo en Gitea.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comparado contra el store original (ultimowow /es/store-bennu) con una sesión
real: markup, CSS y clases que aplica su power.js. Cinco causas distintas:
- Los nombres de ítem salían azules: el layout declaraba `const whTooltips`,
que queda en el ámbito léxico del script y NO crea propiedad en `window`,
que es de donde tooltips.js lee la config -> se ignoraba entera. Ahora
`window.whTooltips`, con colorLinks+iconizeLinks (el original no define
whTooltips y usa esos defaults; sus enlaces acaban con clases `icontinyl q4`).
renameLinks sigue en false: el texto sale de nuestra BD, en español.
- tooltips.js solo recorre el DOM al cargar, así que los enlaces que monta
React (hoja del árbol, carrito) no se teñían. Se pasan por
$WowheadPower.refreshLinks(), igual que hace el original tras cada AJAX.
- globals.css redefinía .item-box/.item-name/.item-img-box/.max-left-table2,
que el tema nw-ryu ya trae del original, y con más especificidad ganaba la
cascada. El peor: un `border-collapse` que anulaba el `border-spacing: 0 8px`
del tema, que es lo que separa las filas del carrito.
- Faltaban la caja "Carrito - <REINO>" y "Personaje seleccionado: <nombre>"
(coloreado por clase), que el original sí tiene.
- El total de la columna "Cant" mostraba el número de líneas en vez de la suma
de cantidades: un ítem que entrega un mazo de 20 debe contar 20, no 1.
Verificado con navegador contra un build de producción: el nombre del ítem sale
con clases `icontinyl q4` y color rgb(163,53,238), el mismo valor exacto que
devuelve el original.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sustituye la base de datos de ítems del sistema antiguo (AoWoW en
wotlk.novawow/wotlk.ultimowow) y los iconos de mirrors por wowhead / zamimg,
con el idioma del tooltip según el locale de la web (es. / www.).
Sistema reutilizable para rutas actuales y futuras:
- lib/wowhead.ts: wowheadUrl/wowheadItemUrl/wowheadWotlkHome (subdominio por
locale), wowheadIcon (zamimg) y wowheadData (atributo data-wowhead, rama WotLK).
- components/WowheadLink.tsx: enlace con tooltip en el locale actual.
- layout raíz: config whTooltips (solo tooltips) + script tooltips.js global;
funciona con contenido añadido por React (carrito, búsquedas).
Aplicado en: send-gift (catálogo + carrito), restore-items, recruit
(RecruitPanel, enlace por item_id + icono zamimg), quest-character (misión/npc/
facción + iconos), y el enlace "WOTLK DB" de la cabecera.
Verificado en prod (:3001): /es -> es.wowhead.com, /en -> www.wowhead.com,
data-wowhead presente, iconos por zamimg.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El nav ya mide igual que el original (1112px, items 174px, Futura) tras el fix de
box-sizing; el CSS del tema se servía sin versión y quedaba cacheado. Se añade
?v=2 a novawow-style.css para bustear la caché del navegador.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>
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>
- 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>
- 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>