Foodtech
Re-architecting Operational Flow in a Multi-Cash SaaS System
Re-architecting Operational Flow in a Multi-Cash SaaS System
Restructuring cash and shift closing so that a register never has to be reopened after it's closed.


Industry
Foodtech
Headquarters
Buenos Aires, Argentina
Founded
2020
Role
Senior Product Designer
Overview
Cactus is a B2B SaaS platform for restaurants, bars and cafés. It runs the daily operation of a venue: orders, tables, payments, cash control and the financial closure at the end of the day.
Shift & Cash is the module that governs that closure. It decides when a register can be closed, what has to be resolved first, and whether the numbers the venue reports match the numbers the system recorded. It’s the last thing anyone touches before going home, and it’s the part of the product where a design mistake stops being a usability problem and becomes a money problem.
I was the only product designer on the platform. I found the structural issues in the closing flow and led the re-architecture of the logic underneath it, working directly with engineering on the state model.
Overview
Cactus is a B2B SaaS platform for restaurants, bars and cafés. It runs the daily operation of a venue: orders, tables, payments, cash control and the financial closure at the end of the day.
Shift & Cash is the module that governs that closure. It decides when a register can be closed, what has to be resolved first, and whether the numbers the venue reports match the numbers the system recorded. It’s the last thing anyone touches before going home, and it’s the part of the product where a design mistake stops being a usability problem and becomes a money problem.
I was the only product designer on the platform. I found the structural issues in the closing flow and led the re-architecture of the logic underneath it, working directly with engineering on the state model.
Problem identification
Problem identification
It’s one in the morning on a Saturday. The venue is empty, the staff want to leave, and the cashier starts closing the register.
The system stops them: there’s a table with an unpaid order. So they leave the closing flow, go back to the floor view, find the table, charge it, and start the closing again from the beginning. It works, but they’ve lost the thread.
Then shift closing runs the same validation a second time and finds something cash closing didn’t catch. In a venue with more than one register, that’s where it got bad: the pending item belonged to a register that was already closed. To resolve it, someone had to reopen a closed register, complete the payment, and close it again. A financial record that was supposed to be final was being reopened as a matter of routine, at one in the morning, by whoever was still awake.
Underneath that, four things were true at once. Cash closing only validated pending payments, and its answer to finding one was to send the user somewhere else. Shift closing repeated validations cash closing had already run, without a shared hierarchy, so the two could disagree. Billing validation was inconsistent and in some paths missing entirely, which meant a shift could close with unbilled sales inside it. And the system was reactive throughout: it waited for the user to hit a wall, then displayed an alert, instead of checking the conditions before offering the action.
The result was interrupted flows, redundant steps, and closures nobody could fully trust — at the exact hour of the day when everyone has the least patience for any of it.
The core issue was not visual. Every screen in that flow was legible. The problem was that nobody had defined what state the system had to be in before each transition was allowed.
It’s one in the morning on a Saturday. The venue is empty, the staff want to leave, and the cashier starts closing the register.
The system stops them: there’s a table with an unpaid order. So they leave the closing flow, go back to the floor view, find the table, charge it, and start the closing again from the beginning. It works, but they’ve lost the thread.
Then shift closing runs the same validation a second time and finds something cash closing didn’t catch. In a venue with more than one register, that’s where it got bad: the pending item belonged to a register that was already closed. To resolve it, someone had to reopen a closed register, complete the payment, and close it again. A financial record that was supposed to be final was being reopened as a matter of routine, at one in the morning, by whoever was still awake.
Underneath that, four things were true at once. Cash closing only validated pending payments, and its answer to finding one was to send the user somewhere else. Shift closing repeated validations cash closing had already run, without a shared hierarchy, so the two could disagree. Billing validation was inconsistent and in some paths missing entirely, which meant a shift could close with unbilled sales inside it. And the system was reactive throughout: it waited for the user to hit a wall, then displayed an alert, instead of checking the conditions before offering the action.
The result was interrupted flows, redundant steps, and closures nobody could fully trust — at the exact hour of the day when everyone has the least patience for any of it.
The core issue was not visual. Every screen in that flow was legible. The problem was that nobody had defined what state the system had to be in before each transition was allowed.
System Complexity
A reliable closing had to hold several things true at the same time: multiple registers open at once, orders and tables still pending payment, billing state including unbilled sales and failed invoices, configurable reconciliation settings, financial matching between Cactus and external payment systems, and role-based permissions over who can close what.
And on top of all of it, the case that broke everything else: a register closed before the operation around it was actually finished.
None of that is solved by making the screens clearer. It’s solved by defining which states are legal and which transitions between them are allowed.
System Complexity
A reliable closing had to hold several things true at the same time: multiple registers open at once, orders and tables still pending payment, billing state including unbilled sales and failed invoices, configurable reconciliation settings, financial matching between Cactus and external payment systems, and role-based permissions over who can close what.
And on top of all of it, the case that broke everything else: a register closed before the operation around it was actually finished.
None of that is solved by making the screens clearer. It’s solved by defining which states are legal and which transitions between them are allowed.
Validation Hierarchy & State Flow
One rule, decided before any screen was worth drawing: by the time shift closing starts, there must be nothing left to resolve.
Everything else follows from it. If shift closing can’t discover new problems, cash closing has to catch all of them. If cash closing has to catch all of them, it has to be able to resolve them without sending anyone anywhere. And if resolution happens inside the flow, reopening a closed register stops being necessary, because the thing that used to force it can no longer survive that long.
It reads as obvious written down. It wasn’t obvious in the product, because cash closing and shift closing had been built at different times, each solving its own problem correctly.
Validation Hierarchy & State Flow
One rule, decided before any screen was worth drawing: by the time shift closing starts, there must be nothing left to resolve.
Everything else follows from it. If shift closing can’t discover new problems, cash closing has to catch all of them. If cash closing has to catch all of them, it has to be able to resolve them without sending anyone anywhere. And if resolution happens inside the flow, reopening a closed register stops being necessary, because the thing that used to force it can no longer survive that long.
It reads as obvious written down. It wasn’t obvious in the product, because cash closing and shift closing had been built at different times, each solving its own problem correctly.



Strategic Re-Architecture
Strategic
Re-Architecture
Restructuring Cash Closing as a Controlled Validation Flow
Previously, closing a register validated pending payments and nothing else, and when it found one it pushed the user out of the flow. Shift closing then repeated the check, sometimes forcing a closed register to be reopened to resolve it. Two validations, no hierarchy between them, and a circular path connecting them.
Now the system runs the checks first and only then decides what to show.
The decision: bring the resolution into the flow instead of routing the user to it. The cheaper alternative was to keep the redirect and return the user to the exact point they left. I argued against it, because that treats the interruption as a navigation problem. The cashier isn’t lost — they’re holding an unfinished closing in their head while the system asks them to go do something else somewhere else.
Restructuring Cash Closing as a Controlled Validation Flow
Previously, closing a register validated pending payments and nothing else, and when it found one it pushed the user out of the flow. Shift closing then repeated the check, sometimes forcing a closed register to be reopened to resolve it. Two validations, no hierarchy between them, and a circular path connecting them.
Now the system runs the checks first and only then decides what to show.
The decision: bring the resolution into the flow instead of routing the user to it. The cheaper alternative was to keep the redirect and return the user to the exact point they left. I argued against it, because that treats the interruption as a navigation problem. The cashier isn’t lost — they’re holding an unfinished closing in their head while the system asks them to go do something else somewhere else.

Introducing Embedded Express Checkout
When a closing starts, the system validates pending orders and unpaid tables. If it finds any, an Express Checkout opens inside the closing flow. The payment is completed there. Nobody leaves, nobody restarts.
The tradeoff: Express Checkout is a reduced version of the real checkout, so anything outside its scope still hands off to the full one. I accepted a second, narrower payment surface in the product to keep the closing intact — which means two places where a payment can be taken, and both have to stay in sync.
Introducing Embedded Express Checkout
When a closing starts, the system validates pending orders and unpaid tables. If it finds any, an Express Checkout opens inside the closing flow. The payment is completed there. Nobody leaves, nobody restarts.
The tradeoff: Express Checkout is a reduced version of the real checkout, so anything outside its scope still hands off to the full one. I accepted a second, narrower payment surface in the product to keep the closing intact — which means two places where a payment can be taken, and both have to stay in sync.
Billing Validation Layer
Once payments clear, the system checks billing state: unbilled sales, invoices that failed against the tax authority. Failed operations are retried in the background before the user is asked to do anything about them.
The decision: retry silently first, surface only what’s still broken. Most billing failures are transient. Showing all of them to a cashier at closing time would train people to dismiss the warning, and a warning everyone dismisses is worse than no warning, because it still looks like a control.
This layer didn’t exist before. A shift could close with unbilled sales inside it and nothing in the product would say so.
Billing Validation Layer
Once payments clear, the system checks billing state: unbilled sales, invoices that failed against the tax authority. Failed operations are retried in the background before the user is asked to do anything about them.
The decision: retry silently first, surface only what’s still broken. Most billing failures are transient. Showing all of them to a cashier at closing time would train people to dismiss the warning, and a warning everyone dismisses is worse than no warning, because it still looks like a control.
This layer didn’t exist before. A shift could close with unbilled sales inside it and nothing in the product would say so.


Configurable Reconciliation Step
With payments and billing resolved, the closing moves to counting. The cashier enters the totals from the POS terminals and the physical cash in the drawer, and the system compares them against what it recorded, surfacing the difference before the closure is confirmed.
The decision: surface discrepancies, don’t block on them. A register that doesn’t balance still has to close — the venue is shutting either way. What the system owes the business is that the difference is recorded and attributable, not that it’s prevented.
Configurable Reconciliation Step
With payments and billing resolved, the closing moves to counting. The cashier enters the totals from the POS terminals and the physical cash in the drawer, and the system compares them against what it recorded, surfacing the difference before the closure is confirmed.
The decision: surface discrepancies, don’t block on them. A register that doesn’t balance still has to close — the venue is shutting either way. What the system owes the business is that the difference is recorded and attributable, not that it’s prevented.
Clean Multi-Cash Finalization
When the register being closed is the last active one, the system verifies the whole shift is clean: no pending payments anywhere, billing confirmed, every register reconciled.
That check is what makes the original rule real. Shift closing no longer has to reach backwards into closed registers, because the last cash closing already proved there’s nothing back there.
Clean Multi-Cash Finalization
When the register being closed is the last active one, the system verifies the whole shift is clean: no pending payments anywhere, billing confirmed, every register reconciled.
That check is what makes the original rule real. Shift closing no longer has to reach backwards into closed registers, because the last cash closing already proved there’s nothing back there.


Shift Closing Optimization
With everything validated upstream, shift closing became a confirmation rather than an investigation. It runs a structured verification pass with a progress indicator, confirms integrity, and allows optional notes for whoever opens tomorrow.
The visible change is small, and that’s the point: it’s the least interesting screen in the flow now, and it used to be where everything went wrong.
Shift Closing Optimization
With everything validated upstream, shift closing became a confirmation rather than an investigation. It runs a structured verification pass with a progress indicator, confirms integrity, and allows optional notes for whoever opens tomorrow.
The visible change is small, and that’s the point: it’s the least interesting screen in the flow now, and it used to be where everything went wrong.
Engineering
Most of this case happened in conversation, not in Figma.
The validation hierarchy had to be agreed before any screen was worth drawing, because the flow is a consequence of the state model rather than the other way around. The work with engineering covered which states a register and a shift can be in and which transitions between them are legal, where the dependencies run between the two lifecycles, how background retries behave while a cashier is waiting, and how registers stay synchronised when several are open at once.
The most useful thing I produced on this project wasn't a screen. It was writing the state and transition model down in one place, so the team stopped resolving the same ambiguity three different ways in three different implementations.
Engineering
Most of this case happened in conversation, not in Figma.
The validation hierarchy had to be agreed before any screen was worth drawing, because the flow is a consequence of the state model rather than the other way around. The work with engineering covered which states a register and a shift can be in and which transitions between them are legal, where the dependencies run between the two lifecycles, how background retries behave while a cashier is waiting, and how registers stay synchronised when several are open at once.
The most useful thing I produced on this project wasn't a screen. It was writing the state and transition model down in one place, so the team stopped resolving the same ambiguity three different ways in three different implementations.
Engineering
Most of this case happened in conversation, not in Figma.
The validation hierarchy had to be agreed before any screen was worth drawing, because the flow is a consequence of the state model rather than the other way around. The work with engineering covered which states a register and a shift can be in and which transitions between them are legal, where the dependencies run between the two lifecycles, how background retries behave while a cashier is waiting, and how registers stay synchronised when several are open at once.
The most useful thing I produced on this project wasn't a screen. It was writing the state and transition model down in one place, so the team stopped resolving the same ambiguity three different ways in three different implementations.
Impact
Some of this is guaranteed by construction and some of it needs measuring, and it's worth keeping the two apart.
What the system now guarantees: reopening a closed register during shift closing went from a routine workaround to an edge case. Pending payments and failed invoices are caught before reconciliation instead of after it. Every closed register has been reconciled against recorded sales, and where there's a difference, it's on the record and attributable. Shift closing runs a structured validation pass instead of firing reactive alerts.
What I'd instrument to prove it: reopen events per venue per month, which should trend to zero and is the cleanest test of whether the architecture holds; support tickets mentioning shift closure, before and after; time from starting a cash closing to confirming it; and share of shifts closing with unresolved billing, which should be zero by design and is worth measuring precisely because it should be.
Impact
Some of this is guaranteed by construction and some of it needs measuring, and it's worth keeping the two apart.
What the system now guarantees: reopening a closed register during shift closing went from a routine workaround to an edge case. Pending payments and failed invoices are caught before reconciliation instead of after it. Every closed register has been reconciled against recorded sales, and where there's a difference, it's on the record and attributable. Shift closing runs a structured validation pass instead of firing reactive alerts.
What I'd instrument to prove it: reopen events per venue per month, which should trend to zero and is the cleanest test of whether the architecture holds; support tickets mentioning shift closure, before and after; time from starting a cash closing to confirming it; and share of shifts closing with unresolved billing, which should be zero by design and is worth measuring precisely because it should be.
Impact
Some of this is guaranteed by construction and some of it needs measuring, and it's worth keeping the two apart.
What the system now guarantees: reopening a closed register during shift closing went from a routine workaround to an edge case. Pending payments and failed invoices are caught before reconciliation instead of after it. Every closed register has been reconciled against recorded sales, and where there's a difference, it's on the record and attributable. Shift closing runs a structured validation pass instead of firing reactive alerts.
What I'd instrument to prove it: reopen events per venue per month, which should trend to zero and is the cleanest test of whether the architecture holds; support tickets mentioning shift closure, before and after; time from starting a cash closing to confirming it; and share of shifts closing with unresolved billing, which should be zero by design and is worth measuring precisely because it should be.
Key Learnings
Validation hierarchy matters more than validation coverage. The old flow validated almost everything it needed to. It just did it in an order that let two checks contradict each other.
A record that can be reopened isn't a record. The reopening wasn't a workaround around the design — it was the design admitting it couldn't guarantee what it claimed.
Proactive checks are a design decision, not an engineering one. Choosing to verify conditions before offering an action, rather than alerting after someone hits the wall, changes what the interface has to say and when it says it.
In financial software, architecture is the user experience. Nobody using this flow will ever see the state model. They'll feel it as whether the closing works the first time, at one in the morning, when everyone wants to go home.
Key Learnings
Validation hierarchy matters more than validation coverage. The old flow validated almost everything it needed to. It just did it in an order that let two checks contradict each other.
A record that can be reopened isn't a record. The reopening wasn't a workaround around the design — it was the design admitting it couldn't guarantee what it claimed.
Proactive checks are a design decision, not an engineering one. Choosing to verify conditions before offering an action, rather than alerting after someone hits the wall, changes what the interface has to say and when it says it.
In financial software, architecture is the user experience. Nobody using this flow will ever see the state model. They'll feel it as whether the closing works the first time, at one in the morning, when everyone wants to go home.
Key Learnings
Validation hierarchy matters more than validation coverage. The old flow validated almost everything it needed to. It just did it in an order that let two checks contradict each other.
A record that can be reopened isn't a record. The reopening wasn't a workaround around the design — it was the design admitting it couldn't guarantee what it claimed.
Proactive checks are a design decision, not an engineering one. Choosing to verify conditions before offering an action, rather than alerting after someone hits the wall, changes what the interface has to say and when it says it.
In financial software, architecture is the user experience. Nobody using this flow will ever see the state model. They'll feel it as whether the closing works the first time, at one in the morning, when everyone wants to go home.
Beyond Shift & Cash
Beyond Shift & Cash
Beyond Shift & Cash
While the Shift & Cash re-architecture was a critical milestone, my work at Cactus extended across multiple operational modules within the platform.
While the Shift & Cash re-architecture was a critical milestone, my work at Cactus extended across multiple operational modules within the platform.
Let’s build systems that scale.
Let’s build
systems that scale.
Designing operational clarity in high-impact SaaS environments.
Contact me




