GA Cheatnow (ManagerZone)

Analizador de Transferencias en curso (analizarTransferencias) para sub=cheatnow: motor de scoring ponderado (PESOS interno), coloreado por severidad, panel resumen (hubs, recíprocos, top de riesgo), filtro y botones [+]. Inyecta a Cruzar Usuarios vía GA.cargarEnCruce. Requiere ga-core.

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/588167/1882654/GA%20Cheatnow%20%28ManagerZone%29.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         GA Cheatnow (ManagerZone)
// @namespace    https://control.managerzone.com/ga
// @version      1.0.0
// @description  Analizador de Transferencias en curso (analizarTransferencias) para sub=cheatnow: motor de scoring ponderado (PESOS interno), coloreado por severidad, panel resumen (hubs, recíprocos, top de riesgo), filtro y botones [+]. Inyecta a Cruzar Usuarios vía GA.cargarEnCruce. Requiere ga-core.
// ==/UserScript==

(function () {
    'use strict';
    window.GA = window.GA || {};
    const GA = window.GA;

    // ============================================================
    //  Analizador de Transferencias en curso  (sub=cheatnow)
    //
    //  Puntúa cada transferencia combinando señales. NINGUNA señal
    //  suelta alcanza: en una muestra real, UB=1 aparece en el 52% de
    //  las filas y X=0 en el 42%. Lo que discrimina es la CONJUNCIÓN.
    //
    //  Señales (pesos en PESOS, editables):
    //   · Reciprocidad  : PS>0 Y PP>0 (se venden en ambas direcciones)
    //   · Historial     : PS>0 o PP>0 (ya comerciaron)
    //   · Regalo        : precio irrisorio frente al valor del jugador
    //   · Sobrepago     : se pagó muy por encima del valor
    //   · Sin competencia: UB=1 (un único postor)
    //   · Hub comprador : el comprador recibe de muchos vendedores
    //   · Hub vendedor  : el vendedor reparte a muchos compradores
    //   · Par repetido  : el mismo par vendedor→comprador varias veces
    //
    //  Agrega: coloreado por severidad, panel resumen (hubs, recíprocos,
    //  top de riesgo), filtro "solo riesgo" y botones [+] con ID real.
    // ============================================================
    function analizarTransferencias() {

        // ---- Pesos (ajustá a tu criterio) ----
        const PESOS = {
            reciproco:      40,   // PS>0 y PP>0
            historial:      25,   // PS>0 o PP>0
            regalo:         25,   // bid/value < RATIO_REGALO
            precioBajo:     12,   // bid/value < RATIO_BAJO
            sobrepago:      20,   // bid/value > RATIO_ALTO
            sinCompetencia: 10,   // UB == 1
            hubComprador:   15,
            hubVendedor:    10,
            parRepetido:    20
        };
        const RATIO_REGALO = 0.01;   // < 1% del valor
        const RATIO_BAJO   = 0.10;   // < 10% del valor
        const RATIO_ALTO   = 3.0;    // paga 3x el valor
        const HUB_COMPRADOR = 3;     // compra a >= N vendedores distintos
        const HUB_VENDEDOR  = 4;     // vende a >= N compradores distintos
        // Calibrados sobre una muestra real de 558 transferencias:
        //   >=60 -> 36 filas (6.5%)   >=50 -> 88 filas (15.8%)
        // Por debajo de UMBRAL_MEDIO la fila NO se pinta (solo lleva el
        // badge con el score), para que el color siga significando algo.
        const UMBRAL_ALTO  = 60;
        const UMBRAL_MEDIO = 50;

        const COL_ALTO  = '#f8caca';
        const COL_MEDIO = '#ffe0b3';
        const COL_BAJO  = '#fff3b0';

        // ---- Ubicar la tabla (por sus cabeceras características) ----
        const tabla = [...document.querySelectorAll('table')].find(t => {
            const h = [...t.querySelectorAll('th')].map(x => x.textContent.trim());
            return h.includes('Seller') && h.includes('Buyer') && h.includes('PS') && h.includes('PP');
        });
        if (!tabla) return;

        const ths = [...tabla.querySelectorAll('thead th')];
        const idx = n => ths.map(t => t.textContent.trim()).indexOf(n);
        const C = {
            sc: idx('SC'), bc: idx('BC'), seller: idx('Seller'), buyer: idx('Buyer'),
            bid: idx('Current bid'), value: idx('Value'), x: idx('X'),
            tb: idx('TB'), ub: idx('UB'), ps: idx('PS'), pp: idx('PP'), end: idx('End date')
        };
        if (C.seller < 0 || C.buyer < 0) return;

        const filas = [...tabla.querySelectorAll('tr')].filter(tr => !tr.querySelector('th'));
        if (!filas.length) return;

        const txt = c => (c?.textContent || '').replace(/\s+/g, ' ').trim();
        const num = c => { const n = parseFloat(txt(c).replace(/\s/g, '').replace(',', '.')); return isNaN(n) ? 0 : n; };
        // El username viene en un <a>, y el user id en el href (searchstr=NNN)
        const userDe = c => {
            const a = c?.querySelector('a');
            if (!a) return { name: txt(c).split(' ')[0] || '', id: '' };
            const m = (a.getAttribute('href') || '').match(/searchstr=(\d+)/);
            return { name: a.textContent.trim(), id: m ? m[1] : '' };
        };

        // ---- 1er pase: leer filas ----
        const datos = filas.map(tr => {
            const c = tr.children;
            const s = userDe(c[C.seller]), b = userDe(c[C.buyer]);
            const bid = num(c[C.bid]), value = num(c[C.value]);
            return {
                tr, celdas: c,
                seller: s.name, sellerId: s.id, buyer: b.name, buyerId: b.id,
                bid, value, ratio: value > 0 ? bid / value : 0,
                ub: C.ub >= 0 ? num(c[C.ub]) : 0,
                ps: C.ps >= 0 ? num(c[C.ps]) : 0,
                pp: C.pp >= 0 ? num(c[C.pp]) : 0
            };
        }).filter(d => d.seller && d.buyer);

        // ---- Redes: hubs y pares repetidos ----
        const compraDe = {}, vendeA = {}, pares = {};
        datos.forEach(d => {
            (compraDe[d.buyer] = compraDe[d.buyer] || new Set()).add(d.seller);
            (vendeA[d.seller] = vendeA[d.seller] || new Set()).add(d.buyer);
            const k = d.seller + '>' + d.buyer;
            pares[k] = (pares[k] || 0) + 1;
        });

        // ---- 2do pase: puntuar ----
        datos.forEach(d => {
            let score = 0;
            const motivos = [];

            if (d.ps > 0 && d.pp > 0) {
                score += PESOS.reciproco;
                motivos.push(`recíproco (PS:${d.ps} PP:${d.pp})`);
            } else if (d.ps > 0 || d.pp > 0) {
                score += PESOS.historial;
                motivos.push(d.ps > 0 ? `${d.ps} venta(s) previa(s)` : `${d.pp} compra(s) previa(s)`);
            }

            if (d.value > 0) {
                if (d.ratio < RATIO_REGALO) {
                    score += PESOS.regalo;
                    motivos.push(`regalo (${d.bid.toLocaleString()} por un jugador de ${d.value.toLocaleString()})`);
                } else if (d.ratio < RATIO_BAJO) {
                    score += PESOS.precioBajo;
                    motivos.push(`precio bajo (${Math.round(d.ratio * 100)}% del valor)`);
                } else if (d.ratio > RATIO_ALTO) {
                    score += PESOS.sobrepago;
                    motivos.push(`sobrepago (${d.ratio.toFixed(1)}× el valor)`);
                }
            }

            if (d.ub === 1) { score += PESOS.sinCompetencia; motivos.push('sin competencia (UB=1)'); }

            const nComprAVend = (compraDe[d.buyer] || new Set()).size;
            if (nComprAVend >= HUB_COMPRADOR) {
                score += PESOS.hubComprador;
                motivos.push(`hub: ${d.buyer} compra a ${nComprAVend} vendedores`);
            }
            const nVendACompr = (vendeA[d.seller] || new Set()).size;
            if (nVendACompr >= HUB_VENDEDOR) {
                score += PESOS.hubVendedor;
                motivos.push(`hub: ${d.seller} vende a ${nVendACompr} compradores`);
            }

            const rep = pares[d.seller + '>' + d.buyer];
            if (rep > 1) { score += PESOS.parRepetido; motivos.push(`par repetido ×${rep}`); }

            d.score = score;
            d.motivos = motivos;
            d.nivel = score >= UMBRAL_ALTO ? 'alto' : score >= UMBRAL_MEDIO ? 'medio' : 'ok';
        });

        // ---- 3er pase: pintar ----
        const tint = (celdas, color) => { for (const td of celdas) td.style.background = color; };
        datos.forEach(d => {
            if (d.nivel === 'alto')  tint(d.celdas, COL_ALTO);
            else if (d.nivel === 'medio') tint(d.celdas, COL_MEDIO);
            // por debajo del umbral medio no se pinta la fila

            if (d.score > 0) {
                const b = document.createElement('span');
                b.textContent = d.score;
                b.title = d.motivos.join(' · ');
                b.style.cssText = 'display:inline-block; margin-left:6px; padding:0 5px; border-radius:3px; font-size:10px; font-weight:bold; color:#fff; background:' +
                    (d.nivel === 'alto' ? '#c0392b' : d.nivel === 'medio' ? '#e67e22' : '#9aa0a6') + ';';
                d.celdas[C.buyer].appendChild(b);
            }

            // [+] con el ID real, para Cruzar Usuarios
            [[C.seller, d.sellerId, d.seller], [C.buyer, d.buyerId, d.buyer]].forEach(([col, id, nom]) => {
                if (!nom) return;
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.textContent = '[+]';
                btn.className = 'ga-plus-tr';
                btn.title = 'Añadir a Cruzar Usuarios';
                btn.style.cssText = 'margin-left:4px; cursor:pointer; background:#007bff; color:#fff; border:none; border-radius:3px; font-size:9px; font-weight:bold; padding:0 3px;';
                btn.addEventListener('click', e => {
                    e.preventDefault();
                    const input = document.getElementById('ga-simple-cross-input');
                    if (!input) return;
                    GA.cargarEnCruce(id || nom);
                    btn.textContent = '[✓]';
                    setTimeout(() => (btn.textContent = '[+]'), 800);
                });
                d.celdas[col].appendChild(btn);
            });
        });

        // ---- Panel resumen ----
        const altos  = datos.filter(d => d.nivel === 'alto').sort((a, b) => b.score - a.score);
        const medios = datos.filter(d => d.nivel === 'medio');
        const recip  = datos.filter(d => d.ps > 0 && d.pp > 0);

        const hubsC = Object.entries(compraDe).filter(([, s]) => s.size >= HUB_COMPRADOR)
            .sort((a, b) => b[1].size - a[1].size);
        const hubsV = Object.entries(vendeA).filter(([, s]) => s.size >= HUB_VENDEDOR)
            .sort((a, b) => b[1].size - a[1].size);

        const panel = document.createElement('div');
        panel.style.cssText = 'margin:12px 0; padding:12px 15px; border:1px solid #c9a0a0; background:#fff8f8; border-radius:5px; font-family:sans-serif; font-size:13px; color:#333;';

        const listaHubs = (arr, verbo) => arr.slice(0, 8).map(([n, s]) =>
            `<li><b>${n}</b> ${verbo} <b>${s.size}</b>: <span style="color:#666; font-size:11px;">${[...s].join(', ')}</span></li>`).join('');

        panel.innerHTML = `
            <div style="font-size:14px; margin-bottom:8px;">
                🚨 <b>Transferencias analizadas:</b> ${datos.length} &nbsp;·&nbsp;
                <span style="background:${COL_ALTO}; padding:1px 6px; border-radius:3px;">Alto: ${altos.length}</span>
                <span style="background:${COL_MEDIO}; padding:1px 6px; border-radius:3px; margin-left:4px;">Medio: ${medios.length}</span>
                <button id="ga-tr-filtro" type="button" style="margin-left:12px; padding:3px 10px; cursor:pointer; background:#c0392b; color:#fff; border:none; border-radius:3px; font-weight:bold;">Ver solo riesgo</button>
                <button id="ga-tr-addtop" type="button" style="margin-left:6px; padding:3px 10px; cursor:pointer; background:#28a745; color:#fff; border:none; border-radius:3px; font-weight:bold;">➕ Cruzar los de riesgo alto</button>
            </div>
            <div style="display:flex; gap:20px; flex-wrap:wrap;">
                <div style="flex:1; min-width:260px;">
                    <b>🔴 Top riesgo</b>
                    <ol style="margin:4px 0 0 18px; padding:0; font-size:12px;">
                        ${altos.slice(0, 10).map(d => `<li><b>${d.seller}</b> → <b>${d.buyer}</b> <span style="color:#c0392b; font-weight:bold;">(${d.score})</span><br><span style="color:#666; font-size:11px;">${d.motivos.join(' · ')}</span></li>`).join('') || '<li style="color:#666;">ninguna</li>'}
                    </ol>
                </div>
                <div style="flex:1; min-width:260px;">
                    <b>🕸 Hubs compradores</b> <span style="color:#777; font-size:11px;">(reciben de varios)</span>
                    <ul style="margin:4px 0 8px 18px; padding:0; font-size:12px;">${listaHubs(hubsC, 'compra a') || '<li style="color:#666;">ninguno</li>'}</ul>
                    <b>🕸 Hubs vendedores</b> <span style="color:#777; font-size:11px;">(reparten a varios)</span>
                    <ul style="margin:4px 0 8px 18px; padding:0; font-size:12px;">${listaHubs(hubsV, 'vende a') || '<li style="color:#666;">ninguno</li>'}</ul>
                    <b>🔄 Recíprocos</b> <span style="color:#777; font-size:11px;">(se venden en ambas direcciones)</span>
                    <ul style="margin:4px 0 0 18px; padding:0; font-size:12px;">
                        ${recip.slice(0, 6).map(d => `<li>${d.seller} ↔ ${d.buyer} <span style="color:#666;">(PS:${d.ps} PP:${d.pp})</span></li>`).join('') || '<li style="color:#666;">ninguno</li>'}
                    </ul>
                </div>
            </div>
        `;
        tabla.parentNode.insertBefore(panel, tabla);

        // Filtro: mostrar solo alto/medio
        let filtrado = false;
        document.getElementById('ga-tr-filtro').addEventListener('click', function () {
            filtrado = !filtrado;
            datos.forEach(d => {
                const ocultar = filtrado && d.nivel !== 'alto' && d.nivel !== 'medio';
                d.tr.style.display = ocultar ? 'none' : '';
            });
            this.textContent = filtrado ? 'Ver todas' : 'Ver solo riesgo';
        });

        // Cargar los de riesgo alto en Cruzar Usuarios
        document.getElementById('ga-tr-addtop').addEventListener('click', () => {
            const input = document.getElementById('ga-simple-cross-input');
            if (!input) { alert('El widget Cruzar Usuarios no está disponible en esta página.'); return; }
            const vals = [];
            altos.forEach(d => {
                [d.sellerId || d.seller, d.buyerId || d.buyer].forEach(v => { if (v) vals.push(v); });
            });
            const n = GA.cargarEnCruce(vals);
            alert(`Se agregaron ${n} usuario(s) de las transferencias de riesgo alto.`);
        });
    }
    GA.analizarTransferencias = analizarTransferencias;

})();