Anuncios de Monetag, apagados por defecto y detrás del consentimiento
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>
This commit is contained in:
@@ -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 `<meta name="google-adsense-account">` en el `<head>` 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
|
||||
|
||||
@@ -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. */}
|
||||
<Analytics />
|
||||
{/* 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. */}
|
||||
<Monetag />
|
||||
{/* 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. */}
|
||||
|
||||
@@ -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 <Header/>
|
||||
* 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 <meta name="google-adsense-account" content={client} />
|
||||
}
|
||||
|
||||
@@ -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 <MonetagScript/> (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 <MonetagScript src={src} zone={zone} />
|
||||
}
|
||||
@@ -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 (
|
||||
<Script
|
||||
src={src}
|
||||
data-zone={zone}
|
||||
data-cfasync="false"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { getSession } from './session'
|
||||
import { isAdmin } from './admin'
|
||||
import { getAccountRankInfo } from './account'
|
||||
|
||||
/**
|
||||
* ¿A este visitante se le sirven anuncios?
|
||||
*
|
||||
* Criterio único para TODA la publicidad (la etiqueta de AdSense y el script de
|
||||
* Monetag), para que no se puedan desincronizar:
|
||||
*
|
||||
* - Admin (allowlist ADMIN_EMAILS o gmlevel de AzerothCore): nunca. La navegación
|
||||
* del staff no debe contar como tráfico con anuncios.
|
||||
* - Rango 2+ (100 PD, ver getAccountRank en lib/account.ts) o cualquier pago
|
||||
* confirmado por Stripe/SumUp: nunca. Los anuncios son la contrapartida de no
|
||||
* haber donado.
|
||||
* - El resto (anónimos y rango 1 sin pagos): sí.
|
||||
*
|
||||
* Los dos cortes del segundo punto son distintos a propósito: 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.
|
||||
*
|
||||
* Corta 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.
|
||||
*/
|
||||
export async function shouldServeAds(): Promise<boolean> {
|
||||
const session = await getSession()
|
||||
if (await isAdmin(session)) return false
|
||||
|
||||
if (session.accountId) {
|
||||
const { level, donatedPD } = await getAccountRankInfo(session.accountId)
|
||||
if (level >= 2 || donatedPD > 0) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user