Los errores de suscripción son errores de estado. Un cliente conserva el acceso después de cancelar. Una cuenta con pagos vencidos paga y sigue bloqueada. Una prueba termina para alguien sin tarjeta y ninguna regla dice qué pasa después. Cada uno de estos casos es una transición que nadie dibujó.
Un diagrama de máquina de estados, normalmente llamado diagrama de estados, enumera cada estado en el que puede estar un objeto y cada evento que lo lleva a otro. Las transiciones que faltan aparecen en la revisión en lugar de en los tickets de soporte. Este artículo construye uno para una suscripción SaaS, con los estados que documenta Stripe Billing para que puedas comparar el modelo con un sistema real, y explica la notación a medida que aparece.
Cuándo dibujar una máquina de estados
Dibuja una cuando un único objeto tenga un ciclo de vida con reglas: un pedido, una suscripción, un pago, un ticket de soporte, un despliegue. Las señales de alarma son conocidas:
- Una columna
statuscon más de tres valores y código que la comprueba en muchos sitios. - Indicadores booleanos como
is_canceledeis_pausedque pueden contradecirse. - Discusiones sobre si algo "puede ocurrir" desde un estado concreto.
Un diagrama de estados es la imagen equivocada para una conversación entre varios participantes, donde un diagrama de secuencia muestra el orden de los mensajes, y para un proceso de negocio con muchas personas y traspasos, donde encaja mejor un diagrama BPMN.
La notación
| Elemento | Notación | Significado |
|---|---|---|
| Estado | Rectángulo con esquinas redondeadas | Una condición en la que el objeto permanece hasta que llega un evento |
| Pseudoestado inicial | Círculo pequeño relleno | Donde empieza la máquina |
| Estado final | Círculo relleno dentro de un anillo | La máquina ha terminado |
| Transición | Flecha etiquetada event [guard] / action |
Se pasa cuando ocurre el evento y la guarda es verdadera |
| Elección | Rombo | Las guardas de las flechas salientes deciden el destino |
| Estado compuesto | Un estado que contiene estados | Reglas que comparte todo lo que hay dentro |
| Comportamiento interno | entry /, exit /, do / |
Se ejecuta al entrar, al salir o mientras dura el estado |
Cada parte de la etiqueta de una transición es opcional. invoice paid es un evento por sí solo, [setting: unpaid] es una guarda por sí sola y cancel / revoke access es un evento con una acción. La sintaxis y los símbolos vienen de la especificación UML 2.5.1.
Los estados de la suscripción
El ejemplo usa los estados del resumen de suscripciones de Stripe, con pagos automáticos con tarjeta:
| Estado | Qué significa | Acceso |
|---|---|---|
incomplete |
El primer pago aún no se ha hecho; el cliente tiene 23 horas | No |
incomplete_expired |
El primer pago nunca se completó; no se cobra nada | No |
trialing |
En periodo de prueba | Sí |
active |
Al corriente | Sí |
past_due |
El pago de la última factura falló o no se intentó | Tú decides |
unpaid |
Los reintentos se agotaron y tu configuración eligió este resultado | No |
paused |
La prueba terminó sin método de pago y se eligió pausar | No |
canceled |
Terminada; Stripe lo describe como un estado terminal | No |
Muchos productos mantienen el acceso durante past_due mientras se reintentan los pagos, y este ejemplo también. Escribe esa decisión en el diagrama; es la que la gente olvida.
Cómo construir las transiciones
Toma cada estado y pregúntate qué eventos pueden ocurrir en él.
- Inicio.
subscribe [trial]lleva atrialing;subscribe [no trial]lleva aincomplete. - Primer pago. Desde
incomplete,invoice paidlleva aactive. Si pasan 23 horas sin pago, la suscripción pasa aincomplete_expiredy Stripe anula la factura. Ese estado es final: para volver a intentarlo, el cliente inicia una suscripción nueva. - Fin de la prueba.
trial endsva a una elección.[payment succeeds]lleva aactive;[no payment method]lleva apauseden este ejemplo. Stripe te deja elegir qué ocurre en ese caso: crear una factura (la opción predeterminada), cancelar o pausar. Dibuja la configuración que de verdad usas, no las tres. - Renovaciones. Desde
active,renewal payment failslleva apast_due, einvoice paidla devuelve. - Reintentos agotados. Otra elección: según tu configuración, la suscripción pasa a
unpaidocanceled, o se queda enpast_due. Desdeunpaid, pagar la factura la devuelve aactive. - Salida.
cancelllevaactiveacanceled. Cancelar al final del periodo no es un estado aparte: la suscripción sigueactivecon un indicador hasta que termina el periodo y luego pasa acanceled.
Un estado compuesto para el acceso
Aquí, tres estados dan acceso: trialing, active y past_due. Dibujarlos dentro de un único estado compuesto, Access granted, te permite escribir la regla una sola vez: entry / grant access y exit / revoke access. Cualquier transición que salga del compuesto revoca el acceso, empiece en el estado que empiece, así que ningún camino puede olvidarlo. Moverse entre estados dentro del compuesto, por ejemplo de trialing a active, no lo abandona y no ejecuta esas acciones.
Contenido del diagrama
- Access granted
- trialing
- active
- past_due
- paused
- incomplete
- incomplete_expired
- unpaid
- canceled
- subscribe [trial]
- subscribe [no trial]
- invoice paid
- after 23 h / void invoice
- trial ends
- [payment succeeds]
- [no payment method]
- resume [payment succeeds]
- renewal payment fails
- retries exhausted
- [setting: unpaid]
- [setting: cancel]
- cancel
Cómo leerlo:
- Arriba. El punto inicial se divide según la prueba:
trialingdentro del compuesto oincompletefuera. Desdeincomplete, el pago lleva aactive, y 23 horas sin pago llevan aincomplete_expiredy al estado final de la derecha. - En el centro, Access granted.
trial endsllega a una elección:activecuando el pago se completa ypaused, a la izquierda, cuando no hay método de pago. Una renovación fallida llevaactiveapast_due, y el pago la devuelve. - Abajo. Cuando se agotan los reintentos, una segunda elección envía la suscripción a
unpaido acanceled.unpaidtodavía puede volver aactivepagando;canceledtermina en el estado final. - Las conexiones que entran en el marco dan acceso, y las que salen lo revocan.
Del diagrama al código
El diagrama se traslada a una tabla de transiciones. Todo lo que no está en la tabla se rechaza:
type Status = 'incomplete' | 'incomplete_expired' | 'trialing' | 'active'
| 'past_due' | 'unpaid' | 'paused' | 'canceled';
const transitions: Record<Status, Partial<Record<string, Status>>> = {
incomplete: { invoice_paid: 'active', expired: 'incomplete_expired' },
trialing: { trial_ended_paid: 'active', trial_ended_no_method: 'paused' },
active: { payment_failed: 'past_due', cancel: 'canceled' },
past_due: { invoice_paid: 'active', retries_exhausted: 'unpaid' },
unpaid: { invoice_paid: 'active' },
paused: { resumed: 'active' },
incomplete_expired: {},
canceled: {},
};
function next(status: Status, event: string): Status {
const target = transitions[status][event];
if (!target) throw new Error(`${event} is not allowed in ${status}`);
return target;
}
Si los eventos llegan de los webhooks de un proveedor de pagos, no des por hecho el orden. La documentación de webhooks de Stripe dice que no garantiza que los eventos lleguen en el orden en que se generaron. Trata cada evento como un motivo para consultar el estado actual de la suscripción y aplica la transición sobre ese estado.
Errores comunes
- Eventos que faltan. Para cada estado, comprueba cada evento. "¿Qué pasa si el cliente cancela mientras está en
past_due?" es una pregunta que el diagrama debe responder. - Indicadores dibujados como estados. Cancelar al final del periodo, un descuento o un número de puestos son propiedades de un estado, no estados.
- Acciones dibujadas como estados. "Enviar el correo de cobro" es una acción en una transición o un comportamiento
entry, no un lugar donde espera la suscripción. - Sin estado final. Muestra dónde termina el ciclo de vida, para que nadie escriba código que reviva una suscripción cancelada.
- Guardas que se solapan o dejan huecos. Las guardas que salen de una elección deben cubrir cada caso exactamente una vez; añade
[else]si hace falta. - Dos ciclos de vida en un diagrama. La suscripción y sus facturas tienen máquinas de estados separadas. Únelas con eventos en lugar de fusionarlas.
Mantén el diagrama junto al código que lo implementa y revisa ambos en el mismo pull request, como se explica en diagramas de arquitectura que no se quedan obsoletos.
Constrúyelo en Archboard
- Inicia sesión con Google o GitHub y crea un diagrama.
- Del pack UML, añade un Estado por cada estado de la suscripción.
- Añade un Marco del pack Básico llamado Access granted alrededor de
trialing,activeypast_due. - Añade del pack Básico una Elipse pequeña y rellena para el estado inicial, un Rombo para cada elección y un Icono de círculo con punto para cada estado final.
- Conecta los estados con conexiones etiquetadas
event [guard] / action. - Añade una Nota UML con las acciones de entrada y salida del estado compuesto.
El ejemplo usa 14 nodos de diagrama, dentro del plan Free. Con un agente de IA conectado por MCP puedes pegar tu enum de estados y dejar que esboce los estados, y después revisar tú cada transición.
Preguntas frecuentes
¿Qué diferencia hay entre un diagrama de estados y un diagrama de flujo? Un diagrama de flujo muestra los pasos de un proceso en orden. Un diagrama de estados muestra las condiciones en las que puede estar un objeto y qué eventos lo mueven entre ellas, así que responde a qué está permitido y no a qué viene después.
¿Cada valor de mi columna de estado debería ser un estado? Cada valor que cambia el comportamiento, sí. Si dos valores se comportan igual en todos los casos, únelos; si un valor esconde dos comportamientos, divídelo.
¿Dónde encajan los webhooks en una máquina de estados? Son eventos. Verifícalos, consulta el estado actual al proveedor y aplica la transición, porque los eventos pueden llegar desordenados o más de una vez.
¿Necesito un estado compuesto? Usa uno cuando varios estados compartan una regla, como el acceso o la facturación. Convierte una regla que repetirías en muchas flechas en una acción de entrada y otra de salida.
¿Cuánto detalle deben tener las etiquetas de transición? Nombra el evento en términos del dominio y añade una guarda solo cuando dos flechas salgan con el mismo evento. Pon en el diagrama las acciones que importan para la corrección, como dar acceso.