Guía estratégica de Ollama + OpenCode CLI para desarrolladores profesionales
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.
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.
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.
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:
ollama serve levanta un servidor HTTP en localhost:11434. Todo el ecosistema (CLI, APIs, herramientas externas como OpenCode CLI) se comunica vía REST.POST /api/generate para inferencia single-turn, POST /api/chat para conversaciones multi-turno con historial.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:
| Modelo | Parámetros | Ventaja clave | Debilidad | Mejor para |
|---|---|---|---|---|
| CodeLlama-34B-Instruct | 34B | Base sólida, fine-tuning estable | Menor rendimiento en TypeScript que los especializados | Python, C++, refactorización general |
| DeepSeek-Coder-33B-Instruct | 33B | Entrenado con 2T tokens de código, fill-in-middle nativo | Mayor VRAM que CodeLlama en ctx largo | Completado de código, TypeScript, Python |
| Phind-CodeLlama-34B-v2 | 34B | Fine-tuning con datos de búsqueda web, mejor en APIs modernas | Overfit a prácticas de Python/JS | Documentación, explicación, código boilerplate |
| WizardCoder-33B-V1.1 | 33B | Evol instruccionado, bueno siguiendo instrucciones complejas | Tiende a generar código verboso | Refactorización guiada, migraciones |
| Mixtral-8x7B-Instruct | 46.7B (MoE, activa ~12B) | Velocidad de 12B con calidad de 46B (MoE) | Mayor VRAM total, menor densidad de conocimiento de código | Razonamiento general, depuración |
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ón | Pesos (34B) | + KV cache 4K | + KV cache 16K | + KV cache 32K | Velocidad (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 |
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.
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.
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.
El Modelfile te permite modificar parámetros de inferencia sin re-cuantizar el modelo. Es tu principal palanca de configuración.
| Parámetro | Función | Impacto en VRAM | Impacto en calidad |
|---|---|---|---|
num_ctx | Tamaño máximo de contexto | ↑ Lineal con la longitud | Ninguno directo |
rope_frequency_base | Frecuencia base de RoPE | Ninguno | Ajusta precisión posicional |
rope_scaling | Escalado lineal/dinámico de RoPE | Ninguno | Permite extrapolar contexto |
num_gpu_layers | Capas en GPU (vs CPU) | ↓ Menos capas = menos VRAM | ↓ Más CPU = más lento |
num_batch | Tokens evaluados en paralelo | ↑ Ligeramente | Ninguno |
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.
| Variable | Propósito | Ejemplo |
|---|---|---|
OLLAMA_GPU_OVERHEAD | Reserva de VRAM para el sistema | OLLAMA_GPU_OVERHEAD=512 |
OLLAMA_MAIN_GPU | Selecciona qué GPU usar | OLLAMA_MAIN_GPU=0 |
OLLAMA_KEEP_ALIVE | Tiempo en VRAM tras último uso | OLLAMA_KEEP_ALIVE=10m |
OLLAMA_NUM_PARALLEL | Solicitudes simultáneas | OLLAMA_NUM_PARALLEL=2 |
OLLAMA_MAX_LOADED_MODELS | Máx. modelos en VRAM simultáneos | OLLAMA_MAX_LOADED_MODELS=1 |
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`);
}
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.
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
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.
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:'"
}
]
}
Las sesiones prolongadas de OpenCode CLI sufren el problema de la ventana de contexto. Estrategias profesionales:
opencode chat --reset y proporciona un resumen de lo discutido.# 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
git diff o extrae solo las funciones relevantes.CONTEXT.md con decisiones arquitectónicas. En lugar de re-explicar, referencia ese archivo.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.
| Tarea | Viabilidad | Contexto | Calidad | Notas |
|---|---|---|---|---|
| Boilerplate | Alta | ~2K | Excelente | CRUDs, DTOs, módulos NestJS |
| Tests unitarios | Alta | ~4K | Buena | Mejor con función + tipo |
| Refactorización simple | Alta | ~4K | Buena | Extraer, renombrar, tipar |
| Explicación de código | Alta | Variable | Excelente | El modelo explica mejor que crea |
| Documentación | Alta | Medio | Buena | JSDoc, Swagger, READMEs |
| Fill-in-middle | Alta | ~2K | Variable | DeepSeek-Coder destaca aquí |
| Migración dependencias | Media | >8K | Media | Contexto se satura rápido |
| Depuración con trazas | Media | >8K | Media-Alta | Limita la traza a lo relevante |
| Análisis seguridad | Media | Medio | Media | SQLi, XSS básicos. No confíes |
| Refactorización compleja | Baja | Muy alto | Baja | Pierde coherencia multi-archivo |
| Integraciones complejas | Baja | Alto | Baja | Alucina endpoints y configs |
tree src/ -I node_modules --charset unicode | head -100#!/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]}`);
}
}
| Situación | Local ~30B | Cloud (GPT-4/Claude) | Decisión |
|---|---|---|---|
| Boilerplate / CRUD | ~10s | ~15s (+red) | ✅ Local |
| Bug complejo multi-archivo | Contexto limitado | 200K ctx, global | ☁️ Cloud |
| Revisar PR de 20 archivos | Chunking manual | Procesa todo | ☁️ Cloud |
| Tests para una función | ~5s, privado | ~10s, datos enviados | ✅ Local |
| Prompt iterativo (5+ rounds) | Contexto se llena | Contexto enorme | ☁️ Cloud |
| Stack trace 500 líneas | No cabe | Cómodo | ☁️ Cloud |
| Documentar función | Instantáneo | ~3s + round-trip | ✅ Local |
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.
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."""
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."""
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."""
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.
"
| Iter | Prompt | Objetivo |
|---|---|---|
| 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 |
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.');
Configura OLLAMA_NUM_PARALLEL=2 para 2-3 solicitudes concurrentes en RTX 3090. Más allá, todas se ralentizan por igual.
# .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:
git log --oneline $(git describe --tags --abbrev=0)..HEAD | opencode prompt "Genera un changelog"git diff HEAD | opencode prompt "Identifica posibles secretos hardcodeados"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.
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:
onBlur con setTimeout de 200ms puede causar parpadeo. Se mejoró con flag mouseDown.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:
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:
isPureTypesFile demasiado simple. Se mejoró con regex más precisa.fs.watch recursive no funciona en todos los sistemas. Fallback a chokidar.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:
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.
# 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
| Tarea | Modelo | Velocidad | VRAM |
|---|---|---|---|
| Autocompletado línea | Qwen2.5-Coder-7B (Q4_K_M) | ~80 t/s | ~5 GB |
| Explicación rápida | DeepSeek-Coder-6.7B (Q5_K_M) | ~70 t/s | ~5.5 GB |
| Tests/refactorización | DeepSeek-Coder-33B (Q4_K_M) | ~30 t/s | ~20 GB |
| Depuración compleja | DeepSeek-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
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.
# 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
| Área | Práctica recomendada |
|---|---|
| Hardware | RTX 3090 24GB para 34B Q4_K_M con 16K de contexto |
| Modelo principal | DeepSeek-Coder-33B-Instruct Q4_K_M (mejor equilibrio calidad/VRAM/velocidad) |
| Modelo secundario | Qwen2.5-Coder-7B para tareas rápidas sin razonamiento profundo |
| Contexto | 8K general, 16K depuración. No 32K en 24GB |
| Prompt | System prompt específico por framework. Patrón "piensa→genera→crítica→mejora" |
| Flujo | Chunking proyectos grandes. Diffs para cambios precisos. CONTEXT.md externo |
| CI/CD | Code review, changelog, detección de secretos con modelo local |
| Seguridad | Nunca expongas Ollama en red. Revisa el código generado |
| Híbrido | Local ~80% tareas. Cloud para contexto masivo o razonamiento complejo |
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:
"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
Guía generada con apoyo de IA local · Julio 2026
Modelo: DeepSeek-V4 · Provider: OpenCode Go