Consolidacion de puertos de stock
Los tres puertos granulares (ReserveStock, ReleaseStock, ConfirmStock) y sus respectivos adaptadores se consolidaron en un unico HandleStockPort con tres operaciones: reserveStock, releaseStock y commitStock. En Inventory, el use case HandleStockForSale implementa las tres operaciones. Esto reduce de 6 archivos a 2 sin perder expresividad.
Eliminacion de QuantityVO
Se elimino QuantityVO por sobreingenieria: solo envolvia un number con un getter y una validacion minima, sin comportamiento de dominio real. Se reemplazo por number directo con validaciones en los puntos correctos:
SaleItem.create()validaquantity > 0retornandoResult.failSaleItem.decrementQuantity()valida que la cantidad resultante no sea negativa
El constructor de SaleItem se hizo private, forzando el uso del factory method create().
Renombramiento de estados y campos
Se renombraron conceptos para reflejar mejor el lenguaje del dominio:
SaleStates→SaleStatus, con valoresDRAFT,CONFIRMED,CANCELLED(antesPENDING,COMPLETED,CANCELLED)completeSale()→confirmSale()- Campos de
SaleItem:id→productId,productName→nameSnapshot,price→priceSnapshot,total→subTotal
Guardas de transicion de estado en Sale
Se identifico que Sale no protegia sus transiciones de estado. Ahora las tres operaciones sensibles solo se permiten desde DRAFT:
confirmSale()retornaResult.failsi no estaDRAFTcancelSale()retornaResult.failsi no estaDRAFTaddItem()retornaResult.failsi no estaDRAFT
CreateSale valida que no exista una venta con el mismo ID.
Fail-fast en AddItemToSale
AddItemToSale ahora verifica el estado de la venta antes de reservar stock. Esto evita reservar inventario innecesariamente cuando la venta ya no acepta operaciones. Si getProductInfo falla despues de reservar, se hace rollback del stock.
Propagacion de errores de dominio
Se realizo una auditoria que revelo multiples puntos donde errores de dominio se silenciaban o no se propagaban. Los cambios:
Sale.create()retornaResult<Error, Sale>— antes ignoraba fallos deSaleItem.create()y continuaba con datos incompletosSale.addItem()retornaResult<Error, void>— mismo problemaRegisterProduct.execute()retorna elResultdelregistry()CreateSale.execute()propaga errores deSale.create()ysave()
Todos los use cases de Sales ahora verifican que getSaleById no retorne undefined, retornando SaleNotFoundError en vez de explotar con getValue()!.
Errores de dominio exportados
Se extrajeron los errores que estaban como clases privadas dentro de los archivos de dominio a domain/Errors/, haciendolos importables:
- Inventory:
InsufficientStockError,InvalidStockOperationError - Sales:
InvalidSaleStateError,InvalidQuantityError,SaleItemNotFoundError - Payment:
PaymentOrderNotFoundError,InvalidPaymentError
Validaciones en Product
releaseStock()falla siquantity > reservedStockconfirmStock()falla siquantity > reservedStockreserveStock()ya no esasyncinnecesariamente
Correccion de bugs en repositorios e infraestructura
InMemorySaleRepository.update()/delete()—findIndexse comparaba consigo mismo (sale.getId() === sale.getId()), siempre retornaba index 0HandleStockForSale.releaseStock()/commitStock()— pasabanundefinedaproductRepository.update()porque operaban sobre el retorno deResult<void>en vez del productoRemoveItemFromSaleahora llamareleaseStockdespues de decrementar — antes el stock quedaba reservado sin liberar
Mejoras al sistema de tests
- Suites con identidad: cada suite ahora tiene
id,nameydescription. Elidse usa como clave de registro,nameydescriptionse muestran en las tarjetas del logbook - Lifecycle hooks: el runner soporta
setupyteardownopcionales por suite, ejecutados antes y despues de todos los tests del suite respectivamente - Tests organizados por iteracion:
package/tests/starting/para la iteracion inicial,package/tests/iter2/para esta iteracion
Event Bus y evento SalesConfirmed
Se implemento un bus de eventos en memoria (InMemoryEventBus) como singleton, con interfaces EventBus y EventHandler<T> en shared/domain/bus/. Al confirmar una venta, RegisterSale publica un evento SalesConfirmed con saleId y totalAmount. Este evento es consumido por el contexto Payment para crear automaticamente una orden de pago.
Nuevo bounded context: Payment
Se agrego el contexto Payment para gestionar ordenes de pago asociadas a ventas confirmadas:
- Agregado
PaymentOrder: vinculado a unsaleId, contiene una coleccion dePayment, calcula cambio para pagos en efectivo, y transiciona aCOMPLETEDcuando el monto total es cubierto. ExponegetStatus()ygetTotalAmount()para consultas externas - Entidad
Payment: representa un pago individual con metodo (CASH,CARD,TRANSFER) y monto PaymentCompletedEventHandler: suscrito aSalesConfirmed, crea unaPaymentOrderautomaticamente al confirmar una venta- Use cases:
CreatePaymentOrder,AddPayment - Infraestructura:
InMemoryPaymentOrderRepository
Reglas de dominio en PaymentOrder
addPayment()retornaResult<InvalidPaymentError, void>— consistente con el patron del proyecto- Rechaza pagos si la order ya esta
COMPLETED - Pagos no-cash que excedan el total retornan error controlado
- Solo pagos en efectivo (CASH) pueden exceder el total, generando cambio
AddPayment(use case) valida que laPaymentOrderexista antes de operar, retornandoPaymentOrderNotFoundErrorInMemoryPaymentOrderRepositoryusaResult.failen vez dethrowenupdate()/delete()
Comunicacion entre contextos
La comunicacion Sales → Payment se da a traves de eventos de dominio, manteniendo los contextos desacoplados. Sales publica SalesConfirmed sin conocer a Payment. Payment se suscribe al evento y reacciona creando la orden de pago.
Otros cambios
GetProductrenombrado aGetProducts— acepta array de IDsGetProductInforenombrado aGetProductsInfo— mismo cambio en el puerto de SalesCreateSalerefactorizado para aceptaritemIdsy resolver productos internamentePriceVO.substract()— nuevo metodo estatico para restar preciosPriceVO.getValueInCents()— nuevo getter para acceso directo al valor en centavos