Built for my own use · installable web app
Mango
A private app I built for my own finances, bringing my accounts, spending, and net-worth history into one place, with Mercado Pago and Brubank integrations and Veronica, a connected AI assistant.
React PWA NestJS PostgreSQL Prisma Supabase Auth Multi-currency GPT Actions
My money was spread across bank accounts and digital wallets. I wanted to know where it was, what I was spending, and how my total net worth had changed over time, without piecing everything together from separate platforms. I couldn't find an existing app that matched my needs and preferences, so I designed and built my own.
A demo with a test account is being developed for this portfolio.
A Tool Built Around My Own Needs
I built Mango around the way I manage my own money: one place for my accounts, expenses, investments, and financial history, with an AI assistant that can use that information directly. That gives me a complete picture without gathering data from each bank or wallet whenever I have a question.
I am its intended user, and I have no plans to release it to the general public. It has sign-in and a database designed to support multiple users, but the product follows my own requirements. Owning the code means I can change how it works, add an integration when I start using another institution, and adapt the app as my needs evolve.
One View Across My Accounts
I wanted to follow my finances across all my accounts, wherever the money was held. Mango connects individual transactions to account balances and reports, so I can move from a total to the history behind it.
Balances and Net Worth Over Time
See where my money is held, review balances across accounts and currencies, and track my total net worth at different points in time. Currency conversions retain the valuation information used to calculate the result.
A Traceable Transaction History
Record and review income, expenses, transfers, debt payments, balance adjustments, and grouped card payments across currencies. Categories, merchants, and tags help me find transactions and understand how I spent my money.
Planning and Reminders
Keep upcoming payments, recurring transactions, subscriptions, budgets, and savings goals alongside the same account history. I can postpone, skip, complete, or record due items and receive browser push reminders.
Investment Context
Investment records include assets, prices, purchases and sales, dividends, broker cash movements, holdings, valuation, and performance, adding context to the picture of my finances.
Mercado Pago and Brubank Integrations
Mango integrates with the Mercado Pago digital wallet and the bank Brubank to bring their transactions into my account history. These integrations save me the repetitive work of recording each transaction manually and make keeping the app up to date more practical.
I can also enter transactions directly in the app or ask Veronica, my connected AI assistant, to record them. Each route contributes to the same financial history and reports.
A Real Use Case: Understanding a Trip's Cost
On a recent trip, I attached the same trip tag to every related transaction. Tags and categories serve different purposes: the category describes the kind of expense, while a tag can connect transactions to a trip or another context. Each transaction can have as many tags as I need, while keeping its category.
After the trip, I filtered by that tag to see the total amount I had spent and break it down by my existing expense categories. The same records supported both views: the cost of the trip as a whole and the kinds of expenses that made up that total.
Veronica: An Assistant with Financial Context
I wanted an assistant that could access my full financial history without asking me to collect information from different platforms first. Veronica is a GPT connected to Mango that can query my accounts, transactions, net worth, and investment information, then use that context to answer my questions.
Record and Maintain Transactions
Veronica can record transactions and modify or delete existing ones. For corrections and deletions, I review and confirm the exact change before it is applied.
Explore Patterns and Ask Questions
Veronica can run complex queries, explore spending trends, detect patterns, and offer savings recommendations grounded in my records. I can ask questions about where my money is, how my spending has changed, or what contributes to my net worth.
Veronica accesses the same backend as the app. Its queries and changes use the same financial rules, so a conversation stays connected to the records I see on screen.
Keeping the Numbers Consistent
Balances are calculated from opening balances and account movements, and reports use that same history. I store amounts as whole numbers in the currency's smallest unit, such as cents, to avoid floating-point rounding errors. Transactions retain their original currency, and converted totals keep the valuation information behind the calculation. Imports, manual edits, and assistant requests all pass through server-side validation.
What I Built
I designed and implemented the frontend and backend, the account and transaction model, financial calculations, reports, imports, planning flows, and Veronica's integration. The installable React app works on phones and desktop. It connects to a NestJS backend, with PostgreSQL for records, Prisma for database access, and Supabase for sign-in and authorization.
Mango architecture: the phone and desktop web app and Veronica, a connected GPT assistant, use a NestJS API, which reads and writes PostgreSQL through Prisma. Supabase provides sign-in and authorization.
App and Veronica
An installable React + Vite web app and my connected GPT assistant.
The API
NestJS checks identity and access permissions and applies the financial rules.
The records
PostgreSQL + Prisma for accounts, transactions, reports, plans, and investment data.
Veronica connects through GPT Actions and OAuth, which let me authorize its access to Mango's API. The server checks identity, data ownership, and permissions.
Notifications Microservice
A shared backend service for internal applications to send notifications, keep an inbox history, and deliver live updates to connected users. I proposed, designed, and built the service.
NestJS PostgreSQL AWS Lambda SQS WebSockets Reusable Microservice System Design
Company project · Confidential
Developed at Lotería de Córdoba. Source code, screenshots, and recordings cannot be shared due to confidentiality.
The Problem
At Lotería de Córdoba, several internal applications needed to alert users about new events. Some alerts needed a lasting place in an inbox; others only mattered while the user was connected. I proposed one shared service so each application could request the delivery it needed through a consistent interface.
My Role
I designed and implemented the backend end to end: the data model, request processing, live delivery, and the integration contract for other applications. I prepared and tested the AWS infrastructure in my personal account and documented the deployment plan. The company's external DevOps team handled production rollout and account-level configuration using that plan.
How a Notification Reaches a User
The application requesting a notification is the producer. It provides the content, intended recipients, and whether the notification should be stored, delivered live, or both.
- The producer submits the request directly or places it on a queue for background processing.
- The service validates the content, source application, and recipients.
- If storage is requested, it saves the notification and each recipient's state in PostgreSQL for later inbox access.
- If live delivery is requested, it finds active connections and sends the event to the connected users through WebSocket.
- For queued requests, failed messages can be isolated through a separate failure queue without failing the whole batch.
If a user is offline, a stored notification remains available when they open their inbox later. A live-only event does not create that inbox history. This distinction lets applications choose the behavior appropriate to each event.
Architecture
Amazon SQS holds queued requests, and AWS Lambda functions process them. PostgreSQL stores notifications, recipient state, and active connections. API Gateway WebSocket maintains the connections used to push live events, while the NestJS REST API lets applications retrieve stored notifications and inbox data.
Diagram source (Mermaid)
flowchart LR
subgraph APIs["Producer APIs"]
direction TB
APIA[Producer API 1]
APIB[Producer API 2]
APIC[Producer API 3]
end
subgraph NS["Notifications Microservice"]
direction LR
SQS[SQS Queue]
DLQ[Dead Letter Queue]
LambdaConsumer["Lambda SQS Consumer"]
LambdaConnect["Lambda on Connect"]
LambdaDisconnect["Lambda on Disconnect"]
DB[(PostgreSQL)]
REST["REST API (NestJS)"]
WSG["API Gateway WebSocket"]
end
subgraph RA["Frontend Applications"]
direction TB
React[React Apps]
end
APIA --> SQS
APIB --> SQS
APIC --> SQS
SQS -->|trigger| LambdaConsumer
LambdaConsumer -->|failed messages| DLQ
LambdaConsumer <-->|read/write| DB
LambdaConsumer -->|"real-time notification push"| WSG
WSG -->|$connect| LambdaConnect
WSG -->|$disconnect| LambdaDisconnect
LambdaConnect -->|syncs connection state| DB
LambdaDisconnect -->|syncs connection state| DB
REST <-->|read/write| DB
WSG <-->|$connect / $disconnect| React
WSG <-->|real-time notification push| React
React <-->|HTTP requests| REST
Design Decisions and Tradeoffs
Direct Requests or Background Processing
Applications can use a direct request-response path or submit queued work. The shared validation contract keeps these entry points consistent while allowing different processing needs.
Storage and Live Delivery Are Independent
Saving an inbox item and delivering an immediate event serve different purposes. Keeping them independent supports lasting notifications, temporary live events, and requests that need both.
Track Who Is Connected
Connection and disconnection handlers update the active-client records used for live delivery. This gives the send path an explicit record of which connections to target.
Stack
NestJS PostgreSQL AWS Lambda Amazon SQS API Gateway WebSocket REST API React clients Asynchronous processing Reusable microservice design
Messaging Microservice
A shared backend service I proposed and built for internal applications to offer email-like inboxes and chat conversations, with independent read status and message organization for each participant.
NestJS PostgreSQL TypeORM Messaging Reusable Microservice System Design
At Lotería de Córdoba, different internal applications needed a way for users to exchange messages. I proposed and built one reusable service as the sole engineer, covering the data model, conversation rules, API, and integration design. Each application could then build its own interface on top of the same messaging foundation.
Company project · Confidential
Developed at Lotería de Córdoba. Source code, screenshots, and recordings cannot be shared due to confidentiality.
One Service for Inboxes and Chat
An internal mailbox needs subjects, sent messages, and folders. A chat needs an ongoing conversation between participants. Both need reliable message storage, read status, and rules for who sees each message. I designed a shared backend that supports both interaction styles, keeping those rules consistent across applications.
From Sending a Message to Reading It
The application sends message content and recipient information to the service. A conversation, or thread, groups the related messages. The service decides which conversation to use and maintains each participant's view of it.
- Validate the request and identify the recipients, including the individual recipients represented by a group.
- Create or reuse a conversation according to the application's messaging mode, then save the message and its recipient records.
- Track read status separately for each recipient, so one person reading a message does not mark it as read for everyone else.
- Let each participant organize the conversation independently using favorites, archive, trash, restore, or deletion from their own view.
Two Conversation Patterns
Email-like inboxes
Applications can configure whether a message starts a new conversation or reuses an existing one, supporting messages organized around a subject.
Chat conversations
Messages between a pair of participants reuse a stable conversation, keeping their ongoing exchange together.
Independent State for Each Participant
I separated the shared message content from the records belonging to each recipient. This lets the service deliver the same message to several people while tracking their read status individually.
Conversation organization is also stored per participant. Archiving or removing a conversation from one person's view does not archive or remove it for the other participants. Validation rules keep these state changes consistent.
Internal Messaging in Autogestión (Gmail-like)
Autogestión is Lotería de Córdoba's internal employee web platform. I fully developed its mailbox and integrated it with this service. Employees compose messages with a subject and body, find colleagues by name or unique employee number (legajo), and manage sent messages, favorites, archive, and trash. In this application, trashed messages are deleted after 30 days.
The employee platform supplies the mailbox interface and colleague selection; the messaging service stores conversations and maintains each participant's message state.
Explore the Employee Attendance & Self-Service Platform →Reuse Inside Other Workflows
The same conversation model can support comments attached to a business record. For example, an application for employee objectives could give each objective its own conversation between employee and manager. The application would control who may comment on that objective, while the service would handle the messages and read state.
Integration with Notification Delivery
Messages and alerts have separate responsibilities. The messaging service stores the conversation; a notification service tells users there is something new to read. I implemented that integration after successful message storage, so a notification outage does not prevent the message from being saved. Read-status updates also attempt to synchronize the related notification without blocking the message flow.
Notifications Microservice →Architecture and Delivery
The service is a NestJS API backed by PostgreSQL, with TypeORM for database access. Message creation and recipient records are saved together in a transaction. Background cleanup removes expired trash in batches, using locks to coordinate work across running instances.
I owned the engineering design, implementation, and integration contracts. An external DevOps partner carried out production deployment and account-level configuration in the company's AWS environment, following my technical blueprint and rollout guidance.
Stack
NestJS PostgreSQL TypeORM JWT REST API Transactional writes Background jobs Reusable microservice design