Saltar al contenido

$ 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.

TypeScriptReactCachingPerformance
useCachedFetch.ts
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.

CI/CDGitHub ActionsDevOpsNode.jsPM2
.github/workflows/deploy.yml
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 host

Agente 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.

TypeScriptLLMOllamaTool calling
agent.ts
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.

Cargando playground…

Stack

toolkit.json

$ cat toolkit.json

// cuando necesito velocidad de iteración

ReactTypeScriptStyled Components

// cuando el proyecto crece

Node.jsExpressNestJSMongoDB

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

$ lighthouse https://www.mauriciorodriguez.dev/en --preset=desktop,mobile

performancedesktop 100/100mobile 98/100
accesibilidaddesktop 100/100mobile 100/100
buenas prácticasdesktop 100/100mobile 100/100
seodesktop 100/100mobile 100/100

Medido el 2026-08-06

Contacto

La forma más rápida de contactarme es por email o LinkedIn.