Plan de M6 — Ventas
Objetivo: implementar M6 según la spec de diseño — la venta como documento inmutable, el piso de precio sostenido por la base, la anulación que revierte, y el cobro como documento aparte.
Dominio: Ventas — 13 reglas, 9 confirmadas.
Estado: ✅ Terminado (26-ago-2026) — las 12 tasks cerradas.
| Task | Commit | |
|---|---|---|
| T1 — Schema e invariantes | ✅ | a249e7a |
| T2 — El cálculo del precio | ✅ | a249e7a |
| T3 — La transacción de la venta | ✅ | 11d164d |
| T4 — Anulación | ✅ | cbaa702 |
| T5 — Crédito | ✅ | 11d164d — quedó dentro de T3 |
| T6 — Cobros y saldo derivado | ✅ | 0f150c4 |
| T7 — Devoluciones | ✅ | 0f150c4 |
| T8 — Códigos de descuento | ✅ | 0f150c4 |
| T9 — Endpoints | ✅ | 2efa24b + 2102209 |
| T10 — Pantalla del mostrador | ✅ | d5aafa5 |
| T11 — La deuda en la ficha | ✅ | d5aafa5 |
| T12 — Bruno, documentación y cierre | ✅ | este commit |
Lo que cambió respecto del plan
Sección titulada «Lo que cambió respecto del plan»La venta NO consume tapa ni sello. El plan de T3 decía que sí. El dominio es
explícito: «se consumen al llenar, no al entregar», y el cierre de producción
ya cuenta botellonesLlenados incluyendo las recargas. Descontarlos otra vez
los contaría dos veces — la planta creería tener menos tapas de las que tiene y
compraría de más. La venta son tres escritos, no cuatro.
T5 quedó dentro de T3. El chequeo compuesto de crédito necesita la deuda, y la deuda se calcula con dos consultas triviales sobre tablas que T1 ya había creado. Separarlo habría sido una task que solo movía código de un archivo a otro.
Lo que apareció escribiendo, y no se había previsto
Sección titulada «Lo que apareció escribiendo, y no se había previsto»-
El trigger de inmutabilidad frenó a su propio autor. La primera versión de la transacción insertaba la venta con total en cero y lo completaba al final;
ventas_solo_anulacionla rechazó, y con razón. La salida no fue abrirle una excepción sino no necesitar elUPDATE:asignarFifoplanifica y bloquea las filas, así que el total se calcula antes de que la venta exista. -
montoAcreditadoera decorativo. Las devoluciones lo guardaban y la deuda derivada no lo restaba. Ahora la fórmula esventas a crédito confirmadas − cobros − devoluciones acreditadas. -
Los fixtures de los tests del middleware chocaban con las rutas reales. Se llamaban
/ventasy/auditoriaa secas; registrarGET /ventasde verdad tiró los 19 tests de ese archivo con un error que apuntaba al lugar equivocado. Todos pasaron a/probar/*.
M6 tiene DOS compuertas
Sección titulada «M6 tiene DOS compuertas»Los milestones anteriores tuvieron una. Este tiene dos, y son de tipos distintos:
La primera es el cálculo del precio. Como el dígito de verificación en M5, no falla: devuelve un número. Un descuento mal aplicado cobra de menos y nadie lo nota hasta el arqueo; uno que perfora el piso deja una venta bajo costo que solo aparece sumando el mes. Va primero, es una función pura sin base de datos, y el piso se prueba con el caso que lo perfora.
La segunda es la transacción de la venta, y es la de M4 otra vez: escribe la venta, sus líneas, el descuento de stock y el consumo de insumos. O quedan las cuatro, o no queda ninguna. Una venta registrada sin descontar stock genera faltantes fantasma que después nadie puede explicar.
T2 precio (puro) ──┐ ├── T3 la transacción ── T4 anulación ──┐T1 schema ─────────┘ ├── T9 endpoints ── T10 POS ── T11 la deuda en la ficha ├── T5 crédito ─────────────────────────┤ ├── T6 cobros y saldo ──────────────────┤ ├── T7 devoluciones ────────────────────┤ └── T8 códigos de descuento ────────────┘ T12 bruno + docs- T2 no depende de nada: es aritmética.
- T3 es la compuerta: nada se construye encima hasta que la atomicidad esté probada.
- T11 es lo que hace que M5 deje de mostrar «sin registrar todavía» en la primera de sus cuatro cuentas.
Restricciones globales
Sección titulada «Restricciones globales»Las mismas de M4 y M5:
- 2 espacios, comillas simples, sin punto y coma. Conventional commits sin
Co-Authored-By. Nunca buildear. - Cada commit deja el proyecto en verde, verificado por código de salida.
- Migraciones en SQL explícito, escritas a mano.
REVOKEexplícito en toda tabla nueva (ADR-0006).- Aplicar la migración a las DOS bases.
- Leer la matriz antes de tocar permisos. Los seis de
ventasy los dos decobrosya existen, con alcancespropio/todoque codificanRN-VEN-08mejor de lo que uno lo escribiría de memoria. - Correr la colección Bruno contra una instancia sobre
aquazaku_test, no contra la base de desarrollo. Y moverBETTER_AUTH_URLcon el puerto.
Task 1 — Schema, migración e invariantes
Sección titulada «Task 1 — Schema, migración e invariantes»Cuatro tablas: ventas, lineas_de_venta, cobros, codigos_de_descuento.
Los invariantes que van a la base:
CHECK (precio_final >= precio_minimo_aplicado)en la línea —RN-VEN-13. Es intra-fila porque el piso se congela junto al precio.CHECK:medio_de_pago = 'credito'implicacliente_id NOT NULL. No hay a quién cobrarle una deuda sin dueño.CHECK: los tres campos de anulación van juntos o los tres nulos.REVOKE UPDATE, DELETEsobreventasylineas_de_venta: una venta confirmada no se edita (RN-VEN-02). Anular cambia el estado, así queventasconserva unUPDATEacotado — se resuelve con un trigger que solo permite la transición aanulada, igual que el append-only deaudit_log.
Task 2 — El cálculo del precio
Sección titulada «Task 2 — El cálculo del precio»Función pura. precio_final = max(precio_lista − descuento, precio_minimo).
Devuelve además si el código se aplicó parcialmente, que es lo que la pantalla necesita para avisar sin rechazar la venta.
Task 3 — La transacción de la venta
Sección titulada «Task 3 — La transacción de la venta»La compuerta. Cuatro escritos o ninguno, con el descuento de stock atómico de M2
—UPDATE … WHERE cantidad_disponible >= :n— como única fuente de verdad sobre
si alcanzaba.
Sin reserva de stock: la spec explica por qué.
Task 4 — Anulación
Sección titulada «Task 4 — Anulación»Revierte al mismo lote, no a stock genérico: el lote tiene su vencimiento y devolver a otro lo falsearía.
Alcance propio para pos y seller, todo para admin. Comentario
obligatorio para todos.
Task 5 — Crédito
Sección titulada «Task 5 — Crédito»El chequeo compuesto de RN-VEN-05: habilitado y verificado y (sin tope
o saldo + venta ≤ tope).
Task 6 — Cobros y saldo derivado
Sección titulada «Task 6 — Cobros y saldo derivado»deuda = ventas a crédito − cobros. Derivado, no materializado: no hay
descuento atómico que defender y dos cobros suman en vez de competir.
Task 7 — Devoluciones
Sección titulada «Task 7 — Devoluciones»Otro flujo que la anulación (RN-VEN-10): no cancela la venta, ajusta
inventario. sano vuelve al lote, vencido va a descarte.
Task 8 — Códigos de descuento
Sección titulada «Task 8 — Códigos de descuento»Alta y baja por admin, vigencia, tope de usos. El contador de usos sube en la
misma transacción de la venta.
Task 9 — Endpoints · Task 10 — Pantalla del mostrador · Task 11 — La deuda en la ficha
Sección titulada «Task 9 — Endpoints · Task 10 — Pantalla del mostrador · Task 11 — La deuda en la ficha»Task 12 — Bruno, documentación y cierre
Sección titulada «Task 12 — Bruno, documentación y cierre»Colección 9-Ventas (y Sesion pasa a 10-).
Definition of Done de M6
Sección titulada «Definition of Done de M6»La de la spec, más:
- Los tests de M1–M5 siguen en verde sin haberlos tocado.
- Forzar el fallo de cualquiera de los cuatro escritos no deja rastro de los otros.
- Quitar el
CHECKdel piso pone un test en rojo.
