Cambios que rompen algo: qué cuenta y cómo lanzarlos
10 min de lectura actualizado el
Un cambio que rompe algo es un cambio que un llamante correctamente escrito no habría sobrevivido. La definición importa porque la mayoría de las discusiones sobre si algo “cuenta” son en realidad discusiones sobre quién lo estaba sujetando mal. Si un llamante siguió tu documentación y tu cambio hizo que su código dejara de funcionar, el cambio rompía algo. Lo que pretendías no tiene nada que ver.
Esa es toda la prueba. El resto de este artículo es lo que se deriva de ella: qué la falla, qué la pasa, cómo detectar un fallo antes de que se mergee, y qué hacer una vez que sabes que estás lanzando uno.
¿Qué cuenta como un cambio que rompe algo?
Aplica la prueba al llamante, no al diff. Un cambio rompe algo cuando un llamante que solo dependía del comportamiento documentado tiene que cambiar su código, su configuración o sus datos para seguir funcionando. Eliminar un campo, renombrar un endpoint, endurecer la validación, cambiar un valor por defecto y cambiar el tipo de un valor califican todos. Añadir un campo opcional no. Corregir un error normalmente no, con una excepción importante más abajo.
| Cambio | ¿Rompe algo? | Por qué |
|---|---|---|
| Eliminar o renombrar un campo, endpoint, flag u opción | Sí | Los llamantes correctos lo referencian |
| Añadir un campo opcional o un endpoint nuevo | No | Las llamadas existentes no cambian |
| Hacer obligatoria una entrada opcional | Sí | Las llamadas que la omitían ahora fallan |
| Endurecer una validación que antes se aceptaba | Sí | Entradas que funcionaban ahora se rechazan |
| Cambiar un valor por defecto | Sí | Los llamantes que no lo fijaron reciben comportamiento nuevo |
| Cambiar un tipo (string a número, valor único a array) | Sí | Los parsers escritos para el tipo documentado fallan |
| Reordenar las claves de un objeto | No | A menos que hayas documentado el orden |
| Corregir un error del que dependían los llamantes | En la práctica, sí | Ver la sección sobre contratos accidentales |
| Subir un límite de tasa o un tope de tamaño | No | Nada de lo que funcionaba deja de funcionar |
| Bajar un límite de tasa o un tope de tamaño | Sí | Tráfico que estaba bien ahora se limita |
| Cambiar la redacción de un mensaje de error | Depende | Rompe algo si lo documentaste o los llamantes hacen match con él |
¿Qué no es un cambio que rompe algo?
Un cambio no rompe nada cuando toda llamada que funcionaba antes sigue funcionando, sin cambios, y sigue significando lo mismo. Añadir un endpoint nuevo, añadir un parámetro de petición opcional, añadir un campo a una respuesta, hacer opcional una entrada obligatoria, subir un límite y mejorar un mensaje de error con el que nadie hace match pasan todos la prueba. Estos cambios aditivos pueden salir en una versión menor con una entrada de changelog corriente.
Los cambios aditivos aun así rompen llamantes en tres situaciones. Un cliente cuyo deserializador rechaza campos desconocidos falla con el primer campo nuevo de la respuesta, así que documenta pronto que los llamantes deben ignorar los campos que no reconocen. Un valor nuevo de un enum rompe a todo llamante con un switch exhaustivo (más sobre eso abajo). Y una respuesta que crece puede empujar a un llamante más allá de un límite de tamaño, un timeout o un ancho de columna en los que nunca tuvo que pensar.
Cuatro filas de la tabla merecen una mirada más de cerca, porque ahí es donde ocurren los desacuerdos.
Los cuatro cambios que rompen algo que los equipos pasan por alto
Contratos accidentales. Si tu API ha devuelto el mismo campo no documentado durante tres años, un llamante ha construido sobre él. La ley de Hyrum es la versión corta: con suficientes usuarios, todo comportamiento observable de tu sistema dependerá de alguien. Por eso “fue una corrección de errores” no es una defensa. La corrección puede ser correcta y aun así romper algo. Lánzala como tal.
Cambios de comportamiento sin cambio de esquema. El campo sigue ahí, el tipo es el mismo, y el
valor ahora significa algo distinto. Un status que antes era active o inactive y ahora
también devuelve suspended rompe a todo llamante con un switch exhaustivo. Un timestamp que pasa
de hora local a UTC rompe a todo el que no leyó la documentación dos veces. Nada en un diff del
archivo OpenAPI muestra esto.
Validación endurecida. Empiezas a rechazar correos sin TLD, o espacios finales, o nombres de más de 80 caracteres. Todo llamante que enviaba exactamente eso ahora recibe un 400 por una petición que funcionaba la semana pasada. Los cambios de validación son los que más se lanzan como una corrección de “endurecimiento”.
Valores por defecto cambiados. Nadie que fijó el valor explícitamente nota nada. Todos los que no lo hicieron, que son la mayoría de los llamantes, reciben comportamiento nuevo sin cambiar una línea. Un valor por defecto cambiado rompe a la mayoría de tus usuarios precisamente porque nunca vieron el ajuste.
¿Cómo se detecta un cambio que rompe algo antes de que se lance?
Compara el contrato del pull request con el contrato de la rama principal, en CI, y haz fallar el build ante una diferencia que rompa algo. Existen herramientas de diff de esquemas para la mayoría de los formatos de interfaz, y cada una conoce las reglas de ruptura de su propio formato:
| Interfaz | Herramienta | Qué compara |
|---|---|---|
| REST (OpenAPI) | oasdiff | Dos specs OpenAPI, con un informe de cambios que rompen algo |
| gRPC (Protobuf) | buf breaking | Archivos .proto, a nivel de wire o de código fuente |
| GraphQL | GraphQL Inspector | Dos esquemas, señalando cambios que rompen algo y peligrosos |
| Crates de Rust | cargo-semver-checks | La API pública frente a la última versión publicada |
| Paquetes de TypeScript | API Extractor | Un informe versionado de la API pública del paquete |
Estas herramientas detectan de forma fiable campos eliminados, operaciones renombradas y tipos cambiados. No pueden ver los dos primeros de los cuatro tipos de arriba, un contrato accidental o un cambio de comportamiento, porque ninguno aparece en un esquema. Usa la herramienta para frenar los obvios y la pregunta de revisión “¿podría notarlo un llamante correcto?” para el resto. El mismo job de CI es un lugar natural para exigir una entrada de changelog, como se describe en exigir entradas de changelog en CI, y cambios de API en gRPC y Protobuf repasa los casos a nivel de wire.
¿Cómo se marca un cambio que rompe algo en un commit?
Con Conventional Commits, un cambio que rompe
algo se marca con un ! antes de los dos puntos (feat(api)!: remove the legacy export endpoint) o
con un pie que empieza por BREAKING CHANGE: seguido de una descripción. Cualquiera de los dos
corresponde a una versión mayor. Escribe el pie como el primer borrador de la entrada de changelog,
nombrando a quién afecta y qué debe hacer. Conventional commits y el changelog
explica hasta dónde llega la convención.
La misma regla vale para las bibliotecas. Una función pública eliminada, un tipo de parámetro restringido o un valor de retorno cambiado es una versión mayor bajo el versionado semántico. Las bibliotecas no siempre la cumplen: un estudio de 119.879 actualizaciones de Maven Central encontró que el 16,6% rompió el versionado semántico, pero solo el 7,9% de los proyectos cliente se vio afectado, porque la mayoría de esos cambios tocaban código que ningún cliente llamaba. La ruptura se mide en el llamante.
¿Cómo se lanza un cambio que rompe algo?
Se lanza abiertamente, con una fecha, con un camino. Los pasos de abajo van en orden, y el último es el que la mayoría de los equipos se saltan: decirle a la gente afectada que lo que estaba esperando ya ha pasado.
- Decide si lo es. Usa la prueba de arriba, no el diff. Si dos ingenieros no están de acuerdo, rompe algo; el desacuerdo es evidencia de que un llamante podría razonablemente haber dependido del comportamiento anterior.
- Versiónalo. Bajo versionado semántico un cambio que rompe algo es una versión mayor. Si llevas una API datada o versionada, va en una versión nueva y la antigua sigue funcionando hasta una fecha declarada. Si no puedes versionar, no estás lanzando un cambio que rompe algo, estás lanzando una caída con una entrada de changelog. Qué esquema lleva la versión es el tema de buenas prácticas de versionado de API.
- Escribe la entrada antes de que se mergee el código. La entrada tiene una forma fija: qué cambia, a quién afecta, qué deben hacer, y para cuándo. Si no puedes llenar las cuatro, el cambio no está listo. La plantilla de notas de versión pone estas entradas primero, con una fecha en vez de un número de versión, exactamente por esto.
- Da un plazo, no un número de lanzamiento. “Eliminado en v5” no significa nada para quien no sigue tus lanzamientos. “Deja de funcionar el 1 de noviembre de 2026” significa lo mismo para todos.
- Proporciona la migración. Un ejemplo de código de la llamada antigua junto a la nueva. Si el cambio es un renombrado, di ambos nombres en la misma frase. Si es un campo eliminado, di adónde fueron los datos.
- Anúncialo en todos los sitios donde estaba documentado el comportamiento anterior. El changelog, la página de docs que describe el endpoint, las notas de versión del SDK, y el header de deprecación en la respuesta si tienes uno.
- Cierra el ciclo. Si una clienta pidió el cambio, o reportó el error que lo motivó, avísale cuando se lance.
¿Cómo se ve una buena entrada de cambio que rompe algo?
Una buena entrada nombra al llamante afectado en la primera línea, indica la fecha, e incluye la corrección. Aquí una para el caso de validación endurecida, en la forma que usamos:
Las direcciones de correo sin dominio se rechazan desde el 1 de noviembre de 2026.
POST /usersyPATCH /users/:idactualmente aceptan valores dealice@localhost. Desde el 1 de noviembre estos devuelven400 invalid_email. Afecta a cualquier integración que cree usuarios desde directorios internos. Migración: envía una dirección completamente cualificada, u omite el campo y fíjalo después. No se necesita ningún cambio si tus direcciones ya tienen dominio, lo cual es cierto para el 99,4% de las cuentas creadas este año.
Dónde vive ese aviso, y qué más debería acompañarlo, es el tema de changelog de API.
El porcentaje al final no es decoración. Le dice a la lectora si debe preocuparse, que es la pregunta con la que abrió la entrada.
¿Por qué no simplemente evitarlos?
Porque la alternativa es peor. Una API que nunca rompe nada acumula cada error que ha cometido: el campo mal nombrado, el valor por defecto equivocado, el timestamp en hora local. Cada uno es un impuesto sobre todo llamante nuevo para siempre, para proteger a llamantes que podrían haber migrado en una tarde. Los equipos con mejor reputación de estabilidad rompen cosas rara vez, según un calendario, con un camino de migración y un aviso que llegó a quienes era para ellos.
La mecánica de ese aviso se cubre en deprecar una API. La entrada misma se redacta de la misma forma que cualquier otra entrada en el feed de changelog: a partir del pull request mergeado, retenida para un humano, luego publicada en el sitio donde los llamantes afectados ya leen.
FAQ
¿Cuál es la diferencia entre un cambio que rompe algo y uno que no? Un cambio que rompe algo obliga a un llamante correcto a cambiar su código, su configuración o sus datos para seguir funcionando. Uno que no rompe nada deja todas las llamadas existentes funcionando con el mismo significado, por eso las adiciones suelen ser seguras y las eliminaciones, los renombrados y las reglas endurecidas normalmente no lo son.
¿Cuenta añadir un campo obligatorio? Sí. Toda llamada existente lo omite, así que toda llamada existente ahora falla. Añádelo como opcional con un valor por defecto sensato, o versiona el endpoint.
¿Cuenta una corrección de errores? Puede ser. Si los llamantes dependían del comportamiento con el error, corregirlo los rompe, diga lo que diga la documentación. Trata cualquier corrección que cambie la salida observable como un cambio que rompe algo, a menos que puedas demostrar que nadie dependía de ella.
¿Aplica el versionado semántico a una API web? La regla sí: los cambios que rompen algo reciben una versión mayor nueva y la antigua sigue funcionando durante un período declarado. El número suele vivir en la URL o en un header de fecha en vez de en una versión de paquete.
¿Cuánto aviso es suficiente? El suficiente para que un llamante encuentre el aviso y haga el trabajo. Noventa días es un piso habitual para APIs públicas; más tiempo para cualquier cosa usada en código que se envía a usuarios finales y no se puede actualizar de forma remota.
Las afirmaciones técnicas de este artículo no se han revisado de forma independiente. Si algo está mal, avísanos y lo corregiremos.