IA Local como Multiplicador de Productividad

Guía estratégica de Ollama + OpenCode CLI para desarrolladores profesionales

🎯 TypeScript · React · NestJS · Node.js ⚡ RTX 3090 · 24–36 GB VRAM · Modelos ~30B 📖 ~10 000 palabras · 9 secciones

Índice

1 Introducción

Estás leyendo esta guía porque ya sabes lo básico: has instalado Ollama, has probado algunos modelos y has visto que la IA local funciona. Ahora necesitas que funcione para ti, como parte integral de tu flujo de trabajo diario, no como un juguete de fin de semana.

El desarrollo con IA local ha alcanzado un punto de inflexión. Con una RTX 3090 (24 GB VRAM) —o dos en paralelo— puedes ejecutar modelos de ~30 000 millones de parámetros cuantizados a 4 o 5 bits, obtener entre 20 y 40 tokens por segundo, y mantener en contexto hasta 16 384 tokens de tu código. Esto no es una promesa de futuro: es lo que funciona hoy.

✅ Por qué IA local, no cloud

Privacidad: tu código fuente nunca abandona tu máquina. Sin cláusulas de uso para entrenar modelos ajenos, sin filtraciones en servidores compartidos.
Latencia: cero round-trips de red. El primer token llega en milisegundos, no segundos.
Coste: $0 por token. Una RTX 3090 cuesta ~700 € y te da años de inferencia sin facturas recurrentes.
Soberanía: cuando internet falla, tu asistente sigue funcionando. Cuando el proveedor cambia su API, tu flujo no se rompe.

Lo que lograrás con esta guía

Hardware objetivo y límites reales

Esta guía está escrita pensando en el siguiente perfil de hardware:

Estas son las velocidades reales que puedes esperar con una RTX 3090 en un modelo de ~30B Q4_K_M:

Estos números no son teóricos. Son el piso que obtienes con una configuración bien ajustada. En secciones posteriores veremos cómo medirlos y optimizarlos.

2 Fundamentos de Ollama y modelos ~30B

2.1 Arquitectura de Ollama

Ollama es un servidor de modelos con API REST, no un CLI monolítico. Esto tiene implicaciones estratégicas importantes para tu flujo de trabajo:

2.2 Modelos de código para ~30B: comparativa

No todos los modelos de ~30B son iguales. La siguiente tabla compara los principales modelos de código en ese rango de parámetros, con sus fortalezas y debilidades específicas:

ModeloParámetrosVentaja claveDebilidadMejor para
CodeLlama-34B-Instruct34BBase sólida, fine-tuning estableMenor rendimiento en TypeScript que los especializadosPython, C++, refactorización general
DeepSeek-Coder-33B-Instruct33BEntrenado con 2T tokens de código, fill-in-middle nativoMayor VRAM que CodeLlama en ctx largoCompletado de código, TypeScript, Python
Phind-CodeLlama-34B-v234BFine-tuning con datos de búsqueda web, mejor en APIs modernasOverfit a prácticas de Python/JSDocumentación, explicación, código boilerplate
WizardCoder-33B-V1.133BEvol instruccionado, bueno siguiendo instrucciones complejasTiende a generar código verbosoRefactorización guiada, migraciones
Mixtral-8x7B-Instruct46.7B (MoE, activa ~12B)Velocidad de 12B con calidad de 46B (MoE)Mayor VRAM total, menor densidad de conocimiento de códigoRazonamiento general, depuración

2.3 Cuantizaciones y consumo de VRAM

La cuantización es la técnica que permite ejecutar modelos grandes en GPUs de consumo. Cuanto menor el número de bits por peso, menos VRAM, pero menor calidad. La tabla muestra el consumo estimado para un modelo de 34B con diferentes cuantizaciones y contextos:

CuantizaciónPesos (34B)+ KV cache 4K+ KV cache 16K+ KV cache 32KVelocidad (RTX 3090)
Q4_K_M~19.2 GB~20.1 GB~22.8 GB~28.1 GB~30 t/s
Q5_K_M~21.5 GB~22.4 GB~25.1 GB~30.4 GB~27 t/s
Q6_K~23.8 GB~24.7 GB~27.4 GB~32.7 GB~24 t/s
Q8_0~28.0 GB~28.9 GB~31.6 GB~18 t/s

2.4 Estimación precisa de memoria

Para calcular si un modelo cabe en tu GPU, usa esta fórmula:

# VRAM total ≈ tamaño_modelo + overhead_ollama + KV_cache
# tamaño_modelo (GB) = params × bits_por_param / 8 / 1e9
# Ejemplo: 34B Q4_K_M ≈ 34e9 × 4.5 / 8 / 1e9 ≈ 19.1 GB
# KV_cache (GB) = 2 × num_layers × ctx_length × dim_head × n_heads × 2 bytes (FP16)
# Para CodeLlama-34B: 2 × 48 × ctx × 128 × 64 × 2 bytes
# ≈ ctx × 0.00157 GB (ej: 16384 ctx → ~1.8 GB)
# Ollama overhead: ~0.5–1.5 GB adicionales

# Regla práctica: 34B Q4_K_M con 16K ctx → ~22.8 GB total
# Esto cabe justo en una RTX 3090 de 24 GB, pero sin margen para otras apps.
⚠️ Cuidado con el mito de los "24 GB disponibles"

Una RTX 3090 reporta 24 486 MiB de VRAM total. Sin embargo, el sistema operativo (X11/Wayland, compositor, navegador con aceleración GPU) puede consumir 200–500 MiB. Ollama añade ~500 MiB de overhead interno. Tu margen real usable es de ~23.5 GB, no 24. Para un modelo de 34B Q4_K_M con 16K de contexto, estás en ~22.8 GB — entra, pero justo. No ejecutes Chrome con aceleración GPU mientras infieres.

2.5 Profundidad técnica: el KV cache y por qué importa

El KV cache es la memoria que almacena las representaciones intermedias (Key y Value) de cada token en el contexto. Crece linealmente con la longitud del contexto y es la razón principal por la que no puedes simplemente poner num_ctx: 131072 en un modelo de 34B en una GPU de consumo.

Cada capa de atención almacena dos matrices (K y V) de tamaño [batch_size, n_heads, seq_len, dim_per_head] en FP16 (2 bytes por elemento). Para un modelo de 34B como CodeLlama:

Esto se suma al tamaño de los pesos. Por eso un modelo de 34B Q4_K_M con 32K de contexto necesita ~28 GB totales — no cabe en una 3090 de 24 GB.

3 Configuración avanzada de Ollama

3.1 El Modelfile como herramienta de ajuste fino

El Modelfile te permite modificar parámetros de inferencia sin re-cuantizar el modelo. Es tu principal palanca de configuración.

Parámetros críticos para contexto largo

ParámetroFunciónImpacto en VRAMImpacto en calidad
num_ctxTamaño máximo de contexto↑ Lineal con la longitudNinguno directo
rope_frequency_baseFrecuencia base de RoPENingunoAjusta precisión posicional
rope_scalingEscalado lineal/dinámico de RoPENingunoPermite extrapolar contexto
num_gpu_layersCapas en GPU (vs CPU)↓ Menos capas = menos VRAM↓ Más CPU = más lento
num_batchTokens evaluados en paralelo↑ LigeramenteNinguno

3.2 Ejemplos prácticos de Modelfile

Escenario A: Contexto estándar (8 192 tokens) — Cómodo en 24 GB, uso diario:

FROM deepseek-coder-33b-instruct:Q4_K_M

# Configuración para 8K de contexto en RTX 3090 24GB
PARAMETER num_ctx 8192
PARAMETER num_batch 512
PARAMETER num_gpu_layers 48
PARAMETER temperature 0.2
PARAMETER top_p 0.95
PARAMETER repeat_penalty 1.1

# System prompt por defecto
SYSTEM """You are an expert TypeScript developer. Write clean, idiomatic code.
Prefer const over let, use explicit types, handle errors properly, and follow
SOLID principles. When generating code, first explain your approach concisely."""

VRAM estimada: ~20.5 GB | Velocidad: ~30 t/s | Uso: diálogo general, refactorización, generación de código.

Escenario B: Contexto largo (16 384 tokens) — Apretado en 24 GB, ideal para sesiones de depuración:

FROM deepseek-coder-33b-instruct:Q4_K_M

# 16K de contexto — ajusta RoPE para evitar degradación
PARAMETER num_ctx 16384
PARAMETER rope_frequency_base 1000000.0  # ↑ para mantener precisión posicional
PARAMETER rope_scaling linear:1.0
PARAMETER num_batch 256                   # Reduce para ahorrar VRAM
PARAMETER num_gpu_layers 48
PARAMETER temperature 0.1
PARAMETER top_p 0.95
PARAMETER repeat_penalty 1.05

SYSTEM """You are a code review assistant. Analyze code carefully before
suggesting changes. Focus on correctness, performance, and type safety."""

VRAM estimada: ~22.8 GB | Velocidad: ~26 t/s | Uso: revisión de PRs, depuración, análisis de archivos completos.

Escenario C: Contexto extremo (32 768 tokens) — Requiere dos GPUs o descargar capas a CPU:

FROM deepseek-coder-33b-instruct:Q4_K_M

# 32K requiere ~28 GB total. Necesitas 2 GPUs o dejar capas en CPU.
PARAMETER num_ctx 32768
PARAMETER rope_frequency_base 2000000.0
PARAMETER num_gpu_layers 32               # Solo 32 capas en GPU, 16 en CPU
PARAMETER num_batch 128
PARAMETER temperature 0.1

# ~8-12 t/s por el offload a CPU, pero permite contexto enorme.

VRAM estimada: ~23 GB en GPU + ~6 GB en RAM | Velocidad: ~10 t/s | Uso: análisis de codebases completos.

3.3 Variables de entorno estratégicas

VariablePropósitoEjemplo
OLLAMA_GPU_OVERHEADReserva de VRAM para el sistemaOLLAMA_GPU_OVERHEAD=512
OLLAMA_MAIN_GPUSelecciona qué GPU usarOLLAMA_MAIN_GPU=0
OLLAMA_KEEP_ALIVETiempo en VRAM tras último usoOLLAMA_KEEP_ALIVE=10m
OLLAMA_NUM_PARALLELSolicitudes simultáneasOLLAMA_NUM_PARALLEL=2
OLLAMA_MAX_LOADED_MODELSMáx. modelos en VRAM simultáneosOLLAMA_MAX_LOADED_MODELS=1

3.4 Monitoreo y diagnóstico

Script para monitorizar VRAM durante inferencia:

#!/usr/bin/env bash
# monitor.sh — watch VRAM usage while Ollama is running
DURATION=${1:-30}
INTERVAL=2
END=$((SECONDS + DURATION))

echo "Timestamp | GPU-Util | Mem-Used | Mem-Free | Temp"
echo "----------|----------|----------|----------|------"

while [ $SECONDS -lt $END ]; do
  nvidia-smi --query-gpu=timestamp,utilization.gpu,memory.used,memory.free,temperature.gpu \
             --format=csv,noheader,nounits \
  | while IFS=',' read ts util mem_used mem_free temp; do
      printf "%-10s| %-8s| %-8s| %-8s| %s\n" "${ts##* }" "${util}%" "${mem_used}MiB" "${mem_free}MiB" "${temp}°C"
  done
  sleep $INTERVAL
done

Script para probar degradación de atención a contextos largos:

#!/usr/bin/env node
// test-context-degradation.mjs
// Evalúa cómo se comporta el modelo al recuperar información
// situada al principio del contexto (recall test).
import { writeFileSync } from 'node:fs';

const BASE_URL = 'http://localhost:11434/api/generate';
const MODEL = 'deepseek-coder-33b:Q4_K_M';

async function testRecall(ctxLength) {
  const preamble = `El código secreto es ABC-${ctxLength}-XYZ. Repite esta línea ${ctxLength} veces para llenar contexto.`;
  const filler = Array.from({ length: ctxLength }, (_, i) => `Línea de relleno número ${i + 1}.`).join('\n');
  const prompt = `${preamble}\n${filler}\n\n¿Cuál es el código secreto?`;

  const response = await fetch(BASE_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: MODEL, prompt, stream: false,
      options: { num_ctx: ctxLength + 500, temperature: 0 }
    })
  });
  const { response: text, eval_duration, prompt_eval_count } = await response.json();
  return { correct: text.includes('ABC'), duration: eval_duration, prompt_tokens: prompt_eval_count };
}

for (const ctx of [1000, 4000, 8000, 12000, 16000]) {
  const result = await testRecall(ctx);
  console.log(`ctx=${ctx} correct=${result.correct} tokens=${result.prompt_tokens} duration=${(result.duration/1e9).toFixed(2)}s`);
}
⚠️ Degradación real en contextos largos

Incluso si tu GPU soporta 32K de contexto, los modelos de ~30B muestran degradación significativa en tareas de recuperación más allá de 16K tokens. Para trabajar con proyectos grandes, usa la técnica de chunking con resúmenes que describimos en la sección 5.

4 OpenCode CLI en profundidad

4.1 Instalación y configuración

OpenCode CLI (opencode.ai) es una herramienta de interfaz para LLMs diseñada específicamente para tareas de código. Se instala desde su repositorio oficial:

# Instalación vía npm (recomendada para desarrolladores Node.js)
npm install -g @opencode/cli

# Verificar instalación
opencode --version

# Configurar para apuntar a Ollama local
opencode config set provider ollama
opencode config set model deepseek-coder-33b-instruct:Q4_K_M
opencode config set base_url http://localhost:11434
opencode config set temperature 0.2

# Ver configuración activa
opencode config list
⚠️ Compatibilidad de versiones

OpenCode CLI está en evolución activa. Los comandos exactos pueden variar entre versiones. Los patrones de uso que describimos aquí (modo pipe, prompts desde fichero, integración con editor) son conceptualmente estables aunque la sintaxis exacta evolucione.

4.2 Comandos avanzados

Modo interactivo: sesión continuada con historial, ideal para depuración:

# Inicia sesión interactiva
opencode chat

# Con archivo de contexto inicial
opencode chat --file src/components/SearchBox.tsx

# Reiniciar sesión (limpiar historial)
opencode chat --reset

Modo pipe (procesamiento programático):

# Pregunta directa desde stdin
cat src/error.ts | opencode prompt "Explica este código y sugiere mejoras"

# Pipeline Unix: combinar herramientas
git diff --cached | opencode prompt "Genera un mensaje de commit descriptivo"

# Procesar salida de tests fallidos
npm test 2>&1 | opencode prompt "Diagnostica estos errores de test"

# Encadenar: extraer, procesar, reemplazar
grep -rn "any" src/ | head -50 | opencode prompt \
  "Convierte estos 'any' a tipos específicos. Devuelve solo diff."

Integración con VSCode Tasks:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "OpenCode: Explicar selección",
      "type": "shell",
      "command": "opencode prompt 'Explica este código TypeScript:' < '${file}'"
    },
    {
      "label": "OpenCode: Generar tests",
      "type": "shell",
      "command": "opencode prompt 'Genera tests unitarios (Jest) para este archivo:' < '${file}'"
    },
    {
      "label": "OpenCode: Revisar cambios",
      "command": "git diff HEAD -- \"${file}\" | opencode prompt 'Revisa estos cambios:'"
    }
  ]
}

4.3 Estrategias para sesiones largas

Las sesiones prolongadas de OpenCode CLI sufren el problema de la ventana de contexto. Estrategias profesionales:

  1. Reinicio programado: Cada N interacciones, ejecuta opencode chat --reset y proporciona un resumen de lo discutido.
  2. Prompt de resumen automático: Antes de resetear, pide al modelo que resuma la sesión.
# Comando al final de una sesión larga
opencode prompt "Resume esta conversación en 3-5 puntos clave.
Incluye: (1) decisiones de diseño tomadas, (2) archivos modificados,
(3) problemas pendientes." > ~/.opencode/summary-$(date +%Y%m%d).md
  1. Contexto diferencial: No envíes el archivo completo cada vez. Usa git diff o extrae solo las funciones relevantes.
  2. Memoria externa: Mantén un archivo CONTEXT.md con decisiones arquitectónicas. En lugar de re-explicar, referencia ese archivo.

5 Tipos de tareas y asignación estratégica

5.1 Matriz de tareas viables

No todas las tareas de codificación se benefician igual de la IA local. La clave está en conocer los límites y actuar en consecuencia.

TareaViabilidadContextoCalidadNotas
BoilerplateAlta~2KExcelenteCRUDs, DTOs, módulos NestJS
Tests unitariosAlta~4KBuenaMejor con función + tipo
Refactorización simpleAlta~4KBuenaExtraer, renombrar, tipar
Explicación de códigoAltaVariableExcelenteEl modelo explica mejor que crea
DocumentaciónAltaMedioBuenaJSDoc, Swagger, READMEs
Fill-in-middleAlta~2KVariableDeepSeek-Coder destaca aquí
Migración dependenciasMedia>8KMediaContexto se satura rápido
Depuración con trazasMedia>8KMedia-AltaLimita la traza a lo relevante
Análisis seguridadMediaMedioMediaSQLi, XSS básicos. No confíes
Refactorización complejaBajaMuy altoBajaPierde coherencia multi-archivo
Integraciones complejasBajaAltoBajaAlucina endpoints y configs

5.2 Técnica de "chunking" para proyectos grandes

  1. Genera un mapa: tree src/ -I node_modules --charset unicode | head -100
  2. Pide un resumen: "Dado este árbol, identifica los 5 archivos más relevantes para implementar X."
  3. Extrae firmas: Solo interfaces, tipos y exports con un script.
#!/usr/bin/env node
// extract-signatures.mjs
import { readFileSync } from 'node:fs';
import { globSync } from 'glob';

const files = globSync('src/**/*.ts');
for (const file of files) {
  const content = readFileSync(file, 'utf-8');
  const regex = /export (function|class|interface|type|const|enum) (\w+)/g;
  let match;
  while ((match = regex.exec(content)) !== null) {
    console.log(`${file}: ${match[0]}`);
  }
}
  1. Divide la tarea: Tipos → lógica → controladores → tests. Cada parte en sesión independiente.
  2. Usa diffs: Pide al modelo cambios como parches unificados.

5.3 Cuándo cambiar a cloud

SituaciónLocal ~30BCloud (GPT-4/Claude)Decisión
Boilerplate / CRUD~10s~15s (+red)✅ Local
Bug complejo multi-archivoContexto limitado200K ctx, global☁️ Cloud
Revisar PR de 20 archivosChunking manualProcesa todo☁️ Cloud
Tests para una función~5s, privado~10s, datos enviados✅ Local
Prompt iterativo (5+ rounds)Contexto se llenaContexto enorme☁️ Cloud
Stack trace 500 líneasNo cabeCómodo☁️ Cloud
Documentar funciónInstantáneo~3s + round-trip✅ Local
💡 Estrategia híbrida recomendada

Usa el modelo local para el 80% de tu día a día (generación, tests, refactorización, documentación) y reserva los servicios cloud para tareas que requieran contexto masivo o razonamiento profundo. Privacidad + velocidad para lo rutinario, potencia para lo complejo.

6 Ingeniería de prompts y flujos avanzados

6.1 System prompts profesionales

Para proyectos React (TypeScript):

SYSTEM """You are a senior React developer specializing in TypeScript.
- Functional components with hooks. No class components.
- Explicit prop interfaces (not `any`).
- useCallback/useMemo only when profiling shows bottleneck.
- Handle loading, error, and empty states explicitly.
- React.forwardRef when needed. 2-space indent, no semicolons, single quotes.
- Add JSDoc for public props. Never disable eslint without explaining why."""

Para NestJS:

SYSTEM """You are a senior NestJS backend engineer.
- Module-based: one module per domain entity.
- DTOs with class-validator + @nestjs/swagger.
- Services injectable, stateless. Repository pattern (TypeORM/Prisma).
- Global ValidationPipe with whitelist:true, transform:true.
- Exception filters for error handling.
- @nestjs/config for env vars. PaginatedDto<T> generic for pagination."""

Para Node.js:

SYSTEM """You are a senior Node.js engineer building CLI tools and scripts.
- ESM (import/export) unless CommonJS required.
- node:fs/promises over callbacks. Proper exit codes.
- meow/commander/yargs for CLI args. Idempotent scripts.
- Shebang #!/usr/bin/env node for executables.
- --help flag with usage examples."""

6.2 Patrones de interacción

"Primero piensa, luego escribe"

opencode prompt "
Analiza el siguiente problema y genera una solución.

PROBLEMA: Implementar un hook useAutoSave que guarde un formulario
cada 3 segundos después del último cambio, pero solo si hay cambios.

PRIMERO: Explica tu enfoque en 3-4 frases.
LUEGO: Implementa el código con tipos explícitos.
DESPUÉS: Enumera 3 casos borde y cómo los manejas.
"

"Genera, critica y mejora"

IterPromptObjetivo
1"Genera un middleware de rate limiting para Express"Baseline rápido
2"Critica tu implementación. ¿Qué falta? ¿Qué es frágil?"Autoevaluación
3"Mejórala basándote en tu crítica. Hazla production-ready."Refinamiento

6.3 Automatización con scripts Node.js + API Ollama

Ejemplo real: añadir tipos TypeScript a un proyecto JavaScript usando un worker pool:

#!/usr/bin/env node
// add-types-worker.mjs
import { readFileSync, writeFileSync } from 'node:fs';
import { globSync } from 'glob';
import { cpus } from 'node:os';

const CONCURRENCY = Math.max(1, cpus().length - 1);
const API_URL = 'http://localhost:11434/api/generate';
const MODEL = 'deepseek-coder-33b-instruct:Q4_K_M';

async function addTypesToFile(filePath) {
  const code = readFileSync(filePath, 'utf-8');
  if (code.trim().length === 0) return { path: filePath, success: true };

  const response = await fetch(API_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: MODEL,
      prompt: `Convierte este JS a TypeScript. Añade tipos explícitos.
      Mantén la lógica exacta. Devuelve SOLO el código TS, sin explicaciones.\n\n\`\`\`js\n${code}\n\`\`\``,
      stream: false,
      options: { temperature: 0.1, num_ctx: 8192 }
    })
  });

  if (!response.ok) return { path: filePath, success: false };
  const data = await response.json();
  const match = data.response.match(/```(?:ts|typescript)?\s*([\s\S]*?)```/);
  const typedCode = match ? match[1] : data.response;
  writeFileSync(filePath.replace(/\.js$/, '.ts'), typedCode, 'utf-8');
  return { path: filePath, success: true };
}

const files = globSync('src/**/*.js').filter(f => !f.includes('.test.'));
console.log(`Procesando ${files.length} archivos con ${CONCURRENCY} workers...`);

for (let i = 0; i < files.length; i += CONCURRENCY) {
  const batch = files.slice(i, i + CONCURRENCY);
  const results = await Promise.all(batch.map(addTypesToFile));
  const failed = results.filter(r => !r.success);
  console.log(`Progreso ${Math.min(i + CONCURRENCY, files.length)}/${files.length} | Fallos: ${failed.length}`);
}

console.log('Completado.');
💡 Rate limiting para Ollama

Configura OLLAMA_NUM_PARALLEL=2 para 2-3 solicitudes concurrentes en RTX 3090. Más allá, todas se ralentizan por igual.

6.4 Integración en CI/CD

# .github/workflows/ai-review.yml
name: AI Code Review
on: [pull_request]

jobs:
  review:
    runs-on: self-hosted  # Máquina con GPU + Ollama
    steps:
      - uses: actions/checkout@v4
      - name: AI Review via OpenCode CLI
        run: |
          git diff origin/main...HEAD | \
          opencode prompt "Revisa este diff. Busca: vulnerabilidades,
          errores de tipo TS, anti-patrones React, errores de manejo.
          Formato: | Archivo | Severidad | Hallazgo | Sugerencia |" \
          > review.md
      - name: Post review
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('review.md', 'utf-8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## 🤖 AI Review\n${review}`
            });

Más ideas para CI/CD local:

7 Ejemplos prácticos en TypeScript

Los siguientes ejemplos muestran flujos reales de trabajo con OpenCode CLI + Ollama. Cada ejemplo incluye el prompt exacto, la respuesta del modelo y el análisis de ajustes manuales necesarios. Expande cada uno para ver los detalles.

🎯 React — Hook useDebouncedState + Búsqueda

Problema: Implementar un hook de debounce reutilizable y un componente de búsqueda con autocompletado.

Prompt:

opencode prompt "
Genera un hook useDebouncedState<T> que:
- Acepte valor inicial y delay en ms
- Devuelva [valorDebounced, setValor, valorInmediato]
- Use setTimeout/clearTimeout, limpie en unmount

Luego un SearchAutocomplete que:
- Use el hook con delay 300ms
- Fetch a /api/search?q={query}
- Maneje: loading, error, empty, Escape key
- Sea accesible (aria-*, role)
"

Respuesta destacada:

// useDebouncedState.ts
export function useDebouncedState<T>(initialValue: T, delay: number): [T, (v: T) => void, T] {
  const [immediate, setImmediate] = useState(initialValue);
  const [debounced, setDebounced] = useState(initialValue);
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const setValue = useCallback((value: T) => {
    setImmediate(value);
    if (timeoutRef.current !== null) clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => setDebounced(value), delay);
  }, [delay]);

  useEffect(() => () => { if (timeoutRef.current) clearTimeout(timeoutRef.current); }, []);
  return [debounced, setValue, immediate];
}

Análisis:

  • ✅ Sin cambios: Hook usable tal cual. Cleanup correcto.
  • ✅ Sin cambios: Manejo de teclado y accesibilidad correctos.
  • ⚠️ Ajuste: onBlur con setTimeout de 200ms puede causar parpadeo. Se mejoró con flag mouseDown.
  • ⏱️ Ahorro: ~35 minutos frente a ~50 manual.
📦 NestJS — CRUD de Invoice con validación

Problema: Generar un módulo NestJS completo para gestión de facturas con DTOs anidados, transformación de fechas y Swagger.

Prompt:

opencode prompt "
Genera un módulo NestJS para entidad Invoice con:
- Crear factura (cliente, líneas, fechas)
- Listar con paginación, obtener por ID
- Actualizar estado (emitida/pagada/cancelada)
- Soft delete (anular)

Técnico:
- DTOs con class-validator + class-transformer
- Swagger decorators, TypeORM + PostgreSQL
- Transformación automática de fechas
- Tests unitarios del servicio
- PaginatedDto genérico
"

Análisis:

  • ✅ Sin cambios: DTOs, entidad, controlador, servicio base.
  • ⚠️ Ajuste: IVA no configurable externamente. Se añadió ConfigService.
  • ⚠️ Ajuste: Faltaba índice compuesto (clienteId, fechaEmision).
  • ⚠️ Ajuste: Transiciones de estado no validadas. Se añadió mapa de transiciones.
  • ⏱️ Ahorro: ~45 min escritura → 30s generación + 15 min ajustes. Ratio ~3:1.
🤖 Node.js — Watcher + Tests Automáticos

Problema: Script que monitoriza una carpeta y, al detectar cambios, pide a Ollama que genere tests Jest para los archivos modificados.

Prompt resumido: Script Node.js ESM con fs.watch, cola de máximo 2 peticiones concurrentes a Ollama, debounce de 2s, ignorar .test.ts, logs con picocolors, argumentos CLI --dir y --concurrency, retry con backoff exponencial.

Análisis:

  • ✅ Sin cambios: Colas, concurrencia, debounce, retries.
  • ⚠️ Ajuste: isPureTypesFile demasiado simple. Se mejoró con regex más precisa.
  • ⚠️ Ajuste: fs.watch recursive no funciona en todos los sistemas. Fallback a chokidar.
  • ⏱️ Ahorro: ~1h escritura → 20 min ajustes.
🐛 Depuración — "Rendered more hooks than during the previous render"

Problema real: Error de React en un componente con llamadas condicionales a hooks.

// Código con error
function Dashboard({ user, config }: Props) {
  const stats = useStats();
  const notifications = useNotifications();

  if (user.role !== 'admin') {
    return <div>Acceso denegado</div>;  // ← early return después de 2 hooks
  }

  const analytics = useAnalytics(config.analytics);  // ← estos hooks se saltan si no es admin
  const { data } = usePrefetch('/api/dashboard');
  const [filter, setFilter] = useState('all');
  ...
}

Corrección: Mover todos los hooks ANTES del early return.

Análisis:

  • ✅ Diagnóstico preciso: Identificó la violación de la regla de hooks.
  • ✅ Solución correcta: Mover hooks antes del return.
  • ✅ Valor añadido: Sugirió eslint-plugin-react-hooks y detección en CI.
  • ⏱️ Ahorro: Depuración típica 15-30 min → 2 min con el prompt.

Estos ejemplos muestran un patrón consistente: el modelo genera ~80-90% del código correcto en el primer intento. La clave está en invertir tiempo en prompts bien estructurados para maximizar la calidad del primer output.

8 Integración con el ecosistema profesional

8.1 OpenCode CLI como compañero continuo

# Terminal 1: editor. Terminal 2: sesión OpenCode persistente
tmux new-session -s coding \; \
  send-keys 'opencode chat' Enter \; \
  split-window -h \; \
  send-keys 'nvim .' Enter

# Atajo: seleccionas código, Ctrl+Shift+C, luego:
xclip -o | opencode prompt "Revisa este código:"

# Exportar sesión a Markdown
opencode chat --export > session-$(date +%Y%m%d-%H%M).md

8.2 Estrategia de modelos múltiples

TareaModeloVelocidadVRAM
Autocompletado líneaQwen2.5-Coder-7B (Q4_K_M)~80 t/s~5 GB
Explicación rápidaDeepSeek-Coder-6.7B (Q5_K_M)~70 t/s~5.5 GB
Tests/refactorizaciónDeepSeek-Coder-33B (Q4_K_M)~30 t/s~20 GB
Depuración complejaDeepSeek-Coder-33B (Q5_K_M, 16K)~25 t/s~24 GB

Script para alternar modelos:

#!/usr/bin/env bash
MODEL_SMALL="qwen2.5-coder:7b"
MODEL_LARGE="deepseek-coder-33b:Q4_K_M"
if [[ "$1" == "--deep" ]]; then shift; opencode --model "$MODEL_LARGE" prompt "$*"
else opencode --model "$MODEL_SMALL" prompt "$*"; fi
💡 Carga bajo demanda

OLLAMA_KEEP_ALIVE=30s para que los modelos se descarguen rápido al no usarlos. Así puedes rotar 2-3 modelos sin mantenerlos todos en VRAM.

8.3 Fine-tuning con LoRA/QLoRA

# Fine-tuning con Unsloth (RTX 3090)
from unsloth import FastLanguageModel
import torch

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="deepseek-ai/deepseek-coder-6.7b-instruct",
    max_seq_length=4096, dtype=torch.bfloat16, load_in_4bit=True,
)

model = FastLanguageModel.get_peft_model(model,
    r=16, target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_alpha=16, lora_dropout=0, bias="none", use_gradient_checkpointing=True,
)

# ~2-3 horas para 6.7B con QLoRA en 3090
# Exportar a GGUF: model.save_pretrained_gguf("./model-lora", tokenizer)
# Luego: ollama create mi-modelo -f Modelfile

8.4 Seguridad

9 Conclusión y recursos

9.1 Resumen de mejores prácticas

ÁreaPráctica recomendada
HardwareRTX 3090 24GB para 34B Q4_K_M con 16K de contexto
Modelo principalDeepSeek-Coder-33B-Instruct Q4_K_M (mejor equilibrio calidad/VRAM/velocidad)
Modelo secundarioQwen2.5-Coder-7B para tareas rápidas sin razonamiento profundo
Contexto8K general, 16K depuración. No 32K en 24GB
PromptSystem prompt específico por framework. Patrón "piensa→genera→crítica→mejora"
FlujoChunking proyectos grandes. Diffs para cambios precisos. CONTEXT.md externo
CI/CDCode review, changelog, detección de secretos con modelo local
SeguridadNunca expongas Ollama en red. Revisa el código generado
HíbridoLocal ~80% tareas. Cloud para contexto masivo o razonamiento complejo

9.2 Mentalidad estratégica

La IA local no es un reemplazo del desarrollador — es un multiplicador de productividad. La diferencia entre un desarrollador que obtiene valor real y uno que solo "juega" con la IA está en tres cosas:

  1. Conocer los límites: Saber qué delegar y qué hacer uno mismo.
  2. Invertir en la interacción: Un prompt bien estructurado produce resultados 10× mejores.
  3. Iterar con criterio: El modelo genera ~80% del código correcto. El 20% restante —casos borde, seguridad, rendimiento— es donde tu experiencia como ingeniero marca la diferencia.
"La IA no va a reemplazar a los desarrolladores. Pero los desarrolladores que usan IA van a reemplazar a los que no."
— Adaptación del aforismo original sobre automatización

9.3 Recursos

9.4 Próximos pasos

  1. Mide tu productividad actual: Una semana de línea base (boilerplate, tests, depuración, docs).
  2. Configura tu stack: Ollama + ~30B + OpenCode CLI + tareas VSCode.
  3. Prueba los ejemplos de la sección 7 en tu propio código.
  4. Itera: Tras 2 semanas, vuelve a medir. Ajusta qué delegas.
  5. Automatiza: CI/CD local y scripts batch para procesamiento recurrente.

Guía generada con apoyo de IA local · Julio 2026

Modelo: DeepSeek-V4 · Provider: OpenCode Go