Herman Primo.

Herman Enrique Primo Escobar

|

Backend Developer | Desarrollo de APIs seguras, automatización y soluciones backend escalables | Python • PHP • NestJs • NextJs • React • MySQL • PostgreSQL • AWS • Git • Linux | Divulgando conocimiento para todos

Villahermosa, Tabasco, México
Herman Enrique Primo Escobar

Experiencia y Skills

Tecnología aplicada en proyectos reales.

Experiencia profesional, formación académica y habilidades técnicas conectadas con soluciones backend, APIs e infraestructura.

PrimoTec

Profesional independiente · Híbrido

Desarrollador full stack

jul 2024 - actualidad · 2 años 1 mes

Villahermosa, Tabasco, México

Desde 2024 he trabajado de manera independiente desarrollando solucion...

Desarrollo Full StackInfraestructura de softwarePythonDjango

Servicios de Mantenimiento y Soporte Técnico

sep 2018 - actualidad · 7 años 11 meses

Villahermosa, Tabasco, México

Desde 2018, brindo servicios tecnológicos y soporte técnico especializ...

Soporte TécnicoInfraestructuraLinuxUbuntu Server

SITAI

Jornada completa · Híbrido

Responsable de TI

nov 2024 - feb 2026 · 1 año 3 meses

Ciudad del Carmen, Campeche, México

Me desempeñé como Responsable de TI y Desarrollador Full Stack en SITAI, participando tanto en el desarrollo de soluciones tecnológicas como en la administración y mantenimiento de la infraestructura tecnológica de la empresa. Trabajé principalmen...

Soporte TécnicoDirección de TIPythonDjango

TecNM | HackatecNM Nacional 2024

Contrato de prácticas · Presencial

Desarrollador de back-end

oct 2024 - nov 2024 · 1 mes

Colima, Colima, México

Participé como Backend Developer en la etapa nacional de HackaTec TecNM 2024, una competencia intensiva de innovación tecnológica desarrollada durante 48 horas continuas, donde colaboré en el desarrollo de una aplicación móvil enfocada en el monit...

Desarrollo web back endBases de datosPythonDjango

TecNM | HackatecNM Regional 2024

Contrato de prácticas · Presencial

Desarrollador de back-end

ago 2024 - sep 2024 · 1 mes

Villahermosa, Tabasco, México

Participé como Backend Developer en HackaTec TecNM 2024, una competencia regional de innovación tecnológica desarrollada durante 36 horas continuas, donde colaboré en el desarrollo de una aplicación enfocada en la atención rápida de emergencias sa...

Desarrollo web back endBases de datosGolangPostgreSQL

TecNM | Campus Villahermosa

Contrato de prácticas · Presencial

Practicante de Soporte y Sistemas

ago 2021 - ago 2024 · 3 años 1 mes

Villahermosa, Tabasco, México

Brindé apoyo técnico y operativo en el centro de cómputo institucional, participando en el desarrollo y actualización de sistemas internos, mantenimiento de plantillas y soporte a plataformas tecnológicas. Colaboré en la revisión y administración...

Soporte TécnicoDesarrollo de softwarePHPBootstrap

1/5

Skills

Backend

1/5

Backend

Python
90%
Django
90%
Django REST Framework
90%
PHP
80%
NestJS
70%
Golang
60%

Frontend

Next.js
85%
React
85%
JavaScript
90%
TypeScript
70%
Tailwind CSS
90%
Bootstrap
85%

Cloud

AWS EC2
75%
AWS RDS
70%

DevOps

Docker
80%
Nginx
80%
Linux
90%
Gunicorn
70%

Base de datos

MySQL
90%
PostgreSQL
85%

Herramientas

Git
90%
Postman
90%

Infraestructura

Ubuntu Server
80%
Linux Server Administration
80%

Mobile

Ionic
75%
Flutter
70%

Diseño UI/UX

Figma
70%
Responsive Design
85%

Testing

Selenium
75%
API Testing
85%

Sobre mí

Diseño la lógica que mantiene funcionando los sistemas.

Backend, APIs, automatización e infraestructura pensada para resolver problemas reales.

Backend Developer enfocado en el desarrollo de APIs seguras, automatización de procesos y soluciones escalables. Me apasiona construir sistemas eficientes, desde la lógica del servidor y la arquitectura backend hasta la integración con bases de datos y servicios en la nube.

He participado en proyectos académicos y tecnológicos relacionados con plataformas digitales, marketplaces y soluciones orientadas a innovación y tecnología. Además, disfruto compartir contenido técnico sobre backend, bases de datos, APIs y arquitectura de software. Actualmente continúo fortaleciendo mis conocimientos y desarrollando soluciones modernas y escalables.

Cliente

API

Backend

DB

Cloud

Paso 1

Cliente

Una persona entra a tu sitio, inicia sesión o realiza una acción.

Servicios

Soluciones tecnológicas para construir, integrar y escalar.

Servicios enfocados en backend, APIs, bases de datos, infraestructura cloud y mantenimiento de aplicaciones.

Proyectos

Soluciones construidas con enfoque real.

Una selección de proyectos donde aplico backend, APIs, bases de datos, infraestructura y desarrollo full stack.

Ver todos los proyectos
Portafolio Profesional
Base de datosAPI REST

Portafolio Profesional

Portafolio profesional desarrollado para centralizar experiencia, habilidades, proyectos, servicios y contenido técnico en una plataforma moderna y escalable.

Blog

Ideas, backend y tecnología explicados con claridad.

Publicaciones técnicas sobre backend, APIs, bases de datos, seguridad, arquitectura y desarrollo de software.

Ver todas las publicaciones
Escribes una dirección en Google Maps… y segundos después ya tienes una ruta completa.
Arquitectura de Software

Escribes una dirección en Google Maps… y segundos después ya tienes una ruta completa.

Abres Google Maps. Escribes tu destino. Presionas buscar. Y en apenas unos segundos aparecen: 📍 Tu ubicación. 🛣️ Varias rutas posibles. ⏱️ El tiempo estimado. 🚦 El tráfico. 🚗 Rutas alternativas. 🚧 Avisos sobre incidentes o restricciones. Todo parece inmediato. Pero detrás de ese mapa hay mucho más que una imagen con calles. 👉 Hay sistemas procesando datos geográficos, tráfico, ubicaciones y rutas constantemente para responder una pregunta aparentemente sencilla: “¿Cómo llego hasta ahí?” 🧠 ¿Qué ocurre cuando buscas un lugar? Supongamos que escribes: Aeropuerto de Cancún Para una persona, ese texto tiene un significado claro. Pero una computadora necesita convertirlo en información que pueda procesar. Una de las primeras tareas consiste en identificar qué lugar estás buscando y asociarlo con una posición geográfica. Este proceso está relacionado con la geocodificación . Conceptualmente: "Aeropuerto de Cancún" ↓ Buscar lugar ↓ Identificar ubicación ↓ Latitud y longitud El resultado puede ser algo parecido a: 21.0365, -86.8771 Ahora el sistema ya no trabaja solamente con un nombre. Tiene un punto concreto sobre la Tierra. 📍 ¿Qué son la latitud y longitud? La mayoría de los sistemas de mapas representan ubicaciones mediante coordenadas. Por ejemplo: Latitud: 21.0365 Longitud: -86.8771 La latitud indica la posición de norte a sur. La longitud indica la posición de este a oeste. Con ambas coordenadas es posible representar una ubicación bastante precisa. Así, nombres como: Mi casa Aeropuerto Restaurante Hotel terminan convirtiéndose internamente en coordenadas que los sistemas pueden utilizar para realizar cálculos. 🔎 Pero buscar un lugar no siempre es tan sencillo Imagina que escribes: San José Puede existir más de un lugar con ese nombre. Entonces el sistema puede necesitar considerar otros datos: Tu ubicación actual. El país. El historial de búsqueda. Lugares cercanos. Popularidad. Contexto de la consulta. El objetivo es intentar determinar qué lugar querías encontrar realmente. Por eso una búsqueda geográfica no consiste únicamente en comparar texto. 🔄 También existe la geocodificación inversa Puede ocurrir el proceso contrario. Tu teléfono conoce tus coordenadas: 17.9894, -92.9475 pero necesitas mostrar una dirección comprensible. Entonces el sistema puede convertir: Coordenadas ↓ Calle ↓ Ciudad ↓ Estado ↓ País A este proceso se le conoce como geocodificación inversa . Es útil para mostrar información como: "Estás cerca de Avenida..." en lugar de obligarte a leer números de latitud y longitud. 🛰️ ¿Cómo sabe dónde estás? Tu teléfono puede determinar su ubicación utilizando varias fuentes. Por ejemplo: 🛰️ GPS. 📶 Redes Wi-Fi. 📡 Redes móviles. 📍 Información del dispositivo. Dependiendo del entorno, estas señales pueden combinarse para obtener una ubicación estimada. El sistema operativo entrega esa información a la aplicación si tiene los permisos necesarios. Entonces Google Maps puede trabajar con algo parecido a: Origen: 17.9894, -92.9475 Destino: 21.0365, -86.8771 Ahora ya conoce los dos extremos del problema. 🛣️ ¿Cómo representa Google Maps las calles? Aquí empieza una de las partes más interesantes. Para un sistema de rutas, una ciudad puede representarse como un enorme grafo . Un grafo está formado principalmente por: Nodos + Conexiones Podemos imaginar las intersecciones como nodos. Y las carreteras como conexiones. Por ejemplo: A ───── B │ │ │ │ C ───── D Cada letra puede representar una intersección. Cada línea representa una calle que permite viajar entre ellas. ⚙️ Las conexiones tienen información adicional Una carretera no es solamente: A → B También puede tener información asociada. Por ejemplo: Distancia: 2.4 km Velocidad estimada: 50 km/h Sentido: una dirección Peaje: no Tipo: avenida También pueden existir reglas como: No girar a la izquierda Carretera cerrada Acceso restringido Solo transporte público Toda esa información puede modificar qué rutas son válidas. 🚦 La mejor ruta no siempre es la más corta Imagina dos opciones. Ruta A 10 km Ruta B 13 km A simple vista podríamos elegir la A. Pero ahora agregamos tráfico: Ruta A 10 km 45 minutos Ruta B 13 km 25 minutos Entonces la ruta más larga puede ser mejor. Por eso los sistemas de navegación no buscan únicamente minimizar distancia. Pueden intentar minimizar: Tiempo estimado considerando las condiciones actuales. 🧠 El problema se parece a buscar un camino dentro de un grafo Conceptualmente tenemos: Origen ↓ Miles de posibles caminos ↓ Destino El sistema necesita encontrar una ruta conveniente entre ambos puntos. En informática existen algoritmos clásicos para resolver problemas de caminos, como: Dijkstra. A*. Pero una plataforma a escala global necesita muchas optimizaciones adicionales para responder rápidamente sobre redes viales gigantescas. La idea esencial sigue siendo: 👉 explorar posibles caminos y encontrar uno con un costo conveniente. 💰 ¿Qué significa “costo” en una ruta? En algoritmos de grafos, cada conexión puede tener un costo. Podría ser simplemente: Distancia Pero en navegación real puede representar una combinación de factores. Por ejemplo: Tiempo de viaje + Tráfico + Restricciones + Tipo de vía + Peajes Dependiendo de tus preferencias. Por ejemplo, si activas: Evitar autopistas el costo o la disponibilidad de ciertas carreteras puede cambiar. Si seleccionas: Evitar peajes el sistema busca rutas diferentes. 🚗 El medio de transporte cambia completamente el problema La ruta correcta depende de cómo estés viajando. No es lo mismo: 🚗 Automóvil. 🚶 Caminar. 🚲 Bicicleta. 🚌 Transporte público. Una carretera puede ser excelente para un automóvil pero imposible para un peatón. Una calle puede permitir bicicletas pero tener restricciones para vehículos. Entonces el sistema puede trabajar con redes y reglas diferentes dependiendo del modo seleccionado. 🚶 Una ruta caminando Si eliges caminar, puede considerar: Banquetas Cruces Puentes peatonales Senderos Escaleras que quizá no formen parte de una ruta para automóvil. 🚲 Una ruta en bicicleta Podría considerar: Ciclovías Pendientes Calles permitidas Infraestructura ciclista dependiendo de los datos disponibles. 🚌 Transporte público Aquí el problema se vuelve todavía más interesante. Ahora no basta con saber dónde están las calles. También necesitas: Rutas de autobús Paradas Horarios Estaciones Transbordos Entonces una ruta puede ser: Caminar 500 m ↓ Tomar autobús ↓ Viajar 7 paradas ↓ Cambiar de línea ↓ Caminar 300 m El backend necesita combinar múltiples tipos de información. 🚦 ¿Cómo sabe dónde hay tráfico? Una ruta puede ser correcta geográficamente y aun así ser terrible por tráfico. Por eso Google Maps puede utilizar diferentes fuentes de información para estimar cómo se están moviendo las carreteras. El sistema puede detectar que normalmente un tramo debería recorrerse en: 5 minutos pero actualmente está tardando: 20 minutos Eso indica que existe una condición diferente a la habitual. 📱 Los dispositivos pueden ayudar a estimar movimiento Los teléfonos y dispositivos que comparten cierta información de ubicación de manera agregada pueden contribuir a estimar cómo se mueve el tráfico. Imagina muchos dispositivos desplazándose sobre la misma avenida. Si normalmente avanzan a: 50 km/h pero ahora la mayoría se mueve a: 8 km/h el sistema puede inferir que existe congestión. Naturalmente, plataformas de este tipo también pueden combinar otras fuentes y modelos para mejorar las estimaciones. 🕒 También existe tráfico histórico No toda predicción depende únicamente de lo que está ocurriendo en este segundo. El sistema puede conocer patrones históricos. Por ejemplo: Lunes 8:00 AM ↓ Esta avenida normalmente tiene tráfico Mientras: Domingo 6:00 AM ↓ Normalmente está despejada Entonces el tiempo estimado puede combinar: Condiciones actuales + Patrones históricos + Características de la ruta Eso ayuda a estimar mejor cuánto tardarás. ⏱️ ¿Cómo calcula el tiempo estimado? Supongamos una ruta dividida en varios segmentos. Segmento A 5 minutos Segmento B 8 minutos Segmento C 4 minutos El sistema puede combinar esos tiempos: 5 + 8 + 4 = 17 minutos Pero la realidad es más compleja. Los tiempos pueden variar constantemente. Por eso el ETA, o Estimated Time of Arrival , puede actualizarse mientras conduces. 🚀 ¿Por qué cambia el tiempo durante el viaje? Empiezas con: Llegada: 15:40 Después: Llegada: 15:46 Y más adelante vuelve a: 15:42 No necesariamente significa que el sistema estuviera “equivocado”. Las condiciones pueden estar cambiando. Por ejemplo: Tráfico nuevo Accidente Carretera despejada Cambio de velocidad Ruta diferente El sistema recalcula continuamente utilizando información más reciente. 🔄 ¿Por qué puede ofrecer una ruta nueva mientras conduces? Imagina que empezaste con: Ruta A 30 minutos Unos minutos después ocurre un accidente. Ahora: Ruta A 50 minutos Pero existe otra alternativa: Ruta B 35 minutos El sistema vuelve a evaluar las posibilidades. Entonces puede mostrar: “Se encontró una ruta más rápida.” Conceptualmente: Ruta actual ↓ Condiciones cambian ↓ Recalcular ↓ Comparar alternativas ↓ Ofrecer nueva ruta 🚧 Accidentes, cierres y restricciones No toda modificación viene del tráfico. También pueden existir eventos como: Accidente Carretera cerrada Obras Inundación Restricción temporal Si esa información llega al sistema, puede modificar inmediatamente el grafo utilizado para calcular rutas. Por ejemplo: A ───── B X │ C Si una conexión deja de estar disponible, el algoritmo tiene que buscar otro camino. ↩️ Los sentidos de circulación también importan Imagina: Calle A pero solamente permite conducir: → No puedes tratarla como: ↔ El grafo debe representar esta restricción. Lo mismo ocurre con: No girar No retorno Solo vuelta derecha La ruta tiene que ser legal y físicamente posible, no solamente corta. 🗺️ ¿El teléfono descarga todo el mapa del mundo? No. Sería completamente innecesario. Tu dispositivo necesita principalmente información relacionada con la zona que estás viendo y utilizando. Cuando mueves el mapa: Zona actual ↓ Usuario desplaza ↓ Solicitar nueva información Se descargan los datos necesarios para representar esa nueva región. Esto permite trabajar con mapas enormes sin cargar todo el planeta en memoria. 🧩 El mapa puede dividirse en pequeñas secciones Los sistemas de mapas suelen dividir la superficie en tiles o fragmentos. Conceptualmente: ┌────┬────┬────┐ │ A │ B │ C │ ├────┼────┼────┤ │ D │ E │ F │ ├────┼────┼────┤ │ G │ H │ I │ └────┴────┴────┘ Si estás viendo: E quizá la aplicación también prepare: B D F H porque podrías desplazarte hacia ellas. Así la interfaz se siente fluida. 🔍 El nivel de zoom también cambia lo que necesitas Cuando estás muy alejado, quizá solo necesitas: Países Estados Carreteras principales Pero al acercarte: Calles Negocios Edificios Puntos de interés Mostrar absolutamente todo desde el principio sería innecesario. El sistema entrega más detalle conforme aumenta el zoom. 📍 Buscar lugares es otro problema diferente Cuando escribes: pizza Google Maps no solamente necesita encontrar coordenadas. También debe buscar negocios o puntos de interés relacionados. Puede considerar cosas como: Distancia Coincidencia de nombre Categoría Relevancia Horario entre otros factores. Entonces: Buscar un lugar y: Calcular una ruta son problemas diferentes que trabajan juntos. 🏪 Información de lugares Una ubicación puede tener datos como: Nombre Dirección Coordenadas Categoría Horario Teléfono Y posiblemente otra información asociada. Cuando seleccionas el lugar, esos datos pueden provenir de servicios distintos de los que calculan la ruta. La aplicación los presenta como si todo fuera una sola cosa. 🧠 Una sola pantalla puede utilizar muchos servicios Cuando ves una ruta, detrás pueden existir sistemas especializados en: Geocodificación ↓ Búsqueda de lugares ↓ Mapas ↓ Ruteo ↓ Tráfico ↓ ETA La interfaz integra todos esos resultados. Por eso una aplicación moderna puede parecer sencilla aunque internamente dependa de muchos componentes. 📡 ¿Todo se calcula desde el backend? No necesariamente todo. El dispositivo también realiza trabajo. Por ejemplo: Renderizar mapa Mostrar animaciones Dibujar ruta Actualizar interfaz Obtener ubicación Pero operaciones que requieren enormes cantidades de datos globales suelen apoyarse fuertemente en infraestructura backend. El teléfono no necesita tener almacenadas todas las calles, condiciones de tráfico y lugares del mundo. 🚘 ¿Qué pasa mientras conduces? Tu dispositivo puede enviar actualizaciones de ubicación periódicamente. Conceptualmente: Ubicación 1 ↓ Ubicación 2 ↓ Ubicación 3 ↓ Ubicación 4 La aplicación compara tu posición con la ruta. Si continúas correctamente: Seguir indicaciones Si te desvías: Ubicación ya no coincide ↓ Recalcular ↓ Nueva ruta Todo ocurre casi automáticamente. ↪️ ¿Cómo sabe cuándo decirte “gira a la derecha”? La ruta no es solamente una línea. Puede estar compuesta por instrucciones. Por ejemplo: Avanza 2 km ↓ Gira a la derecha ↓ Continúa 500 m ↓ Toma la salida La aplicación conoce la geometría de la ruta y tu posición aproximada. Entonces puede determinar cuándo estás cerca de una maniobra y mostrar la instrucción correspondiente. 📍 La ubicación nunca es perfectamente exacta Otro detalle importante: El GPS tiene margen de error. Tu teléfono podría estimar que estás en: Punto A cuando realmente estás algunos metros más adelante. En una carretera con varias calles paralelas esto puede crear problemas. Por eso las aplicaciones de navegación necesitan interpretar la ubicación y asociarla con una vía probable. Esto puede relacionarse con técnicas de map matching . 🛣️ ¿Qué es map matching? Supongamos que el GPS devuelve: • ligeramente fuera de la carretera. El sistema puede determinar: “Probablemente el usuario realmente está sobre esta vía.” Entonces ajusta conceptualmente: GPS aproximado ↓ Carretera más probable ↓ Posición utilizada para navegación Esto ayuda a que la ruta no salte constantemente entre calles cercanas. 📴 ¿Y los mapas sin conexión? Cuando descargas un mapa offline, parte de la información geográfica necesaria se almacena localmente. Entonces puedes utilizar ciertas funcionalidades sin conexión. Pero algunas capacidades pueden verse limitadas porque no tienes acceso a información actualizada. Por ejemplo: Mapa base ✅ Rutas posibles ✅ Tráfico en tiempo real ❌ Cambios recientes ❌ dependiendo de lo que se haya descargado y de la funcionalidad disponible. 📦 Caché también ayuda No tendría sentido descargar continuamente el mismo fragmento del mapa. Por eso puede utilizarse caché. Por ejemplo: Solicitar zona ↓ Guardar temporalmente ↓ Usuario vuelve ↓ Reutilizar Esto reduce: Transferencias. Latencia. Uso de datos. Y mejora la experiencia. 🌎 El verdadero desafío es la escala Calcular una ruta dentro de una ciudad ya es interesante. Pero ahora imagina hacerlo para: Millones de usuarios simultáneamente. Cada uno tiene: Origen diferente Destino diferente Horario diferente Medio de transporte diferente Tráfico diferente El backend debe responder rápidamente a todas esas combinaciones. 📈 Los datos también cambian Una red vial no es estática. Constantemente pueden existir: Nuevas carreteras Calles cerradas Negocios nuevos Cambios de sentido Obras Nuevos accesos La plataforma necesita mantener sus datos actualizados. Una ruta perfecta utilizando datos antiguos puede convertirse en una ruta incorrecta. 🔄 Actualizar mapas es un proceso continuo Conceptualmente: Nuevos datos ↓ Validación ↓ Procesamiento ↓ Actualizar información geográfica ↓ Disponible para usuarios Por eso mantener un mapa global actualizado es un problema de datos además de un problema de visualización. ⚠️ Error común: pensar que Maps solo “dibuja calles” La interfaz visual es solamente una pequeña parte. Para responder: ¿Cómo llego? el sistema puede necesitar resolver: ¿Dónde estás? ¿Dónde está el destino? ¿Qué caminos existen? ¿Qué caminos puedes utilizar? ¿Cuánto tardaría cada uno? ¿Qué tráfico hay? ¿Qué ruta conviene ahora? Todo eso ocurre antes de dibujar una simple línea azul sobre la pantalla. 🛠️ ¿Qué podemos aprender de Google Maps como desarrolladores? Este tipo de sistema deja varias ideas interesantes. ✔️ Modelar correctamente los datos puede hacer posible resolver problemas muy complejos. ✔️ Los grafos son fundamentales para representar redes. ✔️ El resultado “óptimo” depende de qué métrica quieras optimizar. ✔️ Los datos en tiempo real pueden cambiar decisiones previamente correctas. ✔️ Dividir grandes cantidades de información en pequeñas partes facilita su entrega. ✔️ Caché y procesamiento distribuido ayudan a reducir latencia. ✔️ Una interfaz sencilla puede esconder múltiples servicios especializados. 🧩 La realidad Cuando escribes: “Aeropuerto de Cancún” y presionas: Cómo llegar el sistema necesita transformar una frase en todo un problema computacional. Conceptualmente: Texto ↓ Lugar ↓ Coordenadas ↓ Red de carreteras ↓ Rutas posibles ↓ Tráfico y restricciones ↓ Tiempo estimado ↓ Mejor alternativa ↓ Mapa Y después continúa repitiendo parte del proceso mientras conduces. Porque la mejor ruta de hace cinco minutos puede no ser la mejor ruta ahora. 💬 Un mapa digital no solamente necesita saber dónde están las calles. Necesita entender cómo están conectadas, cuánto cuesta recorrerlas y cómo están cambiando las condiciones en ese momento. 👉 La próxima vez que Google Maps diga: “Se encontró una ruta más rápida” recuerda que detrás probablemente se volvió a resolver el mismo problema con información más reciente. 🔥 El backend no se ve, pero sin él, nada funciona.

26 ago 2026
Leer publicación
¿Cómo puede tu aplicación saber que ocurrió algo en otro sistema… sin estar preguntando todo el tiempo?
APIs

¿Cómo puede tu aplicación saber que ocurrió algo en otro sistema… sin estar preguntando todo el tiempo?

Imagina que tu aplicación necesita saber cuándo ocurre algo fuera de ella. Por ejemplo: 💳 Se confirma un pago. 📦 Cambia el estado de un pedido. 👤 Se registra un usuario en otro sistema. ☁️ Termina un procesamiento externo. Una forma de hacerlo sería preguntar constantemente: ¿Ya ocurrió? ¿Y ahora? ¿Ya? ¿Ya? Ese enfoque se conoce como polling . Funciona. Pero puede generar muchas peticiones innecesarias. 👉 Una alternativa mucho más eficiente es hacer que el otro sistema te avise automáticamente cuando ocurra algo importante. Ese es el principio detrás de los webhooks . 🧠 ¿Qué es un webhook? Un webhook es un mecanismo que permite que un sistema envíe una notificación HTTP a otro sistema cuando ocurre un evento. En lugar de que tu aplicación pregunte: ¿El pago ya fue aprobado? el proveedor puede decirte: El pago acaba de ser aprobado. Y enviar automáticamente esa información a tu servidor. Conceptualmente: Sistema externo ↓ Ocurre evento ↓ Webhook ↓ Tu backend Tu aplicación simplemente necesita estar preparada para recibir esa petición. 🌐 Polling vs webhook La diferencia puede verse fácilmente. Polling Tu aplicación ↓ ¿Cambió algo? ↓ No Tu aplicación ↓ ¿Cambió algo? ↓ No Tu aplicación ↓ ¿Cambió algo? ↓ Sí Tu sistema tiene que preguntar repetidamente. Webhook Tu aplicación ↓ Espera Sistema externo ↓ Ocurre evento ↓ Envía notificación ↓ Tu aplicación reacciona Aquí no necesitas realizar consultas continuamente. El otro sistema inicia la comunicación. ⚙️ ¿Cómo recibe un webhook tu aplicación? Primero necesitas crear un endpoint preparado específicamente para recibir estos eventos. Por ejemplo: POST /webhooks/pagos Después registras esa dirección en el proveedor externo. Conceptualmente: https://api.midominio.com/webhooks/pagos Cuando ocurre un evento importante, el proveedor realiza una petición HTTP hacia esa URL. 🚀 El flujo completo Supongamos que utilizas un proveedor externo para procesar pagos. El usuario realiza una compra. El flujo puede ser: Usuario ↓ Realiza pago ↓ Proveedor procesa ↓ Pago aprobado ↓ Proveedor envía webhook ↓ POST /webhooks/pagos ↓ Tu backend recibe evento ↓ Actualiza pedido El usuario no necesita estar esperando a que tu aplicación consulte constantemente al proveedor. 📦 ¿Qué información puede traer un webhook? Depende del proveedor. Pero podría enviar algo como: { "id": "evt_1842", "type": "payment.completed", "data": { "order_id": 1842, "status": "completed" } } Tu backend recibe el contenido. Después puede revisar: ¿Qué evento ocurrió? Por ejemplo: payment.completed Y ejecutar la lógica correspondiente. 💳 Ejemplo con un pago Supongamos que inicialmente tienes: Pedido #1842 Estado: pendiente El proveedor procesa el pago. Después envía: payment.completed Tu backend puede hacer: Webhook recibido ↓ Validar evento ↓ Buscar pedido 1842 ↓ Actualizar estado ↓ Pedido = pagado Y posteriormente podría iniciar otras tareas. Por ejemplo: Pedido pagado ├── Enviar correo ├── Preparar envío └── Generar factura 📦 Otro ejemplo: seguimiento de envíos Imagina que utilizas una empresa de paquetería. Tu sistema guarda: Pedido #500 Estado: enviado La paquetería podría enviarte eventos como: package.in_transit package.out_for_delivery package.delivered Entonces tu aplicación puede actualizar automáticamente: Enviado ↓ En tránsito ↓ En reparto ↓ Entregado sin estar preguntando constantemente por cada paquete. ☁️ Procesos que tardan mucho tiempo Los webhooks también son útiles cuando una operación tarda demasiado como para mantener una petición HTTP abierta. Por ejemplo: Generar video Procesar archivo Analizar documento Crear reporte Convertir imagen Tu aplicación puede iniciar el trabajo: POST /procesar y recibir: Procesamiento iniciado Después continúa haciendo otras cosas. Minutos más tarde, el sistema externo envía: processing.completed mediante webhook. 🧩 Esto permite procesos asíncronos En lugar de: Aplicación ↓ Solicitar procesamiento ↓ Esperar 5 minutos ↓ Respuesta puedes tener: Aplicación ↓ Solicitar procesamiento ↓ Continuar Y después: Servicio externo ↓ Proceso terminado ↓ Webhook ↓ Aplicación Esto es mucho más adecuado para operaciones largas. 🔐 Pero no puedes confiar en cualquier petición Aquí aparece una parte muy importante. Tu endpoint: POST /webhooks/pagos está preparado para recibir información. Pero ¿qué evita que otra persona haga una petición falsa? Por ejemplo: { "type": "payment.completed", "order_id": 1842 } Si tu backend confía únicamente en el cuerpo recibido, alguien podría intentar marcar pedidos como pagados sin haber realizado ningún pago. Por eso los webhooks deben validarse. 🛡️ Verificar la autenticidad del evento Muchos proveedores incluyen una firma criptográfica en la petición. Por ejemplo, mediante un header: Webhook-Signature: abc123... El proveedor y tu servidor comparten cierta información secreta. Tu backend utiliza esa información para comprobar que el mensaje realmente fue generado por el proveedor y que su contenido no fue modificado. Conceptualmente: Webhook recibido ↓ Leer firma ↓ Calcular firma esperada ↓ Comparar ↓ ¿Coincide? ├── Sí → procesar └── No → rechazar Este paso es crítico. ⚠️ HTTPS también es necesario Los webhooks deberían recibirse mediante: HTTPS y no mediante HTTP sin cifrado. Esto protege la comunicación mientras viaja por la red. Pero HTTPS y firma resuelven problemas diferentes. Podemos pensar: HTTPS ↓ Protege la comunicación Firma ↓ Ayuda a verificar el origen e integridad del evento Ambos son importantes. 🚀 ¿Qué pasa cuando el webhook llega correctamente? El proveedor necesita saber que tu servidor recibió el evento. Normalmente espera una respuesta HTTP exitosa. Por ejemplo: 200 OK o algún otro código exitoso según la integración. Conceptualmente: Proveedor ↓ Webhook ↓ Tu servidor ↓ Procesar ↓ 200 OK Con esa respuesta el proveedor puede considerar que la entrega fue aceptada. 💥 ¿Y si tu servidor está caído? Supongamos: Proveedor ↓ Webhook ↓ ❌ Tu servidor no responde El proveedor podría recibir: Timeout o: 500 Internal Server Error Muchos servicios implementan reintentos. Por ejemplo: Intento 1 → Error ↓ Esperar Intento 2 → Error ↓ Esperar más Intento 3 → Correcto Esto permite que una pequeña caída de tu servidor no haga que pierdas el evento inmediatamente. 🔁 Por eso un webhook puede llegar varias veces Este detalle es fundamental. Supongamos: Webhook llega ↓ Tu backend actualiza pedido ↓ Pero la respuesta 200 nunca llega al proveedor Desde el punto de vista del proveedor: No sé si lo recibió. Entonces puede enviarlo de nuevo. Ahora tu aplicación recibe: payment.completed por segunda vez. 👉 Por eso debes asumir que un webhook puede ser duplicado. 🔐 Aquí aparece nuevamente la idempotencia Supongamos que recibes: evento_id = evt_12345 La primera vez: ¿Ya procesé evt_12345? ↓ No ↓ Procesar ↓ Guardar evt_12345 La segunda: ¿Ya procesé evt_12345? ↓ Sí ↓ No volver a ejecutar efectos Así evitas situaciones como: Enviar dos correos Generar dos facturas Descontar inventario dos veces Un webhook confiable debe soportar duplicados. 🗄️ Guardar los eventos recibidos puede ser muy útil Una estrategia común consiste en registrar información como: event_id tipo fecha payload estado Por ejemplo: evt_12345 payment.completed 2026-08-15 procesado Esto permite: Detectar duplicados. Investigar errores. Auditar eventos. Reprocesar fallos. Saber qué ocurrió. Cuando una integración falla en producción, este historial puede ser extremadamente útil. ⚡ Conviene responder rápido Otro punto importante: No siempre deberías ejecutar toda la lógica pesada antes de responder al proveedor. Imagina: Webhook ↓ Generar factura ↓ Enviar correo ↓ Actualizar inventario ↓ Procesar reporte ↓ Esperar 15 segundos ↓ 200 OK El proveedor podría asumir que tu endpoint tardó demasiado y considerar la entrega fallida. Incluso podría hacer un reintento. 🚀 Una estrategia mejor Puedes hacer: Webhook recibido ↓ Validar firma ↓ Guardar evento ↓ Enviar a cola ↓ 200 OK Y después: Worker ↓ Procesar evento ↓ Actualizar datos ↓ Enviar correo ↓ Generar factura Así confirmas rápidamente la recepción y continúas el trabajo de forma asíncrona. 📬 Webhooks y colas funcionan muy bien juntos Conceptualmente: Proveedor ↓ Webhook ↓ Tu API ↓ Cola de mensajes ↓ Worker ↓ Procesamiento Esto permite desacoplar: Recepción del evento de: Procesamiento del evento Si el procesamiento tarda o falla, no necesitas mantener al proveedor esperando. ⚠️ Pero responder 200 demasiado pronto también puede ser peligroso Supongamos: Recibir webhook ↓ 200 OK ↓ Intentar guardar ↓ ❌ Base de datos falla Ahora el proveedor cree que todo está correcto. Pero tú perdiste el evento. Por eso una estrategia segura suele ser confirmar después de haber almacenado de forma confiable la información necesaria. Por ejemplo: Recibir ↓ Validar ↓ Persistir evento ↓ 200 OK ↓ Procesar después Así aunque el worker falle posteriormente, el evento ya quedó registrado. 🧩 ¿Qué pasa si el evento llega desordenado? Este es otro problema real. Imagina: Evento A: pedido_enviado Evento B: pedido_entregado Normalmente esperamos: Enviado ↓ Entregado Pero por problemas de red o reintentos podrías recibir: Entregado ↓ Enviado Si actualizas el estado ciegamente, podrías terminar regresando el pedido a un estado anterior. 🧠 No siempre puedes asumir el orden Tu procesamiento puede necesitar considerar: Timestamp del evento. Versión. Estado actual. Secuencia. Reglas del negocio. Por ejemplo: Estado actual = entregado Evento recibido = enviado Tal vez debas ignorarlo porque representa información anterior. Esto depende de la integración. ⏰ Los webhooks también pueden llegar tarde Un evento puede ocurrir a: 10:00 pero llegar a: 10:05 por problemas de red o reintentos. Por eso no siempre debes interpretar: Hora de recepción como: Hora en que ocurrió el evento Si el proveedor incluye un timestamp del evento, puede ser útil conservarlo. 💳 Un webhook tampoco siempre debe ser tu única fuente de verdad Imagina que recibes: payment.completed Dependiendo del nivel de riesgo de la operación, tu sistema puede decidir verificar adicionalmente el estado directamente con el proveedor. Por ejemplo: Webhook recibido ↓ Verificar firma ↓ Consultar operación al proveedor ↓ Confirmar estado ↓ Actualizar pedido No siempre es necesario. Pero en operaciones críticas puede utilizarse como capa adicional de validación. 🔎 También existe el problema del replay Supongamos que un atacante obtiene una petición válida previamente enviada. Después intenta reenviarla varias veces. Aunque la firma sea correcta, el evento es antiguo. Por eso algunos esquemas de firma también incluyen información temporal. El backend puede comprobar: Firma válida ✅ Timestamp reciente ✅ Evento no procesado ✅ antes de aceptarlo. 🧠 Webhook no es exactamente lo mismo que WebSocket Los nombres pueden confundir. Un webhook normalmente es: Servidor A ↓ Petición HTTP puntual ↓ Servidor B Un WebSocket mantiene una conexión bidireccional abierta: Cliente ↔ Servidor Los webhooks son especialmente útiles para comunicación servidor a servidor basada en eventos. No son una conexión permanente. 🧩 Tampoco es lo mismo que polling Podemos resumir: Polling Yo te pregunto constantemente. Webhook Tú me avisas cuando algo ocurre. WebSocket Mantenemos una conexión abierta para comunicarnos. Cada uno resuelve problemas diferentes. 🚀 Ejemplo completo con ecommerce Imagina este proceso: Usuario crea pedido ↓ Pedido = pendiente ↓ Redirigir a proveedor de pagos El proveedor procesa el pago. Después: Proveedor ↓ payment.completed ↓ POST /webhooks/payments Tu backend: 1. Recibe evento 2. Verifica firma 3. Revisa event_id 4. Guarda evento 5. Responde 200 6. Envía procesamiento a cola Después el worker: 7. Busca pedido 8. Confirma que no fue procesado 9. Marca pedido como pagado 10. Reserva o actualiza inventario 11. Genera factura 12. Envía confirmación Ahora tienes un flujo mucho más robusto. ⚠️ Error común: confiar en el frontend para confirmar pagos Imagina que después de pagar el proveedor redirige al navegador hacia: /compra-exitosa Y el frontend dice al backend: "El pago fue exitoso." Eso no debería ser suficiente para marcar una compra como pagada. El usuario controla el navegador. Podría intentar acceder directamente a esa URL. La confirmación debería venir desde una fuente confiable, como el proveedor mediante un webhook o una consulta verificada desde el backend. 🔄 ¿Qué ocurre si tu aplicación necesita reconsultar eventos? Aunque los webhooks reduzcan muchísimo el polling, algunos sistemas combinan ambas estrategias. Por ejemplo: Webhook = mecanismo principal pero también: Proceso periódico ↓ Buscar inconsistencias ↓ Consultar proveedor Esto puede funcionar como mecanismo de reconciliación si algún evento se perdió o quedó sin procesar. 🧾 Reconciliación Imagina: Proveedor: Pago 100 = completado Tu sistema: Pago 100 = pendiente Un proceso periódico puede detectar esa diferencia y corregirla. Esto es especialmente importante en sistemas financieros. Los webhooks son muy útiles. Pero diseñar pensando en recuperación y reconciliación aumenta la confiabilidad. 🔍 Observabilidad También necesitas saber qué está ocurriendo con tus webhooks. Conviene medir: Eventos recibidos Eventos válidos Eventos rechazados Duplicados Errores de procesamiento Tiempo de procesamiento Por ejemplo: Webhook payment.completed ↓ 500 errores en 10 minutos debería llamar inmediatamente la atención. 🚨 Alertas Puedes generar alertas cuando: Aumentan errores o: Se acumulan eventos pendientes o: No llegan webhooks durante demasiado tiempo si normalmente existe tráfico constante. Así puedes detectar fallos antes de que los usuarios los reporten. 🧪 ¿Qué deberías probar? No pruebes únicamente: Webhook válido ↓ 200 OK También escenarios como: Firma inválida Evento duplicado Evento fuera de orden Payload inválido Base de datos caída Worker caído Procesamiento lento Evento antiguo Los casos difíciles son precisamente los que suelen aparecer en producción. 🛠️ Buenas prácticas ✔️ Utiliza HTTPS. ✔️ Verifica la firma o mecanismo de autenticidad proporcionado por el proveedor. ✔️ No confíes únicamente en el contenido del payload. ✔️ Guarda un identificador único del evento. ✔️ Diseña el procesamiento para soportar duplicados. ✔️ Registra los eventos recibidos. ✔️ Responde rápidamente después de asegurar la recepción. ✔️ Utiliza colas para trabajos pesados. ✔️ Considera que los eventos pueden llegar tarde o fuera de orden. ✔️ Implementa monitoreo y alertas. 🧩 La realidad La idea de un webhook parece sencilla: Ocurre algo ↓ Enviar POST Pero construir una integración confiable requiere pensar en muchas más cosas. ¿Quién envió el evento? ¿Ya lo procesé? ¿Y si llega dos veces? ¿Y si llega tarde? ¿Y si mi servidor está caído? ¿Y si el procesamiento falla? Ese es el verdadero reto. Los webhooks permiten que diferentes sistemas reaccionen a eventos sin estar consultándose constantemente. Pero la confiabilidad depende de cómo recibes, validas y procesas esos eventos. 💬 Una buena integración no necesita preguntar continuamente si algo cambió. Puede estar preparada para recibir el cambio, validarlo y reaccionar cuando ocurra. 👉 La próxima vez que un pago cambie automáticamente de "pendiente" a "confirmado" sin que actualices manualmente la página, probablemente haya existido algún mecanismo basado en eventos trabajando detrás. 🔥 El backend no se ve, pero sin él, nada funciona.

25 ago 2026
Leer publicación
Cuando una aplicación pide /usuarios/25… ¿cómo sabe la API qué debe hacer?
APIs

Cuando una aplicación pide /usuarios/25… ¿cómo sabe la API qué debe hacer?

Abres una aplicación. Consultas tu perfil. Ves tus pedidos. Actualizas una dirección. Creas un nuevo registro. Cada una de esas acciones puede provocar una petición distinta hacia el backend. Por ejemplo: GET /usuarios/25 GET /usuarios/25/pedidos POST /pedidos PATCH /usuarios/25 Pero... 👉 ¿Qué representan realmente esas direcciones? Son endpoints . Y son una de las formas principales en las que una API organiza el acceso a sus recursos. 🧠 ¿Qué es un endpoint? Un endpoint es un punto específico dentro de una API al que un cliente puede enviar una petición. Normalmente combina: Método HTTP + Ruta Por ejemplo: GET /productos puede significar: 👉 Obtener una lista de productos. Mientras que: POST /productos puede utilizarse para crear uno nuevo. La ruta es la misma: /productos pero la operación cambia dependiendo del método HTTP utilizado. ⚙️ ¿Qué representa realmente una ruta? En una API REST es común organizar las rutas alrededor de recursos . Por ejemplo: /usuarios /productos /pedidos /pagos Cada uno representa un tipo de información que existe dentro del sistema. Después, los métodos HTTP indican qué queremos hacer con esos recursos. Por ejemplo: GET /productos puede significar: Consultar productos Mientras que: POST /productos puede significar: Crear producto Y: DELETE /productos/25 puede indicar: Eliminar producto 25 Esto hace que la API sea más predecible. 📦 Recursos y colecciones Una ruta como: /productos normalmente representa una colección . Es decir: Producto 1 Producto 2 Producto 3 Producto 4 ... Mientras que: /productos/25 representa un recurso específico. En este caso: Producto con ID 25 Así podemos tener: GET /productos ↓ Obtener colección y: GET /productos/25 ↓ Obtener un producto 🚀 Un ejemplo completo Supongamos que tenemos una tienda en línea. Cuando el usuario entra al catálogo, el frontend podría enviar: GET /productos El backend responde con una lista. Después el usuario selecciona el producto 42. Entonces: GET /productos/42 Cuando agrega productos y crea un pedido: POST /pedidos Y si posteriormente consulta ese pedido: GET /pedidos/850 Cada endpoint representa una entrada concreta hacia una parte del sistema. 🧩 ¿Qué ocurre dentro del backend? Cuando llega una petición como: GET /usuarios/25 el servidor necesita encontrar qué parte del código debe procesarla. Conceptualmente: Request ↓ Router ↓ Controller ↓ Service ↓ Base de datos ↓ Response La arquitectura exacta depende del framework. Pero normalmente existe un componente de enrutamiento que revisa: Método = GET Ruta = /usuarios/25 y decide qué función debe ejecutarse. 🛣️ El router El router funciona como una especie de mapa. Puede tener reglas conceptualmente parecidas a: GET /usuarios/:id → obtenerUsuario() POST /usuarios → crearUsuario() DELETE /usuarios/:id → eliminarUsuario() Cuando llega: GET /usuarios/25 el router reconoce que: 25 corresponde al parámetro: id y envía la petición hacia la lógica correspondiente. 🔢 Parámetros de ruta Los valores que aparecen dentro de una URL pueden utilizarse para identificar recursos. Por ejemplo: GET /usuarios/25 Aquí: 25 es un parámetro de ruta. Podríamos expresarlo como: /usuarios/{id} Entonces: id = 25 Otro ejemplo: GET /productos/900 significa: id = 900 Estos parámetros permiten crear rutas dinámicas sin definir un endpoint diferente para cada registro. 🧬 Recursos relacionados También podemos representar relaciones. Por ejemplo: GET /usuarios/25/pedidos Esto puede interpretarse como: 👉 Obtener los pedidos pertenecientes al usuario 25. Conceptualmente: Usuario 25 ↓ Pedidos Entonces la URL ya comunica bastante información por sí sola. Eso ayuda a que la API sea intuitiva. ⚠️ Pero no conviene anidar demasiado Podríamos terminar creando algo como: /usuarios/25/pedidos/80/productos/15/comentarios/2 Aunque técnicamente puede funcionar, empieza a ser difícil de mantener y entender. Muchas veces una ruta más directa puede ser mejor. Por ejemplo: /pedidos/80 o: /pedidos/80/productos La estructura debe representar relaciones útiles sin convertir la URL en una cadena interminable. 🔎 Query Parameters No toda la información debe ir dentro de la ruta. También existen los query parameters . Por ejemplo: GET /productos?categoria=laptops Aquí: categoria=laptops no identifica necesariamente un producto específico. Sirve para filtrar la colección. También podríamos tener: GET /productos?pagina=2 o: GET /productos?orden=precio Los query parameters suelen utilizarse para: Filtros. Ordenamiento. Paginación. Búsquedas. Opciones adicionales. 🚀 Un ejemplo con filtros Supongamos: GET /productos devuelve todos los productos. Pero queremos solamente los disponibles. Podríamos hacer: GET /productos?disponible=true Después queremos ordenarlos: GET /productos?disponible=true&orden=precio Así mantenemos el recurso: /productos y utilizamos parámetros para modificar la consulta. 🧠 Los métodos HTTP Los métodos HTTP ayudan a expresar qué tipo de acción se quiere realizar. Los más comunes son: GET POST PUT PATCH DELETE Cada uno tiene un propósito diferente. 🔎 GET Se utiliza principalmente para consultar información. Por ejemplo: GET /usuarios o: GET /usuarios/25 Una petición GET debería limitarse a recuperar datos. No debería utilizarse para modificar información importante. ➕ POST Suele utilizarse para crear recursos. Por ejemplo: POST /usuarios con un cuerpo: { "nombre": "Herman", "email": "correo@email.com" } El backend procesa la información y crea el nuevo usuario. ✏️ PUT PUT suele utilizarse para reemplazar o actualizar un recurso completo según el contrato de la API. Por ejemplo: PUT /usuarios/25 podría enviar toda la representación actualizada del usuario. 🩹 PATCH PATCH suele utilizarse cuando queremos actualizar solamente ciertos campos. Por ejemplo: PATCH /usuarios/25 con: { "nombre": "Nuevo nombre" } Solo se modifica esa propiedad. 🗑️ DELETE Se utiliza para eliminar un recurso. Por ejemplo: DELETE /usuarios/25 indica que queremos eliminar el usuario 25. El backend todavía debe verificar si esa operación está permitida. ⚠️ El método no sustituye las validaciones Que alguien envíe: DELETE /usuarios/25 no significa que el backend tenga que eliminarlo inmediatamente. Primero puede verificar: ¿Existe? ↓ ¿Usuario autenticado? ↓ ¿Tiene permisos? ↓ ¿Puede eliminar este recurso? Los endpoints representan operaciones. Pero las reglas de seguridad y negocio continúan siendo responsabilidad del backend. 🔐 Un endpoint también debe controlar acceso Imagina: GET /usuarios/25 El servidor encuentra al usuario 25. Pero todavía debe preguntarse: ¿Quién está haciendo la petición? Tal vez ese endpoint solamente pueda ser utilizado por: Usuario 25 o por un administrador. Entonces el flujo podría ser: Request ↓ Autenticación ↓ Autorización ↓ Buscar usuario ↓ Responder Un endpoint bien diseñado no solamente encuentra datos. También protege quién puede utilizarlos. 📥 El cuerpo de la petición Algunos endpoints necesitan recibir información adicional. Por ejemplo: POST /pedidos podría enviar: { "productos": [ { "id": 25, "cantidad": 2 } ] } La ruta indica: /pedidos El método indica: POST Y el cuerpo contiene: Datos del nuevo pedido Estas tres partes trabajan juntas. 📤 La respuesta también forma parte del contrato El frontend necesita saber qué puede esperar. Por ejemplo: GET /productos/25 puede responder: { "id": 25, "nombre": "Laptop", "precio": 18000 } Pero si no existe: 404 Not Found Si no tiene permisos: 403 Forbidden Si ocurre un problema interno: 500 Internal Server Error Diseñar buenos endpoints también implica definir respuestas consistentes. 🚦 Los códigos HTTP ayudan a comunicar resultados Algunos códigos comunes son: 200 OK 201 Created 204 No Content 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 409 Conflict 500 Internal Server Error Por ejemplo: POST /usuarios si crea correctamente el recurso podría devolver: 201 Created Esto permite que el cliente entienda qué ocurrió sin depender únicamente del contenido de la respuesta. ⚠️ Error común: rutas como nombres de funciones Es común encontrar APIs con rutas como: /obtenerProductos /crearProducto /eliminarProducto /actualizarProducto Funcionan. Pero en un diseño REST suele ser más predecible utilizar: /productos y dejar que el método indique la acción. Por ejemplo: GET /productos POST /productos PATCH /productos/25 DELETE /productos/25 Esto hace que el diseño sea más uniforme. 🚫 Otro ejemplo poco consistente Imagina: GET /getUsers POST /new-product DELETE /removeOrder/25 PATCH /editCustomer/10 Cada endpoint utiliza una convención diferente. Un desarrollador tendría que memorizar cada ruta. Una estructura consistente sería más fácil: GET /usuarios POST /productos DELETE /pedidos/25 PATCH /clientes/10 La predictibilidad importa. 🧩 Sustantivos en lugar de verbos En REST normalmente las rutas representan recursos. Por eso suele preferirse: /productos en lugar de: /obtenerProductos porque el método HTTP ya representa la acción: GET Entonces: GET + /productos ya significa: Obtener productos ⚠️ Pero REST tampoco significa que nunca pueda haber acciones Hay operaciones que no encajan perfectamente en CRUD. Por ejemplo: POST /pedidos/25/cancelacion o alguna variante equivalente. La idea no es forzar artificialmente todo. El objetivo es mantener una interfaz clara y coherente. Las reglas REST son una guía de diseño, no una competencia por crear la URL más "pura". 🧱 CRUD y endpoints Muchas APIs comienzan con operaciones CRUD: Create Read Update Delete que pueden mapearse a: POST GET PUT/PATCH DELETE Por ejemplo: POST /productos ↓ Create GET /productos/25 ↓ Read PATCH /productos/25 ↓ Update DELETE /productos/25 ↓ Delete Esto crea una relación fácil de entender. 🔢 ¿Qué pasa con la versión de una API? Con el tiempo, una API puede cambiar. Por eso algunas utilizan rutas como: /api/v1/productos Y más adelante: /api/v2/productos El versionado ayuda a introducir cambios importantes sin romper inmediatamente a clientes que todavía utilizan la versión anterior. No todas las APIs necesitan exactamente esta estrategia, pero es una práctica común. 🌐 El frontend no necesita conocer el código interno Supongamos que el backend cambia completamente. Antes: Controller ↓ MySQL Después: Controller ↓ Service ↓ PostgreSQL ↓ Redis Si el contrato sigue siendo: GET /productos/25 y la respuesta mantiene el mismo formato, el frontend no debería necesitar saber que la implementación cambió. Este es uno de los beneficios más importantes de una API. 🤝 El endpoint funciona como un contrato El cliente necesita conocer: Ruta Método Datos de entrada Respuesta Posibles errores Por ejemplo: POST /usuarios puede establecer: Entrada: nombre email contraseña Respuesta: id nombre email Mientras ese contrato se mantenga, el equipo backend puede modificar muchos detalles internos. 📚 Por eso la documentación importa Una buena documentación debería explicar cada endpoint. Por ejemplo: GET /productos/{id} Parámetros id: Identificador del producto Respuesta { "id": 25, "nombre": "Laptop" } Errores 404 → producto no encontrado 401 → usuario no autenticado Esto hace que consumir la API sea mucho más sencillo. 🧪 Los endpoints también deben probarse No basta con probar: GET /productos/25 ↓ 200 OK También deberías comprobar: GET /productos/999999 ↓ 404 o: GET /usuarios/25 ↓ Usuario sin permisos ↓ 403 Y: POST /productos ↓ Datos inválidos ↓ 400 Una API tiene que comportarse correctamente tanto en escenarios exitosos como en errores. 📊 Un endpoint también puede necesitar paginación Imagina: GET /productos y tienes: 2,000,000 productos No deberías devolverlos todos en una sola respuesta. Puedes usar: GET /productos?page=1&limit=50 Entonces: /productos sigue siendo el recurso. Los parámetros controlan cómo queremos consultarlo. 🔎 Búsquedas También podrías tener: GET /productos?search=laptop El endpoint continúa representando productos. Simplemente estamos aplicando una condición. Esto suele ser más limpio que crear: /buscarProductoLaptop o rutas distintas para cada búsqueda. 📈 Ordenamiento Podríamos utilizar: GET /productos?sort=precio o: GET /productos?sort=-precio según la convención elegida. La idea es mantener una estructura consistente. 🛠️ Buenas prácticas ✔️ Utiliza nombres claros para los recursos. ✔️ Mantén una convención consistente entre endpoints. ✔️ Utiliza correctamente los métodos HTTP. ✔️ Usa parámetros de ruta para identificar recursos. ✔️ Utiliza query parameters para filtros, ordenamiento y paginación cuando tenga sentido. ✔️ Evita anidaciones excesivamente profundas. ✔️ Define respuestas y códigos HTTP consistentes. ✔️ Valida siempre la información recibida. ✔️ Protege cada endpoint con autorización cuando corresponda. ✔️ Documenta claramente el contrato. 🧩 La realidad Cuando el frontend envía: GET /usuarios/25/pedidos no está diciendo cómo debe funcionar internamente el backend. No sabe: Qué controlador existe Qué servicio se ejecuta Qué ORM utilizas Qué consulta SQL se genera Qué base de datos tienes Solo conoce una interfaz. El backend recibe la petición, interpreta la ruta y ejecuta toda la lógica necesaria detrás. Por eso los endpoints son una pieza fundamental en el diseño de APIs. 💬 Una API bien diseñada no solamente funciona. También permite que alguien pueda mirar: GET /usuarios/25/pedidos y tener una buena idea de lo que va a recibir. La consistencia hace que una API sea mucho más fácil de aprender, mantener e integrar. 👉 Cuando diseñes un endpoint, intenta pensar desde la perspectiva de quien va a consumirlo. Si necesita memorizar reglas completamente diferentes para cada ruta, probablemente todavía exista espacio para simplificar la API. 🔥 El backend no se ve, pero sin él, nada funciona.

24 ago 2026
Leer publicación
Tu formulario dice que el dato es válido… ¿pero qué pasa si alguien se salta el formulario?
Seguridad

Tu formulario dice que el dato es válido… ¿pero qué pasa si alguien se salta el formulario?

Creas un formulario. El usuario escribe su correo. El frontend verifica que tenga un formato correcto. Impide enviar campos vacíos. Limita ciertos valores. Deshabilita botones cuando algo no cumple las reglas. Todo parece estar protegido. Pero hay un detalle muy importante: 👉 El usuario no está obligado a utilizar tu interfaz. Puede abrir las herramientas del navegador. Modificar una petición. Llamar directamente a tu API. Utilizar Postman. Crear un script. O incluso construir su propio cliente. Por eso existe una regla fundamental en backend: ⚠️ Nunca confíes ciegamente en los datos que llegan desde el cliente. 🧠 ¿Qué es la validación del lado del servidor? La validación del lado del servidor consiste en comprobar nuevamente los datos cuando llegan al backend. Aunque el frontend ya los haya validado antes. Por ejemplo, una petición podría intentar enviar: edad: -200 email: "hola" precio: -500 rol: "admin" El backend debe decidir si esos valores son válidos antes de procesarlos o almacenarlos. No debería asumir que son correctos solo porque vienen desde tu propia aplicación. ⚙️ ¿Por qué validar dos veces? Porque frontend y backend cumplen responsabilidades diferentes. El frontend valida principalmente para mejorar la experiencia. Por ejemplo: Correo inválido puede mostrarse inmediatamente mientras el usuario escribe. Eso evita enviar una petición innecesaria. Pero esa validación puede eliminarse, modificarse o ignorarse. Por eso el flujo correcto suele ser: Usuario ↓ Frontend valida ↓ Petición HTTP ↓ Backend valida otra vez ↓ Lógica de negocio ↓ Base de datos Podemos resumirlo así: 👉 El frontend ayuda al usuario. 👉 El backend protege al sistema. 🚀 Un ejemplo real: una tienda en línea Supongamos que el frontend envía esta información: { "producto_id": 25, "cantidad": 2, "precio": 500 } A simple vista parece correcto. Pero un usuario podría interceptar la petición y cambiarla: { "producto_id": 25, "cantidad": 2, "precio": 1 } Si el backend confía directamente en: precio = 1 podría terminar procesando una operación manipulada. El problema no está en que el formulario haya fallado. El problema está en que el servidor confió en un dato que nunca debió considerar autoritativo. 💳 Los valores importantes deben venir del servidor En un ecommerce, el cliente puede enviar: producto_id = 25 cantidad = 2 Pero el backend debería obtener por sí mismo información sensible como: precio descuento real impuestos disponibilidad Por ejemplo: Cliente: producto_id = 25 cantidad = 2 Después: Backend ↓ Consultar producto 25 ↓ Precio real = $500 ↓ Calcular total Así el cliente no decide cuánto cuesta el producto. Solo indica qué desea comprar. 🔐 Otro ejemplo: roles Imagina un formulario de registro. El frontend normalmente envía: { "nombre": "Herman", "email": "usuario@email.com" } Pero alguien modifica la petición: { "nombre": "Herman", "email": "usuario@email.com", "rol": "admin" } Si el backend guarda automáticamente todos los campos recibidos, podría existir un problema muy serio. Por eso el servidor debe controlar qué propiedades puede modificar cada usuario. Por ejemplo: Campos permitidos: nombre email telefono Y rechazar o ignorar: rol es_admin permisos saldo cuando no corresponde. ⚠️ Aquí aparece el problema de Mass Assignment Un error común ocurre cuando el backend toma directamente todo el objeto recibido y lo guarda. Conceptualmente: request.body ↓ guardar directamente Supongamos que el modelo contiene: nombre email rol activo saldo Pero el formulario solamente muestra: nombre email Eso no significa que un atacante no pueda enviar los otros campos. Por eso es mucho más seguro definir explícitamente qué propiedades pueden recibirse. 🧩 Validar no significa solo revisar formatos Cuando hablamos de validación, muchas personas piensan únicamente en: ¿El email tiene @? ¿La edad es un número? ¿El campo está vacío? Pero existen varios tipos de validación. Por ejemplo: Validación de tipo edad = número Validación de formato email = formato válido Validación de rango cantidad >= 1 Validación de longitud nombre <= 100 caracteres Validación de negocio ¿Hay suficiente inventario? Validación de permisos ¿Este usuario puede modificar este recurso? Todas son necesarias en diferentes contextos. 🚨 Formato válido no significa dato permitido Supongamos: edad = 25 Es un número válido. Pero ahora: cantidad = 999999999 También es un número válido. Eso no significa que tu aplicación deba aceptarlo. El backend puede establecer reglas como: cantidad mínima = 1 cantidad máxima = 100 Por eso validar el tipo de dato no es suficiente. También necesitas validar su significado dentro del negocio. 🧠 Validaciones de negocio Imagina una aplicación de reservas. El usuario envía: fecha = 15 de agosto hora = 10:00 La fecha tiene formato correcto. La hora también. Pero el backend todavía debe comprobar: ¿Existe ese horario? ¿Está disponible? ¿Ya pasó la fecha? ¿El usuario puede reservar? El dato es sintácticamente correcto. Pero podría no ser válido para la operación. 🔍 Validación y autorización son cosas diferentes Supongamos que recibes: pedido_id = 500 El ID existe. Entonces la validación es correcta. Pero todavía necesitas preguntar: 👉 ¿El usuario autenticado tiene permiso para ver ese pedido? Podemos tener: Pedido 500 existe ✅ Formato correcto ✅ Usuario autenticado ✅ pero: Pedido pertenece a otro usuario ❌ El backend debe rechazar la operación. Por eso validar datos y comprobar permisos son responsabilidades distintas. 🚀 Ejemplo de autorización Una petición: GET /pedidos/500 no debería hacer únicamente: Buscar pedido 500 ↓ Devolver Debería considerar: Buscar pedido 500 ↓ ¿Existe? ↓ ¿Pertenece al usuario? ↓ Sí → devolver No → rechazar No basta con validar que el ID sea correcto. 📱 ¿Y si los datos vienen de una app móvil? Exactamente la misma regla. Puedes controlar completamente el código de tu aplicación móvil. Pero una vez publicada, alguien puede analizar sus peticiones y reproducirlas desde otro cliente. El backend no debería asumir: "Si viene de mi app móvil, entonces es seguro." Desde el punto de vista del servidor, sigue siendo información externa. 🤝 ¿Y si vienen de otra API? Tampoco deberías confiar automáticamente. Supongamos que integras un sistema externo. Recibes: { "estado": "pagado" } Aunque confíes en ese proveedor, todavía conviene comprobar: Que la petición realmente venga del origen esperado. Que tenga la firma correcta. Que la estructura sea válida. Que el evento corresponda a una operación existente. Toda entrada externa necesita algún nivel de validación. 🧾 Validar la estructura de una petición Supongamos que esperas: { "nombre": "Herman", "email": "correo@email.com" } Pero recibes: { "nombre": [], "email": 123, "dato_extra": "algo" } El backend debería detectar que la estructura no cumple con el contrato esperado. Por eso frameworks y librerías suelen permitir definir esquemas. Conceptualmente: nombre: string requerido máximo 100 email: string formato email requerido Si no se cumple: 400 Bad Request 🧱 Rechazar campos inesperados En algunos sistemas puede ser conveniente rechazar propiedades que no estaban definidas. Por ejemplo, esperas: nombre email pero recibes: nombre email rol Puedes responder: Campo inesperado: rol Esto ayuda a detectar clientes mal implementados y reduce el riesgo de aceptar accidentalmente información sensible. ⚠️ Nunca construyas consultas SQL con entrada directa Validar la entrada también es importante para evitar ciertos ataques. Una mala práctica sería construir una consulta concatenando directamente valores enviados por el usuario. Conceptualmente: "SELECT * FROM usuarios WHERE email = '" + email + "'" Esto puede abrir la puerta a SQL Injection . En lugar de eso deben utilizarse: Consultas parametrizadas. Prepared statements. ORMs correctamente utilizados. Por ejemplo: SELECT * FROM usuarios WHERE email = ? La entrada del usuario se trata como un dato y no como parte del código SQL. 🧼 Validar y sanitizar no son exactamente lo mismo Estos conceptos suelen confundirse. Validar significa decidir si un valor cumple las reglas. Por ejemplo: edad = -5 ↓ Inválido Sanitizar significa transformar o limpiar determinados valores antes de utilizarlos en cierto contexto. Por ejemplo: Eliminar espacios innecesarios Normalizar texto Pero sanitizar no debería utilizarse como excusa para aceptar cualquier cosa. Si el dato no tiene sentido, muchas veces lo correcto es rechazarlo. 📝 Ejemplo con nombres El usuario envía: " Herman " El backend puede normalizar: "Herman" Eso puede ser razonable. Pero si recibe: "" no tiene sentido convertirlo mágicamente en otro valor. Debe rechazarse si el campo es obligatorio. 🔢 Conversión automática también puede ser peligrosa Algunos frameworks convierten automáticamente: "25" a: 25 Eso puede ser cómodo. Pero hay que saber exactamente qué comportamiento tendrá la aplicación. Por ejemplo: "abc" no debería terminar convirtiéndose silenciosamente en: 0 si ese resultado cambia la lógica del negocio. Las conversiones implícitas deben manejarse con cuidado. 🚀 La base de datos también puede validar La validación no termina necesariamente en el backend. La propia base de datos puede proteger ciertas reglas mediante: NOT NULL UNIQUE FOREIGN KEY CHECK Por ejemplo: CHECK (precio >= 0) puede impedir que exista: precio = -500 aunque un error en el código intente guardarlo. Esto crea múltiples capas de protección. 🧩 Defensa en profundidad Una arquitectura saludable puede tener: Frontend ↓ Validación UX Backend ↓ Validación de entrada ↓ Reglas de negocio ↓ Autorización Base de datos ↓ Restricciones Cada capa cumple un propósito. No se trata de confiar completamente en una sola. 🧠 ¿Por qué seguir validando en frontend entonces? Porque la experiencia del usuario importa. Sin validación frontend: Usuario completa formulario ↓ Enviar ↓ Esperar servidor ↓ Error: correo inválido Con validación frontend: Usuario escribe correo incorrecto ↓ Mensaje inmediato Eso es mucho más agradable. Entonces no debemos eliminar la validación frontend. Simplemente entender que: 👉 No es una barrera de seguridad suficiente. ⚠️ Botones deshabilitados tampoco protegen la API Supongamos que tienes: <button disabled> Eliminar cuenta </button> Visualmente el usuario no puede presionarlo. Pero si conoce el endpoint: DELETE /usuarios/15 podría intentar llamarlo directamente. El backend debe comprobar si esa acción realmente está permitida. La interfaz controla la experiencia. La API controla la operación. 🚨 Ocultar un botón tampoco es autorización Imagina que los administradores ven: [Eliminar usuario] y usuarios normales no. Eso está bien visualmente. Pero un usuario normal podría descubrir el endpoint. Por eso esta lógica: No mostrar botón debe complementarse con: Backend: ¿Es administrador? Si no: 403 Forbidden Ocultar elementos no reemplaza los permisos. 🔐 Datos especialmente sensibles Hay ciertos valores que casi nunca deberían confiarse al cliente. Por ejemplo: rol permisos saldo precio final descuento autorizado propietario del recurso estado interno El usuario podría enviar una intención. Pero el backend debe calcular o verificar el valor real. Por ejemplo: Cliente: cupón = VERANO20 El backend debe comprobar: ¿Existe? ¿Está activo? ¿Aplica a este usuario? ¿Aplica a este producto? ¿Cuánto descuento corresponde? No debería recibir: descuento = 90% y aplicarlo directamente. 💰 Ejemplo con total de compra Una mala implementación: { "productos": [25, 30], "total": 1 } Backend: total recibido = 1 ↓ Cobrar $1 Una mejor estrategia: productos = [25, 30] ↓ Consultar precios reales ↓ Calcular subtotal ↓ Aplicar descuentos válidos ↓ Calcular impuestos ↓ Calcular total ↓ Cobrar El servidor debe ser la fuente de verdad para la lógica crítica. 🧪 También debes probar entradas inválidas Muchos desarrolladores prueban únicamente: nombre correcto email correcto cantidad correcta Pero también deberías probar: campo vacío número negativo texto demasiado largo tipo incorrecto campo adicional ID inexistente usuario sin permisos Una API no solamente debe funcionar con entradas correctas. También debe fallar correctamente ante entradas incorrectas. 📏 Límites de tamaño Imagina un campo: nombre que normalmente tiene: 30 caracteres Pero alguien envía: 10 millones de caracteres Aunque técnicamente sea un string, no significa que debas aceptarlo. Por eso conviene definir límites. Por ejemplo: nombre: máximo 100 caracteres Lo mismo ocurre con: Archivos. Arrays. JSON. Comentarios. Imágenes. Los límites también forman parte de la validación. 📁 Subida de archivos Si permites cargar archivos, tampoco deberías confiar únicamente en: accept=".jpg" del frontend. Un usuario puede saltarse esa restricción. El backend debería revisar aspectos como: Tamaño Tipo permitido Extensión Formato real y almacenarlos de forma segura. La validación del navegador es solo una comodidad. 🧠 Los errores también deben diseñarse Cuando un dato es inválido, la API debería responder de manera clara. Por ejemplo: { "error": "El campo cantidad debe ser mayor a 0" } Esto permite que el frontend muestre un mensaje útil. Pero hay que evitar respuestas que revelen información sensible. Por ejemplo, no deberíamos devolver detalles internos como: Ruta interna del servidor Credenciales Stack traces completos Consultas SQL especialmente en producción. 📋 Un formato consistente ayuda Puedes definir una estructura uniforme para errores: { "error": "validation_error", "fields": { "email": "Formato inválido", "edad": "Debe ser mayor a 18" } } Esto facilita que frontend y backend trabajen juntos. El cliente sabe exactamente cómo interpretar la respuesta. ⚠️ Validar absolutamente todo tampoco significa llenar el código de if Una mala implementación puede terminar así: if nombre... if email... if edad... if precio... if cantidad... repetido por todos lados. Los frameworks modernos suelen ofrecer: Schemas. Serializers. DTOs. Validators. Form objects. Estas herramientas ayudan a centralizar las reglas. El objetivo no es escribir validaciones manuales desorganizadas. Es establecer contratos claros. 🧩 Ejemplo conceptual de esquema Podríamos definir: CrearUsuario nombre: string requerido máximo 100 email: string requerido formato email edad: integer mínimo 18 máximo 120 Entonces todas las peticiones que no cumplan ese esquema se rechazan antes de llegar a la lógica principal. Esto mantiene el código mucho más limpio. 🌐 La API es una frontera de confianza Una forma útil de pensarlo es: Exterior ↓ ──── frontera ──── ↓ Backend Todo lo que cruza esa frontera debe considerarse potencialmente incorrecto. No necesariamente malicioso. A veces simplemente puede ser: Un bug del frontend. Una versión antigua de la app. Una integración mal implementada. Un dato corrupto. Un error humano. La validación también protege contra errores accidentales. 🚀 No todo usuario intenta atacar tu sistema Este punto también es importante. Cuando decimos: "No confíes en el cliente" no significa asumir que cada usuario es un atacante. Significa diseñar el sistema de forma que su integridad no dependa de que todos los clientes se comporten perfectamente. Incluso tu propio frontend puede tener bugs. El backend debe mantener sus reglas independientemente de eso. 🛠️ Buenas prácticas ✔️ Valida tipos, formatos, longitudes y rangos en el backend. ✔️ Define claramente qué campos acepta cada endpoint. ✔️ Rechaza o ignora campos inesperados según tu diseño. ✔️ No confíes en precios, roles, permisos o totales enviados por el cliente. ✔️ Calcula valores sensibles desde fuentes controladas por el servidor. ✔️ Comprueba autorización además de validación. ✔️ Utiliza consultas parametrizadas u ORMs correctamente configurados. ✔️ Mantén restricciones importantes también en la base de datos. ✔️ Define límites para textos, arrays y archivos. ✔️ Devuelve errores claros sin exponer información interna. 🧩 La realidad La interfaz que construyes no es una barrera de seguridad. Un formulario puede decir: Solo números positivos pero alguien puede enviar: -500 Un dropdown puede permitir únicamente: usuario moderador pero alguien puede intentar enviar: admin Un botón puede estar deshabilitado. Pero la API todavía puede recibir la petición. Por eso toda información externa debe pasar por validaciones y reglas definidas por el servidor. 💬 La validación del frontend mejora la experiencia. La validación del backend protege la integridad del sistema. Y las restricciones de la base de datos pueden funcionar como una última capa de defensa. 👉 La próxima vez que agregues una validación a un formulario, pregúntate también: ¿Qué ocurriría si alguien enviara esta petición sin utilizar mi formulario? Si tu backend continúa protegiendo correctamente la operación, entonces vas por buen camino. 🔥 El backend no se ve, pero sin él, nada funciona.

23 ago 2026
Leer publicación
Una petición falla… ¿deberías intentarlo otra vez?
Arquitectura de Software

Una petición falla… ¿deberías intentarlo otra vez?

Tu backend necesita comunicarse con otro servicio. Envía una petición. Pero algo ocurre. ⏱️ El servidor tarda demasiado. 🌐 La conexión falla temporalmente. ⚠️ El servicio responde con un error. En ese momento aparece una decisión importante: 👉 ¿Debemos volver a intentarlo? Muchas veces sí. Pero hacerlo sin control puede convertir un pequeño fallo temporal en un problema mucho mayor. Para eso existen las políticas de reintentos . 🧠 ¿Qué es una política de reintentos? Una política de reintentos, o Retry Policy , define qué debe hacer una aplicación cuando una operación falla. En lugar de abandonar inmediatamente, el sistema puede esperar un momento y volver a intentarlo. Por ejemplo: Petición ↓ ❌ Falla ↓ Esperar ↓ Reintento 1 ↓ ❌ Falla ↓ Esperar más ↓ Reintento 2 ↓ ✅ Correcto La idea es sencilla: si el error fue temporal, un segundo intento puede resolverlo sin que el usuario siquiera lo note. 🌐 ¿Por qué una petición puede fallar temporalmente? En sistemas distribuidos, que una solicitud falle no significa necesariamente que exista un problema permanente. Puede ocurrir por razones como: Una pequeña interrupción de red. Un servidor saturado. Un timeout. Un balanceador momentáneamente ocupado. Un servicio reiniciándose. Una dependencia externa con problemas temporales. Por ejemplo: Backend ↓ Servicio externo ↓ 503 Service Unavailable Tal vez el servicio vuelva a estar disponible 500 milisegundos después. En ese caso, reintentar puede ser una estrategia válida. ⚙️ Pero no todos los errores deberían reintentarse Este punto es fundamental. Supongamos que envías: POST /usuarios con un correo inválido. El servidor responde: 400 Bad Request Si vuelves a enviar exactamente la misma información: Intento 1 → 400 Intento 2 → 400 Intento 3 → 400 nada va a cambiar. El problema no es temporal. La petición está mal construida. Reintentar solamente genera tráfico innecesario. 🚦 ¿Qué errores suelen ser candidatos? Depende del contexto, pero normalmente tiene más sentido considerar errores como: Timeout Connection reset 502 Bad Gateway 503 Service Unavailable 504 Gateway Timeout porque pueden representar fallos temporales. En cambio, errores como: 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found generalmente no se solucionan simplemente esperando y repitiendo exactamente la misma petición. Siempre debe evaluarse el significado real del error. 🚀 Un ejemplo real con pagos Imagina que tu aplicación necesita conectarse con una pasarela de pagos. Envía: Procesar pago Pero el proveedor responde: 503 Service Unavailable Tu backend puede decidir: Esperar 500 ms ↓ Intentar nuevamente Si el segundo intento funciona: 200 OK el fallo queda resuelto automáticamente. El usuario quizá nunca se entere de que existió un problema. Ese es uno de los principales beneficios de una buena política de reintentos. ⚠️ Pero aquí aparece un problema importante Imagina que el proveedor sí procesó el pago. Pero tu backend nunca recibió la respuesta. El flujo fue: Backend ↓ Proveedor ↓ Pago procesado ↓ ❌ Respuesta perdida Desde tu aplicación parece que la operación falló. Entonces decides reintentar. Si la operación no está protegida, podrías procesar el pago nuevamente. Por eso los reintentos deben combinarse con otro concepto importante: 👉 idempotencia . 🔐 Reintentos e idempotencia Si una operación crítica puede ejecutarse más de una vez, deberías diseñarla para reconocer duplicados. Por ejemplo: Idempotency-Key: pago-ABC123 Primer intento: pago-ABC123 ↓ Procesar ↓ Pago realizado Segundo intento: pago-ABC123 ↓ Ya procesado ↓ No volver a cobrar Así puedes reintentar de forma mucho más segura. 🔁 ¿Por qué no reintentar inmediatamente? Supongamos que un servicio está teniendo problemas. Tu aplicación envía una solicitud. Falla. Entonces haces esto: Retry Retry Retry Retry Retry sin esperar. Eso puede empeorar la situación. El servicio ya está saturado. Y ahora le estás enviando todavía más tráfico. 💥 El problema de una retry storm Imagina que tienes: 10,000 solicitudes por segundo y todas comienzan a fallar. Si cada una realiza tres reintentos inmediatos, podrías generar: 10,000 solicitudes originales + 30,000 reintentos = 40,000 solicitudes Justo cuando el servicio está teniendo más dificultades. Esto puede provocar una retry storm . En lugar de ayudar al sistema a recuperarse, terminas aumentando todavía más la carga. ⏳ Aquí entra el backoff Una estrategia mejor consiste en esperar antes de cada reintento. A esto se le conoce como: Backoff Por ejemplo: Intento 1 ↓ Falla ↓ Esperar 1 segundo Intento 2 ↓ Falla ↓ Esperar 2 segundos Intento 3 ↓ Falla ↓ Esperar 4 segundos Cada vez esperamos un poco más. Esto da tiempo al servicio para recuperarse. 📈 Backoff exponencial Una estrategia muy común es el Exponential Backoff . El tiempo de espera aumenta progresivamente. Por ejemplo: 1 segundo 2 segundos 4 segundos 8 segundos 16 segundos En lugar de insistir constantemente: 1s 1s 1s 1s 1s el sistema reduce gradualmente la presión sobre la dependencia. 🎲 ¿Qué es jitter? Ahora imagina que tienes 10,000 clientes. Todos fallan exactamente al mismo tiempo. Todos utilizan backoff: Esperar 5 segundos Cinco segundos después... los 10,000 vuelven a intentarlo exactamente al mismo tiempo. Eso puede generar otro pico. Para evitarlo se puede utilizar jitter . ⚙️ ¿Cómo funciona el jitter? En lugar de esperar exactamente: 5 segundos cada cliente espera una pequeña variación. Por ejemplo: Cliente A → 4.8 segundos Cliente B → 5.3 segundos Cliente C → 4.5 segundos Cliente D → 5.7 segundos Así los reintentos quedan distribuidos a lo largo del tiempo. Esto reduce el riesgo de provocar otro pico de tráfico sincronizado. ⏱️ Los timeouts son igual de importantes Una política de reintentos necesita timeouts. Supongamos que haces: Petición ↓ Esperar indefinidamente El sistema podría quedarse bloqueado durante demasiado tiempo. En cambio, puedes definir: Timeout = 3 segundos Si el servicio no responde: 3 segundos ↓ Cancelar ↓ Evaluar retry Esto evita que recursos importantes permanezcan ocupados sin límite. ⚠️ Timeout demasiado largo Imagina: Timeout = 60 segundos y después: 3 reintentos En el peor escenario, una operación podría tardar varios minutos antes de fallar definitivamente. Eso puede ser inaceptable para una API. Por eso timeout y cantidad de reintentos deben diseñarse juntos. ⚠️ Timeout demasiado corto El extremo contrario también puede ser problemático. Si configuras: Timeout = 100 ms pero el servicio normalmente responde en: 150 ms vas a generar errores artificialmente. Y después reintentos innecesarios. Por eso los valores deberían basarse en métricas reales. 🧠 Una política de retry tiene varias decisiones No consiste simplemente en: retry = true Debes definir: ¿Qué errores reintentar? ¿Cuántas veces? ¿Cuánto esperar? ¿Qué timeout usar? ¿Usar backoff? ¿Usar jitter? ¿Qué hacer al agotar intentos? Estas decisiones dependen del servicio y del impacto que tenga la operación. 🚀 Ejemplo de una política Podríamos tener: Timeout: 3 segundos Máximo de reintentos: 3 Backoff: Exponencial Jitter: Sí Errores reintentables: 502 503 504 Timeout El comportamiento sería: Request ↓ 503 ↓ Esperar ~1s ↓ Retry ↓ 503 ↓ Esperar ~2s ↓ Retry ↓ 200 La solicitud terminó funcionando sin insistir agresivamente. 🧩 ¿Qué ocurre cuando se agotan los reintentos? En algún momento tienes que dejar de intentarlo. Por ejemplo: Intento 1 ❌ Intento 2 ❌ Intento 3 ❌ Después: Stop A partir de ahí la aplicación debe decidir qué hacer. Podría: Devolver un error. Guardar una tarea para después. Enviar el trabajo a una cola. Utilizar un fallback. Alertar al equipo. Depende completamente del tipo de operación. 📧 Un ejemplo donde puede esperarse Supongamos que intentas enviar un correo de bienvenida. El proveedor falla. Puedes hacer algunos reintentos. Si todos fallan, quizá no sea necesario bloquear el registro del usuario. Podrías guardar: Correo pendiente y procesarlo posteriormente mediante una cola. El usuario ya quedó registrado. El correo puede enviarse después. 💳 Un ejemplo donde necesitas más cuidado En pagos la situación es distinta. No puedes simplemente asumir: Falló ↓ Intentar indefinidamente Porque el primer intento podría haberse procesado aunque tú no hayas recibido la respuesta. Aquí necesitas: Idempotencia. Estados claros. Confirmaciones. Reconciliación. Los reintentos por sí solos no solucionan el problema. 🔥 Reintentos + Circuit Breaker Las políticas de retry suelen combinarse con el patrón Circuit Breaker . Por ejemplo: Request ↓ Retry controlado ↓ Servicio Si existen algunos fallos temporales: Retry puede resolverlos. Pero si el servicio lleva mucho tiempo fallando: Circuit Breaker OPEN entonces dejamos de enviar solicitudes temporalmente. ⚡ ¿Por qué combinarlos? Sin Circuit Breaker: Servicio caído ↓ Request ↓ 3 retries ↓ Request ↓ 3 retries ↓ Request ↓ 3 retries El sistema continúa enviando tráfico. Con Circuit Breaker: Muchos errores ↓ Circuit OPEN ↓ No más solicitudes temporalmente Así se evita insistir sobre una dependencia que claramente está teniendo problemas. 🧱 Reintentos en varios niveles pueden multiplicarse Este es un problema menos evidente. Imagina: Frontend ↓ Backend A ↓ Backend B ↓ Servicio externo Cada nivel tiene: 3 reintentos Una sola acción del usuario podría generar muchas más llamadas de las esperadas. Por ejemplo, si cada capa reintenta la anterior: 3 × 3 × 3 podrías terminar con una gran cantidad de intentos. Por eso las políticas de reintentos deben coordinarse entre componentes. 🎯 No todos los servicios deberían reintentar En algunas arquitecturas es mejor decidir un único punto responsable de los reintentos. Por ejemplo: Cliente ↓ API ↓ Servicio externo Quizá únicamente la API implemente retry. Así tienes mejor control sobre: Cantidad total de intentos Latencia Carga Evitas que cada capa haga lo mismo sin saber qué están haciendo las demás. 📡 También debes respetar al servicio externo Algunas APIs responden con encabezados indicando cuánto tiempo deberías esperar. Por ejemplo: Retry-After: 30 Esto puede significar: "Espera 30 segundos antes de volver a intentarlo." Ignorar esa información y bombardear el servicio puede empeorar el problema o provocar bloqueos. Cuando un proveedor ofrece instrucciones de retry, conviene respetarlas. 🚦 429 Too Many Requests Un caso importante es: 429 Too Many Requests Significa que estás enviando más solicitudes de las permitidas. Reintentar inmediatamente sería exactamente lo contrario de lo que necesitas. Lo correcto puede ser: 429 ↓ Esperar ↓ Reducir ritmo ↓ Reintentar después De nuevo, el backoff es importante. 🔍 Monitorear los reintentos Si tu sistema está haciendo muchos retries, probablemente algo esté ocurriendo. Puedes medir: Requests totales Retries realizados Retries exitosos Retries agotados Por ejemplo: 1,000 requests 200 retries Eso podría ser una señal de que alguna dependencia está degradada. 📊 Una métrica interesante: retry rate Podrías observar: Retry Rate = 2% en condiciones normales. Pero de repente: Retry Rate = 45% Eso indica que algo cambió. Aunque la mayoría de usuarios siga recibiendo respuestas correctas, tu sistema está teniendo que trabajar mucho más para conseguirlas. 🚨 Los reintentos pueden ocultar problemas Esto también es importante. Supongamos que un servicio falla una vez de cada tres solicitudes. Gracias al retry: Intento 1 ❌ Intento 2 ✅ los usuarios quizá no noten nada. Pero el problema sigue existiendo. Si no monitoreas los reintentos, podrías pensar: "Todo funciona perfectamente." cuando realmente existe una dependencia inestable. 🧪 Probar escenarios de fallo No deberías probar solamente: Servicio responde 200 También conviene probar: Timeout 503 429 Conexión interrumpida Servicio lento Tres errores consecutivos Y verificar: ¿Cuántos retries realiza? ¿Cuánto tarda? ¿Respeta el máximo? ¿Aplica backoff? ¿Genera duplicados? La resiliencia también necesita pruebas. ⚠️ Cuidado con las operaciones no idempotentes Reintentar: GET /products normalmente es relativamente seguro. Pero reintentar: POST /payments puede tener efectos secundarios. Por eso antes de aplicar una política automática hay que preguntarse: 👉 ¿Qué pasa si esta operación realmente sí se ejecutó la primera vez? Si la respuesta es: Se cobraría dos veces necesitas protección adicional. 🛠️ Buenas prácticas ✔️ Define un máximo de reintentos. ✔️ Utiliza timeout para no esperar indefinidamente. ✔️ Reintenta solamente errores que puedan ser temporales. ✔️ Utiliza backoff para espaciar intentos. ✔️ Agrega jitter cuando muchos clientes puedan fallar simultáneamente. ✔️ Protege operaciones críticas con idempotencia. ✔️ Respeta Retry-After cuando el servicio lo proporcione. ✔️ Evita políticas de retry duplicadas en muchas capas. ✔️ Combina retry con Circuit Breaker cuando exista riesgo de fallas prolongadas. ✔️ Monitorea cuántos reintentos realiza realmente tu aplicación. 🧩 La realidad Una petición puede fallar por razones completamente temporales. En esos casos, volver a intentarlo puede hacer que el problema desaparezca sin afectar al usuario. Pero un reintento mal diseñado puede transformar: Un pequeño error en: Miles de nuevas solicitudes ↓ Más carga ↓ Más errores ↓ Más reintentos ↓ Servicio completamente saturado La diferencia está en tener una política clara. No se trata de intentar hasta que funcione. Se trata de saber: Cuándo intentar Cuánto esperar Cuántas veces hacerlo Cuándo detenerse 💬 La resiliencia no consiste en insistir infinitamente. Consiste en reconocer cuándo un fallo probablemente sea temporal, recuperarse de él de forma controlada y saber cuándo dejar de intentarlo. 👉 La próxima vez que implementes un retry , no pienses solamente en el segundo intento. Piensa también en qué ocurriría si miles de solicitudes estuvieran haciendo exactamente lo mismo al mismo tiempo. 🔥 El backend no se ve, pero sin él, nada funciona.

22 ago 2026
Leer publicación
🔗 ¿Qué es una clave foránea y cómo conecta las tablas de una base de datos?
Base de datos

🔗 ¿Qué es una clave foránea y cómo conecta las tablas de una base de datos?

Imagina que estás desarrollando una tienda en línea. Tienes una tabla con todos los usuarios. Otra con los pedidos. Otra con los productos. Otra con las direcciones. Otra con los pagos. Ahora imagina que un usuario realiza una compra. Surge una pregunta muy importante. 👉 ¿Cómo sabe la base de datos que ese pedido pertenece exactamente a ese usuario? ¿Dónde guarda esa relación? ¿Tiene que copiar toda la información del cliente dentro de cada pedido? La respuesta es: No. Para eso existen las claves foráneas (Foreign Keys). Son uno de los pilares de cualquier base de datos relacional y permiten que distintas tablas trabajen juntas como si fueran una sola. 🧠 ¿Qué es una clave foránea? Una clave foránea ( Foreign Key ) es un campo que crea una relación entre dos tablas. Su función consiste en apuntar hacia la clave primaria de otra tabla. En otras palabras... es un enlace. Una referencia. Un puente que conecta información relacionada. Gracias a ella la base de datos entiende cómo se relacionan los datos sin necesidad de duplicarlos. 💡 Piensa en una credencial escolar Imagina una universidad. Existe un expediente para cada alumno. Cada uno tiene un número único. Por ejemplo: Matrícula: 202500123 Ahora imagina la biblioteca. Cuando un estudiante pide un libro... la biblioteca no escribe nuevamente: 👤 Nombre completo. 🏠 Dirección. 📞 Teléfono. Solo registra la matrícula. Porque esa matrícula ya identifica al alumno. La clave foránea funciona exactamente igual. ⚙️ Un ejemplo sencillo Supongamos estas dos tablas. 👤 Usuarios ID Nombre 1 Ana 2 Carlos 📦 Pedidos Pedido Usuario_ID 101 1 102 2 103 1 Observa la columna: Usuario_ID Ese campo es una clave foránea . No guarda el nombre del usuario. Guarda el identificador del usuario. Gracias a eso la base de datos sabe inmediatamente que: 📦 Pedido 101 pertenece a Ana. 📦 Pedido 102 pertenece a Carlos. 📦 Pedido 103 también pertenece a Ana. Y todo sin repetir el nombre del cliente una y otra vez. 🚀 ¿Por qué es tan importante? Las claves foráneas permiten: ✅ Relacionar información entre tablas. ✅ Evitar duplicar datos. ✅ Mantener la consistencia. ✅ Facilitar consultas complejas. ✅ Reducir errores. Sin ellas... cada tabla sería completamente independiente. Y relacionar información sería mucho más complicado. 💡 Imagina una tienda enorme Supongamos que tienes: 👤 2 millones de usuarios. 📦 50 millones de pedidos. Si cada pedido guardara: Nombre. Correo. Dirección. Teléfono. Toda esa información estaría repetida millones de veces. Eso significaría: 📈 Más espacio ocupado. 🐌 Consultas más lentas. ⚠️ Mayor riesgo de inconsistencias. Con una clave foránea... cada pedido solo guarda algo como: Usuario_ID = 25 Y cuando necesita conocer el nombre del cliente... simplemente consulta la tabla Usuarios. Mucho más eficiente. 🌐 ¿Qué sucede cuando consultas información? Imagina esta consulta. Quieres obtener todos los pedidos de Ana. La base de datos hace algo parecido a esto. Primero encuentra: Ana Su ID es: 1 Después busca todos los pedidos donde: Usuario_ID = 1 Y automáticamente obtiene: 📦 Pedido 101. 📦 Pedido 103. Todo gracias a la relación creada por la clave foránea. 💳 Un ejemplo bancario Supongamos una aplicación bancaria. Existe una tabla llamada: 🏦 Cuentas. Y otra llamada: 💸 Transferencias. Cada transferencia pertenece a una cuenta. No tendría sentido guardar nuevamente: Nombre del cliente. CURP. Dirección. Correo. Cada transferencia solo necesita almacenar: Cuenta_ID Después, cuando sea necesario... la base de datos recupera toda la información del titular desde la tabla correspondiente. 🛒 Otro ejemplo muy común Piensa en Amazon. Un pedido puede contener varios productos. Existe una tabla: 📦 Productos. Y otra: 🛒 Detalle_Pedido. Cada fila del detalle contiene algo parecido a esto. Pedido_ID Producto_ID Cantidad Observa que: Producto_ID es otra clave foránea. Gracias a ella la base de datos sabe exactamente qué producto forma parte del pedido. 🌐 ¿Qué sucede si intentas guardar información inválida? Supongamos que intentas registrar este pedido. Pedido: 104 Usuario_ID: 99 Pero en la tabla Usuarios no existe ningún usuario con ID 99. Si la clave foránea está correctamente configurada... la base de datos responde algo parecido a: ❌ Operación rechazada. ¿Por qué? Porque impedir relaciones inválidas es precisamente una de sus funciones. Así evita que aparezcan pedidos de usuarios inexistentes. 🛡️ Esto se conoce como integridad referencial Las claves foráneas ayudan a mantener algo llamado: Integridad referencial. Suena complicado. Pero la idea es muy sencilla. 👉 Toda referencia debe apuntar a un registro que realmente exista. Eso garantiza que la información permanezca consistente. Y evita errores muy difíciles de detectar. ⚠️ ¿Qué ocurre si eliminas un usuario? Aquí aparece otro escenario interesante. Supongamos que existe: 👤 Usuario 25. Y tiene: 📦 350 pedidos. ¿Qué debería hacer la base de datos si eliminas ese usuario? Existen varias estrategias. 🚫 RESTRICT No permite eliminar el usuario. Porque todavía existen pedidos relacionados. 🗑️ CASCADE Elimina también todos los pedidos relacionados. 🔄 SET NULL Mantiene los pedidos. Pero elimina la relación. Cada proyecto decide cuál estrategia tiene más sentido. ⚠️ Un error muy común Muchos principiantes hacen algo parecido a esto. Tabla Pedidos. Pedido NombreCliente CorreoCliente TelefonoCliente DireccionCliente Todo queda duplicado. Si el cliente cambia su nombre. Hay que modificar cientos de pedidos. Si cambia su correo. Hay que actualizar miles de registros. Eso rompe uno de los principios más importantes del diseño de bases de datos. Con una clave foránea... solo existe un registro del usuario. Todos los pedidos apuntan hacia él. 🛠️ ¿Qué tipos de relaciones permiten? Las claves foráneas hacen posible prácticamente todas las relaciones de una base de datos. 👤 Uno a Uno (1:1) Un usuario. Una configuración de perfil. 📦 Uno a Muchos (1:N) Un usuario. Muchos pedidos. 🔄 Muchos a Muchos (N:M) Un estudiante. Muchos cursos. Y cada curso tiene muchos estudiantes. Aquí normalmente aparece una tabla intermedia que contiene dos claves foráneas. 🌐 ¿Dónde las utilizas todos los días? Aunque no lo notes... las claves foráneas están presentes en prácticamente cualquier aplicación. 📦 Pedidos y clientes. 💬 Comentarios y publicaciones. ❤️ Likes y usuarios. 📧 Correos y destinatarios. 🎵 Canciones y álbumes. 🎬 Películas y actores. 📚 Libros y autores. Son el mecanismo que mantiene unidas todas esas piezas. ⚠️ Otro error frecuente Pensar que una clave foránea sirve únicamente para relacionar tablas. También protege la información. Porque impide crear relaciones inexistentes. Eso convierte a la propia base de datos en una capa adicional de validación. Incluso aunque exista un error en el backend. 🧩 La realidad Cada vez que consultas los pedidos de un cliente... los comentarios de una publicación... las materias inscritas por un alumno... o los productos de una compra... la base de datos está utilizando claves foráneas para conectar información distribuida en distintas tablas. Y aunque nunca aparezcan en la interfaz... son una de las razones por las que una base de datos relacional puede organizar millones de registros sin perder consistencia. 🚀 Conclusión Las claves foráneas son el mecanismo que permite conectar tablas dentro de una base de datos relacional. Gracias a ellas es posible evitar información duplicada, mantener la integridad de los datos y construir relaciones claras entre diferentes entidades. Mientras la clave primaria identifica de forma única un registro... la clave foránea indica cómo ese registro se relaciona con otros. Sin ellas, las tablas serían simples listas independientes. Con ellas, se convierten en un sistema capaz de representar relaciones complejas de forma ordenada, eficiente y segura. 💬 Las claves primarias identifican los datos. Las claves foráneas construyen las relaciones entre ellos. 👉 ¿Cuál ha sido la relación más compleja que te ha tocado modelar en una base de datos: 1:1, 1:N o N:M? 👀 🔥 El backend no se ve, pero sin él, nada funciona. 📚 Versión completa y ampliada disponible en mi blog: https://hermanprimo.dev/blog/que-es-una-clave-foranea-y-como-conecta-las-tablas #SQL #BaseDeDatos #ForeignKey #Backend #SoftwareEngineering #DatabaseDesign #PostgreSQL #MySQL #ArquitecturaSoftware

21 ago 2026
Leer publicación

Contacto

¿Tienes una idea, proyecto, propuesta laboral o colaboración?

Escríbeme un mensaje por WhatsApp.

Información de contacto

Redes

Mensaje rápido

Generar mensaje para WhatsApp

Llena los campos y se abrirá WhatsApp con un mensaje listo para enviar.