$ whoami
Las matemáticas me enseñaron a ver los problemas como rompecabezas. El código me enseñó a resolverlos. Ahora, estoy enseñando a una IA a unirse.
Cómo pienso
Tres decisiones, y el razonamiento detrás de cada una.
Optimización bajo tráfico alto
Las páginas de artículos de una plataforma de medios pasaron de unos cientos a decenas de miles de lectores concurrentes en minutos, cuando una noticia se volvió viral — y todos estaban golpeando el mismo endpoint de la API al mismo tiempo.
Cuando el tráfico se dispara, la peor decisión es dejar que cada usuario dispare su propia llamada al backend. Así es como diseño la capa de caching para que aguante el pico sin caerse.
Decisión: Caching tipo stale-while-revalidate para las llamadas a la API, combinado con deduplicación de requests concurrentes, para que un pico de tráfico produzca una sola llamada al backend en vez de miles idénticas.
type CacheEntry<T> = {
data: T;
fetchedAt: number;
inFlight: Promise<T> | null;
};
const cache = new Map<string, CacheEntry<unknown>>();
// Serve cached data instantly; only go back to the network once it's this old.
const STALE_AFTER_MS = 30_000;
export function useCachedFetch<T>(key: string, fetcher: () => Promise<T>) {
const [data, setData] = useState<T | null>(
() => (cache.get(key)?.data as T | undefined) ?? null
);
useEffect(() => {
const entry = cache.get(key) as CacheEntry<T> | undefined;
const isStale = !entry || Date.now() - entry.fetchedAt > STALE_AFTER_MS;
// Fresh cache hit: the UI already has the data, nothing to do.
if (!isStale) return;
// Dedupe: if a request for this key is already in flight — say, from
// another component mounted a millisecond earlier during the same
// spike — piggyback on it instead of firing a second identical call.
const request =
entry?.inFlight ??
fetcher().then((result) => {
cache.set(key, { data: result, fetchedAt: Date.now(), inFlight: null });
return result;
});
cache.set(key, {
data: entry?.data as T,
fetchedAt: entry?.fetchedAt ?? 0,
inFlight: request,
});
// Trade-off: on failure we keep serving the last good (stale) value
// instead of surfacing an error — resilience over freshness.
request.then(setData).catch(() => {});
}, [key, fetcher]);
return data;
}Multi-repo / DevOps
Un equipo de ingeniería pequeño desplegaba tanto a un entorno de QA como a producción, con deploys hechos a mano — lo que significaba que lo único que separaba una rama rota de producción era que alguien se acordara de correr los checks ese día.
Esto no es código de feature — es diseño de proceso. El trabajo aquí no fue escribir una función ingeniosa, fue asegurar que todo el equipo pudiera desplegar sin que nadie tuviera que acordarse de tener cuidado.
Decisión: Un pipeline de CI/CD con gates de calidad (lint, test, build) que toda rama debe pasar, donde QA despliega automáticamente desde develop y producción solo se despliega desde main detrás de un entorno que requiere aprobación de un revisor.
name: CI/CD
on:
push:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --coverage
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy-qa:
# develop merges go straight to QA. This environment exists to break —
# the gate is "did it build," not "did a human sign off."
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-latest
environment: qa
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
- run: ./scripts/deploy.sh qa # pm2 reload on the QA host
deploy-production:
# Production only ships from main, and only behind a required reviewer
# on the "production" GitHub environment — one bad merge shouldn't be
# able to reach real users without a second set of eyes.
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
- run: ./scripts/deploy.sh production # pm2 reload on the prod hostAgente de IA local con Ollama
Un proyecto personal: un agente local que corre completamente en el dispositivo contra Ollama, y necesita actuar sobre el mundo real — revisar el clima, hacer una conversión, buscar algo — sin darle nunca al modelo acceso directo al sistema de archivos, la red, o la shell.
El modelo no puede ejecutar nada. Puede pedir. Este es el patrón que traza esa línea — y también es la base del playground de abajo, simplificado para correr completamente en tu navegador.
Decisión: Orquestación de tool-calling: al modelo se le da un catálogo fijo de herramientas con nombre, su respuesta se parsea buscando una llamada a herramienta estructurada, y solo el orquestador — nunca el modelo mismo — la ejecuta.
interface Tool {
name: string;
description: string;
execute: (args: Record<string, unknown>) => string;
}
const tools: Tool[] = [
{
name: "get_weather",
description: "Get the current weather for a city",
execute: ({ city }) => `${city}: 22°C, clear skies`,
},
{
name: "convert_currency",
description: "Convert an amount from one currency to another",
execute: ({ amount, from, to }) =>
`${amount} ${from} ≈ ${(Number(amount) * 1.08).toFixed(2)} ${to}`,
},
];
// The model never calls a function directly — it returns structured JSON
// naming a tool and its arguments. We parse that decision ourselves and
// decide whether to act on it, which is what keeps the model sandboxed.
interface ToolCall {
tool: string;
args: Record<string, unknown>;
}
function parseToolCall(modelOutput: string): ToolCall | null {
try {
const parsed = JSON.parse(modelOutput);
if (typeof parsed.tool === "string") return parsed as ToolCall;
} catch {
// Not valid JSON — treat the output as a plain text reply instead.
}
return null;
}
export async function runAgentTurn(modelOutput: string): Promise<string> {
const call = parseToolCall(modelOutput);
// No tool call parsed: the model just answered in plain text.
if (!call) return modelOutput;
const tool = tools.find((t) => t.name === call.tool);
if (!tool) return `Error: no tool named "${call.tool}"`;
return tool.execute(call.args);
}Pruébalo tú mismo
El patrón de tool-calling del caso de estudio de arriba, en vivo.
Edita el código, cambia lo que "respondió" el modelo, y ejecútalo — todo corre acá mismo en tu navegador, sin ningún servidor de por medio.
Stack
$ cat toolkit.json
// cuando necesito velocidad de iteración
// cuando el proyecto crece
También en la caja de herramientas
También he llevado código a producción con esto — no es mi opción por defecto, pero está listo si un proyecto lo requiere.
Frontend
JavaScript · HTML · CSS · Sass/Less/Scss · jQuery · Angular · Redux · GraphQL
Backend, CMS y datos
PHP · WordPress · Wagtail · Python · Java · SQL
Cloud y DevOps
Git · GitHub · GitLab · Bitbucket · AWS · GCP · PM2 · Keycloak
Herramientas y testing
Webpack · npm · Jest · Enzyme · React Testing Library · SEO
Verificado, no solo afirmado
Los números de performance y accesibilidad detrás de este sitio.
No es una insignia que me inventé — es una auditoría real de Lighthouse contra un build de producción, y puedes volver a correrla tú mismo cuando quieras.
$ lighthouse https://www.mauriciorodriguez.dev/en --preset=desktop,mobile
Medido el 2026-08-06