Every amount in the core is a Decimal, and it travels as text, not as a binary float — sale signatures hash Decimal::to_string(), so a SHA-256 stays reproducible across platforms and versions. A malformed amount never becomes a silent zero: the backend has exactly one parser, and it fails loudly.
pub fn total_bs(&self) -> Decimal {
self.cantidades
.iter()
.zip(self.precios_usd.iter())
.zip(self.tasas_bloqueadas.iter())
.fold(Decimal::ZERO, |acc, ((q, p), t)| acc + *q * *p * *t)
}
fn decimal_de(texto: &str) -> Result<Decimal, UIError> {
Decimal::from_str(texto.trim().replace(',', ".").as_str())
.map_err(|_| UIError::new("monto invalido", "Se esperaba un numero decimal"))
}
Settlement sums per line with each line's locked rate, so one ticket can mix rates and still reconcile to the cent. The TypeScript side mirrors it with integer cents (Money.toCents, sum, usdToBsCents), and property tests assert exact sums and ≤1-cent reversibility.