@c80/domain
v1.1.0
Published
Reglas de negocio ESPEJADAS del ecosistema barman (puntos, redondeo de dinero, volumen de receta y vaso, nivel del dispensador, zona horaria, ventana horaria de expendio, mensaje de error, modos del sistema). TypeScript puro, sin Angular ni Nest. Fuente u
Readme
@c80/domain
Reglas de negocio espejadas del ecosistema barman (decision D45, 2026-08-28). Lib TypeScript
pura: sin Angular, sin Nest, cero dependencias de runtime. Solo importa TIPOS de @c80/types.
La regla
Una regla de negocio que mas de un proceso calcula igual vive aca, UNA vez; nunca se copia en un consumidor. Si zerath, vulcan o un front necesitan la misma cuenta (puntos de una recarga, si una receta entra en el vaso, el nivel de un dispensador), la importan de
@c80/domain. Una copia local "para el preview" es exactamente el bug que esta lib mata: preview y acreditacion divergian porque chillbox tenia su propiacalculatePoints, y kyron/midas/chillbox su propioICE_RESERVE_ML.
La fuente de verdad de cada formula es zerath (dueño del negocio): esta lib copia su SEMANTICA exacta y zerath pasa a consumirla (F2 de C-contrato-3). Cambiar una regla = cambiar aca + publicar + propagar a zerath y vulcan (pin exacto) en el mismo lote; las apps del monorepo la toman por path.
Contenido
| Modulo | Exporta | Origen (semantica) |
|---|---|---|
| money.util | roundTo, round2, roundArs (half-up), ceil2 (ceil a 2 dec con colapso del ruido binario) | zerath/common/utils/money.util.ts |
| points.util | calculatePoints(amountArs, pointValue) — CEIL a 2 decimales; lanza si pointValue <= 0 | zerath/common/utils/point-calculation.util.ts |
| recipe-volume.constants | RECIPE_VOLUME = { UNIT_ML: 45, CUP_CAPACITY_ML: 500, ICE_RESERVE_ML: 100 } | box-defaults.constants + serve.constants de zerath |
| recipe-volume.util | countRecipeDoses, calculateRecipeTotalMl(parts, unitMl?), calculateRecipeVolume(parts, unitMl, hasIce), recipeFitsCup(parts, unitMl, cupCapacityMl, hasIce) — regla D7: sum(count*unitMl) + ICE_RESERVE_ML (si hasIce) <= cupCapacityMl | zerath/common/utils/cup-capacity.util.ts |
| slot-level.util | liquidLevelRatio, calculateCurrentMl (lo que zerath persiste), computeSlotLevel (% y alerta de los paneles), LOW_LEVEL_PERCENT, SlotLevel, SlotLevelStatus | zerath/common/utils/level-sensor.util.ts + @c80/panel slot-level.util |
| datetime.constants | APP_TIMEZONE (America/Argentina/Buenos_Aires) | zerath/common/utils/datetime.constants.ts |
| error.util | getErrorMessage(err, fallback?) — Error / string / objeto con message + cause[].description (SDK de MP) | zerath/common/utils/error.util.ts (oleada B) |
| selling-window.util | isWithinSellingWindow(window, at, timeZone?) (cruce de medianoche incluido), nextOpeningAt, formatSellingWindow, isValidSellingWindow/isValidTimeOfDay, SELLING_WINDOW_TIME_PATTERN | regla D50 nueva (1.1.0): zerath la aplica al canjear, los paneles la validan y chillbox la muestra |
| staff-pin.constants | STAFF_PIN = { MIN_LENGTH: 4, MAX_LENGTH: 6, PATTERN } | regla D51 nueva (1.1.0): el PIN del kiosco |
| chargeback.constants | CHARGEBACK_APPEAL = { MAX_MESSAGE_LENGTH: 2000 } | regla D52 nueva (1.1.0): tope del descargo |
| system-mode.constants | SYSTEM_MODES (array tipado contra el union SystemMode del contrato: sumar un modo sin tocarlo no compila) + isSystemMode | literal validModes de websocket.gateway.ts de zerath |
Que NO va aca: tipos del cable (eso es @c80/types), infra Angular (@c80/core), logica que solo
un proceso ejecuta (queda en ese repo).
Consumidores
- zerath-back y vulcan-arm: paquete npm con pin EXACTO (
"@c80/domain": "1.1.0"), junto con@c80/types.peerDependenciesdeclara@c80/types(>=3.1.0 <4.0.0desde 1.1.0: consume el unionSystemModey el tipoSellingWindow, que entro en 3.1.0). - apps y libs del monorepo: por path (
tsconfig.base.json), sin version. Boundaries: tagscope:domainsolo depende descope:contract;scope:panelyscope:apppueden usarla;scope:shared(ui/core/avatar) NO (son genericas, cero acoples con el barman).
import { calculatePoints, recipeFitsCup, RECIPE_VOLUME, APP_TIMEZONE, isWithinSellingWindow, STAFF_PIN } from '@c80/domain';Build dual CJS/ESM y release
Mismo mecanismo que @c80/types (scripts/build-dual-lib.js domain): dist/libs/domain/esm/
(ESM, stub {"type":"module"}) + dist/libs/domain/cjs/ (CommonJS para NestJS). El @c80/types
que ve el build es el .d.ts de dist/libs/types (el build de domain depende del de types:
dependsOn: ["^build"]); el import es solo de tipos, no hay require en runtime.
pnpm build:domain # arma dist/libs/domain
npx nx release --projects=domain --dry-run --skip-publish # previsualizar
pnpm release:domain # gate + version + CHANGELOG + tag [email protected] + publishEl bump lo deciden los conventional commits desde el ultimo tag [email protected] (feat = minor, fix =
patch, BREAKING CHANGE/! = major). Antes de publicar, validar el runtime del dist: require e
import deben cargar y exponer calculatePoints (el gate no lo detecta).
