Tutoriales

Prueba de CaptchaAI antes de la migración completa: Guía de ejecución paralela

La forma más segura de saber si CaptchaAI encaja en tu stack es no decidirlo a ciegas: haz que resuelva los mismos CAPTCHA que tu proveedor actual, al mismo tiempo, y deja que los números decidan. Eso es una ejecución paralela. Envías cada desafío a ambos servicios en simultáneo y comparas, con tu propio tráfico, las tres cosas que importan: tasa de resolución, velocidad y costo. Así la migración deja de ser una apuesta y pasa a ser una decisión respaldada por datos.

Por qué una prueba paralela vale más que un benchmark de marketing

Las cifras de una página de producto no reflejan tus patrones de tráfico. Tus CAPTCHA tienen características propias: sitekeys concretos, configuración de proxy y distribución geográfica de tus usuarios. Un benchmark genérico no ve nada de eso.

Una ejecución paralela sí. Al resolver el mismo desafío con ambos proveedores a la vez, aíslas la única variable que te interesa —el proveedor— y dejas fija la carga real. Así afloran diferencias que un promedio publicitario oculta: que un servicio sea rápido en horas valle pero se degrade en tu pico de la tarde.

Qué vas a medir

Antes de escribir código, ten claras las métricas. Son las que separan una decisión informada de una corazonada:

Métrica Cómo medirla
Tasa de éxito successful_solves / total_attempts × 100
Tiempo medio de resolución Desde el envío hasta recibir la solución
Tiempo de resolución P95 Percentil 95 de los tiempos de resolución
Tasa de error por tipo Cuenta cada código de error por separado
Costo por resolución Gasto total dividido entre las soluciones válidas
Validez del token ¿El token devuelto funciona realmente en el sitio de destino?

La última fila es la más olvidada: un token puede llegar rápido y barato y aun así ser rechazado por el sitio de destino. Un token que no pasa la verificación es un fallo, aunque la API haya respondido status: 1.

La arquitectura de una ejecución paralela

El montaje es sencillo: un enrutador delante de ambos proveedores y un recolector de métricas detrás.

                    ┌──────────────┐
                    │ Your App     │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │ CAPTCHA      │
                    │ Router       │
                    └──┬───────┬───┘
                       │       │
              ┌────────▼──┐ ┌──▼────────┐
              │ Current   │ │ CaptchaAI │
              │ Provider  │ │           │
              └────────┬──┘ └──┬────────┘
                       │       │
                    ┌──▼───────▼──┐
                    │ Metrics     │
                    │ Collector   │
                    └─────────────┘

Implementación en Python

Una capa común para ambos proveedores

El primer paso es una abstracción que hable con cualquier proveedor de estilo in.php / res.php. Como CaptchaAI mantiene ese patrón, la misma clase sirve para tu proveedor actual y para el retador: solo cambian la URL y la API key.

import os
import time
import requests
from dataclasses import dataclass, field
from typing import Optional
from concurrent.futures import ThreadPoolExecutor


@dataclass
class SolveResult:
    provider: str
    success: bool
    solution: Optional[str] = None
    error: Optional[str] = None
    elapsed: float = 0.0
    cost: float = 0.0


class CaptchaProvider:
    def __init__(self, name, submit_url, result_url, api_key):
        self.name = name
        self.submit_url = submit_url
        self.result_url = result_url
        self.api_key = api_key
        self.session = requests.Session()

    def solve_recaptcha(self, sitekey, pageurl):
        start = time.time()

        resp = self.session.post(self.submit_url, data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": 1
        })
        data = resp.json()
        if data.get("status") != 1:
            return SolveResult(
                provider=self.name, success=False,
                error=data.get("request"), elapsed=time.time() - start
            )

        captcha_id = data["request"]

        for _ in range(60):
            time.sleep(5)
            result = self.session.get(self.result_url, params={
                "key": self.api_key, "action": "get",
                "id": captcha_id, "json": 1
            }).json()

            if result.get("status") == 1:
                return SolveResult(
                    provider=self.name, success=True,
                    solution=result["request"], elapsed=time.time() - start
                )
            if result.get("request") != "CAPCHA_NOT_READY":
                return SolveResult(
                    provider=self.name, success=False,
                    error=result.get("request"), elapsed=time.time() - start
                )

        return SolveResult(
            provider=self.name, success=False,
            error="TIMEOUT", elapsed=time.time() - start
        )

El ejecutor de pruebas en paralelo

Con la capa común lista, lanzas ambos proveedores a la vez con un ThreadPoolExecutor. Al final, el ejecutor genera un informe con tasa de éxito, tiempos y errores por cada lado.

class ParallelTestRunner:
    def __init__(self, primary, challenger):
        self.primary = primary
        self.challenger = challenger
        self.results = {"primary": [], "challenger": []}

    def run_test(self, sitekey, pageurl, num_runs=20):
        print(f"Running {num_runs} parallel solves...")

        for i in range(num_runs):
            with ThreadPoolExecutor(max_workers=2) as executor:
                primary_future = executor.submit(
                    self.primary.solve_recaptcha, sitekey, pageurl
                )
                challenger_future = executor.submit(
                    self.challenger.solve_recaptcha, sitekey, pageurl
                )

                primary_result = primary_future.result()
                challenger_result = challenger_future.result()

            self.results["primary"].append(primary_result)
            self.results["challenger"].append(challenger_result)

            print(f"  Run {i+1}/{num_runs}: "
                  f"{self.primary.name}={'OK' if primary_result.success else 'FAIL'} "
                  f"({primary_result.elapsed:.1f}s) | "
                  f"{self.challenger.name}={'OK' if challenger_result.success else 'FAIL'} "
                  f"({challenger_result.elapsed:.1f}s)")

        return self.generate_report()

    def generate_report(self):
        report = {}
        for label, results in self.results.items():
            total = len(results)
            successes = sum(1 for r in results if r.success)
            times = [r.elapsed for r in results if r.success]
            errors = [r.error for r in results if not r.success]

            report[label] = {
                "provider": results[0].provider if results else "unknown",
                "total": total,
                "successes": successes,
                "success_rate": (successes / total * 100) if total else 0,
                "avg_time": sum(times) / len(times) if times else 0,
                "min_time": min(times) if times else 0,
                "max_time": max(times) if times else 0,
                "errors": errors
            }

        return report


# Usage
current = CaptchaProvider(
    name="CurrentProvider",
    submit_url="https://current-provider.com/in.php",
    result_url="https://current-provider.com/res.php",
    api_key="current_key"
)

captchaai = CaptchaProvider(
    name="CaptchaAI",
    submit_url="https://ocr.captchaai.com/in.php",
    result_url="https://ocr.captchaai.com/res.php",
    api_key=os.environ["CAPTCHAAI_API_KEY"]
)

runner = ParallelTestRunner(primary=current, challenger=captchaai)
report = runner.run_test(
    sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    pageurl="https://example.com/form",
    num_runs=20
)

for label, stats in report.items():
    print(f"\n{stats['provider']}:")
    print(f"  Success rate: {stats['success_rate']:.1f}%")
    print(f"  Avg time: {stats['avg_time']:.1f}s")
    print(f"  Min/Max: {stats['min_time']:.1f}s / {stats['max_time']:.1f}s")
    if stats['errors']:
        print(f"  Errors: {stats['errors']}")

Enrutar un porcentaje del tráfico real

Cuando la validación te convenza, manda una fracción del tráfico de producción a CaptchaAI con vuelta atrás automática si algo falla. Empieza por un 10 % y súbelo a medida que ganes confianza.

import random


class TrafficSplitter:
    def __init__(self, primary, challenger, challenger_pct=10):
        self.primary = primary
        self.challenger = challenger
        self.challenger_pct = challenger_pct

    def solve(self, sitekey, pageurl):
        if random.randint(1, 100) <= self.challenger_pct:
            result = self.challenger.solve_recaptcha(sitekey, pageurl)
            if not result.success:
                # Fall back to primary on failure
                return self.primary.solve_recaptcha(sitekey, pageurl)
            return result
        return self.primary.solve_recaptcha(sitekey, pageurl)


# Start with 10%, increase as confidence builds
splitter = TrafficSplitter(current, captchaai, challenger_pct=10)
result = splitter.solve(sitekey="...", pageurl="...")

Implementación en JavaScript / Node.js

Si tu stack es Node.js, la misma lógica se traslada con axios y Promise.all:

const axios = require("axios");

class CaptchaProvider {
  constructor(name, submitUrl, resultUrl, apiKey) {
    this.name = name;
    this.submitUrl = submitUrl;
    this.resultUrl = resultUrl;
    this.apiKey = apiKey;
  }

  async solveRecaptcha(sitekey, pageurl) {
    const start = Date.now();
    try {
      const submit = await axios.post(this.submitUrl, null, {
        params: { key: this.apiKey, method: "userrecaptcha", googlekey: sitekey, pageurl, json: 1 },
      });
      if (submit.data.status !== 1) {
        return { provider: this.name, success: false, error: submit.data.request, elapsed: (Date.now() - start) / 1000 };
      }

      const captchaId = submit.data.request;
      for (let i = 0; i < 60; i++) {
        await new Promise((r) => setTimeout(r, 5000));
        const poll = await axios.get(this.resultUrl, {
          params: { key: this.apiKey, action: "get", id: captchaId, json: 1 },
        });
        if (poll.data.status === 1) {
          return { provider: this.name, success: true, solution: poll.data.request, elapsed: (Date.now() - start) / 1000 };
        }
        if (poll.data.request !== "CAPCHA_NOT_READY") {
          return { provider: this.name, success: false, error: poll.data.request, elapsed: (Date.now() - start) / 1000 };
        }
      }
      return { provider: this.name, success: false, error: "TIMEOUT", elapsed: (Date.now() - start) / 1000 };
    } catch (err) {
      return { provider: this.name, success: false, error: err.message, elapsed: (Date.now() - start) / 1000 };
    }
  }
}

async function parallelTest(current, captchaai, sitekey, pageurl, runs = 20) {
  const results = { current: [], captchaai: [] };

  for (let i = 0; i < runs; i++) {
    const [currentResult, captchaaiResult] = await Promise.all([
      current.solveRecaptcha(sitekey, pageurl),
      captchaai.solveRecaptcha(sitekey, pageurl),
    ]);

    results.current.push(currentResult);
    results.captchaai.push(captchaaiResult);

    console.log(`Run ${i + 1}/${runs}: ${current.name}=${currentResult.success ? "OK" : "FAIL"} ` +
      `(${currentResult.elapsed.toFixed(1)}s) | ${captchaai.name}=${captchaaiResult.success ? "OK" : "FAIL"} ` +
      `(${captchaaiResult.elapsed.toFixed(1)}s)`);
  }

  for (const [label, data] of Object.entries(results)) {
    const successes = data.filter((r) => r.success).length;
    const times = data.filter((r) => r.success).map((r) => r.elapsed);
    const avgTime = times.length ? times.reduce((a, b) => a + b, 0) / times.length : 0;
    console.log(`\n${label}: ${successes}/${runs} success (${((successes / runs) * 100).toFixed(1)}%), avg ${avgTime.toFixed(1)}s`);
  }
}

// Run
const currentProvider = new CaptchaProvider("CurrentProvider", "https://current-provider.com/in.php", "https://current-provider.com/res.php", "current_key");
const captchaai = new CaptchaProvider("CaptchaAI", "https://ocr.captchaai.com/in.php", "https://ocr.captchaai.com/res.php", process.env.CAPTCHAAI_API_KEY);

parallelTest(currentProvider, captchaai, "SITE_KEY", "https://example.com", 20);

Un plan de migración por fases

No pases del 0 al 100 de golpe. Este calendario reduce el riesgo y deja un punto de retorno en cada etapa:

Fase Duración División de tráfico Objetivo
1. Validación 1 día 0 % en vivo, solo paralelo Verificar la compatibilidad de la API
2. Prueba en sombra 3 días 5 % a CaptchaAI (con respaldo) Recopilar métricas de referencia
3. Aumento gradual 1 semana 25 % → 50 % → 75 % Monitorear en cada nivel
4. Transición completa 100 % CaptchaAI Dar de baja al proveedor anterior

Un detalle para presupuestar: CaptchaAI factura por thread concurrente, no por resolución, y cada plan incluye resoluciones ilimitadas por thread. Para un equipo en España o Latinoamérica que automatiza la QA de un portal de cita previa y factura en USD, la prueba es un costo mensual fijo, no una factura que crece con cada CAPTCHA resuelto.

Problemas frecuentes en una prueba paralela

Problema Causa Solución
CaptchaAI parece más lento en la prueba Diferencias de latencia de red Prueba desde servidores de producción, no desde tu máquina local
Tasas de éxito distintas Los pools de resolución de cada proveedor varían Ejecuta más de 50 resoluciones para tener significación estadística
Un token funciona con un proveedor pero no con el otro El token caducó antes de usarse Usa los tokens de inmediato, en cuanto los recibas
La prueba paralela duplica el costo Resuelves el mismo CAPTCHA dos veces Presupuesta el período de prueba; sale más barato que una migración fallida

Preguntas frecuentes

¿Cuánto cuesta mantener dos proveedores durante la prueba?

Pagas ambas suscripciones a la vez, pero el peso depende del modelo. Con un proveedor por resolución, el gasto sube con cada CAPTCHA de más. Con CaptchaAI, al ser thread-based con resoluciones ilimitadas, el retador añade un costo mensual fijo que no crece por probar más. El período es corto y sale más barato que una migración fallida.

¿Qué plan de CaptchaAI conviene para una prueba paralela?

Para el volumen típico, BASIC ($15/mes, 5 threads) o STANDARD ($30/mes, 15 threads) cubren de sobra. Cada thread atiende un CAPTCHA en vuelo y queda libre al terminar; si disparas mucha concurrencia, sube de tier.

¿Cómo evito afectar a mis usuarios reales al probar en producción?

Usa la división de tráfico con respaldo automático: envías un pequeño porcentaje (empieza en el 5–10 %) a CaptchaAI y, si una resolución falla, vuelves al proveedor actual en el mismo request. El usuario no nota nada y tú recoges métricas reales.

¿Cuántas resoluciones necesito para fiarme de los números?

Un mínimo de 50 por proveedor, y mejor más de 100 repartidas en distintas franjas horarias. La carga varía durante el día; una muestra pequeña en una sola hora da una foto engañosa de la tasa de éxito y los tiempos.

¿Y si mi tráfico incluye tipos que CaptchaAI no resuelve, como hCaptcha?

CaptchaAI no resuelve hCaptcha ni FunCaptcha, así que esos desafíos no migran: mantenlos en tu proveedor actual y mueve solo lo que sí cubre (reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3, CAPTCHA de imagen y texto). Ejecuta pruebas por tipo y pondera según tu tráfico real.

Artículos relacionados

Próximos pasos

Lanza una prueba paralela sin riesgos: crea tu cuenta en CaptchaAI y compara el rendimiento con tu propio tráfico.

Guías relacionadas:

Los comentarios están deshabilitados para este artículo.