DMarlon Studio

Lección 20 · 20 de 20

Proyecto: procesar pedidos · Dart

Combine clases selladas, genéricos, async y JSON en Dart en un pequeño sistema de pedidos: la lógica que hay detrás de una app Flutter.

Dart · Proyecto: procesar pedidos en DMarlon Studio: el código del ejemplo y su salida en la terminal

Este programa reúne el curso en una pequeña capa de lógica de pedidos, la que tendría una aplicación Flutter debajo de sus pantallas. Empieza con un tipo `Result<T>` sellado y genérico: una operación termina en Ok, con un valor, o en Err, con un motivo. Así los fallos esperados (falta de existencias, pago rechazado) son datos que se tratan, no excepciones que se escapan.

Order.fromJson usa una lista de inicialización y, dentro, un for con un patrón de lista: cada línea del JSON es un array [sku, qty, cents] que se desestructura y se guarda como un record (String, int, int). total suma cantidad por precio con fold, y toJson devuelve el resumen que se enviará después.

process es el servicio asíncrono simulado: espera 10 ms por línea, busca las líneas cuyo producto no tiene existencias suficientes en el mapa stock y devuelve un Err si encuentra alguna o si el total pasa de 100 euros (10000 céntimos); si no, un Ok. Como es async, su tipo es Future<Result<Order>>.

main decodifica los pedidos y los procesa todos en paralelo con Future.wait(orders.map(process)). Aunque cada uno tarde distinto, los resultados llegan en el orden de los pedidos, así que la salida no cambia. El switch sobre el Result sellado es exhaustivo y extrae value o reason; después, if (r case Ok(:final value)) se queda con los aceptados y jsonEncode produce el informe, llamando a toJson() de cada pedido.

Clases, records, patrones, genéricos, async y JSON, todo en 34 líneas. En DMarlon Studio, Dart es el lenguaje de los destinos Flutter, y este es justo el tipo de código que va en la capa de modelo y lógica de esas aplicaciones, separado de los widgets que lo muestran.

Código · main.dart

import 'dart:convert';// Either a value or the reason it failed: a switch must handle both.sealed class Result<T> {}class Ok<T> extends Result<T> { final T value; Ok(this.value); }class Err<T> extends Result<T> { final String reason; Err(this.reason); }class Order {  final String id;  final List<(String sku, int qty, int cents)> lines;  Order.fromJson(Map<String, dynamic> json) : id = json['id'],        lines = [for (final [String sku, int qty, int cents] in json['lines']) (sku, qty, cents)];  int get total => lines.fold(0, (sum, line) => sum + line.$2 * line.$3);  Map<String, dynamic> toJson() => {'id': id, 'total': total};}const stock = {'MUG': 5, 'TEE': 0, 'CAP': 3};Future<Result<Order>> process(Order order) async { // a simulated service  await Future.delayed(Duration(milliseconds: 10 * order.lines.length));  final missing = order.lines.where((line) => (stock[line.$1] ?? 0) < line.$2);  if (missing.isNotEmpty) return Err('${order.id}: no stock of ${missing.first.$1}');  return order.total > 10000 ? Err('${order.id}: payment declined') : Ok(order);}const incoming = '[{"id": "A-1", "lines": [["MUG", 2, 1250]]}, {"id": "A-2", "lines": [["CAP", 1, 1500], '    '["TEE", 1, 1999]]}, {"id": "A-3", "lines": [["CAP", 3, 4000]]}, {"id": "A-4", "lines": [["MUG", 1, 1250]]}]';Future<void> main() async {  final orders = [for (final json in jsonDecode(incoming)) Order.fromJson(json)];  final results = await Future.wait(orders.map(process)); // all in parallel  for (final result in results) print(switch (result) {        Ok(:final value) => 'OK    ${value.id}: ${value.total} cents',        Err(:final reason) => 'ERROR $reason',      });  final accepted = [for (final r in results) if (r case Ok(:final value)) value];  print(jsonEncode({'accepted': accepted, 'revenue': accepted.fold(0, (s, o) => s + o.total)}));}

Salida · dart run main.dart

OK    A-1: 2500 cents
ERROR A-2: no stock of TEE
ERROR A-3: payment declined
OK    A-4: 1250 cents
{"accepted":[{"id":"A-1","total":2500},{"id":"A-4","total":1250}],"revenue":3750}

Para recordar

  • Un Result sellado convierte los fallos esperados en datos.
  • Los patrones de lista leen JSON compacto con seguridad.
  • Future.wait procesa en paralelo y mantiene el orden.
  • El switch exhaustivo obliga a tratar el éxito y el error.
  • toJson y jsonEncode preparan la salida para otra API.

Ejercicio

Añada al informe final un campo rejected con la lista de motivos de los pedidos rechazados, obtenida con otro if (r case Err(:final reason)).