12 min

Auditar código con un LLM local: Qwen3 Coder Next, aciertos y fallos

Cómo usar un LLM local como Qwen3 Coder Next para auditar tu código base: ahorra un 85 % de tokens frente a Opus y filtra sus errores con confianza en la fase de QA.

Ejecuto una copia local de Qwen3 Coder Next en una máquina bajo mi mesa. Acorraló una race condition en mi código de producción que se me había pasado. También me dijo, con total confianza, que crypto.randomUUID() no funciona en Cloudflare Workers. Sí funciona. Esa tensión —bugs reales mezclados con tonterías dichas con confianza— es lo que hace que la revisión de código con un LLM local sea interesante, pero difícil y, si sabes trabajar con ella, útil de cara a ahorrar tokens.


El argumento a favor de un colega más barato

Llevo construyendo ContentMarketingIdeas.co —un SaaS de copiloto editorial— íntegramente con Claude Code. Opus escribe el código, ejecuta los tests, despliega los workers. Brilla en ese trabajo. Pero cada archivo que lee cuesta tokens de entrada, y esos tokens se acumulan rápido cuando escaneas ocho workers con miles de líneas cada uno.

Así que empecé a preguntarme: ¿podría conseguir que algo local hiciera el escaneo inicial? No para reemplazar a Opus. Más bien como un primer par de ojos: marcar lo obvio, cazar problemas estructurales, detectar cosas por las que había pasado porque estoy demasiado cerca del código. Así Opus solo necesita leer el resumen de 1.000 tokens de Qwen en lugar del archivo completo de 3.500 a 10.000 tokens. Eso es aproximadamente un 85 % menos de tokens de entrada. Nada mal.

La máquina que ejecuta el modelo local se llama hopper. Un escritorio con unos decentes 104 GB de VRAM de GPU, y está sentada obedientemente junto a mi estación de trabajo principal. LM Studio sirve el modelo a través de una API HTTP compatible con OpenAI en http://hopper:1234, y Claude Code habla con él a través de converly-lm, un servidor MCP que construí específicamente para este tipo de delegación.

Qué probé

Ocho archivos de producción de mi proyecto. Código real de Cloudflare Workers: rutas de API, consumidores de cola, pipelines de enriquecimiento, producción de artículos. No ejemplos de juguete. 67 rutas de API, 8 workers, base de datos D1, Queues, Vectorize, unos 30 MCP. El tipo de código base donde los bugs se esconden en las juntas entre servicios.

Un archivo cada vez. System prompt sencillo: «Eres un revisor senior de código TypeScript. Revisa el siguiente código de Cloudflare Worker en busca de bugs, problemas de seguridad, problemas de rendimiento y calidad de código». El archivo completo en el mensaje de usuario. Temperatura 0,2. Sin instrucciones especiales, sin contexto del proyecto, sin CLAUDE.md inyectado. Solo el código y un rol.

Luego hice QA de cada hallazgo con Opus. De acuerdo, en desacuerdo, parcialmente de acuerdo, con razonamiento para cada uno. 74 hallazgos en 8 archivos, cada uno con un veredicto graduado. Ese es el conjunto de datos.

Los números

54 hallazgos en 6 archivos por parte de Coder Next. De esos, de 14 a 17 eran accionables: una tasa de acierto del 26 al 31 por ciento. Razonable. La mayoría de herramientas de análisis estático se mueven en ese rango de relación señal-ruido (ten en cuenta que este es código ya escrito por Opus 4.6).

Y luego pasé 2 archivos por Qwen3 Coder 30B-A3B-Instruct, un modelo distinto con cuantización agresiva de 3 bits. Mismo prompt. Mismos archivos. 20 hallazgos, del 0 al 15 por ciento accionables. Más cerca de ser solo ruido.

MétricaCoder NextCoder 30B-A3B
Archivos revisados62
Hallazgos totales5420
Tasa de acierto accionable26-31 %0-15 %
Tasa de errores fácticos5,6 %10 %
Tiempo medio de respuesta~55 s~76 s
Mejor hallazgoRace condition en Promise.allNinguno accionable

La cuantización lo mató. Coder 30B-A3B producía hallazgos genéricos de manual: especulaciones de «podría llevar a», marcando claves de API «pasadas sin validación» (carece de sentido para los secretos de wrangler) y, en un momento, admitiendo explícitamente que estaba alucinando: «No se muestra directamente... pero se asume». Me reí con ese. La calidad del modelo importa más que el tamaño del modelo para la revisión de código. La cuantización agresiva elimina justo el matiz que necesitas: la capacidad de distinguir una decisión de diseño deliberada de un bug real.

Qué encontró (lo bueno)

Lo destacado: una race condition en mi delivery worker. Tenía un contador que seguía las imágenes de cabecera generadas dentro de un map con Promise.all. Múltiples callbacks concurrentes leyendo e incrementando la misma variable. Un bug clásico de mutación asíncrona: podría superar el límite de imágenes por entrega. Había mirado ese código decenas de veces y nunca lo había notado. Qwen lo cazó en 50 segundos.

También cazó: consultas duplicadas a la base de datos en mi enrichment worker (consultando user_sites dos veces para los mismos datos cuando bastaría con un Set en caché), llamadas a JSON.parse sin protección en la síntesis que podían tirar el worker con datos corruptos, y un vector XSS en mi conversor de markdown a HTML. Ese último era un agujero de seguridad feo: no estaba escapando el HTML antes de aplicar el formato con regex, de modo que las URLs de los enlaces podían inyectar scripts.

Cinco arreglos aplicados al código base real a partir de esta auditoría. Bugs reales, parches reales, commits reales.

En qué se equivocó (las tonterías dichas con confianza)

Tres errores fácticos en 54 hallazgos: una tasa de error fáctico del 5,6 %. Coder Next me dijo que crypto.randomUUID() no está disponible en Cloudflare Workers. Lo está: es una API estándar de Web Crypto. Afirmó que btoa() no existe en Workers. Existe: los Workers implementan Web APIs, no APIs de Node.js. Y marcó mi patrón de consulta parametrizada de D1 como inyección SQL, cuando map(() => '?').join(',') con .bind(...ids) es literalmente el enfoque correcto para D1.

El patrón es predecible: Qwen no sabe para qué runtime está revisando el código. Asume convenciones de Node.js y marca cualquier cosa que no encaje. Una vez conoces este punto ciego, el QA se vuelve rápido: aprendes a ojear los hallazgos de conocimiento del runtime y a saltarlos. Pero si hubieras confiado a ciegas en las etiquetas de severidad «Critical», habrías perdido tiempo «arreglando» cosas que no estaban rotas.

La otra debilidad constante: la inflación de la severidad de seguridad. Qwen marcaba cosas como Critical que en realidad eran de bajo riesgo en contexto. ¿Nombres de columna SQL dinámicos a partir de un esquema validado con Zod? No es inyección. ¿UUIDs generados por el sistema a partir de una consulta previa a la base de datos? No es entrada controlada por el usuario. No sabe distinguir los niveles de confianza, así que todo parece peligroso.

Plantillas de prompt que dieron los mejores resultados

Muerto de simple. Sin conjuntos de instrucciones elaborados, sin ejemplos few-shot, sin andamiaje de chain-of-thought. Dos mensajes: un rol de sistema y el archivo completo.

Para auditoría:

System: "You are a senior TypeScript code reviewer. Review the following Cloudflare Worker code for bugs, security issues, performance problems, and code quality."
User: [full file content]
Temperature: 0.2

Para arreglos:

System: "You are a code fixer for TypeScript Cloudflare Workers. Given specific code and the issue, produce ONLY the corrected code. No explanations."
User: [exact code block] + [issue description] + [existing pattern to follow]
Temperature: 0.1

El prompt de arreglo incluía un patrón existente del mismo archivo: «sigue este patrón de try/catch + console.warn que ya se usa en la línea 166». Esa pista de pattern-matching marcó la diferencia entre una nota de 95 % (los guardes de JSON parse) y una de 50 % (el arreglo de la consulta duplicada, donde no se proporcionó ningún patrón y Qwen alucinó la API de D1 entera).

Todo con los ajustes por defecto de LM Studio. Sin sampling personalizado, sin ajuste de repeat penalty, sin ajustes de ventana de contexto. Descargué el modelo, lo cargué, empecé a enviar peticiones HTTP. Que funcionase tan bien con los valores por defecto es parte del mensaje: no necesitas ser un ingeniero de prompts ni un friki de la cuantización para sacar resultados útiles de esto.

¿Puede arreglar lo que encuentra?

Hasta cierto punto. Pasé los mismos 5 prompts de arreglo por ambos modelos: entradas idénticas, salidas graduadas. Combate justo.

ArregloComplejidadCoder NextCoder 30BGanador
Consulta duplicada a la BDMedia50 %40 %Coder Next
Arreglo de XSSMedia70 %65 %Coder Next
try/catch de descifradoBaja85 %70 %Coder Next
Guardes de JSON parseBaja95 %80 %Coder Next
URLs hardcodedBaja75 %75 %Empate

Coder Next ganó todos los arreglos excepto el empate en las URLs hardcoded. Además fue 1,9 veces más rápido de media (10 segundos frente a 19 segundos por arreglo). Pero la verdadera conclusión es el gradiente de complejidad: los arreglos mecánicos de baja complejidad puntuaron del 75 al 95 por ciento. Los arreglos de complejidad media que requerían entender la API de D1 o los patrones de respuesta de Hono puntuaron del 50 al 70 por ciento, con bugs que rompían producción en cada salida.

Ambos modelos alucinaron sintaxis de la API de D1. Coder Next inventó for await (const row of db.query(...)). Coder 30B inventó db.select(...). Ninguno es un método real de D1. Ambos usaron new Response(...) en lugar del c.json() de Hono para las respuestas de error. El arreglo de descifrado de Coder 30B tenía un error de ámbito que no compilaría: appPassword declarado dentro de un bloque try pero usado fuera de él.

Solo uno de cada cinco arreglos estaba listo para producción sin edición manual: los guardes de JSON parse. Ese puntúo 95 % porque es puramente mecánico: copiar el patrón try/catch existente, envolver las llamadas sin protección, añadir un console.warn. No requiere conocimiento de la API.

La regla práctica: si el arreglo es «añade una cláusula guard siguiendo este patrón existente», Qwen puede hacerlo. Si el arreglo requiere entender la superficie de la API del framework, hazlo tú mismo o pásaselo a Opus.

La economía de tokens

Esto es lo que ahorró de verdad el modelo de delegación en todo el experimento:

MétricaValor
Tokens de archivo enviados a Qwen (auditoría)~60.500
Tokens de archivo enviados a Qwen (arreglos)~8.000
Tokens de salida de Qwen~10.500
Entrada de Opus para QA (resúmenes de Qwen)~10.500
Entrada de Opus evitada (archivos en crudo)~60.500
Ahorro de tokens de entrada~85 %
Coste de Opus ahorrado~0,91 $
Coste de Qwen0 $
Tiempo total (todos los archivos + arreglos)~15 minutos

0,91 $ ahorrados. No es exactamente para jubilarse. Pero esto fueron 8 archivos; una revisión completa de un código base de 40 archivos al mismo ritmo ahorraría aproximadamente 4,50 $ por pasada, y si ejecutas auditorías cada semana o antes de cada despliegue importante, eso se acumula. Sinceramente, el mayor valor no son los dólares. Es la perspectiva nueva. Qwen encontró cosas por las que yo había pasado porque no comparte mi modelo mental del código. No sabe lo que yo pretendía, así que lo cuestiona todo. A veces es molesto (¡deja de decirme que console.log es malo en Workers!), a veces es justo lo que necesitas.

Qué mejoraría esto

Contexto. Ese es el mayor hueco. Qwen no conoce el CLAUDE.md de mi proyecto, el documento que registra cada decisión deliberada, cada trampa, cada trade-off de «elegimos esto a propósito». La mitad de los falsos positivos desaparecerían si el modelo tuviera ese contexto. La otra mitad son huecos de conocimiento del runtime.

Lo que de verdad movería la aguja:

Conciencia de la API del runtime. Un breve anexo al system prompt: «Este código se ejecuta en Cloudflare Workers. Las Web APIs (crypto.randomUUID, btoa, fetch, URL, TextEncoder) están disponibles. Las APIs de Node.js (Buffer, fs, path) no». Solo con eso se eliminarían los errores fácticos más bochornosos.

Pistas sobre el nivel de confianza. Algo como: «Las variables llamadas userId, briefId, siteId son UUIDs generados por el sistema a partir de consultas previas a la base de datos, no entrada controlada por el usuario». Esto reduciría a la mitad la inflación de la severidad de seguridad.

Inyección del registro de decisiones. Introducir la sección relevante del CLAUDE.md en el system prompt de cada archivo. «maxOutputTokens es 8192 a propósito (optimización de coste de la sesión 13)» evitaría que el modelo lo marcase como un riesgo.

El trade-off es la ventana de contexto: cada adición come espacio para el código real. ¿Pero un preámbulo de 500 tokens que evite 10 falsos positivos por archivo? Obviamente merece la pena. Aún no lo he probado. Es lo siguiente.

Errores que cometí por el camino

1. Confiar en las etiquetas de severidad. Qwen marca cosas como «Critical» que son de bajo riesgo en contexto. El flag de inyección SQL sobre una consulta parametrizada. El de «falta la crypto API» sobre una Web API estándar. Aprendí a leer cada hallazgo Critical con escepticismo y a verificar contra la documentación real del runtime antes de actuar.

2. Enviar fragmentos de código en lugar de archivos completos. Los experimentos iniciales con código parcial dieron peores resultados. El modelo necesita ver los imports, las definiciones de tipos y el contexto circundante para hacer buenos juicios. Archivos completos, de uno en uno.

3. Esperar arreglos de un auditor. La detección y la remediación son habilidades distintas. Qwen es genuinamente bueno detectando problemas estructurales: consultas duplicadas, falta de manejo de errores, race conditions. Pero pedirle que escriba el código corregido es una tarea distinta con una tasa de éxito mucho menor, sobre todo para cualquier cosa que vaya más allá de un pattern matching sencillo.

4. Comparar modelos por volumen en lugar de por calidad. Coder 30B producía 10 hallazgos por archivo, lo mismo que Coder Next. La diferencia estaba totalmente en la calidad: del 0 al 15 por ciento accionable frente al 26 al 31 por ciento. Más hallazgos no significa mejor revisión.

5. No guardar los prompts en crudo. Casi no guardo los archivos JSON con los prompts y respuestas exactos. Tener esos artefactos reproducibles es lo que hizo posible la comparación directa. Guarda todo.


Así que el modelo de delegación funciona. Modelo local para detección, modelo frontera para QA, tu propio juicio para cualquier cosa que vaya más allá de arreglos de complejidad sencilla. El QA es innegociable: una tasa de error fáctico del 6,8 % significa que no puedes saltártelo. ¿Pero un 85 % de ahorro de tokens y una race condition real cazada en la primera pasada? Acepto ese trato siempre.

Artículos relacionados