# Adan Condori — Full Content Corpus > Source: https://adancondori.github.io/. Senior Engineering Leader, 10+ years building payment systems. This file concatenates the full markdown content of every blog post for AI engines that prefer single-file ingestion. Generated on 2026-06-21. --- ================================================================================ ## Post: 2019-12-05-android-vs-kotlin Source file: _posts/2019-12-05-android-vs-kotlin.md ================================================================================ --- layout: post title: "Kotlin vs Java - Spanish" modified: categories: excerpt: > Entonces, estas son las razones por las que mudarse a Kotlin es una de las mejores cosas que puede hacer. tags: [] image: feature: date: 2019-12-05T19:23:53-07:00 --- ## **Kotlin vs Java** En mis 7 años desarrollando Android Java sigue siendo estandar para el desarrollo de aplicaciones nativas, pero no quiero decir que sea mejor o bueno, recordemos que Kotlin es un idioma con soporte oficial para escribir aplicaciones de Android, asi mismo kotlin en los últimos años a ganado bastante popularidad, a continuación listo algunas razones porque mudarse a kotlin. - El lenguaje y el entorno están maduros - Está perfectamente integrado con Android Studio - Su evolución está bien cubierta - Es mucho más seguro que Java - Kotlin es multi-plataforma Entonces, estas son las razones por las que mudarse a Kotlin es una de las mejores cosas que puede hacer. | **Parámetro** | **Java** | **Kotlin** | | ------------------ | --------- | --------------- | | Compilacion | Bytecodes | Máquina virtual | | Seguridad nula | Χ | √ | | Expresión Lambda | Χ | √ | | Matriz invariante | Χ | √ | | Campos no privados | √ | Χ | | Casts inteligentes | Χ | √ | | Miembros estáticos | √ | Χ | | Tipos de comodines | √ | Χ | | Objetos Singletons | √ | √ | ================================================================================ ## Post: 2020-10-15-desing-patterns Source file: _posts/2020-10-15-desing-patterns.md ================================================================================ --- layout: post title: "Desing Patterns - Spanish" modified: categories: excerpt: > Son técnicas de desarrollo aplicables bajo el paradigma orientado a objetos que tienen como objetivo principal dar una solución genérica a problemáticas tipicas y recurrentes que se presentan en la etapa de diseño en cualquier metodología de desarrollo de software. tags: [] image: feature: date: 2020-10-15T23:23:53-07:00 --- ## Patrones de Diseño (Desing Patern) Soy un programdor y muchas siempre escuche hablar de patrones de diseño. Y de seguro que has manejado o conoces algunos patrones como el conocido Singleton. Los patrones de diseño son muy útiles al momento de hacer software, por lo cual cada programados deberia conocer por lo menos los patrones mas conocidos de esta manera evitaremos dolores de cabeza. Bueno a acontinuación explicare la importancia del uso de estos patrones en un software. ### ¿Qué son los patrones de diseño? Son técnicas de desarrollo aplicables bajo el paradigma orientado a objetos que tienen como objetivo principal dar una solución genérica a problemáticas tipicas y recurrentes que se presentan en la etapa de diseño en cualquier metodología de desarrollo de software. ### ¿Por qué usar patrones de diseño? Como ya vimos en el artículo sobre [principios de diseño](https://www.genbetadev.com/metodologias-de-programacion/doce-principios-de-diseno-que-todo-desarrollador-deberia-conocer), si queremos desarrollar aplicaciones robustas y fáciles de mantener, debemos cumplir ciertas "reglas". Lo pongo entre comillas porque aunque estas reglas de diseño son recomendables (muy recomendables), no son obligatorias. Siempre podemos decidir no aplicarlas. Aunque si no lo hacemos, hay que ser conscientes de la razón de no aplicarlas y de sus consecuencias. **Los patrones de diseño nos ayudan a cumplir muchos de estos principios o reglas de diseño**. Programación [SOLID](https://www.genbetadev.com/paradigmas-de-programacion/solid-cinco-principios-basicos-de-diseno-de-clases), control de cohesión y acoplamiento o reutilización de código son algunos de los beneficios que podemos conseguir al utilizar patrones. ================================================================================ ## Post: 2020-10-20-software-design Source file: _posts/2020-10-20-software-design.md ================================================================================ --- layout: post title: "¿What is Software Design? - Spanish" modified: categories: excerpt: > La ingenieria de softtware es un conjunto de procesos en la cual se define test unitarios, arquitectura, componentes, interfaces y entre otras características con el objetivo de obtener la solución requerida. tags: [] image: feature: date: 2020-10-20T23:23:53-07:00 --- ### ¿Qué es Diseño de de Software? La ingenieria de softtware es un conjunto de procesos en la cual se define test unitarios, arquitectura, componentes, interfaces y entre otras características con el objetivo de obtener la solución requerida. Asi mismo el diseño de software viene desde la sexta generación de computadoras 1900 en adelante, tal vez en ese entonces nos se conocia el termino Diseño de Software, pero si se ultilizaban los mismos pasos que se utilizan en la actualidad como la captura de requisitos, arquitectura, diseño de interfaces etc., pero en los ultimos 10 años, al diseño de software le ha acompañado las metodologias de desarrollo como Lean, Scrum con el objetivo de mejorar proceso de desarrollo asi mismo el diseño de software. Algunas procesos del diseño de software han hido perfeccionando a lo largo del tiempo como las formas de realizar capturas de requisitos, nuevas arquitecturas de software, la automatización de pruebas, tambien la documentacion se ha mejorando, disminuyendo y adaptandose a la necesitad real del Software. Desde nuestro punto de vista el Desarrollo de software mantiene los siguientes pasos, captura de requisitos, análisis, Diseño del software, implementación y pruebas, tomando como factor importanto el Diseño de Software ya que este puntos sera el que defina si el sistemas sera robusto y algo pequeño, ya que en el Diseño de Software se define la Arquitectura del software, interfaces, tecnologías, si se aplicaran patrones de diseño etc. El tiempo de Diseño de Software deberia ser la mitad del tiempo estimado tomando en cuenta los siguientes puntos segun Davis: - **El diseño tendría que ser rastreable por el modelo de análisis.** - **El diseño no tendría que reinventar la rueda.** - **El diseño debe "minimizar la distancia intelectual" entre el software y el problema tal y como existe en el mundo real.** - **El diseño tiene que exhibir uniformidad e integración.** - **El diseño tendría que ser estructurado para adaptarse al cambio.** - **El diseño tendría que estar estructurado para degradarse suavemente, incluso cuándo los datos, los acontecimientos o las condiciones operativas son irregulares.** - **El diseño no es codificación, la codificación no es diseño.** - **La calidad del diseño tendría que ser evaluado cuando se está creando, no después.** - **El diseño tendría que ser revisado para minimizar los errores conceptuales (semánticos)**. ### Conclución: El Diseño de Software es muy importante en el desarrollo de software ya que esta definira la estabilidad, el rendimiento, los nuevos cambios que se necesiten. Por lo cual se debe tomarse un buen tiempo de análisis para definir el Diseño del Software. ### Referencias: https://es.wikipedia.org/wiki/Dise%C3%B1o_de_software https://sesitdigital.com/tendencias-de-desarrollo-de-software-para-el-2020/ https://www.monografias.com/trabajos73/diseno-software/diseno-software.shtml ================================================================================ ## Post: 2020-10-21-notes-git Source file: _posts/2020-10-21-notes-git.md ================================================================================ --- layout: post title: "Notes Git - Spanish" modified: categories: excerpt: > Mis notas sobre el curso y mi experiencia con Git y gitHub tags: [] image: feature: date: 2021-04-29T08:23:53-07:00 --- # Notes Git (Spanish) ## Configuración de Git **Primer paso: Generar tus llaves SSH**. Recuerda que es muy buena idea proteger tu llave privada con una contraseña. ```bash ssh-keygen -t rsa -b 4096 -C "youremail@example.com" ``` **Segundo paso**: Terminar de configurar nuestro sistema. **En Windows y Linux**: ```bash # Encender el "servidor" de llaves SSH de tu computadora: eval $(ssh-agent -s) # Añadir tu llave SSH a este "servidor": ssh-add ruta-donde-guardaste-tu-llave-privada ``` **En Mac**: ```bash # Encender el "servidor" de llaves SSH de tu computadora: eval "$(ssh-agent -s)" # Si usas una versión de OSX superior a Mac Sierra (v10.12) # debes crear o modificar un archivo "config" en la carpeta # de tu usuario con el siguiente contenido (ten cuidado con # las mayúsculas): Host * IPQoS=throughput ## this line is optional in diferents version on mac AddKeysToAgent yes UseKeychain yes IdentityFile ruta-donde-guardaste-tu-llave-privada # Añadir tu llave SSH al "servidor" de llaves SSH de tu # computadora (en caso de error puedes ejecutar este # mismo comando pero sin el argumento -K): ssh-add -K ruta-donde-guardaste-tu-llave-privada ``` ## Ejemplo Usando git  ## Comandos de ayuda Como volver atrás después de hacer un **git add .** ```sh git add . // or git add name_file ``` luego borramos los cambios desde el staging ```sh git rm name_file git rm --cached name_file ``` Como comparar el historial de un archivo modificado ```sh git log git diff hash1 hash2 ``` Como volver commit anterior ```bash git reset hash --hard ``` Mostrar todos los log y diferencias ```bash git log --stat ``` Crear alias para mostrar gráfico en consola ```bash arbolito="git log --all --graph --decorate --oneline" ``` ## Manejo de Tags ```bash # Creación de tagg git tag -a nametag -m "mensaje personalizado referente del tag" hashcommit # Sube todos los tags creados git push origin --tags # Borra Tags locales git tag -d nametag # Borrar Tags de los repositorios remotos git push origin :refs/tags/tagName ``` Delete Tag ```bash git tag -d ``` ## Buscar palabras en archivos y commit - **`git grep color`** --> use la palabra color - **`git grep la`** --> donde use la palabra la - **`git grep -n color`** –> en que lineas use la palabra color - **`git grep -n platzi`** --> en que lineas use la palabra platzi - **`git grep -c la`** --> cuantas veces use la palabra la - **`git grep -c paltzi`** --> cuantas veces use la palabra platzi - **`git grep -c “
”`** –> cuantas veces use la etiqueta
- **`git log-S “cabecera”`** --> cuantas veces use la palabra cabecera en
todos los commits.
- **`grep`** –> para los archivos`
- **`log`** -> para los commits.
## Comandos colaborativos
- **`git shortlog`**: Ver cuantos commits a hecho los miembros del equipo
- **`git shortlog -sn`**: Las personas que han hecho ciertos commits
- **`git shortlog -sn --all`**: Todos los commits (también los borrados)
- **`git shortlog -sn --all --no-merges`**: muestra las estadisticas de los comigs del repositorio donde estoy
- **`git config --global alias.stats “shortlog -sn --all --no-merges”`**: configura el comando “shortlog -sn --all --no-merges” en un Alias en las configuraciones globales de git del pc
- **`git blame -c blogpost.html`**: Muestra quien ha hecho cambios en dicho archivo identado
- **`git blame --help`**: Muestra en el navegador el uso del comando
- **`git blame archivo -L 35, 60 -c`**: Muestra quien escribio el codigo con informacion de la linea 35 a la 60, EJ: `git blame css/estilos.css -L 35, 60 -c`
- **`git branch -r`**: Muestra las Ramas remotas de GitHub
- **`git branch -a`**: Muestra todas las Ramas del repo y remotas de GitHub
## Estados del archivos
Untracked -> staging -> repository local -> remote
## Migrar repositorio
Mover contenido de repositorio Git a otro repositorio conservando el Historia
```sh
git clone
cd go-directore
git branch -a // To see a list of the different branches
git branch -r | grep -v '\->' | while read remote; do git branch --track "${remote#origin/}" "$remote"; done
git fetch --all
git pull --all
git remote rm origin
git remote add origin
✅ Ofrecer más opciones al usuario final
✅ Cambiar o eliminar pasarelas obsoletas sin fricción
✅ Ahorrar cientos de horas en mantenimiento
✅ Minimizar errores humanos en integraciones sensibles
---
### 🧭 8. Reflexión Final
Invertir en una arquitectura de pagos limpia es una decisión estratégica. En lugar de reescribir todo con cada nuevo proveedor, construyes una base sólida para el futuro.
Si estás por integrar tu segunda o tercera pasarela, ¡detente y piensa en la arquitectura! Vale más hacerlo bien una vez que mal diez veces.
================================================================================
## Post: 2025-01-28-Seguridad_en_Transacciones_3DS
Source file: _posts/2025-01-28-Seguridad_en_Transacciones_3DS.md
================================================================================
---
layout: post
title: 🛡️🔒 ¡Seguridad en Transacciones 3DS! 🔒🛡️
modified:
categories: 3Ds, Card, Payments
excerpt: >
Mis experiencia con 3Ds
tags: []
image:
feature:
date: 2025-01-28T08:12:53-07:00
---
# "Seguridad en Transacciones 3DS: Experiencias para Desarrolladores (O Cómo Evitar que tu Sistema de Pagos se Convierta en un Circo)"
En el mundo actual de los pagos digitales, garantizar la seguridad en las transacciones es tan esencial como tener café en una reunión matutina de programadores. La autenticación 3DS (Three-Domain Secure) se ha convertido en un pilar fundamental para proteger tanto a usuarios como a empresas. En este post, compartiré mis experiencias implementando flujos 3DS en sistemas de pago, abordando mejores prácticas, errores comunes y consideraciones de UX para mantener altas las tasas de conversión (y evitar que tus usuarios huyan despavoridos).
## ¿Qué es 3DS y por qué es importante?
3DS es un protocolo de seguridad diseñado para prevenir fraudes en las transacciones en línea. Es como ese amigo paranoico que siempre pregunta "¿estás seguro?" antes de hacer cualquier cosa – pero en este caso, ¡realmente lo necesitamos! Mediante un proceso de autenticación adicional, este sistema ayuda a verificar que el titular de la tarjeta sea quien realmente realiza la compra, reduciendo así el riesgo de transacciones no autorizadas. Porque, seamos honestos, nadie quiere descubrir que "alguien" compró cinco televisores de última generación con su tarjeta mientras dormía plácidamente.
## Implementación de flujos 3DS: Buenas prácticas (o cómo no convertir tu checkout en una prueba de obstáculos)
### Integración fluida con la experiencia del usuario:
Asegúrate de que el proceso de autenticación no interrumpa la experiencia de compra más que una actualización de Windows en plena presentación importante. La comunicación clara y el diseño intuitivo son esenciales para evitar que los usuarios abandonen el proceso y se vayan a comprar a la competencia (o peor aún, a Amazon).
En mi experiencia implementando 3DS, el flujo típico funciona así: tu frontend envía solo los IDs del producto y descuentos al backend, donde realizas todos los cálculos sensibles antes de conectarte al proveedor de pagos. Pro-tip: agrega encriptación entre frontend y backend, porque nunca sabes cuándo algún hacker aburrido decidirá que tu sistema es su próximo proyecto de fin de semana.
Cuando integras con una pasarela de pagos, te ofrecerán opciones con y sin 3DS. Con 3DS, generalmente consumirás un servicio que te devolverá un HTML, te redireccionará a su sitio, o te proporcionará un SDK con el formulario 3DS. Este componente suele ser personalizable en colores.
Dependiendo del proveedor, la respuesta puede ser síncrona o asíncrona vía webhook. Para manejar esto, necesitarás almacenar temporalmente la información de la transacción en tu base de datos o Redis, generando un UUID único para completar el flujo. Es como dejar una nota para tu yo del futuro diciendo "Hey, recuerda completar esta transacción cuando el banco responda".
### Pruebas exhaustivas:
Realiza pruebas de extremo a extremo en diferentes escenarios como si fueras un detective obsesivo: diferentes dispositivos, navegadores y condiciones de red. Porque si tu sistema 3DS funciona perfectamente en Chrome pero falla en Safari, tendrás a todos los usuarios de Apple enviándote correos electrónicos más salados que las palomitas del cine.
Coordina con tu proveedor si se requiere algún proceso de certificación. Por experiencia, en LATAM estos procesos suelen ser más cortos que un sprint de desarrollo, mientras que en Asia son más largos que la documentación de un proyecto legacy.
Como desarrollador, serás el primero en probar la integración, así que conocerás todos los posibles errores antes que nadie (¡felicidades por este dudoso honor!). Luego, pásalo al equipo de QA para una segunda revisión. Si puedes conseguir clientes beta para probar, mejor aún - ellos encontrarán errores que ni siquiera sabías que eran posibles.
Y no olvides hacer pruebas en producción sacrificando algunos dólares. Sí, tu departamento de finanzas te mirará raro cuando vea "Gastos de prueba: $10" en el informe, pero es mejor que explicar por qué todos los pagos fallan en producción.
### Actualización constante:
Mantente al día con las últimas versiones y recomendaciones del protocolo 3DS. Las actualizaciones pueden incluir mejoras de seguridad que protejan tanto a la plataforma como a sus usuarios. Recuerda: en el mundo de la seguridad digital, quedarse atrás es como usar "password123" como contraseña en 2025.
### Monitoreo y análisis:
Implementa herramientas de monitoreo para detectar comportamientos anómalos y medir la tasa de conversión en cada fase del flujo. Porque sin datos, estás tan a ciegas como un murciélago en una discoteca con luces estroboscópicas.
Para esto, puedes usar herramientas como Kibana, Sentry, CloudWatch de AWS, o cualquier otra que tu equipo conozca y esté dispuesto a implementar (o sea, la que alguien del equipo ya haya usado y pueda configurar sin tener que leer toda la documentación).
Prepárate para dar soporte al menos durante un mes después del lanzamiento. Los logs serán tus mejores amigos - o tus peores enemigos si olvidaste registrar la información correcta. Asegúrate de almacenar suficientes datos para reconstruir el flujo cuando algo falle a las 3 AM y tengas que depurar medio dormido.
## Errores comunes a evitar (o cómo no sabotear tu propio sistema)
### Interrupciones innecesarias:
Un flujo de autenticación demasiado complejo es como pedir 17 documentos para entrar a una fiesta. Evita pasos redundantes y asegúrate de que cada interacción aporte valor a la seguridad sin sacrificar la usabilidad. Nadie quiere sentir que está aplicando para un préstamo hipotecario cuando solo quiere comprar calcetines.
Si el proceso de pago toma más de 15 segundos, prepárate para ver a tus usuarios abandonar más rápido que desarrolladores en una reunión de planificación que se extiende después de las 5 PM.
Coordina estrechamente con tu proveedor y tu equipo. Si encuentras impedimentos, comunícalos inmediatamente a tu líder. Y si descubres un error en producción o estimaste mal los tiempos (¿quién no lo ha hecho?), informa a tu equipo de inmediato. Recuerda: el único error imperdonable es el que ocultas hasta que explota.
### Falta de personalización:
No todos los usuarios son iguales, igual que no todos los desarrolladores sobreviven con la misma cantidad de café. Considera estrategias que permitan adaptar el proceso de autenticación según el perfil del usuario o el nivel de riesgo de la transacción. Tu abuela de 80 años comprando flores online no necesita el mismo nivel de seguridad que alguien adquiriendo criptomonedas a las 3 AM.
### Mala integración con el sistema de pagos:
Una integración deficiente puede llevar a errores de comunicación entre los sistemas, como esa pareja que no se habla después de una discusión. Es vital trabajar de cerca con proveedores y socios tecnológicos para asegurar una implementación robusta. Porque cuando los sistemas no se comunican bien, el único mensaje que recibe el usuario es "Error desconocido" (la frase más temida en el universo digital, justo después de "Hemos actualizado nuestra política de privacidad").
Al iniciar la integración, solicita toda la documentación posible: Postman collections, videos de uso, Swagger, y lo más importante: un número de teléfono y correo para consultas. Por experiencia, crear grupos de WhatsApp, Slack o Teams con el proveedor acelera la resolución de problemas. Los correos pueden tardar entre 1 y 24 horas en ser respondidos, tiempo suficiente para que tu ansiedad construya un castillo y lo derrumbe varias veces.
## Consideraciones de UX para mantener la conversión (o cómo no espantar a tus clientes)
La seguridad no debe estar reñida con una buena experiencia de usuario, igual que la pizza no debería estar reñida con la piña (aunque esto último sigue siendo controvertido y podría iniciar una guerra civil en tu equipo de desarrollo).
### Claridad en la comunicación:
Informa al usuario sobre cada paso del proceso de autenticación. Mensajes claros y precisos ayudan a reducir la ansiedad y a generar confianza. "Estamos verificando tu identidad" suena mucho mejor que "Procesando... espere... procesando..." durante 30 segundos de silencio incómodo que hace que el usuario se pregunte si debería refrescar la página (spoiler: no debería).
### Optimización para dispositivos móviles:
Dado el creciente uso de smartphones para realizar pagos (porque, ¿quién usa computadoras de escritorio estos días?), el flujo 3DS debe estar completamente optimizado para dispositivos móviles, con tiempos de carga más rápidos que las excusas de un desarrollador cuando le preguntan cuándo estará lista la función prometida para "finales de semana".
### Feedback en tiempo real:
Proporciona retroalimentación inmediata en caso de errores o validaciones. Esto permite que el usuario corrija rápidamente cualquier inconveniente sin sentirse más perdido que un programador junior en una reunión de arquitectura donde todos hablan en acrónimos que nadie se molesta en explicar.
## Conclusión
Implementar un flujo 3DS robusto es un balance entre fortalecer la seguridad y mantener una experiencia de usuario óptima, algo así como intentar comer saludable pero no renunciar completamente al chocolate. Siguiendo estas mejores prácticas y evitando los errores comunes, los desarrolladores pueden crear sistemas de pago seguros que no afecten negativamente las tasas de conversión.
En mi experiencia liderando integraciones de pago, he aprendido que la comunicación clara con proveedores, usuarios y equipo es tan importante como el código mismo. En un entorno donde la confianza del cliente es fundamental, invertir en un proceso de autenticación eficiente y amigable es clave para el éxito a largo plazo.
¿Qué estrategias han implementado en sus proyectos para optimizar la seguridad sin comprometer la experiencia del usuario? ¿Alguna vez han tenido que lidiar con un sistema 3DS tan complicado que parecía diseñado por un comité de gatos caminando sobre teclados? ¿O quizás han experimentado ese momento de pánico cuando un pago falla en producción y el cliente es el CEO de la empresa? ¡Los leo en los comentarios!
================================================================================
## Post: 2025-06-01-Trazabilidad_en_Transacciones_3DS
Source file: _posts/2025-06-01-Trazabilidad_en_Transacciones_3DS.md
================================================================================
---
layout: post
title: 🛡️🔒 Seguridad y Trazabilidad en Transacciones de Pagos 🔍💳
modified:
categories: [3DS, Card, Payments]
excerpt: >
Descubre cómo diseñamos una arquitectura robusta para trazabilidad de pagos 3DS en sistemas distribuidos. De la incertidumbre al diagnóstico preciso.
tags: [3DS, Payments, Ruby on Rails, Logging, Observability]
image:
feature:
date: 2025-6-24T08:12:53-07:00
---
## 🧵 El Hilo Invisible: Trazabilidad en el Caos Distribuido
> _“El pago falló.”_
Tres palabras que pueden parecer simples, pero en el contexto de un sistema distribuido, suenan como una alarma en medio de la noche. Para nosotros, los desarrolladores, no son solo un error: son el inicio de una búsqueda a través de múltiples servicios, servidores y eventos asincrónicos que, muchas veces, no hablan entre sí.
En plataformas que procesan miles de transacciones por minuto, los logs tradicionales no son la solución. Son parte del problema. Por eso, en este artículo quiero compartir contigo una estrategia real y probada, que convierte el caos en claridad: **trazabilidad de punta a punta** en flujos de pago con 3DS.
---
## 🔥 El Problema: Diagnóstico en la Oscuridad
Imagina este flujo típico de una transacción:
1. El usuario hace clic en **"Pagar"**.
2. La petición llega al backend (por ejemplo, en el `servidor-03`).
3. Se ejecuta la lógica que interactúa con la **pasarela de pago**.
4. Unos minutos después, llega un **webhook** a otro servidor (`servidor-08`).
5. Ese webhook dispara un **WebSocket** para actualizar al usuario en tiempo real.
Ahora... algo falla. ¿Dónde está el error? ¿Se cayó el webhook? ¿La pasarela no respondió? ¿El socket nunca se emitió?
Peor aún: el equipo comercial solo te da el correo del usuario y te dice *"revisá por qué no le llegó el cobro"*. Sin trazabilidad, estás buscando una aguja en un pajar.
---
## 🧠 La Solución: `trace_id`, Nuestro Hilo de Ariadna
Para unir todos los puntos de ese flujo, necesitas un **ID de correlación**. Ese `trace_id` nace al inicio de la solicitud y vive durante todo el recorrido, de extremo a extremo.
Con él:
- Tienes **contexto compartido** entre servicios.
- Puedes **reconstruir el flujo completo** en herramientas como Kibana o Datadog.
- Evitas depender de logs desordenados y fragmentados.
---
## 🛠️ Cómo lo Implementamos
### 1. `CurrentAttributes`: Contexto Seguro y Limpio
Usamos `ActiveSupport::CurrentAttributes` para mantener el `trace_id` accesible, sin tener que pasarlo como parámetro entre métodos. Un middleware o filtro en el controlador base hace lo siguiente:
- Revisa si el header de la petición ya trae un `trace_id`.
- Si no existe, genera un `UUID` nuevo y lo guarda como contexto global.
Esto asegura que cada flujo tenga un identificador único y consistente.
---
### 2. Módulo de Logging Reutilizable
Creamos un módulo (`PaymentLogger`, por ejemplo) con un solo método:
```ruby
log_payment_event(tag:, data:)
````
Este método:
* Enriquecer automáticamente cada log con:
* `trace_id`
* `timestamp`
* `hostname`
* Datos del usuario (si están disponibles)
* Formatea los logs como JSON estructurado.
* Ofusca datos sensibles como tarjetas, emails, tokens, etc.
De esta forma, el logging no es una tarea, sino una característica clave del sistema.
---
### 3. Instrumentación Estratégica
Aplicamos `log_payment_event` en los puntos críticos del flujo, ejemplos en ruby on rails:
#### 🟢 Inicio (API Controller)
```ruby
log_payment_event(tag: 'payment_flow.started', data: { params: request.params })
```
#### 🔁 Interacción con Pasarela (Service Layer)
```ruby
log_payment_event(tag: 'payment_gateway.request', data: { gateway_params: ... })
log_payment_event(tag: 'payment_gateway.response', data: { response: ..., status: 'success' })
```
#### 📩 Webhook (Confirmación Asíncrona)
* Extraemos el `order_id` del webhook.
* Lo usamos para setear nuevamente el `trace_id`.
```ruby
log_payment_event(tag: 'webhook.received', data: { payload: ... })
```
#### 📡 WebSocket (Notificación al Usuario)
```ruby
log_payment_event(tag: 'socket.emitted', data: { channel: ..., event: ... })
```
---
## 🔍 Resultado: Diagnóstico en Tiempo Real
Cuando alguien del equipo de soporte te da un `user_id`, simplemente haces esto:
1. **Buscas el `user_id` en Kibana:**
`json.context.user_id: "12345"`
2. **Extraes el `trace_id`:**
`"uuid-abc-123"`
3. **Rastreás todo el flujo:**
`json.trace_id: "uuid-abc-123"`
Y así obtienes toda la historia: solicitud, respuesta, webhook, socket… todo en orden y con contexto.
---
## 🎯 Conclusión: Trazabilidad es Confiabilidad
Diseñar un sistema de trazabilidad no es un lujo, es un requisito. En plataformas de pagos con 3DS, donde los errores pueden costar dinero (y reputación), no tener visibilidad es inaceptable.
Con un enfoque basado en `trace_id`, logs estructurados y contexto compartido, transformamos un sistema opaco en una máquina transparente, confiable y lista para escalar.
> *La próxima vez que leas “el pago falló”, no sentirás pánico. Tendrás el mapa.*
---
================================================================================
## Post: 2025-08-15-definition-of-ready-definition-of-done
Source file: _posts/2025-08-15-definition-of-ready-definition-of-done.md
================================================================================
---
layout: post
title: Definition of Ready y Definition of Done - Los guardianes de la calidad en desarrollo ágil
modified:
categories: Agile, Software Engineering, Best Practices
excerpt: >
Cómo DoR y DoD transforman el caos en un proceso predecible y de alta calidad
tags: []
image:
feature:
date: 2025-08-15T08:12:53-07:00
---
## Introducción: El problema del "casi listo"
¿Cuántas veces has escuchado esto en tu carrera?
* "Esta tarea está lista para desarrollar" → Te sientas a trabajar y te faltan 10 piezas de información
* "Este feature está terminado" → Lo deploys y encuentras 5 bugs en producción
* "Solo falta un detallito" → Ese detallito toma 3 días más
En mis más de 10 años desarrollando software, he visto equipos paralizados por la falta de claridad sobre dos preguntas fundamentales:
1. **¿Cuándo una tarea está realmente lista para empezar?**
2. **¿Cuándo un feature está realmente terminado?**
La respuesta a estas preguntas son dos conceptos que separan equipos amateur de equipos profesionales:
* **Definition of Ready (DoR)** - El contrato de entrada
* **Definition of Done (DoD)** - El contrato de salida
> Este artículo es una guía práctica y técnica sobre cómo implementar DoR y DoD en equipos de desarrollo modernos, con ejemplos reales de sistemas complejos que gestionan pagos, reservaciones y operaciones críticas.
---
## 1. ¿Qué es Definition of Ready (DoR)?
**Definition of Ready es el checklist que asegura que una historia de usuario tiene suficiente claridad, contexto y detalle antes de que el equipo comience a trabajar en ella.**
### El costo de no tener DoR
Sin DoR, esto es lo que pasa:
* El desarrollador empieza a trabajar y descubre que falta información → **Bloqueo**
* Hace suposiciones incorrectas → **Retrabajo**
* Interrumpe a PM/Product constantemente → **Pérdida de foco**
* Las estimaciones están mal porque no había contexto completo → **Sprint fallido**
**Caso real que viví:**
Un desarrollador tomó un ticket que decía "Arreglar bug en pagos". Sin DoR:
* No sabía qué gateway estaba fallando (Stripe, PayPal, BAC?)
* No sabía cómo reproducir el bug
* No sabía el impacto (¿cuántos usuarios afectados?)
* Pasó 4 horas investigando antes de poder empezar
Con DoR, esas 4 horas se habrían ahorrado.
---
## 2. Criterios de DoR - El Checklist Definitivo
### 2.1 Contexto y Justificación
**Qué debe incluir:**
- Título descriptivo con número de ticket
- El "por qué" de la funcionalidad
- Valor de negocio o problema que resuelve
- Módulo/área del sistema afectada
**Ejemplo real:**
**❌ Mal (sin DoR):**
```
Ticket: Arreglar rangos de edad
```
**✅ Bien (con DoR):**
```
PLAT-2768 | Corregir rangos de edad superpuestos en pricing de turnos
**Contexto:**
Los grupos de edad 18-65 y 65-100 se superponen. Un usuario de 65 años
aparece en ambos grupos, causando precios duplicados en facturación.
**Impacto:**
- 150 usuarios afectados en último mes
- Descuadre en reportes financieros
- Confusión en clientes (+10 tickets de soporte)
**Módulo:** Pricing / Reservations
```
### 2.2 Criterios de Aceptación Verificables
**Qué debe incluir:**
- Happy path (caso de éxito)
- Edge cases y escenarios de error
- Validaciones necesarias
- Comportamiento esperado del sistema
**Ejemplo con casos reales:**
```
**Criterios de Aceptación:**
Happy Path:
✅ El rango 18-65 debe cambiar a 18-64
✅ El rango 65-100 permanece sin cambios
✅ Un usuario de 64 años debe tener precio del grupo 18-64
✅ Un usuario de 65 años debe tener SOLO precio del grupo 65-100
Edge Cases:
✅ Usuario sin edad definida → usar precio default
✅ Edad = 0 o negativa → error de validación
✅ Edad > 150 → error de validación
Validaciones:
✅ No puede haber rangos superpuestos
✅ Rangos deben cubrir todas las edades posibles (1-100+)
✅ Precio debe ser mayor a 0
```
### 2.3 Alcance Técnico Definido
**Qué debe incluir:**
- Archivos/módulos probablemente afectados
- Integraciones o dependencias externas
- Migraciones de datos si son necesarias
- Impacto en APIs (GraphQL, REST)
- Cambios en packs/packages específicos
**Ejemplo real:**
```
**Alcance Técnico:**
Backend (Ruby on Rails):
- Models: PricingRule, ShiftPrice, AgeGroup
- Validations: AgeGroupValidator (actualizar lógica de rangos)
- Services: PricingCalculator (recalcular para edades límite)
- Migrations: UpdateOverlappingAgeRanges
APIs afectadas:
- GraphQL: shiftPricing query
- REST: /api/v1/pricing/calculate (deprecado pero aún en uso)
Frontend:
- React components: PricingForm, AgeRangeSelector
- Redux: pricing slice
Migraciones:
- Actualizar ~500 registros de pricing_rules existentes
- Recalcular precios históricos afectados (opcional)
Packs/Packages:
- Core app (no packs externos)
```
### 2.4 Multi-tenancy y Consideraciones de Scope
Para sistemas SaaS o multi-tenant:
```
**Multi-tenancy:**
- Scope: Por facility (cada instalación tiene sus rangos)
- Timezone: No aplica (edad no depende de timezone)
- Datos existentes: 45 facilities con rangos a actualizar
- Configuración: Migración automática + opción manual post-deploy
**Backward Compatibility:**
- Mobile app v2.3+ soporta nuevos rangos
- Mobile app < v2.3 seguirá usando API legacy (sin cambios)
```
### 2.5 Seguridad y Compliance
```
**Seguridad:**
- Datos sensibles: No (solo rangos de edad, no PII)
- Autorización: Solo admins pueden editar pricing rules (CanCanCan :manage, PricingRule)
- Auditoría: Loggear cambios en pricing_rules_audit_log
- Compliance: No aplica PCI/GDPR
**Permisos requeridos:**
- Desarrollo: :facility_admin o :super_admin
- QA: :facility_admin en staging
```
### 2.6 Plan de Testing
```
**Testing Strategy:**
Unit Tests:
- AgeGroupValidator#validate_no_overlaps
- PricingCalculator#price_for_age(64) → grupo 18-64
- PricingCalculator#price_for_age(65) → grupo 65-100
Integration Tests:
- Crear reserva con usuario edad 64 → precio correcto
- Crear reserva con usuario edad 65 → precio correcto
- Actualizar pricing rule → validaciones funcionan
System Tests (Playwright/Cypress):
- Admin crea nuevo pricing rule con rangos válidos
- Admin intenta crear rangos superpuestos → error visible
Testing Manual:
- Verificar migración en staging antes de producción
- Probar con 3-5 facilities reales
- Validar cálculos históricos no afectados
```
### 2.7 Dependencias y Blockers
```
**Dependencias:**
- PLAT-2750: Refactor de PricingCalculator (debe estar en develop)
- Staging con datos recientes (snapshot de prod < 7 días)
**Blockers:**
- Ninguno identificado
**Riesgos:**
- Migración podría tomar >10min en prod (500k registros)
Mitigación: Ejecutar en horario de bajo tráfico
```
---
## 3. ¿Qué es Definition of Done (DoD)?
**Definition of Done es el checklist que asegura que una historia está completamente terminada, probada, documentada y lista para producción.**
### El costo de no tener DoD
Sin DoD:
* Features "terminados" que rompen en producción
* Tests que faltan o están skippeados
* Documentación que nunca se actualiza
* Code review superficial porque no hay estándar claro
* Deuda técnica que crece exponencialmente
**Caso real:**
Un desarrollador hizo merge de un feature "terminado":
* Funcionaba en su máquina ✅
* Pero no tenía tests ❌
* Rompió el build de CI ❌
* No documentó las variables de entorno nuevas ❌
* Deployment falló en staging ❌
Resultado: 2 días de downtime, 3 desarrolladores bloqueados, PM frustrado, cliente enojado.
Con DoD, esto no habría pasado del code review.
---
## 4. Criterios de DoD - El Checklist de Calidad
### 4.1 Código Implementado y Clean
```
**Checklist:**
✅ Funcionalidad completa según criterios de aceptación
✅ Código sigue convenciones del proyecto (RuboCop, ESLint, Prettier)
✅ No hay código comentado sin justificación
✅ No hay console.log o binding.pry olvidados
✅ Variables tienen nombres descriptivos
✅ Métodos tienen responsabilidad única (SRP)
**Validación:**
bundle exec rubocop -A # Ruby
npm run lint # JavaScript
bundle exec rails runner 'system("packwerk check")' # Pack boundaries
```
### 4.2 Tests Completos y Pasando
**El mínimo aceptable:**
```
**Unit Tests:**
✅ Toda lógica de negocio tiene tests
✅ Edge cases cubiertos
✅ Mocks/stubs usados apropiadamente
✅ Coverage no disminuye (mínimo mantener %)
**Integration Tests:**
✅ Flujos completos end-to-end
✅ Integraciones con servicios externos (Stripe, AWS, etc.)
✅ Background jobs probados
**System Tests (si aplica):**
✅ UX crítica probada (checkout, login, pagos)
✅ Tests no son flaky (usar Timecop, stubs consistentes)
**Validación:**
bundle exec rspec # Todos los tests
SIMPLECOV_REPORT=true bundle exec rspec # Con coverage
bin/test_system system_specs/pricing/ # System tests
```
**Ejemplo de test bien hecho:**
```ruby
# spec/validators/age_group_validator_spec.rb
RSpec.describe AgeGroupValidator do
describe '#validate_no_overlaps' do
context 'when ranges overlap' do
it 'adds error for overlapping ranges' do
rule1 = build(:pricing_rule, age_min: 18, age_max: 65)
rule2 = build(:pricing_rule, age_min: 65, age_max: 100)
validator = AgeGroupValidator.new([rule1, rule2])
expect(validator.valid?).to be false
expect(validator.errors).to include('Age ranges cannot overlap')
end
end
context 'when ranges do not overlap' do
it 'is valid' do
rule1 = build(:pricing_rule, age_min: 18, age_max: 64)
rule2 = build(:pricing_rule, age_min: 65, age_max: 100)
validator = AgeGroupValidator.new([rule1, rule2])
expect(validator.valid?).to be true
end
end
end
end
```
### 4.3 Manejo de Timezone y Time (crítico en apps globales)
```
✅ Usar Time.current en lugar de Time.now
✅ Usar Time.zone.parse en lugar de Time.parse
✅ Respetar timezone de facility/user según contexto
✅ Tests con tiempo usan Timecop.freeze para evitar flakiness
**Ejemplo correcto:**
# ❌ Mal
created_at = Time.now
due_date = Date.today + 7.days
# ✅ Bien
created_at = Time.current
due_date = 7.days.from_now
# ✅ Mejor (con timezone específico)
created_at = Time.current.in_time_zone(facility.timezone)
```
### 4.4 Consideraciones de Pago y Finanzas
Para features que tocan dinero:
```
✅ Usar transacciones de DB para operaciones financieras
✅ Jobs de pago son idempotentes
✅ Validar montos y precisión (cents vs dollars)
✅ Manejar correctamente gateway selection
✅ Loggear TODAS las operaciones financieras
✅ Nunca hacer cálculos con floats (usar Money gem o cents en integers)
**Ejemplo de idempotencia:**
def process_payment(payment_id)
payment = Payment.find(payment_id)
return if payment.processed? # Idempotencia
Payment.transaction do
result = gateway.charge(payment.amount_cents)
payment.update!(
status: 'processed',
gateway_transaction_id: result.id
)
PaymentAuditLog.create!(payment: payment, action: 'processed')
end
rescue Stripe::CardError => e
payment.update!(status: 'failed', error: e.message)
raise
end
```
### 4.5 Background Jobs Robustos
```
✅ Jobs reciben argumentos como hash con symbolized keys
✅ Validar args.is_a?(Hash) antes de procesar
✅ Variables inicializadas antes de rescue blocks
✅ Jobs son idempotentes (pueden ejecutarse múltiples veces)
✅ Retry logic configurado apropiadamente
✅ Dead letter queue para failures críticos
**Ejemplo robusto:**
class ProcessRefundJob < ApplicationJob
queue_as :critical
retry_on Stripe::APIConnectionError, wait: :exponentially_longer, attempts: 5
discard_on Stripe::InvalidRequestError
def perform(args)
raise ArgumentError, 'Hash expected' unless args.is_a?(Hash)
args = args.deep_symbolize_keys
payment_id = args.fetch(:payment_id)
reason = args.fetch(:reason, 'customer_request')
payment = Payment.find(payment_id)
return if payment.refunded? # Idempotencia
# ... lógica de refund
rescue => e
ErrorTracker.notify(e, payment_id: payment_id)
raise
end
end
```
### 4.6 Seguridad y Autorización
```
✅ Endpoints tienen autorización apropiada (CanCanCan, Pundit)
✅ No se exponen IDs de DB en APIs (usar UUIDs o secure_ids)
✅ Datos sensibles están encriptados (attr_encrypted)
✅ SQL injection prevenido (usar placeholders, no string interpolation)
✅ XSS prevenido (sanitize inputs)
✅ CSRF tokens en formularios
**Ejemplo de autorización correcta:**
# app/controllers/api/v1/pricing_rules_controller.rb
class Api::V1::PricingRulesController < ApiController
load_and_authorize_resource # CanCanCan
def update
# authorize! ya fue ejecutado por load_and_authorize_resource
if @pricing_rule.update(pricing_rule_params)
render json: @pricing_rule
else
render json: { errors: @pricing_rule.errors }, status: :unprocessable_entity
end
end
private
def pricing_rule_params
params.require(:pricing_rule).permit(:age_min, :age_max, :price_cents)
end
end
# app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
return unless user.present?
if user.admin?
can :manage, PricingRule
elsif user.facility_manager?
can :manage, PricingRule, facility_id: user.facility_id
else
can :read, PricingRule
end
end
end
```
### 4.7 CI/CD Checks Pasando
```
✅ RuboCop sin warnings
✅ Brakeman sin vulnerabilidades nuevas
✅ Tests en CI pasando (no solo local)
✅ Coverage no disminuyó
✅ Build artifacts generados correctamente
✅ Docker image builds (si aplica)
**Validación local antes de push:**
bundle exec rails ci # Ejecuta todos los checks
bundle exec brakeman -z # Security scan
bundle exec pronto run -c develop # Code review automatizado
```
### 4.8 Migraciones de Base de Datos
```
✅ Migraciones son reversibles (método down implementado)
✅ Migraciones de datos si es necesario (backfill)
✅ Probadas en staging antes de prod
✅ Indexes apropiados para queries nuevas
✅ Compatible con zero-downtime deployment
✅ Considera volumen de datos en producción
**Ejemplo de migración reversible:**
class UpdateAgeRangesBounds < ActiveRecord::Migration[6.1]
def up
# Actualizar rangos 18-65 a 18-64
PricingRule.where(age_min: 18, age_max: 65).find_each do |rule|
rule.update_columns(age_max: 64, updated_at: Time.current)
end
end
def down
# Revertir cambios
PricingRule.where(age_min: 18, age_max: 64).find_each do |rule|
rule.update_columns(age_max: 65, updated_at: Time.current)
end
end
end
```
### 4.9 Documentación Actualizada
```
✅ Comentarios en código complejo explicando el "por qué"
✅ Métodos públicos documentados (YARD, JSDoc)
✅ GraphQL schema tiene descriptions
✅ README actualizado si hay nuevos setup steps
✅ Variables de entorno documentadas (.env.example)
✅ API docs actualizados (Swagger/OpenAPI)
✅ Cambios arquitectónicos documentados en /docs
**Ejemplo de documentación clara:**
# app/services/pricing_calculator.rb
# Calcula el precio de una reserva considerando:
# - Edad del usuario (aplica descuentos por grupo etario)
# - Horario del turno (peak vs off-peak)
# - Membresía activa (descuentos adicionales)
# - Promociones vigentes
#
# @param user [User] Usuario que hace la reserva
# @param shift [Shift] Turno a reservar
# @return [Money] Precio final en cents
# @raise [PricingError] Si no se puede calcular precio
#
# @example
# calculator = PricingCalculator.new(user, shift)
# price = calculator.calculate
# # => #
✅ **Los incidentes se resuelven más rápido** (de horas a minutos)
✅ **Mejora la satisfacción del cliente** porque los bugs se atienden inmediatamente
✅ **Se fortalece la cultura de ownership** compartido
✅ **Se documenta mejor** porque el guardia tiene tiempo para escribir
En proyectos anteriores donde implementé este rol:
* ⬇️ **MTTR bajó de 4 horas a 45 minutos**
* ⬆️ **Sprint completion rate subió del 65% al 85%**
* 📈 **Customer satisfaction aumentó 20 puntos**
---
## 6. Buenas Prácticas para No Morir en el Intento
Después de años haciendo esto, aprendí qué funciona y qué no:
### 1. Canal dedicado y estructura de requests
Los requests del guardia pueden llegar por diferentes canales, **dependiendo de las herramientas que use tu empresa**:
**Canales comunes:**
* **Slack/Teams:** Canal dedicado como `#oncall-incidents` o `#guardia-sprint`
* **Jira/Linear:** Tickets con label `[GUARDIA]` o `[TRIAGE]`
* **PagerDuty/Opsgenie:** Para incidentes críticos de producción
* **Email:** Para reportes de clientes o soporte (menos común)
**Estructura de un Request bien formado:**
Todo request debe contener esta información mínima para ser atendido eficientemente:
**📋 Template de Request para Guardia**
- **Título:** [PROD] Timeout en checkout de pagos
- **Prioridad:** 🔴 Critical / 🟠 High / 🟡 Medium / 🟢 Low
**Descripción:**
Usuario no puede completar el pago. Al hacer clic en "Pagar", la página se queda cargando indefinidamente.
**Flujo de Reproducción:**
1. Ir a https://app.ejemplo.com/checkout
2. Seleccionar método de pago "Tarjeta de crédito"
3. Ingresar datos: 4242 4242 4242 4242
4. Click en "Pagar"
5. ❌ Error: La página se queda en loading
**Logs/Screenshots/Video:**
- Video: https://loom.com/share/abc123
- Screenshot del console: [adjunto]
- Error en logs: "Stripe timeout after 30s"
**Usuario afectado:**
- Email: cliente@ejemplo.com
- ID: 12345
- Browser: Chrome 120 / MacOS
**Reportado por:** @soporte-team
**Fecha/Hora:** 2025-10-10 14:30 UTC
**Ejemplo en Slack:**
```
#oncall-incidents
@guardia 🔴 URGENT
**Título:** Payment gateway down - Stripe webhooks failing
**Descripción:**
Últimos 50 webhooks de Stripe están fallando.
Pagos se procesan pero no se confirman en el sistema.
**Logs:**
https://datadog.com/logs?query=stripe+webhook+500
**Impacto:**
~20 usuarios afectados en últimos 15 minutos
**Acción inmediata sugerida:**
Revisar Sidekiq queue y status de workers
```
**Ejemplo en Jira:**
| Campo | Valor |
|-------|-------|
| **Tipo** | Bug - Guardia |
| **Prioridad** | High |
| **Labels** | [GUARDIA], [PROD], [PAYMENTS] |
| **Título** | Usuario no puede editar perfil |
| **Descripción** | Ver template arriba |
| **Attachments** | video.mp4, screenshot.png, logs.txt |
| **Asignado a** | Guardia actual (rotar automáticamente) |
### 2. Tablero de triage con prioridades y estados
**Todo request debe tener dos dimensiones: Prioridad y Estado.**
**Prioridades y SLAs:**
| Prioridad | SLA | Acción |
|-----------|-----|--------|
| 🔴 Critical | < 15 min | Escala inmediatamente |
| 🟠 High | < 2 horas | Resuelve en el día |
| 🟡 Medium | < 1 día | Agenda para próximos días |
| 🟢 Low | Backlog | Envía a sprint planning |
**Estados del ciclo de vida:**
| Estado | Descripción | Quién lo actualiza |
|--------|-------------|-------------------|
| **📥 To Do** | Request recibido, pendiente de revisión | Sistema/Reporter |
| **🔍 Investigating** | Guardia está analizando logs, reproduciendo el bug | Guardia |
| **🚧 In Progress** | Solución en desarrollo o aplicándose | Guardia |
| **⏸️ Blocked** | Requiere información adicional o depende de otro equipo | Guardia |
| **✅ Done** | Resuelto y verificado en producción | Guardia |
| **📋 Backlog** | No urgente, se agenda para próximo sprint | Guardia/Tech Lead |
| **🔄 Escalated** | Requiere intervención del tech lead o equipo completo | Guardia |
**Ejemplo de flujo completo en Jira:**
1. Soporte crea ticket → Estado: 📥 To Do
2. Guardia lo toma → Estado: 🔍 Investigating
3. Reproducido, inicia fix → Estado: 🚧 In Progress
4. Necesita info del usuario → Estado: ⏸️ Blocked
5. Info recibida, continúa → Estado: 🚧 In Progress
6. Fix deployado y verificado → Estado: ✅ Done
**Pro tip:** Configura un dashboard tipo Kanban con estas columnas para visualizar el estado en tiempo real.
### 3. Automatiza alertas
```yaml
# datadog-monitors.yml
monitors:
- name: "Stripe Webhook Failures"
query: "sum(last_5m):sum:stripe.webhook.failed > 10"
message: "@slack-#oncall URGENT: Stripe webhooks failing"
- name: "Sidekiq Queue Blocked"
query: "avg(last_10m):avg:sidekiq.queue_latency > 300"
message: "@pagerduty High latency detected"
```
### 4. Retrospectiva de incidentes
Cada semana, en la retro del sprint:
* ¿Cuántos incidentes hubo?
* ¿Cuáles fueron recurrentes?
* ¿Qué podemos automatizar o prevenir?
* ¿El guardia tuvo suficiente soporte?
---
## 7. Caso Real: Una Semana como Guardia del Sprint
**Lunes 9:00 AM**
* Recibo el pase de guardia del compañero anterior
* Reviso el dashboard: 3 alertas menores del fin de semana
* Clasifico y creo tickets
**Martes 2:30 PM**
* 🚨 Alerta crítica: Payment gateway timeout en Stripe
* Analizo logs → Job bloqueando Sidekiq
* Cancelo el job, libero cola, notififico
* Tiempo total: 18 minutos
**Miércoles 11:00 AM**
* Soporte reporta: "Usuario no puede reservar canchas"
* Reproduzco el bug → validación incorrecta
* Fix en 25 minutos, deploy a producción
**Jueves**
* Día tranquilo, actualizo runbooks
* Limpio jobs fallidos del mes anterior
* Reviso métricas de uso
**Viernes 4:00 PM**
* Hago handoff al siguiente guardia
* Comparto aprendizajes de la semana
* 📊 Resultado: 12 incidentes atendidos, 8 bugs resueltos, 0 escalaciones
---
## 8. Reflexión Final
Ser guardia del sprint no es solo "apagar incendios". Es cuidar la salud del producto, proteger el tiempo del equipo y asegurar que los usuarios sigan confiando en la plataforma.
Es un rol de **responsabilidad**, **empatía** y **técnica**.
Si tu equipo todavía no tiene este rol implementado, te invito a probarlo. Empieza con una semana, rota entre 2-3 personas, y mide el impacto.
Te garantizo que en un mes, no querrán volver a trabajar sin un guardia del sprint.
---
**¿Has sido guardia del sprint? ¿Qué estrategias te funcionaron mejor? Déjame saber en los comentarios o contáctame directamente.**
*Escrito desde la trinchera, con café y mucho cariño técnico.*
================================================================================
## Post: 2026-05-07-ADRs-en-sistemas-de-pagos
Source file: _posts/2026-05-07-ADRs-en-sistemas-de-pagos.md
================================================================================
---
layout: post
title: ADRs en sistemas de pagos - por qué documentar decisiones te salva de auditorías, incidentes y reescrituras
modified:
categories: Architecture, Payments, Documentation
excerpt: >
Por qué los Architecture Decision Records son innegociables cuando manejas dinero, compliance y proveedores externos.
tags: []
image:
feature:
date: 2026-05-07T09:00:00-07:00
---
## Introducción: "¿Por qué hicimos esto así?"
Imagínate esta escena: estás en una reunión de incidentes. Producción está cayéndose porque un gateway de pagos respondió con un timeout y el sistema reintentó la transacción tres veces. Resultado: el cliente fue cobrado tres veces.
Alguien pregunta lo inevitable:
> *"¿Por qué reintentamos tres veces sin idempotency key? ¿Quién decidió esto?"*
Silencio incómodo. El que tomó la decisión ya no trabaja en la empresa. El commit dice *"add retry logic"* y no hay más contexto. Nadie sabe si fue una decisión deliberada, un workaround temporal, o simplemente algo que se copió de un tutorial.
Esto, queridos lectores, es exactamente el problema que los **ADRs (Architecture Decision Records)** vienen a resolver. Y en sistemas de pagos, **no tenerlos es jugar a la ruleta con el dinero del negocio**.
En este artículo te explico qué son, por qué en payments son críticos, qué decisiones siempre deberías documentar, y cómo no caer en los errores típicos.
---
## 1. ¿Qué es un ADR (y por qué no es solo "documentación más")?
Un ADR es un documento corto que captura **una decisión arquitectónica importante** junto con su **contexto** y sus **consecuencias**.
La estructura mínima es brutal de simple:
* **Título** — qué se decidió.
* **Estado** — propuesto, aceptado, deprecado, superseded.
* **Contexto** — qué problema o restricción motivó la decisión.
* **Decisión** — qué se eligió hacer.
* **Consecuencias** — qué ganamos y qué perdemos.
> ✅ *La wiki documenta el **qué**. El ADR documenta el **porqué**.*
Esa diferencia es la que cambia todo. Un nuevo dev puede leer el código y entender qué hace. Pero solo un ADR le dice **por qué se hizo así y qué alternativas se descartaron**.
---
## 2. ¿Por qué los sistemas de pagos son un caso especial?
He trabajado con sistemas de logística, e-commerce, reservas y pagos. Te lo digo con honestidad: **payments es donde más caro pagas la falta de ADRs**. Y aquí está el porqué:
* 💰 **El costo de equivocarse se mide en dinero real.** Un retry mal implementado es un chargeback. Un webhook mal documentado es una transacción perdida. No es un bug cosmético, es revenue.
* 🔒 **El compliance manda.** PCI-DSS, SCA (Strong Customer Authentication), PSD2, 3DS v2. Los auditores **te van a preguntar por qué tu sistema hace X**, y "porque sí" no es una respuesta válida.
* 🔌 **Dependes de terceros.** Stripe, PayPal, Adyen, Braintree. Cuando ellos cambian su API, tú tienes que reaccionar. Si no documentaste por qué elegiste un flow en particular, **vas a estar reverse-engineering tus propias decisiones**.
* ⏳ **Las decisiones envejecen rápido.** Lo que era óptimo hace dos años hoy es deuda técnica. Sin ADRs, no sabes qué decisiones revisar.
---
## 3. Las 5 decisiones que SIEMPRE deberías documentar como ADR
No todo merece un ADR. Pero en payments, estas cinco categorías son innegociables:
### 3.1 Elección y estrategia de payment gateway
¿Single gateway o multi-gateway? ¿Por qué Stripe sobre Adyen para ese mercado? ¿Qué criterios pesaron — fees, cobertura geográfica, soporte 3DS, time-to-market?
### 3.2 Estrategia de reintentos e idempotencia
¿Cuántos reintentos? ¿Con qué backoff? ¿Cómo se garantiza idempotencia? Este ADR solo te puede salvar de un incidente como el de la introducción.
### 3.3 Manejo de 3DS / SCA
¿Qué flow se usa — challenge obligatorio, frictionless, fallback? ¿Qué exenciones aplican (low-value, trusted beneficiary)? Aquí el compliance se mezcla con UX, y las decisiones son polémicas.
### 3.4 Tokenización y almacenamiento de datos sensibles
¿Vault propio o del provider? ¿Qué scope de PCI te queda (SAQ-A vs SAQ-D)? Esta decisión define **literalmente qué tan caro es tu compliance anual**.
### 3.5 Webhooks y reconciliación
¿Entrega garantizada o eventual consistency? ¿Cómo se manejan los duplicados? ¿Hay reconciliación batch además del stream de eventos? Aquí es donde la realidad de los sistemas distribuidos te golpea más fuerte.
---
## 4. Anatomía de un ADR real (ejemplo)
Veamos cómo se vería un ADR concreto para el caso de la introducción:
```markdown
# ADR-007: Adoptar idempotency keys en todas las llamadas a gateways
## Estado
Aceptado — 2026-03-15
## Contexto
El 12 de marzo tuvimos un incidente: un timeout en el gateway X
disparó el retry automático de nuestro cliente HTTP. La transacción
se procesó tres veces. Impacto: 47 clientes cobrados duplicado,
~USD 8.200 en refunds y soporte.
Las llamadas actuales no envían un identificador único que permita
al gateway deduplicar.
## Decisión
Todas las llamadas a gateways de pago deberán incluir un header
`Idempotency-Key` con un UUID v4 generado en el momento de crear
la intención de pago, no en el momento del retry.
La clave se persiste en la tabla `payment_attempts` y se reutiliza
en todos los reintentos del mismo intento de pago.
## Consecuencias
+ Eliminamos cobros duplicados por reintentos.
+ Cumplimos con la recomendación oficial de Stripe y Adyen.
- Requiere migración de los servicios existentes (ver ADR-008).
- Aumenta ligeramente el tamaño de la tabla `payment_attempts`.
## Alternativas consideradas
- Deshabilitar reintentos automáticos: rechazada, degrada UX.
- Locks distribuidos: rechazada, complejidad operativa innecesaria.
```
¿Ves? **Corto, contextual, accionable.** Cualquier dev que llegue en seis meses entiende qué pasó, por qué se hizo así, y qué no se debe romper.
---
## 5. Errores comunes al escribir ADRs en payments
Después de ver decenas de ADRs (algunos míos, algunos heredados), estos son los errores más frecuentes:
* ❌ **Escribirlos después del incidente.** El ADR debería ser parte del proceso de decisión, no el postmortem. Si lo escribes después, es un *Incident Report* con otro nombre.
* ❌ **Escribir novelas de 20 páginas.** Si un ADR no se lee en 5 minutos, nadie lo va a leer. Punto.
* ❌ **No marcar como *superseded*.** Cuando una decisión cambia, el ADR viejo debe quedar marcado y enlazar al nuevo. De lo contrario, conviven dos verdades.
* ❌ **Mezclar ADR con runbook o spec de producto.** El ADR no explica *cómo operar* el sistema ni *qué funcionalidad* tiene. Solo *por qué se decidió* así.
* ❌ **No referenciarlos desde el código o los PRs.** Un ADR que nadie linkea es un ADR que nadie encuentra.
---
## 6. ¿Dónde viven los ADRs?
Mi recomendación, simple y probada:
1. **Carpeta `/docs/adr/` en el mismo repo.** Numeración secuencial: `0001-payment-gateway-selection.md`, `0002-retry-strategy.md`, etc.
2. **Referenciados desde los PRs.** Toda decisión arquitectónica nueva debe linkear al ADR correspondiente.
3. **Vinculados a la documentación de alto nivel.** Si tienes documentación en capas (executive, approach, detail), los ADRs son la capa más profunda — donde viven las decisiones reales.
4. **Indexados en un README** dentro de la carpeta, con título, estado y fecha.
No necesitas Confluence, no necesitas Notion. **Markdown plano, versionado con el código, revisable por PR.** Esa es la magia.
---
## Conclusión: Los ADRs no son burocracia, son seguros de vida
He visto equipos que evitaban los ADRs porque "no tenemos tiempo". Y luego he visto a esos mismos equipos perder **semanas** intentando entender por qué un sistema de pagos hace lo que hace, en medio de una auditoría PCI o de un cambio de gateway.
Los ADRs te protegen:
* Ante **auditorías de compliance** — puedes mostrar el *porqué* de cada decisión.
* Ante **el onboarding de nuevos devs** — dejan de hacer las mismas preguntas una y otra vez.
* Ante **ti mismo en seis meses** — porque sí, vas a olvidar por qué hiciste eso.
En sistemas de pagos, donde cada decisión toca dinero, compliance y proveedores externos, los ADRs no son un *nice-to-have*. Son **infraestructura para que tu arquitectura sobreviva al tiempo**.
> *Si vas a tomar una decisión que en seis meses alguien va a cuestionar — y en payments **todas** lo son — escríbela como ADR. Tu yo futuro te lo va a agradecer.*
---
*En el próximo post de esta serie hablaremos de cómo comunicar arquitectura de payments a stakeholders no técnicos — el arte de pasar de diagramas a decisiones.*
================================================================================
## Post: 2026-06-01-Arquitectura-Base-Sistema-Pagos-Multi-Gateway
Source file: _posts/2026-06-01-Arquitectura-Base-Sistema-Pagos-Multi-Gateway.md
================================================================================
---
layout: post
title: Arquitectura base de un sistema de pagos multi-gateway - capas, patrones, comunicación y estrategias que aprendí en años de payments
modified:
categories: Architecture, Payments, Software Design, Patterns
excerpt: >
Una guía completa — desde las capas del dominio hasta los WebSockets y listeners — sobre cómo diseño sistemas de pagos que soportan múltiples pasarelas sin volverse inmantenibles.
tags: []
image:
feature:
date: 2026-06-01T10:00:00-07:00
---
## Introducción: por qué casi todos los sistemas de pagos terminan siendo un infierno
Llevo varios años trabajando con sistemas de pagos. He integrado más de 15 pasarelas distintas — Stripe, PayPal, Adyen, CardConnect, Xendit, Razorpay, BAC, OnePay, PixelPay y un largo etcétera. Y si algo aprendí es esto:
> *Casi nadie diseña la arquitectura de un sistema de pagos antes de necesitarla. Se va añadiendo un gateway, luego otro, luego otro, hasta que el código se vuelve inmantenible.*
El patrón que veo repetirse en empresa tras empresa es siempre el mismo: el primer gateway se integra "rápido y sucio" porque hay que salir a producción. El segundo se copia del primero. El tercero ya empieza a tener sus condicionales especiales. Y para el quinto, agregar un gateway nuevo es un proyecto de **3 meses** que toca 15 archivos distintos.
Yo mismo viví eso. Y tras varias rondas de refactor — algunos exitosos, otros que tuve que rehacer — fui consolidando un blueprint mental de cómo debería diseñarse un sistema de pagos multi-gateway desde el día uno. Este post es ese blueprint.
No es una receta universal. Es **lo que yo haría si arrancara hoy un sistema de pagos** que tiene que soportar múltiples pasarelas, múltiples países, múltiples métodos de pago, y crecer sin colapsar. Si llegaste tarde y ya tienes un sistema hecho un desastre, también te sirve: las últimas secciones cubren la estrategia de migración.
Está dirigido a arquitectos, tech leads y devs senior que tienen que tomar decisiones de diseño. No voy a entrar en detalles específicos de Stripe vs Adyen — voy a hablar de **conceptos generalizables** que aplican sin importar el stack, el lenguaje o la región.
Vamos.
---
# Parte I — Fundamentos
## 1. ¿Por qué multi-gateway no es opcional?
Cuando alguien me dice *"para qué tantos gateways, con uno basta"*, mi respuesta siempre incluye estos cinco puntos:
* 🌍 **Cobertura geográfica.** No existe un único PSP que cubra el mundo entero con buen pricing. Stripe es excelente en US/EU pero en Latinoamérica deja huecos. En India, RBI exige procesadores locales. En Brasil, Pix cambió las reglas del juego.
* 💸 **Tarifas competitivas.** Tener dos PSPs en paralelo te da **poder de negociación**. Le dices a Stripe "Adyen me cobra X" y de repente bajan su fee. Si solo tienes uno, estás a merced de su pricing.
* 🛡️ **Resiliencia.** Los PSPs se caen. Stripe ha tenido incidentes globales de varias horas. Si el 100% de tu revenue pasa por un solo gateway, una caída del PSP **es una caída tuya**. Con failover bien diseñado, los usuarios ni se enteran.
* 🏦 **Métodos de pago locales.** Pix en Brasil, OXXO en México, iDEAL en Holanda, UPI en India, SEPA en Europa. Cada región tiene su método preferido — y los locales suelen tener mejor conversión y menores fees que las tarjetas.
* ⚖️ **Regulación.** PSD2 + SCA en Europa, RBI en India, Open Finance en Brasil. Cada año aparecen nuevas exigencias. Estar atado a un solo PSP te deja a su ritmo de implementación.
He visto a empresas perder el equivalente a **cientos de miles de dólares** en una sola tarde por depender de un único gateway que se cayó. La pregunta no es *si* va a pasar; es *cuándo*.
---
## 2. Los 7 anti-patrones que matan a un sistema multi-gateway
Antes de hablar de cómo hacerlo bien, déjame mostrarte qué he visto repetirse en cada sistema que tuve que rescatar. Si alguno de estos te suena familiar, ya sabes por dónde empezar:
1. **God-model `Payment`.** Un único modelo de 1500+ líneas que mezcla ledger, callbacks, cobros, refunds, cuentas por cobrar, créditos, eventos y notificaciones realtime. Cualquier cambio toca a 30 personas.
2. **HTTP dentro de transacciones de base de datos.** Un `before_create` o un `transaction do ... gateway.charge ... end` mantiene locks de DB abiertos durante la llamada al PSP. Cuando Rails (o tu framework) reintenta y el PSP ya cobró → **doble cobro real**.
3. **Dispatch dinámico sin contrato.** `"#{gateway.classify}::Charge".constantize.call`. Sin capability check, sin tipado, sin observabilidad por gateway. Funciona en demo, se cae en producción.
4. **Webhooks sin disciplina.** Sin verificación de firma, sin dedupe, sin persistencia del payload crudo. He visto pagos perdidos solo porque un crash mató al worker antes de procesar el webhook.
5. **Frontend acoplado al gateway.** Un `switch (gateway)` de 13 ramas en el cliente. Agregar un gateway = tocar el front, el back, el mobile, y rezar.
6. **Procesamiento síncrono donde no debería serlo.** Bloquear al usuario 30 segundos esperando que un PSP latinoamericano responda. Spoiler: timeouts, retries del usuario, doble cobro.
7. **Una sola fuente de verdad.** Confiar 100% en el webhook. O solo en el response síncrono. O solo en el cron de reconciliation. Spoiler: las tres mienten en algún momento. **Necesitas cruzarlas**.
> *Si tu sistema actual tiene 3 o más de estos, no estás solo. Lo que sigue es cómo no caer en ellos.*
---
## 3. Arquitectura por capas — el blueprint base
La base de todo es separar responsabilidades en capas claras, donde **cada capa solo conoce la inmediatamente inferior**. Esto es lo que llamo el blueprint base — funciona en cualquier lenguaje, en cualquier framework.
```mermaid
graph TB
subgraph "Presentation"
C[Controllers / GraphQL / REST endpoints]
end
subgraph "Public API (Facade)"
F[Payments::Api]
end
subgraph "Application (Use Cases)"
UC[CreatePaymentIntent · Capture · Refund · Void · ProcessWebhook]
end
subgraph "Domain"
D[PaymentIntent · PaymentSession · PaymentTransaction · WebhookEvent]
end
subgraph "Infrastructure Adapters"
A[StripeAdapter · AdyenAdapter · LocalPSPAdapter ...]
end
subgraph "Integration / Messaging"
I[Webhooks · WebSockets · Listeners · Outbox · Pub/Sub]
end
C --> F --> UC --> D
UC --> A
UC --> I
I --> UC
```
Las **6 capas que separo siempre**:
| Capa | Responsabilidad | Lo que NO hace |
|---|---|---|
| **Presentation** | Cargar recursos, autorizar, delegar al facade, renderizar | Lógica de pricing, selección de gateway, llamadas HTTP al PSP |
| **Public API (Facade)** | Único entry point al dominio de pagos. Retorna resultados tipados | Permitir que código externo toque internals |
| **Application (Use Cases)** | Orquestar el flujo: idempotency → gateway selection → adapter call → persistir | Hablar HTTP directamente; saltarse el dominio |
| **Domain** | Entidades, state machines, invariantes financieras | Conocer HTTP, SDKs o adapters concretos |
| **Infrastructure Adapters** | Un adapter por gateway. Habla HTTP/SDK. Normaliza errores | Tocar el ledger; conocer el dominio de negocio |
| **Integration / Messaging** | Webhooks entrantes, sockets, eventos de dominio, outbox | Lógica de negocio; persistencia financiera |
La regla de oro es: **el dominio no sabe que existe Stripe. El controller no sabe que existe Adyen.** Toda la complejidad gateway-específica vive en los adapters.
---
# Parte II — Las abstracciones core
## 4. Las abstracciones del dominio que no pueden faltar
Estas son las 9 abstracciones que aparecen en todo sistema de pagos serio que he construido o auditado. Si alguna falta, hay un dolor garantizado más adelante:
* **`PaymentIntent`** — la intención de negocio. Es lo que el usuario quiere lograr: cobrar X monto por Y producto. Tiene una **state machine** explícita (`created → authorized → captured → refunded`) e invariantes financieras. Es la unidad de trabajo del dominio.
* **`PaymentTransaction`** — el log técnico **append-only**, una fila por cada llamada al PSP (authorize, capture, refund, void, tokenize). Es donde reconciliation va a buscar la verdad cuando el PSP y tu ledger no coinciden.
* **`PaymentSession`** — la sesión PSP-hosted. Cuando usas Drop-in, hosted fields, 3DS challenge o redirect, el PSP te devuelve un `session.id` con TTL. Eso no pertenece al `PaymentIntent` — vive aparte.
* **`PaymentMethod` / `Token`** — el método de pago tokenizado. Nunca guardes PAN/CVV. Guarda el token del PSP + metadata (last4, brand, exp).
* **`WebhookEvent`** — el evento crudo del PSP, persistido **antes** de procesar. Incluye payload, headers, IP, firma verificada y un `UNIQUE(gateway, event_id)` que garantiza dedupe a nivel DB.
* **`IdempotencyRecord`** — el recovery point. No alcanza con un `Idempotency-Key`; necesitas saber **en qué paso del flujo estás** para no repetir el cobro.
* **`GatewayRegistry` + `CapabilityRegistry`** — el catálogo de gateways y qué puede hacer cada uno. El dominio consulta capacidades *antes* de actuar.
* **`OperationResult`** — éxito o fracaso **tipado**. `CardDeclined`, `RequiresAction`, `InsufficientFunds` son **resultados**, no excepciones. Las excepciones quedan para bugs reales.
* **`OutboxEntry`** — el patrón outbox. Garantiza que un evento de dominio se publique exactamente cuando la transacción de DB se commitea, no antes, no después.
> *La diferencia entre un sistema que escala y uno que no es si estas 9 piezas existen como conceptos explícitos o están **dispersas como columnas y banderas en un único modelo gigante**.*
---
## 5. Los 8 flujos canónicos (+ reconcile)
Toda operación que cualquier PSP del mundo te puede ofrecer se reduce a estos **8 flujos**. Si tu sistema tiene 30 "operaciones distintas", lo más probable es que tengas 8 mal mapeadas.
| Flujo | Qué hace |
|---|---|
| `tokenize` | Convertir un método de pago en un token reutilizable |
| `authorize` | Reservar fondos sin cobrar |
| `capture` | Cobrar fondos previamente autorizados |
| `sale` | Authorize + Capture en un solo paso |
| `refund_full` | Devolver el monto completo |
| `refund_partial` | Devolver parte del monto |
| `void` | Cancelar una autorización no capturada |
| `webhook_event` | Procesar un evento asíncrono del PSP |
Más uno extra que rara vez se modela bien:
| Flujo | Qué hace |
|---|---|
| `reconcile` | Cruzar el estado del PSP contra mi ledger |
Cada gateway implementa **solo el subset que su capability registry declara**. Por ejemplo: BAC no soporta `void`, Razorpay no soporta `capture` separado de `sale`. Tu dominio lo sabe vía el `CapabilityRegistry` y no intenta llamar a operaciones inexistentes.
> *Si tu equipo está debatiendo "¿esto es un capture o un sale?", es señal de que el modelo está bien — porque la pregunta tiene una sola respuesta correcta. Si están debatiendo "¿esto es un `processPayment_v2_for_BAC` o un `processPayment_alternate`?", es señal de que el modelo no existe.*
---
## 6. Capability Registry — el cerebro detrás del multi-gateway
Esta es la pieza que más subestiman los equipos. El `CapabilityRegistry` es donde declaro **qué puede hacer cada gateway**:
```yaml
stripe:
hosted_fields: true
3ds: true
auth_capture: true
one_step_sale: true
refund_partial: true
void: true
card_on_file: true
recurring: true
webhook: true
adyen:
hosted_fields: true
3ds: true
auth_capture: true
refund_partial: true
void: true
card_on_file: true
webhook: true
bac:
direct_pan: true
one_step_sale: true
refund_full: true
refund_partial: false
void: false
webhook: false
```
¿Por qué importa tanto?
Porque el dominio puede preguntar antes de actuar:
```ruby
if registry.supports?(gateway, :void)
adapter_for(gateway).void(intent)
else
adapter_for(gateway).refund_full(intent)
end
```
Sin esto, terminas con `if gateway == 'bac'` regados por todo el código. Con esto, **agregar un gateway nuevo es declarar sus capacidades y escribir su adapter — no tocar 15 archivos**.
Es también lo que el frontend consulta vía API: *"para este facility, con este método de pago, ¿qué puedo ofrecer al usuario?"*. La UI se vuelve dinámica sin acoplarse a gateways específicos.
---
# Parte III — Patrones de diseño que uso siempre
## 7. Patrones de diseño aplicados a payments
Voy a ser concreto: estos son los **patrones que efectivamente uso** en sistemas de pagos, no una lista académica de GoF. Cada uno resuelve un problema específico.
### 7.1 Adapter
Un contrato uniforme (`GatewayAdapter`), N implementaciones (`StripeAdapter`, `AdyenAdapter`…). Cada adapter expone los flujos canónicos que su gateway soporta. La inspiración clara es **ActiveMerchant** del mundo Ruby.
### 7.2 Strategy
Selección dinámica del gateway según contexto: país, moneda, método de pago, monto, hora del día. El Strategy vive en una capa por encima de los adapters y decide *cuál* usar.
### 7.3 Registry
Lookup por nombre + metadata. El `GatewayRegistry` resuelve `"stripe"` → `StripeAdapter`. El `CapabilityRegistry` resuelve `("stripe", :3ds)` → `true`.
### 7.4 Factory
Construye adapters con sus dependencias inyectadas (credenciales del facility, logger, HTTP client). Centraliza la creación.
### 7.5 Chain of Responsibility
Un pipeline de validaciones antes de llamar al PSP: ¿tenemos credenciales? ¿el monto es válido? ¿el método de pago tiene los permisos? ¿hay anti-fraude que aprobar? Cada handler decide si continúa o aborta.
### 7.6 Saga / Process Manager
Para flujos largos: authorize → esperar 3DS → confirm → capture. **No uso transacciones distribuidas** entre mi DB y el PSP — es imposible. Modelo cada paso como una etapa de saga con su propio commit local + idempotency.
### 7.7 Outbox Pattern
El más subestimado de todos. Cuando creo un `PaymentIntent`, escribo en la misma transacción de DB:
1. El `PaymentIntent` con estado `authorized`.
2. Una fila en `outbox_entries` con el evento `PaymentAuthorized`.
Un worker independiente lee la outbox y publica al bus (Kafka, RabbitMQ, Pub/Sub). **Garantía exactly-once a costa de potencialmente entregar duplicados — pero nunca de perder eventos**.
### 7.8 Circuit Breaker
Cuando un PSP está caído, no quiero seguir mandándole requests. El circuit breaker abre el circuito tras N fallos consecutivos, deja pasar requests de prueba después de un cooldown, y se recupera solo. Sin esto, una caída del PSP **te tumba a ti**.
### 7.9 Bulkhead
Separo los workers / threads / colas por gateway. Si Xendit está lento y satura su pool, **Stripe sigue funcionando**. Sin bulkheading, un PSP lento contamina a todos los demás.
---
## 8. Idempotencia — la disciplina que evita el doble cobro
Si tuviera que elegir **un único concepto** que define la madurez de un sistema de pagos, sería este. Y casi nadie lo hace bien.
Lo que la mayoría hace: mandar un `Idempotency-Key` al PSP. Está bien, pero **no es suficiente**.
Lo que hace Brandur en su famoso post sobre Rocket Rides (te lo recomiendo si no lo has leído): cada operación de pago es una secuencia de pasos, y cada paso es un **recovery point**. Si crasheo en el paso 3, al reintentar arranco desde el paso 3 — no desde cero.
Pseudo-flujo:
```
1. CreatePaymentIntent → recovery_point = 'intent_created'
2. CallGatewayAuthorize → recovery_point = 'authorized'
3. PersistTransactionLog → recovery_point = 'logged'
4. PublishEvent → recovery_point = 'published'
5. Done → recovery_point = 'done'
```
Si crasheo entre 2 y 3, el reintento ve `authorized` y **no vuelve a llamar al PSP**. Solo persiste el log y publica el evento.
**La regla de oro:** nunca llamar al PSP dentro de una transacción de DB. La secuencia es:
1. Persistir el intento + idempotency key (commit).
2. Llamar al PSP (sin transacción abierta).
3. Persistir el resultado + actualizar recovery point (commit).
El escenario más peligroso es el **timeout del PSP**. Si el PSP no responde, **no sabes si cobró o no**. La única salida segura: dedicar el siguiente reintento a **consultar el estado** (no a reintentar el cobro). Aquí es donde el `Idempotency-Key` te salva — al consultar, el PSP devuelve el resultado de la primera llamada.
Idempotency vive a **tres niveles**: en el cliente (no doble click), en tu API (no doble request del cliente), y en el adapter (no doble llamada al PSP).
---
# Parte IV — Comunicación con el mundo exterior
## 9. Webhooks — la pieza más subestimada
Los webhooks son donde más he visto perder pagos. Tres reglas innegociables:
### 9.1 Persistir el payload crudo ANTES de procesar
La secuencia que uso:
1. Recibo el HTTP request del PSP.
2. Verifico firma (timing-safe — `==` no sirve, leak de side-channel).
3. Persisto `WebhookEvent` con raw payload, headers, IP, `signature_verified`, y un `UNIQUE(gateway, event_id)`.
4. Encolo job de procesamiento.
5. Respondo `200 OK` al PSP.
6. El worker procesa el evento desde la tabla, **no desde el request HTTP**.
¿Por qué? Si el worker crashea, el evento sigue en la DB. Si el PSP reenvía, el `UNIQUE` lo deduplica. Si necesito debuggear, el raw payload está ahí.
### 9.2 Responder 200 OK antes de procesar
Muchos PSPs reintentan agresivamente si tardas más de 5 segundos. **No proceses el evento de forma síncrona**. Persiste, encola, responde 200, y procesa async. Si después falla, lo retomas — ya tienes el raw.
### 9.3 Orden de eventos
Los webhooks **no llegan en orden**. Puedes recibir `payment.refunded` antes que `payment.captured` por race conditions del PSP. Tu state machine debe ser tolerante: si llega `refunded` y el intent está en `authorized`, lo dejas en una cola de "pendiente de reordenar" y reintenta cuando llegue `captured`.
> *Y por favor: nunca confíes en que el webhook es la única señal. Es **una** señal. La verdad la define reconciliation.*
---
## 10. Sockets y comunicación en tiempo real
Aquí entran flujos que muchos olvidan diseñar. Cuando un pago es **asíncrono** (Xendit, 3DS con redirect, Pix con QR), el usuario está mirando una pantalla esperando el resultado. ¿Cómo le aviso cuándo termina?
### 10.1 WebSockets
Conexión bidireccional persistente. Útil cuando necesitas push del backend al cliente. El patrón típico:
1. El cliente abre socket y se subscribe a `payment_intent.{id}`.
2. Backend recibe webhook → procesa → publica evento interno.
3. Un listener escucha el evento y emite por el socket al cliente subscrito.
4. El cliente recibe el evento y actualiza la UI.
### 10.2 Server-Sent Events (SSE)
Más simple que WebSocket si solo necesitas comunicación **del backend al cliente**. HTTP plano, reconnect automático, fácil de operar. Lo uso cuando el flujo es solo "esperar resultado de pago" y no requiere bidirección.
### 10.3 Long polling
Cuando WebSocket no es viable (corporate proxies, ambientes legacy). El cliente hace request, el backend lo retiene hasta tener resultado o timeout, y responde. El cliente repite. Sirve, pero escala peor.
### 10.4 Push via PubSub provider (Pusher, Ably, Pub/Sub)
Para mobile + multi-cliente. El cliente se subscribe a un canal y el backend publica al canal. Manejas menos infraestructura propia.
### Tabla comparativa rápida
| Mecanismo | Bidireccional | Operativamente | Cuándo lo uso |
|---|---|---|---|
| WebSocket | Sí | Complejo (sticky sessions, scale-out) | Apps con muchas interacciones realtime |
| SSE | No (solo server→client) | Simple | Notificación de resultado de pago |
| Long polling | No | Sencillo, mal performance | Ambientes con restricciones de red |
| PubSub provider | Sí | Tercerizado | Mobile + multi-device |
---
## 11. Listeners y arquitectura event-driven
Cuando un pago se autoriza, suelen pasar **muchas cosas**: enviar email, actualizar inventario, notificar al CRM, gatillar workflows de loyalty, escribir al data warehouse. Si lo haces todo síncrono en el use case, el código se vuelve un monstruo.
La solución es publicar **eventos de dominio** y dejar que listeners reaccionen.
### 11.1 Domain events vs Integration events
Esta diferencia casi nadie la respeta y es crítica:
* **Domain events** — internos a tu sistema. Ejemplo: `PaymentAuthorized`. Los publicas en proceso o en un bus interno. Los consume tu propio código.
* **Integration events** — para sistemas externos. Ejemplo: `customer.charge.completed`. Los publicas a Kafka/Pub/Sub. Los consume otro servicio o equipo.
Mezclarlos es un error: cambias un domain event interno y rompes un consumidor externo del que no sabías.
### 11.2 Listeners idempotentes
Los eventos se **reentregan**. Tu listener debe ser idempotente. Si recibe el mismo evento dos veces, no manda dos emails, no carga dos veces el saldo. Esto se logra con un `processed_events` table o con un check explícito antes de actuar.
### 11.3 Event sourcing parcial
No hago event sourcing completo en payments (es overkill y complica la consistencia financiera), pero sí uso `PaymentTransaction` **como log de eventos**: una fila append-only por cada acción significativa. El estado del `PaymentIntent` se deriva del último estado válido, pero la historia está intacta.
### 11.4 Cómo evito que se vuelva un caos
- **Registro explícito** de listeners. Nada de auto-discovery mágico.
- **Naming convention**: `On