diff --git a/README.md b/README.md index 0cd349c..cf5b4bb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ npm run dev # http://127.0.0.1:3000 | `ADMIN_EMAILS` / `ADMIN_GMLEVEL` | Acceso al panel de administración | | `CRON_SECRET` | Protege el endpoint de reconciliación de SumUp | | `NEXT_PUBLIC_GA_ID` | Google Analytics 4 (p. ej. `G-XXXXXXXXXX`). **Vacío o sin poner = analítica desactivada**, no se carga nada. Además, aunque esté puesta, solo se carga si el visitante acepta la categoría «Analíticas» del banner de cookies. Al ser `NEXT_PUBLIC_*` se incrusta en el build: activar o desactivar exige `npm run build` + reinicio | +| `MONETAG_SRC` / `MONETAG_ZONE` | Script de anuncios de Monetag (MultiTag). Salen del panel: Sites → canal → Get Tag; el `src` varía por zona. **Cualquiera de las dos vacía = anuncios desactivados**. Aunque estén puestas, solo se cargan si el visitante acepta la categoría «Marketing» del banner y pasa el filtro de `lib/ads.ts`. El push necesita además `public/sw.js` del panel. Se leen en el servidor: activar/desactivar **sin rebuild** | | `ADSENSE_CLIENT_ID` | ID de editor de Google AdSense (`ca-pub-...`). Emite `` en el `` de todas las páginas. **Vacío o sin poner = etiqueta desactivada**. No la ven ni los admins ni las cuentas de rango 2+ o que hayan pagado alguna vez (ver `components/AdSense.tsx`). Se lee en el servidor en cada petición: activar o desactivar es tocar el `.env` + reinicio, **sin rebuild** | ## 3. Producción diff --git a/web-next/app/[locale]/layout.tsx b/web-next/app/[locale]/layout.tsx index 15b02b5..28105bd 100644 --- a/web-next/app/[locale]/layout.tsx +++ b/web-next/app/[locale]/layout.tsx @@ -11,6 +11,7 @@ import { Footer } from '@/components/Footer' import { CookieBanner } from '@/components/CookieConsent' import { Analytics } from '@/components/Analytics' import { AdSense } from '@/components/AdSense' +import { Monetag } from '@/components/Monetag' import '../globals.css' export function generateStaticParams() { @@ -100,6 +101,9 @@ export default async function LocaleLayout({ {/* Analítica: solo si hay NEXT_PUBLIC_GA_ID y el visitante ha aceptado la categoría «Analíticas» del banner. Ver components/Analytics.tsx. */} + {/* Anuncios de Monetag: solo si hay MONETAG_SRC + MONETAG_ZONE, el visitante + no es admin ni ha pagado, y ha aceptado «Marketing». Ver components/Monetag.tsx. */} + {/* Tooltips de wowhead (WotLK) en todos los enlaces .../wowhead.com/... y elementos con data-wowhead. Funciona con contenido añadido por React (carrito, búsquedas) porque delega el hover en el documento. */} diff --git a/web-next/components/AdSense.tsx b/web-next/components/AdSense.tsx index ef61b23..7bddbaf 100644 --- a/web-next/components/AdSense.tsx +++ b/web-next/components/AdSense.tsx @@ -1,42 +1,25 @@ -import { getSession } from '@/lib/session' -import { isAdmin } from '@/lib/admin' -import { getAccountRankInfo } from '@/lib/account' +import { shouldServeAds } from '@/lib/ads' /** - * Etiqueta de verificación de Google AdSense, con TRES interruptores: + * Etiqueta de verificación de Google AdSense, con DOS interruptores: * * 1. `ADSENSE_CLIENT_ID` en el .env. Vacío o sin poner = no se emite nada. Es el * interruptor del administrador. No lleva prefijo NEXT_PUBLIC_ a propósito: * esto se renderiza en el servidor (el layout ya es dinámico porque
- * lee la sesión), así que no hace falta exponerlo al bundle del cliente. - * 2. Admin (allowlist 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, ver getAccountRank en lib/account.ts) y pagos: - * solo ve anuncios el rango 1 (Newbie, el de por defecto) que ADEMÁS nunca ha - * pagado. Son dos cortes distintos a propósito: cualquier pago confirmado por - * Stripe o SumUp los quita por mínimo que sea, y el mínimo de SumUp (0,20 € = - * 20 PD) no llega a los 100 PD del rango 2, así que mirar solo el rango dejaría - * con anuncios a alguien que ya ha pagado. - * - * Ambas comprobaciones cortan en seco sin tocar la BD si no hay sesión, así que el - * visitante anónimo (el caso normal) no paga ninguna consulta extra. + * lee la sesión), así que no hace falta exponerlo al bundle del cliente ni + * rebuildear para activarlo o desactivarlo. + * 2. El filtro común de publicidad (`shouldServeAds`, ver lib/ads.ts): fuera para + * admins y para quien sea rango 2+ o haya pagado alguna vez. * * Ojo: esto es solo la etiqueta de verificación de la cuenta, no carga adsbygoogle.js - * ni escribe cookies, por eso no pasa por el banner de consentimiento. El día que se - * añada el script de anuncios sí tendrá que ir detrás de la categoría «Marketing» - * (useConsent('marketing'), ver components/Analytics.tsx). + * ni escribe cookies, por eso no pasa por el banner de consentimiento (el script de + * anuncios sí lo hace, ver components/Monetag.tsx). */ export async function AdSense() { const client = process.env.ADSENSE_CLIENT_ID if (!client) return null - const session = await getSession() - if (await isAdmin(session)) return null - - if (session.accountId) { - const { level, donatedPD } = await getAccountRankInfo(session.accountId) - if (level >= 2 || donatedPD > 0) return null - } + if (!(await shouldServeAds())) return null return } diff --git a/web-next/components/Monetag.tsx b/web-next/components/Monetag.tsx new file mode 100644 index 0000000..0c78a44 --- /dev/null +++ b/web-next/components/Monetag.tsx @@ -0,0 +1,32 @@ +import { shouldServeAds } from '@/lib/ads' +import { MonetagScript } from './MonetagScript' + +/** + * Script de anuncios de Monetag (MultiTag: popunder + push + in-page push + vignette). + * + * TRES interruptores: + * + * 1. `MONETAG_SRC` y `MONETAG_ZONE` en el .env. Si falta cualquiera de los dos no se + * carga nada: ese es el interruptor de encendido/apagado. Los dos valores salen + * del panel de Monetag (Sites → tu canal → Get Tag) y son propios de cada zona, + * por eso el `src` también es variable y no está fijado en el código. + * Sin prefijo NEXT_PUBLIC_: se leen en el servidor y se pasan como props, así que + * activar o desactivar es tocar el .env + reinicio, SIN rebuild. + * 2. El filtro común de publicidad (`shouldServeAds`, ver lib/ads.ts): fuera para + * admins y para quien sea rango 2+ o haya pagado alguna vez. + * 3. El consentimiento «Marketing» del visitante, en (cliente). + * + * OJO con el push de MultiTag: además de esto necesita un `sw.js` servido desde la + * RAÍZ del dominio (https://www.nightspire.gg/sw.js). Se descarga del panel de + * Monetag y se deja en `public/sw.js`, que Next sirve tal cual en la raíz. Sin ese + * fichero el resto de formatos funcionan, pero las notificaciones push no. + */ +export async function Monetag() { + const src = process.env.MONETAG_SRC + const zone = process.env.MONETAG_ZONE + if (!src || !zone) return null + + if (!(await shouldServeAds())) return null + + return +} diff --git a/web-next/components/MonetagScript.tsx b/web-next/components/MonetagScript.tsx new file mode 100644 index 0000000..07f3390 --- /dev/null +++ b/web-next/components/MonetagScript.tsx @@ -0,0 +1,31 @@ +'use client' + +import Script from 'next/script' +import { useConsent } from './CookieConsent' + +/** + * Parte de cliente del script de Monetag: solo existe para mirar el consentimiento, + * que vive en el navegador (localStorage + evento `ns:consentchange`). + * + * A diferencia de la etiqueta de AdSense, esto SÍ pasa por el banner: MultiTag trae + * popunder, push, in-page push y vignette, y todos escriben cookies o piden permisos + * al navegador. Cargarlo sin mirar la categoría «Marketing» convertiría el banner en + * mentira y, con visitantes en la UE, en un problema legal. + * + * `useConsent` reacciona en caliente: entra en cuanto se acepta y deja de renderizarse + * si se revoca, sin recargar. Arranca en `false`, así que hasta confirmar el + * consentimiento no se carga nada. + */ +export function MonetagScript({ src, zone }: { src: string; zone: string }) { + const consent = useConsent('marketing') + if (!consent) return null + + return ( +