Tipos fijos y seguridad con Enums en Dart

Un enum es un tipo de dato que define un conjunto fijo de valores constantes. Imagina que necesitas representar el estado de un pedido: puede estar pendiente, procesando, enviado o entregado. En lugar de usar números (1, 2, 3) o cadenas de texto ('pending', 'sent'), que son propensos a errores de escritura, utilizas un enum. Esto crea un tipo nuevo y nominal; es decir, OrderStatus.pending no es solo un texto, es una instancia real del tipo OrderStatus.

Esto funciona así porque el compilador de Dart reconoce al enum como un tipo con un conjunto de valores cerrado. Al usar un enum, ganas type safety (seguridad de tipos): no puedes asignar accidentalmente un valor que no esté definido en la enumeración. Además, los enums ofrecen exhaustividad en las estructuras switch; si intentas evaluar un enum en un switch y olvidas un caso, el compilador te obligará a incluirlo (si usas switch expressions de Dart 3) o te dará una advertencia, evitando errores lógicos en producción.

Debes usarlo siempre que una variable solo pueda tomar un conjunto limitado de opciones conocidas de antemano. Si usas constantes de tipo int o String para esto, lo que rompes es la integridad de tu código: un desarrollador podría pasar un 99 a una función que solo espera estados de pedido, o escribir "procesando" con una sola “s”, provocando errores que solo se detectarán cuando el programa ya esté corriendo (runtime errors), en lugar de detectarlos mientras escribes el código.

enum OrderStatus {
  pending,
  processing,
  shipped,
  delivered,
}

void main() {
  // Declaración de una variable con el tipo enum
  final currentStatus = OrderStatus.shipped;

  // 1. Uso con Switch Expression (Dart 3)
  // El compilador verifica la exhaustividad: si agregas un caso a 
  // OrderStatus y no lo incluyes aquí, el código no compilará.
  final mensaje = switch (currentStatus) {
    OrderStatus.pending => 'Esperando pago del cliente',
    OrderStatus.processing => 'Preparando el paquete',
    OrderStatus.shipped => 'El repartidor va en camino',
    OrderStatus.delivered => 'Pedido recibido con éxito',
  };

  print('Estado del pedido: $mensaje');

  // 2. Propiedades automáticas
  // .name devuelve el identificador como String
  print('Nombre técnico: ${currentStatus.name}'); 
  // .index devuelve la posición (empezando en 0)
  print('Índice de posición: ${currentStatus.index}'); 
  // .toString() devuelve "EnumName.valueName"
  print('Representación completa: ${currentStatus}'); 

  // 3. Acceso a todos los valores mediante .values
  print('Lista de todos los estados: ${OrderStatus.values}');

  // 4. Uso como llaves en un Map para mapear estados a otros datos
  final iconosEstado = {
    OrderStatus.pending: '⏳',
    OrderStatus.processing: '⚙️',
    OrderStatus.shipped: '🚚',
    OrderStatus.delivered: '✅',
  };

  print('Icono actual: ${iconosEstado[currentStatus]}');

  // 5. Uso en un Set para asegurar valores únicos
  final estadosCompletados = {OrderStatus.delivered};
  print('¿Está completado?: ${estadosCompletados.contains(currentStatus)}');
}

En el ejemplo anterior, definimos OrderStatus con cuatro valores. Al asignar currentStatus = OrderStatus.shipped, estamos usando una instancia específica de ese tipo.

Cuando ejecutamos el switch (currentStatus), estamos usando una switch expression de Dart 3. Fíjate que no hay la palabra clave break; esto es porque las expresiones de switch en Dart son exhaustivas por diseño: el compilador analiza el tipo OrderStatus y, si no cubres pending, processing, shipped y delivered, el programa simplemente no compilará. Esto es lo que garantiza que no dejes casos sin manejar.

Para las propiedades automáticas, currentStatus.name nos devuelve simplemente "shipped", lo cual es útil para logs o interfaces, mientras que currentStatus.index nos da el 2, ya que es el tercer elemento (contando desde 0). La propiedad OrderStatus.values nos devuelve una List<OrderStatus> con todos los elementos, lo cual es extremadamente útil para iterar sobre todas las opciones posibles. Finalmente, el Map llamado iconosEstado utiliza los elementos del enum como llaves; esto es mucho más seguro que usar un Map<String, String>, porque el compilador garantiza que solo uses llaves válidas de tipo OrderStatus.

El error frecuente

Un error clásico es intentar manejar estados usando String para “ahorrar tiempo”, lo que introduce errores de sintaxis difíciles de rastrear.

// ❌ PRÁCTICA INCORRECTA
void procesarPedido(String status) {
  if (status == 'shipped') { // ¿Qué pasa si alguien pasa 'Shipped' con S mayúscula?
    print('En camino');
  }
}

void main() {
  procesarPedido('shippped'); // Error de dedo: no lanza error de compilación,
                               // pero el programa no hará nada o fallará en runtime.
}

// ✅ PRÁCTICA CORRECTA
void procesarPedido(OrderStatus status) {
  if (status == OrderStatus.shipped) {
    print('En camino');
  }
}

void main() {
  procesarPedido(OrderStatus.shipped); // El compilador te protege.
}

Si intentas pasar un valor que no existe en el tipo OrderStatus, el compilador te detendrá inmediatamente, evitando que el error llegue al usuario final.

57

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio