Alex HerreraSoftware Developer · Cybersecurity focus
ES
← Back to projects

Professional

Collections assistant | Statements and payments

The client checks a balance, reports a payment or promises a date on WhatsApp. The model interprets; the code decides the action and what it may disclose.

  • AI
  • Full Stack
Organisation
Intercargo Panamá — via Kaizen Apps CRopens in a new tab
Role
Development
Period
2026-07 — Present

Stack

  • TypeScript
  • Node.js
  • MySQL
  • WhatsApp
  • Claude
  • Docker
  • Google Cloud Run

Context

WhatsApp collections for Intercargo’s operation in Panama. The client writes in, checks their statement, reports a payment or promises a date, and all of it happens inside the conversation.

Problem

Collections relied on lists of clients to be contacted one by one. Every payment receipt was reviewed by a person before the balance was up to date.

The client asked for that cycle to be automated. The debtor checks their own balance and attaches their own receipt, and the team steps in only where judgement is needed.

Technical decisions

A collections assistant discloses other people’s balances. If the model decided who sees them, every reply would depend on it never being wrong, and a language model does not give that guarantee.

So the model does one thing: turn a natural-language message into an intent and, where applicable, a date.

Everything else is code: validating identity, querying the account, recording a payment promise and deciding whether a reminder is due. The conversation runs on a state machine that returns actions, and a separate layer translates them into the WhatsApp transport.

The service uses no web framework: it starts a server with node:http and works over nine tables of its own, separate from the rest of the corporate platform. The model provider is swappable, with two implementations behind the same interface.

Architecture

The model occupies one stretch of the path. What goes into it and what comes out are structures the code validates before anything continues.

MessageIntentIdentityActionReply

A phone number is not enough as an identity. Until identity is verified, the conversation discloses nothing about the account.

The model returns an intent, not a command. The available actions are a closed set defined in code, so an intent with no matching action executes nothing.

Working days, holidays and the contact window are computed on the server. Anyone who asks to stop receiving messages stops receiving them. The reminder goes out one working day before the due date.

Every turn stores the message received, the intent resolved and the action executed. An unexpected reply can be reconstructed later without consulting the model again.

Identity is resolved before the action: without it, there is no balance to show.

The state machine returns actions and a separate layer translates them into the transport, so the conversation can be run without Meta credentials or a WhatsApp line.

Result

The service is in operation over nine tables of its own, separate from the rest of the platform. One command replays an entire dialogue against the real logic, and that is how the hard cases are tested: a promise with an ambiguous date, a payment reported twice, a client asking to stop receiving messages.

What I learned

That simulator walks the state machine end to end, so it catches a flow regression without locating it: when it fails, it points at the whole dialogue rather than the module. Tests over the action layer are the next step.