En la Parte 1 vimos partición de equivalencia y valores límite — las técnicas que te dicen qué valores probar.

Hoy subimos de nivel. Tablas de decisión y transición de estados te dicen qué combinaciones probar y qué caminos puede tomar tu sistema. Son las técnicas que separan al QA que ejecuta casos de prueba del QA que los diseña.

Serie ISTQB con código

Esta es la Parte 2 de 3. Si no leíste la Parte 1 (partición de equivalencia y valores límite), te recomiendo empezar por ahí — los conceptos se construyen uno sobre otro. En la Parte 3 aplicaremos las cuatro técnicas juntas en un caso práctico E2E completo.


Primero: la solución del ejercicio de la Parte 1

En la Parte 1 te pedí que diseñaras las particiones y valores límite para un formulario de registro con 4 campos. Aquí va la solución:

Particiones

CampoParticiones válidasParticiones inválidas
EmailCon @ y dominio (user@mail.com)Sin @ (usermail), sin dominio (user@), vacío
Contraseña8 a 64 caracteresMenos de 8, más de 64, vacía
Edad13 a 120Menor a 13, mayor a 120, 0, negativo
Código postal5 dígitos (12345)Menos de 5, más de 5, con letras (1234A), vacío

Valores límite

CampoBajo mín.MínimoMáximoSobre máx.
Contraseña (longitud)7 chars8 chars64 chars65 chars
Edad1213120121
Código postal (longitud)4 dígitos5 dígitos5 dígitos6 dígitos

Los datos de prueba diseñados

// Diseño universal — funciona con cualquier framework
const registroTests = {
email: {
válidos: ["usuario@correo.com", "a@b.co"],
inválidos: [
{ valor: "sinArroba", error: "Email inválido" },
{ valor: "sin@dominio", error: "Email inválido" },
{ valor: "", error: "El email es obligatorio" },
],
},
contraseña: {
válidos: ["A".repeat(8), "A".repeat(64)], // límites
inválidos: [
{ valor: "A".repeat(7), error: "Mínimo 8 caracteres" }, // bajo mínimo
{ valor: "A".repeat(65), error: "Máximo 64 caracteres" }, // sobre máximo
],
},
edad: {
válidos: [13, 120], // límites
inválidos: [
{ valor: 12, error: "Debes tener al menos 13 años" }, // bajo mínimo
{ valor: 121, error: "Edad no válida" }, // sobre máximo
{ valor: -1, error: "Edad no válida" }, // negativo
],
},
códigoPostal: {
válidos: ["12345"],
inválidos: [
{ valor: "1234", error: "Debe tener 5 dígitos" },
{ valor: "123456", error: "Debe tener 5 dígitos" },
{ valor: "1234A", error: "Solo dígitos numéricos" },
],
},
};

Si tu diseño se parece a esto, vas por buen camino. Si te faltó alguna partición inválida — eso es exactamente lo que estas técnicas previenen.


Tabla de decisión (Decision Table Testing)

Qué dice el syllabus (en 3 líneas)

Cuando el comportamiento del sistema depende de combinaciones de condiciones, una tabla de decisión mapea todas las combinaciones posibles y el resultado esperado de cada una. Cada columna es una regla. Cada fila es una condición o una acción.

El bug que lo explica

E-commerce. Reglas de descuento:

  • Los empleados tienen 20% de descuento
  • Los cupones dan 15% de descuento
  • Los miembros premium tienen envío gratis

Cada regla por separado funcionaba perfecto. El equipo probó empleado con descuento, cupón con descuento, premium con envío gratis. Todo verde.

Nadie probó: empleado + cupón + premium. ¿Se acumulan los descuentos? ¿El 20% aplica sobre el precio original o sobre el precio ya rebajado por el cupón?

Un cliente interno compró un monitor de $500 con las tres condiciones activas. El sistema aplicó 20% + 15% = 35% de descuento, más envío gratis. Pagó $325. El precio correcto era $340 (20% sobre el original, y el cupón no era acumulable con descuento de empleado).

$15 de diferencia por compra. Multiplicado por 200 empleados comprando en el Black Friday.

El bug no estaba en ninguna regla individual. Estaba en la combinación.

La tabla de decisión

Las condiciones son binarias (sí/no). Con 3 condiciones, hay 2³ = 8 combinaciones posibles:

ReglaEmpleadoCupónPremiumDescuentoEnvío gratis
R1NoNoNo0%No
R2NoNo20%No
R3NoNo15%No
R4NoNo0%
R5No20% (cupón no acumulable)No
R6No20%
R7No15%
R820% (cupón no acumulable)

8 combinaciones. 8 tests. Cada uno cubre un escenario que el sistema debe manejar correctamente. Sin la tabla, R5 y R8 nunca se prueban — y ahí es donde viven los bugs.

Los bugs más caros no están en las reglas. Están en las combinaciones que nadie probó.

El diseño de los datos

// Tabla de decisión — diseño de pruebas independiente de herramienta
const reglasDescuento = [
// Empleado Cupón Premium → Descuento esperado Envío gratis
{
empleado: false,
cupón: false,
premium: false,
descuento: "0%",
envíoGratis: false,
},
{
empleado: true,
cupón: false,
premium: false,
descuento: "20%",
envíoGratis: false,
},
{
empleado: false,
cupón: true,
premium: false,
descuento: "15%",
envíoGratis: false,
},
{
empleado: false,
cupón: false,
premium: true,
descuento: "0%",
envíoGratis: true,
},
{
empleado: true,
cupón: true,
premium: false,
descuento: "20%",
envíoGratis: false,
},
{
empleado: true,
cupón: false,
premium: true,
descuento: "20%",
envíoGratis: true,
},
{
empleado: false,
cupón: true,
premium: true,
descuento: "15%",
envíoGratis: true,
},
{
empleado: true,
cupón: true,
premium: true,
descuento: "20%",
envíoGratis: true,
},
];

Fíjate: cada objeto es una fila de la tabla. Las propiedades booleanas son las condiciones. Los valores esperados son las acciones. Este diseño es el test. El framework solo lo ejecuta.

En código (ejemplo con Playwright)

import { test, expect } from "@playwright/test";
test.describe(
"Descuentos — tabla de decisión",
{
tag: "@istqb",
},
() => {
reglasDescuento.forEach((regla, i) => {
const nombre =
[
regla.empleado ? "empleado" : null,
regla.cupón ? "cupón" : null,
regla.premium ? "premium" : null,
]
.filter(Boolean)
.join(" + ") || "sin condiciones";
test(`R${i + 1}: ${nombre}${regla.descuento} desc, envío ${regla.envíoGratis ? "gratis" : "normal"}`, async ({
page,
}) => {
await page.goto("/checkout");
// Configurar condiciones
if (regla.empleado) {
await page.getByLabel("Soy empleado").check();
}
if (regla.cupón) {
await page.getByLabel("Código cupón").fill("DESCUENTO15");
await page.getByRole("button", { name: "Aplicar" }).click();
}
if (regla.premium) {
// Asumimos que el usuario premium ya está logueado
await expect(page.getByTestId("badge-premium")).toBeVisible();
}
// Verificar resultados
await expect(page.getByTestId("descuento")).toHaveText(regla.descuento);
if (regla.envíoGratis) {
await expect(page.getByText("Envío gratis")).toBeVisible();
} else {
await expect(page.getByText("Envío gratis")).not.toBeVisible();
}
});
});
},
);

Con un solo forEach sobre el array de reglas, tienes 8 tests que cubren todas las combinaciones. Cambia el array y los tests se adaptan automáticamente. Cambia de framework y solo reescribes las líneas de interacción.

Lo que el examen te va a preguntar

Pregunta típica: “Un sistema tiene 4 condiciones binarias. ¿Cuántas reglas tiene la tabla de decisión completa?” Respuesta: 2⁴ = 16.

Tablas reducidas (collapsed)

El syllabus también habla de tablas reducidas: si ciertas combinaciones producen el mismo resultado, puedes agruparlas. Por ejemplo, si “premium = sí” siempre da envío gratis sin importar las otras condiciones, puedes usar un guión (—) en las columnas irrelevantes. En el examen, esto reduce el número de tests necesarios. En la práctica, yo prefiero la tabla completa — los bugs se esconden en las combinaciones que “no deberían importar”.


Transición de estados (State Transition Testing)

Qué dice el syllabus (en 3 líneas)

Cuando un sistema puede estar en diferentes estados y cambia de uno a otro mediante eventos, un diagrama de transición de estados mapea todos los estados posibles, las transiciones válidas, y — lo más importante — las transiciones que no deberían ser posibles.

El bug que lo explica

Sistema de pedidos en un e-commerce. Los estados eran:

Pendiente Confirmado Enviado Entregado

El equipo probó el camino feliz: Pendiente → Confirmado → Enviado → Entregado. Funciona perfecto.

También probaron cancelar: Pendiente → Cancelado. Funciona.

Nadie probó: Enviado → Cancelado.

Un cliente canceló un pedido que ya estaba en camino. El sistema lo aceptó, le devolvió el dinero, pero el paquete ya había salido del almacén. El repartidor llegó, entregó el producto, y el cliente se quedó con el producto y con la devolución.

El bug: la transición Enviado → Cancelado no debía existir, pero nadie lo validó.

El diagrama completo

No basta con probar los caminos que funcionan. Hay que probar los que no deberían funcionar:

Estado actualEventoEstado esperado¿Válido?
PendienteConfirmarConfirmado
PendienteCancelarCancelado
ConfirmadoEnviarEnviado
ConfirmadoCancelarCancelado
EnviadoEntregarEntregado
EnviadoCancelarNo (el bug)
EntregadoCancelarNo
EntregadoDevolverDevuelto
CanceladoConfirmarNo
5 transiciones válidas — el camino feliz y las alternativas permitidas
4 transiciones inválidas — las que no deberían existir y donde viven los bugs

Los tests de transiciones válidas verifican que tu sistema funciona. Los tests de transiciones inválidas verifican que tu sistema no se rompe.

El diseño de los datos

// Mapa de transiciones — diseño independiente de herramienta
const transiciones = {
válidas: [
{ desde: "pendiente", evento: "confirmar", hasta: "confirmado" },
{ desde: "pendiente", evento: "cancelar", hasta: "cancelado" },
{ desde: "confirmado", evento: "enviar", hasta: "enviado" },
{ desde: "confirmado", evento: "cancelar", hasta: "cancelado" },
{ desde: "enviado", evento: "entregar", hasta: "entregado" },
{ desde: "entregado", evento: "devolver", hasta: "devuelto" },
],
inválidas: [
{
desde: "enviado",
evento: "cancelar",
error: "No se puede cancelar un pedido enviado",
},
{
desde: "entregado",
evento: "cancelar",
error: "No se puede cancelar un pedido entregado",
},
{
desde: "cancelado",
evento: "confirmar",
error: "No se puede reactivar un pedido cancelado",
},
{ desde: "entregado", evento: "enviar", error: "Transición no permitida" },
],
};

La estructura es clara: un array de transiciones válidas (lo que sí debe pasar) y otro de inválidas (lo que no debe pasar). Los bugs viven en el segundo array.

En código (ejemplo con Playwright)

import { test, expect } from "@playwright/test";
test.describe(
"Pedidos — transiciones de estado",
{
tag: "@istqb",
},
() => {
// Transiciones válidas: verificar que el estado cambia correctamente
transiciones.válidas.forEach(({ desde, evento, hasta }) => {
test(`${desde}${evento}${hasta}`, async ({ page }) => {
// Crear pedido y llevarlo al estado inicial
await page.goto("/admin/pedidos/test");
await page.getByTestId("set-estado").selectOption(desde);
// Ejecutar la transición
await page.getByRole("button", { name: evento }).click();
// Verificar el nuevo estado
await expect(page.getByTestId("estado-actual")).toHaveText(hasta);
});
});
// Transiciones inválidas: verificar que el sistema las rechaza
transiciones.inválidas.forEach(({ desde, evento, error }) => {
test(`${desde}${evento} → BLOQUEADO`, async ({ page }) => {
await page.goto("/admin/pedidos/test");
await page.getByTestId("set-estado").selectOption(desde);
await page.getByRole("button", { name: evento }).click();
// El estado NO debe cambiar y debe mostrar error
await expect(page.getByTestId("estado-actual")).toHaveText(desde);
await expect(page.getByText(error)).toBeVisible();
});
});
},
);

10 tests. 6 válidos, 4 inválidos. Cubres todos los caminos y todos los muros. Y si mañana agregan un nuevo estado (por ejemplo, “En preparación” entre Confirmado y Enviado), solo agregas filas al array — los tests se generan solos.

Lo que el examen te va a preguntar

Diagrama de estados: El examen te puede dar un diagrama y pedirte que identifiques cuántos estados, transiciones o tests necesitas.

Tabla de transición de estados: Te pueden dar una tabla y pedir que identifiques transiciones inválidas o faltantes.

N-switch coverage

El syllabus menciona 0-switch (probar cada transición individual) y 1-switch (probar secuencias de 2 transiciones: Pendiente → Confirmado → Enviado). Para el examen CTFL, enfócate en 0-switch — es lo que más preguntan. El concepto de 1-switch aparece más en nivel avanzado.

Pregunta típica del examen: “Un sistema tiene 4 estados y 6 transiciones válidas. ¿Cuántos casos de prueba se necesitan para cubrir todas las transiciones válidas?” Respuesta: 6 (uno por transición).


Combinando las 4 técnicas

Después de dos partes, ya tienes las cuatro técnicas de diseño de pruebas del Capítulo 4:

TécnicaTe diceCuándo usarla
Partición de equivalenciaQué grupos de valores probarCampos con rangos o categorías
Valores límiteDónde exactamente probar dentro de cada grupoCampos numéricos, longitudes, fechas
Tabla de decisiónQué combinaciones de condiciones probarReglas de negocio con múltiples condiciones
Transición de estadosQué caminos y bloqueos probarFlujos con estados (pedidos, usuarios, pagos)
4 Técnicas del Cap. 4
~40% Peso en el examen
Valor en tu trabajo

Ninguna técnica reemplaza a otra — cada una ataca un tipo diferente de problema. El QA que las domina no necesita que le digan qué probar. Lo ve.


Ejercicio: diseña antes de codear

Un sistema de suscripciones tiene:

Estados: Gratuita → Básica → Premium → Cancelada → Suspendida

Reglas de upgrade:

  • Gratuita puede pasar a Básica o Premium
  • Básica puede pasar a Premium
  • Cualquier plan activo puede cancelarse
  • Una cuenta Suspendida (por falta de pago) solo puede pasar a Cancelada o reactivarse al plan anterior
  • No se puede hacer downgrade (Premium → Básica)

Tu tarea:

  1. Dibuja el diagrama de transición de estados (en papel, en tu cabeza, o en código)
  2. Identifica todas las transiciones válidas e inválidas
  3. Diseña el array de datos de prueba
  4. Bonus: Si el upgrade a Premium tiene 3 condiciones (tarjeta válida, email verificado, sin deuda), ¿cuántas reglas tiene la tabla de decisión?
La solución en la Parte 3

En la Parte 3 resolveremos este ejercicio y lo integraremos en un caso práctico E2E completo: un flujo de e-commerce donde las cuatro técnicas trabajan juntas. De las particiones del registro al diagrama de estados del pedido — todo conectado.


Cheat sheet

ConceptoFórmula rápidaPregunta clave
Tabla de decisiónN condiciones binarias = 2ᴺ reglas”¿Qué pasa cuando se combinan estas condiciones?”
Tabla reducidaAgrupar reglas con mismo resultado”¿Puedo reducir sin perder cobertura?”
Transición de estadosTests = transiciones válidas + inválidas”¿Qué pasa si intento una transición prohibida?“
0-switch1 test por transición”¿Cada transición individual funciona?”

Lo que viene

En la Parte 3 todo se conecta. Un caso práctico E2E completo — un flujo de e-commerce de punta a punta — donde aplicamos las cuatro técnicas juntas. Registro (particiones + límites), checkout (tabla de decisión), y seguimiento del pedido (transición de estados). Un test suite completo que puedes adaptar a tu proyecto.

Si no quieres perdértela, suscríbete al newsletter. Llega antes que nadie.