AI Engineer · Full-Stack Developer · Lima, Perú

Llevo LLMs a producción sin que rompan nada.

Soy Rodrigo Latorre. Cinco años construyendo software y los últimos dedicados a sistemas donde un modelo toma decisiones y el código responde por ellas. La parte difícil nunca fue llamar al modelo: es que su respuesta entre a una base de datos sin romperla, sin mentir y sin costar una fortuna.

5+
años en producciónCV — Ene 2021 a hoy
4
sistemas que puedo enseñarCV — proyectos destacados
15 000
líneas de TypeScript en dos SaaS propios7.600 (MediaBriefs) + 7.400 (Sin Anestesia)
7
puntos de IA en un solo sistemaSin Anestesia §3

02 — Principios

Seis reglas que aprendí poniendo modelos en producción.

Usar un modelo es fácil. Lo difícil es que su salida entre a una base de datos sin romperla, sin mentir y sin volverse cara. Estas son las decisiones que sostienen mis pipelines — todas salen de código que está corriendo ahora mismo.

02.1

El modelo juzga, el código calcula.

El LLM nunca produce un número que importe.

Claude agrupa titulares por tema —juicio semántico que ningún algoritmo de keywords hace bien— y ahí termina su trabajo. Todas las métricas las computa el código comparando contra el estado anterior de la base. Si un tema sale marcado como urgente, puedo explicar exactamente por qué; un score puntuado por el modelo no sería reproducible ni defendible ante un editor.

MediaBriefs

El modelo juzga, el código calcula.text
mentions     ← cuántos artículos cayeron en el cluster
mediaCount   ← cuántos medios distintos lo cubren
velocity     ← mentions actuales / mentions de la corrida anterior
score        ← mentions×2 + mediaCount×3 + velocity×4
Reglas deterministas y auditables. Ninguna la decide el modelo.
02.2

Tool-use forzado, cero parseo de JSON.

El modelo no puede responder prosa aunque quiera.

Todo el acceso al modelo pasa por un único método que declara una herramienta con su input_schema y fuerza tool_choice. La única salida posible es una llamada a esa herramienta con esa forma. No hay parseo de JSON libre en ningún punto del sistema, y el esquema cumple doble función: documenta cada campo para el modelo y garantiza la forma para Mongoose.

MediaBriefs · Sin Anestesia

Tool-use forzado, cero parseo de JSON.ts
// Un solo cliente para seis casos de uso.
await client.messages.create({
  model,
  tools: [{ name, input_schema }],
  tool_choice: { type: 'tool', name },   // no hay salida en prosa
})
Las restricciones de formato viven en el esquema, no en el prompt.
02.3

Referencia por índice, no reescritura.

Elimina una clase entera de alucinación y abarata el output.

El clustering manda 200 titulares numerados y pide de vuelta índices, no títulos. El modelo no puede devolver un titular ligeramente distinto ni una URL inventada: solo puede señalar cuáles de los que le di van juntos. Ahorra los tokens de salida, que son los caros, y el código valida trivialmente con una comparación de rango.

MediaBriefs

Referencia por índice, no reescritura.text
[0] Ministro de Economía anuncia recorte del presupuesto
[1] Congreso aprueba moción de censura
...
→ { label: "Crisis presupuestal", articleIndices: [0, 14, 37] }
En vez de reescribir 200 titulares, devuelve ~200 enteros.
02.4

La IA nunca tumba el sistema.

Cada llamada tiene definido qué pasa cuando falla.

Si el clustering falla, se conservan los temas anteriores: prefiero datos de hace dos horas a una pantalla vacía. Si la diarización no está disponible, la transcripción sale igual con un flag que viaja al frontend — la app sabe cuándo muestra un resultado degradado y lo dice, en vez de fingir que hubo un solo hablante.

MediaBriefs

La IA nunca tumba el sistema.text
Clustering   → devuelve null y conserva los clusters anteriores
NER          → devuelve [], el cluster existe sin personas
Briefing     → cae a la descripción del artículo representativo
Diarización  → todo como "Hablante 1" + diarizationEnabled: false
Cuatro políticas distintas. Ninguna propaga el error.
02.5

El usuario confirma antes de que la IA escriba.

La IA propone, la persona dispone.

El mapeo de columnas al importar contactos se revisa antes de confirmar; los mensajes de WhatsApp se aprueban antes de enviarse; los clips se aprueban antes de publicarse. Y donde el error tiene consecuencia real —sugerir un invitado que no existe— el nombre que devuelve el modelo se resuelve contra la base de datos: si no está, se descarta. El modelo nunca crea contactos.

Sin Anestesia

02.6

Medir, no suponer.

La métrica correcta es la del negocio, no la del paper.

Recorté el 20 % de un prompt tras verificar con count_tokens que el andamiaje costaba ~7.000 tokens por episodio. Y antes de self-hostear Whisper construí un benchmark que no mide WER sino cobertura de nombres propios: si el modelo local escribe «Bolarte» en vez de «Boluarte», el highlight se elige peor aunque el WER global se vea bien. Umbrales de decisión definidos por adelantado, antes de ver los resultados.

Sin Anestesia

Medir, no suponer.text
≥ 95 %  cobertura de nombres propios → migrar
85–95 %                                 → probar el modelo grande
< 85 %                                  → quedarse con la API

Segundo filtro: < 2× tiempo real (compite con los renders de ffmpeg)
La transcripción era el 72 % del gasto de IA. El riesgo no era el costo, era la calidad.

03 — Trabajo

Cuatro sistemas, contados por sus decisiones.

De todo lo que he construido en estos años, estos cuatro son los que puedo enseñar: están desplegados, los usa alguien que no soy yo y su cliente me deja contarlos. No son capturas sueltas — es el problema que había, cómo está construido y qué decisión sostiene cada uno.

MediaBriefs

En producción

SaaS de inteligencia editorial para periodistas

Un CRM de programas, invitados y entrevistas con dos capas de IA encima: un radar que convierte las noticias del día en briefings accionables, y transcripción de entrevistas separada por hablante.

pipelinetext
6 feeds RSS peruanos (El Comercio, Gestión, BBC Mundo…)
   └─ NewsArticle ────────────────► ~200 artículos de las últimas 24 h
      └─ Claude Haiku 4.5 ────────► agrupa en 8–15 temas (clustering semántico)
         └─ CÓDIGO ───────────────► mentions, mediaCount, velocity, score, isNew
            └─ TopicDefinition + Snapshot ──► histórico temporal del tema
               └─ Claude Haiku 4.5 ────────► NER: personas, cargo, organización
                  └─ PersonProfile ────────► identidad canónica + alias
                     └─ match contra Guests ──► "este invitado tuyo encaja"
                        └─ Claude Haiku 4.5 ──► briefing · 3 preguntas · riesgo
El pipeline editorial completo. En paralelo corre el de transcripción.
7 600
líneas de TypeScript en el backend
25
módulos de dominio NestJS
61
endpoints en el contrato OpenAPI
88
esquemas versionados
32
pantallas en el frontend
~200
artículos procesados cada 2 h
  • NestJS
  • TypeScript
  • Node.js
  • MongoDB
  • Redis + Bull
  • Autenticación con sesión rotativa
Ver el case study

Sin Anestesia

En producción

Automatización de contenido para un programa periodístico

De un link de YouTube a Shorts verticales publicados con subtítulos y copy por red. Y de un feed RSS a "llama a esta persona hoy", con el mensaje de WhatsApp ya redactado.

pipelinetext
link YouTube
   └─ yt-dlp ──────────► video fuente (mp4 ≤1080p)
      └─ ffmpeg ───────► audio mono 16 kHz 16 kbps
         └─ Whisper ───► transcripción con timestamps por palabra
            └─ Claude Sonnet 5 ──► N momentos clip-eables (inicio, fin, gancho)
               └─ ffmpeg ────────► corte + fondo 9:16 + subtítulos ASS quemados
                  └─ Claude Sonnet 5 ──► títulos, hashtags y caption por red
                     └─ YouTube / TikTok / Meta ──► publicado
Cada etapa es idempotente: si Bull reintenta, no vuelve a pagar la transcripción.
7 400
líneas de TypeScript (94 archivos)
14
pantallas en el frontend
7
usos distintos de IA en el sistema
−20 %
de prompt recortado, medido con count_tokens
4
contenedores en producción
72 %
del gasto de IA era transcripción
  • NestJS
  • TypeScript
  • MongoDB
  • Redis + Bull
  • ffmpeg (libass)
  • yt-dlp
Ver el case study

Procesador RO

Entregado

Registro de Operaciones · UIF-Perú / SBS

Pipeline en Python que convierte el PDF mensual de facturas de una casa de cambio en el reporte regulatorio de 26 columnas que exige la UIF-Perú, resolviendo automáticamente contra SUNAT los datos que la factura no contiene.

pipelinetext
PDF mensual (48–81 páginas)
   └─ pdfplumber ────────► extracción de texto por página
      └─ ¿factura válida? ──no──► omitida, con el motivo al log
         └─ parser tolerante ──► 41 formas estructurales
            └─ ¿≥ USD 5.000? ──no──► descartada, con su monto al log
               └─ InvoiceRecord
                  └─ SUNAT headless ──► representante legal
                     └─ merge idempotente + backfill
                        └─ orden por fecha + renumeración
                           └─ Excel UIF · 26 columnas
Nada se descarta en silencio: cada omisión deja su motivo registrado.
386
páginas de PDF procesadas
41
formas estructurales de la línea de operación
148
filas generadas en 6 meses
33
consultas a SUNAT para 148 filas
87
facturas sobre el umbral de USD 5.000
~1 500
líneas de código
  • Python 3
  • pdfplumber
  • openpyxl
  • Selenium + Chrome headless
  • Tkinter
  • ThreadPoolExecutor
Ver el case study

Plataforma de screening

Entregado

Sanciones y PEP para sujetos obligados · UIF-SBS

Plataforma multi-tenant que colapsa la verificación contra listas de sanciones y de personas expuestas políticamente en una sola consulta, y persiste la evidencia de cada búsqueda para el regulador.

5
listas internacionales de sanciones cruzadas
+1
maestro PEP peruano (JNE + Contraloría)
1
consulta reemplaza la búsqueda fuente por fuente
API
para incrustar el screening en onboarding de terceros
  • Next.js
  • NestJS
  • MongoDB
  • Docker
  • Multi-tenant
  • API pública
Ver el case study

04 — Proceso

Construyo con agentes, no genero con IA.

MediaBriefs no solo tiene IA dentro: se construyó con agentes de forma deliberada, con una arquitectura pensada para que varios trabajaran sin pisarse. Esa diferencia se nota en el resultado.

El contrato OpenAPI como frontera entre agentes

El sistema son tres repositorios, no dos: backend, contrato y frontend. Cada agente tiene su dominio acotado y prohibido leer el otro lado. Eso resuelve el problema real de trabajar con agentes en un sistema grande: el contexto es finito y compartirlo todo degrada el resultado.

mediabriefs-backend    ← un agente trabaja aquí
mediabriefs-openapi    ← la frontera: openapi.json (61 paths, 88 schemas)
mediabriefs-frontend   ← otro agente trabaja aquí

La frontera se verifica sola

El frontend no escribe tipos a mano: los genera desde openapi.json con openapi-typescript y consume el API con openapi-fetch. Si el backend cambia un payload sin actualizar el contrato, el frontend deja de compilar. La coordinación entre los dos lados no depende de que nadie recuerde nada — la impone el compilador.

el backend rompe el contrato
   └─► el frontend no compila
        └─► nadie tiene que acordarse de nada

Contexto duradero, con el porqué de cada decisión

CLAUDE.md con stack y convenciones, ARCHITECTURE.md con una tabla de decisiones y su razón, API_RULES.md con el envelope de respuesta y la paginación. Las convenciones no son genéricas, son las de este proyecto. Documentar el porqué —"Joi para validar env: falla rápido si falta una variable"— es justo lo que un agente no puede inferir del código y lo que evita que "arregle" algo intencional.

CLAUDE.md         stack · estructura · convenciones · comandos
ARCHITECTURE.md   cada decisión CON SU RAZÓN + roadmap por fases
API_RULES.md      envelope · errores · paginación · soft delete
El porqué es lo que un agente no puede inferir leyendo el código.

Skills propias por dominio

Capacidades empaquetadas y versionadas con el repo, en vez de reexplicarlas en cada conversación: un experto en NestJS para el backend, y en el frontend una cadena de diseño completa que va de una idea vaga de UI a componentes React validados por AST.

.claude/skills/
   backend   → nestjs-expert
   frontend  → enhance-prompt → stitch-design → design-md
                  → react-components → stitch-loop

Higiene de repositorio

Commits con scope convencional y el contrato versionado en paralelo al código que lo implementa. El historial permite reconstruir qué se hizo y por qué, que es exactamente la diferencia entre un proyecto construido con IA y uno generado por IA.

feat(billing):   …
fix(payments):   …
feat(contract):  align billing paths and schemas with implemented API

05 — Sobre mí

Ingeniero de producto que se especializó en IA.

Rodrigo Latorre Quispe

Llevo cinco años en la misma consultora, en Lima. Entré a maquetar componentes en React y terminé diseñando los pipelines de IA de los productos de la empresa. Ese recorrido completo —front, back, infraestructura, modelo— es lo que me permite decidir dónde poner un LLM y, más importante, dónde no ponerlo.

Mi criterio se resume en una frase: el modelo juzga y el código calcula. Un LLM es extraordinario resolviendo juicio semántico y pésimo produciendo un número que alguien tendrá que defender después. Casi todos los problemas que he visto en sistemas con IA vienen de confundir esos dos trabajos.

Priorizo el dominio de la IA, la robustez en producción y la calidad del entregable. Me interesa el software que sigue funcionando cuando algo falla: si el modelo no responde, el sistema degrada y lo dice, no finge. Eso es lo que hace que un producto con IA sea usable de verdad y no una demo.

Idiomas

Español — NativoInglés — Avanzado (C1)

06 — Trayectoria

De maquetar componentes a diseñar pipelines de IA.

Cinco años en la misma empresa, que es su propia señal: entré como Front-End y la recorrí entera hasta liderar la ingeniería de IA.

Ene 2021 — presenteLima, Perú

Primesoft Consulting SRL

AI Engineer & Full-Stack Developer

Empecé como desarrollador Front-End con React y fui evolucionando hacia roles de ingeniería full-stack y de IA a lo largo de mi trayectoria en la empresa.

  • Desarrollé e implementé múltiples aplicaciones potenciadas por IA en producción —chatbots, transcripción automática y dashboards— integrando las APIs de Claude, ChatGPT y Gemini.
  • Construí aplicaciones full-stack de extremo a extremo con Next.js y NestJS, contenerizadas con Docker y respaldadas por MongoDB, del desarrollo a producción.
  • Aceleré el ciclo de desarrollo con IA: refactorización de aplicaciones legacy, refinamiento de tests y preparación de ambientes, además del despliegue de servicios en AWS.
  • Ejecuté campañas de marketing optimizadas con IA para aumentar el tráfico y las ventas hacia landing pages, con un sistema de diseño coherente entre anuncios, landings y productos.
  • React
  • Next.js
  • NestJS
  • Python
  • MongoDB
  • Docker
  • AWS
  • Claude
  • GPT
  • Gemini

07 — Stack

Las herramientas que elijo, y para qué.

Sin barras de porcentaje: nadie sabe qué significa "React 92 %". Lo que sigue es lo que uso en sistemas que están corriendo.

IA / GenAI
  • Claude (Sonnet 5, Haiku 4.5)
  • GPT
  • Gemini
  • Llama
  • AWS Bedrock / Titan
  • Tool-use y salidas estructuradas
  • Whisper
  • pyannote (diarización)
  • Prompt engineering
  • AWS Rekognition (liveness, compare faces)

Roster por tarea: calidad donde la salida es el producto, velocidad donde es extracción.

Backend
  • Python
  • FastAPI
  • Node.js
  • NestJS
  • Express
  • Microservicios
  • Redis / Bull
  • MongoDB
  • PostgreSQL

NestJS por módulos de dominio con DI: cada capacidad es aislada y sustituible.

Frontend & Móvil
  • React
  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind v4
  • Swift
  • SwiftUI
  • UIKit
  • Combine
  • Flutter
Cloud & DevOps
  • AWS (EC2, S3, CloudFront, Lightsail)
  • Docker
  • GitFlow
  • Scrum
Contratos & calidad
  • OpenAPI 3
  • openapi-typescript
  • openapi-fetch
  • Jest
  • Supertest

El contrato como frontera verificada por el compilador, no por la memoria de nadie.

Diseño
  • Sistema de diseño y marca
  • Figma
  • Sketch

08 — Colaboración

Los problemas que sé resolver.

Cuatro situaciones que ya me tocó desenredar, en equipo propio o en proyecto acotado. Si la tuya se parece a alguna, ya tenemos por dónde empezar.

09 — Contacto

Cuéntame qué estás construyendo.

Da igual si es una idea sin empezar o un pipeline que se rompe cada semana: escríbeme, te digo qué haría y si soy la persona adecuada. Sin compromiso y sin discurso de venta.

 

Respondo personalmente cada mensaje. No hay bots ni asistentes.