CE
Ezequiel Blog
Volver al blog

Las 5 cosas que aprendí creando sistemas reales (y no proyectos de tutorial)

Después de años construyendo plataformas que usan personas reales, aprendí que el gap entre un tutorial y producción es enorme. Acá están las cinco lecciones que más dolieron.

1 min de lectura
Terminal con código corriendo en producción

Hay una diferencia enorme entre el código que escribís siguiendo un tutorial y el código que escribís cuando hay usuarios reales del otro lado, servidores que se caen a las 3 AM, y datos que no podés perder.

Llevo años construyendo sistemas que la gente realmente usa. Plataformas de gestión, APIs que procesan transacciones, infraestructura que tiene que estar arriba el fin de semana. Y en ese proceso, me pegué contra paredes que ningún curso me preparó para enfrentar.

Estas son las cinco cosas que más aprendí, y que más duelen cuando las ignorás.


1. Los errores en producción son distintos a los errores en desarrollo

En un tutorial, cuando algo falla, lo ves en la consola. En producción, los errores ocurren en silencio, en el servidor de alguien más, a las 2 AM del domingo.

El primer sistema real que puse en producción no tenía logging. Literalmente nada. Cuando algo fallaba, los usuarios me escribían por WhatsApp diciéndome “la app no anda” y yo no tenía ninguna forma de saber qué pasó, cuándo pasó, ni por qué.

Lo que aprendí:

Logeá todo lo que importa, desde el día uno. No después. Cada request que falla, cada excepción no controlada, cada acción crítica del usuario. Fecha, hora, contexto, stack trace completo.

Diferenciá niveles: INFO para el flujo normal, WARN para situaciones inesperadas pero manejables, ERROR para lo que rompe la experiencia del usuario, CRITICAL para lo que requiere atención inmediata.

Configurá alertas reales. Un log que nadie lee no sirve de nada. Yo uso Telegram para las alertas críticas — un bot que me manda mensaje cuando hay un 500 o cuando una tarea programada falla. Parece simple. Es lo que me salva a las 3 AM.

Y la lección más importante: los errores no controlados no deberían tirar toda la aplicación. Aprendé a manejar excepciones globalmente. Una sola ruta que explota no debería matar el servidor entero.


2. La autenticación real no se parece en nada al tutorial de JWT

Yo también hice el tutorial de “autenticación con JWT en Express en 20 minutos”. Lo hice al menos tres veces. Y lo que implementé en producción es completamente distinto.

El tutorial te dice: generá un token, verificalo en cada request, listo. Lo que el tutorial no te dice:

¿Qué pasa cuando el usuario cierra sesión? Con JWT stateless puro, no podés invalidar un token antes de que expire. Si alguien cierra sesión, el token sigue siendo válido hasta que venza. Si alguien te roba el token, el dueño no puede hacer nada.

¿Qué pasa si el token vence mientras el usuario está usando la app? De repente recibe un 401 en medio de lo que estaba haciendo. Mala experiencia. Necesitás refresh tokens.

¿Qué pasa con el brute force? Si no tenés rate limiting en el endpoint de login, alguien puede intentar millones de contraseñas. Y lo van a intentar. Lo sé porque me pasó.

Lo que implemento hoy en sistemas reales:

  • Access token de vida corta (15 minutos)
  • Refresh token con rotación (se invalida y genera uno nuevo en cada uso)
  • Blacklist de tokens en Redis para invalidación inmediata al logout
  • Rate limiting por IP y por usuario en todos los endpoints de autenticación
  • Lockout temporal después de N intentos fallidos

¿Es más complejo que el tutorial? Sí. ¿Es necesario cuando hay usuarios reales? Absolutamente.


3. La base de datos que funciona en dev te destruye en producción

Mi base de datos de desarrollo tenía 50 filas. La de producción llegó a 200.000 filas en tres meses. La query que antes tardaba 2ms tardaba 8 segundos.

Nadie me dijo que tenía que agregar índices. Ningún tutorial me explicó qué es el connection pooling. Y nadie me habló de migraciones hasta que rompí datos de usuarios reales por una migración mal ejecutada.

Los índices no son opcionales. Cualquier columna por la que filtrás o ordenás frecuentemente necesita un índice. Sin índice, la base de datos escanea toda la tabla para cada query. Con 100 filas no lo notás. Con 100.000, es un desastre.

Las migraciones en producción son operaciones de riesgo. Agregar una columna NOT NULL sin valor default en una tabla con millones de filas puede lockear la tabla entera y tirar la app. Hay formas de hacerlo sin downtime, y necesitás aprenderlas antes de necesitarlas, no después.

El connection pooling salva vidas. Tu app no debería abrir una nueva conexión a la base de datos por cada request. Tenés que reusar conexiones. En Node.js con PostgreSQL, si no configurás un pool, podés agotar las conexiones disponibles bajo carga y la app simplemente deja de poder conectarse.

Siempre tenés que tener backups automáticos, y tenés que probar que funcionan. Un backup que nunca probaste restaurar es un backup que no existe.


4. Desplegar no es subir archivos al servidor

Durante mucho tiempo, mi “deploy” era: conectarme por SSH, hacer git pull, reiniciar el proceso con pm2 restart, y rezar.

Funcionaba. Hasta que no funcionó. Un git pull que rompió algo en producción mientras había usuarios activos. Sin forma de hacer rollback rápido. Sin zero-downtime deployment. Sin nada.

Lo que aprendí sobre deployments reales:

Zero-downtime deployment no es un lujo. Si tu proceso de deploy baja la app aunque sea 5 segundos, estás generando errores para usuarios activos. Con un proxy reverso (Nginx, Caddy) y un proceso que reinicia gracefully, podés deployar sin que nadie lo note.

Las variables de entorno no son opcionales. Nada de credenciales en el código. Nada. Ni en un .env que subís al repo. Las secrets van en variables de entorno del servidor, en un vault, o en el sistema de secrets de tu CI/CD.

Los health checks te salvan. Un endpoint /health que responde 200 cuando todo está bien. Tu proxy reverso lo consulta y, si falla, no manda tráfico al servidor que está caído. Simple, pero crítico.

El CI/CD no es para equipos grandes. Un pipeline básico con GitHub Actions que corre tests, valida el build, y deploya solo cuando pasa todo, puede ser configurado en una tarde y te va a salvar muchas veces.

La diferencia entre deployar con miedo y deployar con confianza es tener el proceso automatizado y probado.


5. Los usuarios reales usan tu app de formas que nunca imaginaste

Cuando terminás un proyecto de tutorial, vos sos el único usuario. Sabés exactamente cómo usarlo porque lo construiste vos. Los usuarios reales no saben nada de cómo lo construiste, y no les importa.

Algunos ejemplos reales de lo que encontré cuando llegaron usuarios reales:

Un usuario pegó texto con emojis en un campo que guardaba en una columna que no soportaba utf8mb4. La fila entera rompía silenciosamente. Perdió datos. No me enteré hasta que me escribió.

Un usuario puso un punto y coma en un nombre de empresa y rompió una exportación CSV que usaba coma como separador (yo había asumido que nadie iba a poner separadores en un nombre).

Usuarios que dejan la sesión abierta una semana y esperan que siga funcionando. Usuarios que hacen click en el botón de submit cinco veces seguidas porque “parece que no hizo nada”. Usuarios que usan la app en un teléfono de 2018 con 512MB de RAM.

Lo que esto me enseñó:

Nunca asumas cómo el usuario va a usar algo. Validá en el backend siempre, sin importar lo que valides en el frontend. El frontend se puede bypassear.

El estado de loading importa. Si un botón no se deshabilita después del primer click, el usuario lo va a clickear mil veces. Cada click es un request al servidor. Eso rompe cosas.

La performance en devices lentos es una feature. Probá tu app en modo “throttle” en las DevTools. Si tarda 8 segundos en cargar con 3G slow, estás perdiendo usuarios.

El feedback visual no es diseño, es funcionalidad. Un usuario que no sabe si su acción se procesó va a intentar de nuevo. Eso genera duplicados, datos corruptos, y confusión.


El gap real

El gap entre un proyecto de tutorial y un sistema en producción no es de conocimiento técnico. Es de mentalidad.

En un tutorial, vos sos el autor y el usuario. Conocés el happy path porque lo diseñaste. Sabés lo que hace cada botón. No vas a romper nada a propósito.

En producción, los usuarios no son vos. Los errores van a ocurrir. Los datos van a ser inesperados. El servidor se va a caer en el peor momento posible.

La única forma de cruzar ese gap es construir cosas reales, con usuarios reales, y aprender de las cosas que se rompen. Ningún tutorial te puede dar eso.

Pero conocer de antemano estas cinco áreas — logging, autenticación robusta, bases de datos a escala, deployments seguros, y UX real — te va a ahorrar una cantidad enorme de dolor.

Construí cosas. Rompé cosas. Aprendé de las cosas que rompen.