Racoonity

La tienda ya sabe qué pasó

Racoonity ya podía vender. Ahora también puede cancelar una venta, conservar su historia y empezar a entender lo que ocurre durante la operación.

Por El equipo de Racoonity •
7 min de lectura
  • #racoonity
  • #desarrollo
  • #arquitectura
  • #openspec
  • #mvp

En la iteración anterior ocurrió algo bastante importante para Racoonity:

la tienda pudo vender por primera vez.

Una venta dejó de ser una idea en un documento y se convirtió en una operación real que atravesaba catálogo, inventario, pagos y caja hasta terminar persistida como una venta confirmada.

Era un buen momento.

También era insuficiente.

Porque una tienda en la que todo sale bien no es una tienda.

Es una demo.

Vender era solamente la mitad del problema

Una operación real tiene días bastante menos elegantes.

Alguien captura algo mal.

Un cliente cambia de opinión.

Una venta necesita cancelarse.

El inventario tiene que regresar a donde estaba.

El historial tiene que seguir diciendo que aquella venta existió.

Y, quizá más importante, cuando alguien pregunte al final del día:

¿Qué pasó aquí?

el sistema debería ser capaz de responder.

Ese fue el objetivo de nuestra sexta iteración:

I6 — Control y supervisión.

No queríamos agregar veinte nuevas funciones.

Queríamos cerrar el ciclo que habíamos comenzado con la primera venta.

Una venta cancelada no desaparece

Una de las primeras decisiones fue bastante sencilla conceptualmente:

cancelar una venta no significa borrarla.

Una venta confirmada puede pasar a estar cancelada, pero conserva su número, sus productos, sus precios, sus datos históricos y el registro de quién y cuándo realizó la cancelación.

La operación ocurrió.

Después fue cancelada.

Ambas cosas son parte de la historia.

Esto parece una distinción pequeña hasta que empiezas a construir reportes, auditorías o simplemente intentas averiguar por qué el inventario tiene cierta cantidad.

Borrar información suele hacer que los problemas desaparezcan de la pantalla.

No necesariamente de la realidad.

La mercancía tiene memoria

Cancelar la venta también tenía otra consecuencia inevitable:

había que devolver el inventario.

Aquí apareció una regla importante.

Supongamos que una venta sacó mercancía de la ubicación A.

Después alguien cambia la configuración de la tienda y ahora las ventas utilizan la ubicación B.

Si aquella venta original se cancela, la mercancía no debe aparecer mágicamente en B.

Debe volver a A.

Racoonity utiliza el movimiento original como evidencia de dónde salió realmente cada producto.

La configuración actual puede cambiar.

La historia no.

También dejamos una segunda protección para impedir que el mismo movimiento sea revertido dos veces.

Porque devolver una unidad es correcto.

Devolverla dos veces porque dos procesos intentaron cancelar la misma venta al mismo tiempo sería una excelente manera de fabricar mercancía mediante software.

Todavía no tenemos ese módulo.

Y entonces aparecieron dos mapaches jalando el mismo tornillo

Las primeras pruebas funcionaban bien.

Así que hicimos lo razonable:

intentamos romperlas.

Probamos dos cancelaciones simultáneas.

Una cancelación mientras se cerraba una sesión.

Una cancelación mientras otra operación modificaba exactamente el mismo inventario.

Lecturas mientras una cancelación estaba ocurriendo.

Reintentos de la misma operación.

Y eventualmente PostgreSQL nos dio un regalo:

un deadlock real.

Dos operaciones perfectamente válidas estaban tomando recursos en distinto orden y, bajo suficiente concurrencia, podían terminar esperando una por la otra.

Era exactamente el tipo de problema que queríamos encontrar aquí y no meses después en una tienda.

La solución terminó fortaleciendo una pieza más general de Racoonity: nuestro coordinador transaccional ahora puede reintentar de forma controlada ciertas colisiones que PostgreSQL detecta como recuperables.

Después repetimos las carreras.

Una vez.

Otra.

Y otra.

Cientos de ejecuciones después, el deadlock no volvió.

Las pruebas de concurrencia tienen esa peculiaridad: cuando encuentran algo, dejan de sentirse como una pérdida de tiempo bastante rápido.

Cancelar una venta no significa devolver el dinero

También tuvimos que separar dos conceptos que parecen iguales hasta que dejan de serlo.

Una cosa es el resultado comercial de una venta.

Otra es la historia de cómo fue pagada.

En este MVP, cancelar una venta no implementa todavía un reembolso.

Por eso el pago original permanece registrado.

Los reportes comerciales pueden decir:

  • cuánto se vendió originalmente;
  • cuánto terminó cancelado;
  • cuánto permanece como venta neta.

Mientras que el historial de pagos sigue diciendo cuánto dinero fue registrado mediante cada método de pago.

No intentamos fingir que ambas cifras tienen que cuadrar mediante una reconciliación que todavía no existe.

Eso llegará cuando Racoonity tenga conceptos suficientes para hacerlo correctamente.

Preferimos una verdad incompleta a una mentira convenientemente redonda.

La tienda empieza a poder explicarse

Con las cancelaciones funcionando, apareció la segunda mitad de I6:

supervisión.

Hasta ahora Racoonity sabía ejecutar operaciones.

Desde esta iteración también empieza a saber resumirlas.

Podemos consultar ventas históricas, buscar por distintos criterios, obtener resúmenes por periodo, conocer qué productos se vendieron, revisar una sesión y ver cómo se distribuyeron los pagos.

También apareció el primer resumen de operación pensado para alimentar un dashboard:

ventas del día, cantidad de ventas, ticket promedio, productos con poco inventario y las operaciones más recientes.

Nada espectacular.

Nada de gráficas tridimensionales girando mientras un mapache ejecutivo señala una tendencia trimestral.

Todavía.

Pero ya existe algo mucho más importante:

los datos tienen una interpretación definida.

Una venta cancelada aparece en la historia, pero no infla las ventas netas.

El pago continúa existiendo porque todavía no hubo un reembolso.

Los productos vendidos se calculan utilizando la información histórica de la venta y no lo que diga actualmente el catálogo.

Y las fechas se interpretan utilizando el huso horario de la empresa, no el reloj casualmente configurado en el servidor.

Son detalles aburridos.

También son exactamente los detalles que convierten un reporte en algo que puedes empezar a creer.

Incluso podemos llevárnoslo en CSV

I6 también agregó una salida bastante menos emocionante pero probablemente bastante útil:

CSV.

Algunos reportes pueden obtenerse tanto como respuesta normal del sistema como en un archivo tabular.

No construimos un gigantesco motor de reportes.

No agregamos un diseñador.

No apareció Racoon Excel con sombrero.

La exportación es simplemente otra representación de los mismos datos ya autorizados y filtrados.

Mismos resultados.

Mismos permisos.

Otra forma de consumirlos.

Simple por defecto.

Apágalo y vuélvelo a encender

Antes de considerar terminada la iteración hicimos una última prueba.

Construimos una operación completa en una instancia de Racoonity:

vendimos.

cancelamos.

consultamos los resultados.

Y apagamos todo.

Después levantamos una instancia completamente nueva utilizando solamente la misma base de datos.

La nueva instancia pudo reconstruir la venta cancelada, su auditoría, los movimientos de inventario, el pago original y los reportes correspondientes.

Incluso repetir la cancelación utilizando la misma clave de idempotencia devolvió el resultado original sin crear nuevos movimientos ni eventos.

Parece obvio.

Pero es una de mis pruebas favoritas.

Porque demuestra que el conocimiento del sistema está donde debe estar.

No escondido accidentalmente en memoria.

No dependiendo de que un proceso nunca se reinicie.

Persistido.

¿Qué puede hacer ahora la tienda?

Al terminar I6, Racoonity puede recorrer un ciclo bastante más parecido al de una operación real.

Puede vender.

Puede cancelar completamente una venta.

Puede devolver correctamente el inventario.

Puede conservar el historial del pago.

Puede mantener una auditoría de lo ocurrido.

Puede consultar y resumir sus ventas.

Puede supervisar una sesión.

Puede obtener indicadores básicos de la operación.

Puede exportar algunos resultados.

Y puede hacer todo eso después de reiniciarse.

Todavía no existen devoluciones parciales.

No existen reembolsos reales.

No existe arqueo de caja.

No existe un motor avanzado de reportes.

No intentamos resolver en una iteración todo lo que eventualmente podría necesitar un ERP.

Seguimos construyendo una capa cada vez.

Seis iteraciones después

Cuando comenzamos este primer MVP había muchísimo trabajo que no producía ninguna pantalla bonita.

Runtime.

Contratos.

Autorización.

Catálogo.

Inventario.

Clientes.

Cajas.

Pagos.

Ventas.

Durante bastante tiempo Racoonity parecía más una colección de cimientos que una tienda.

Pero algo empieza a cambiar.

Primero la mercancía existió en algún lugar.

Después la tienda pudo abrir.

Después pudo vender.

Y ahora, cuando algo ocurre dentro de ella, empieza a ser capaz de entenderlo y contarlo.

Todavía falta camino.

Pero cada vez cuesta un poquito más llamarlo solamente un experimento.

El mapache ya está trabajando.