← Back to work
0 to 1 Low-code to code Data modelling Nonprofit

The people doing the hardest job had the worst tools.

Bicho Feliz runs on volunteers, a spreadsheet and a WhatsApp group. I know how bad the spreadsheet was because I helped build it. Most of the people filling it in do so from a phone, in the dead minutes of the day, with an animal in the other hand. So I rebuilt it into something they could actually use, alone and for free.

Role: Designer and builder For: Bicho Feliz Scale: 92 animals, 23 volunteers Built with: FlutterFlow, then code on Supabase
The short version

I started as a foster carer in Buenos Aires. Eight cats came through my flat in a year. When I moved to Europe I wanted to keep helping and did not know how from here, so I took on the post-adoption follow-ups and then whatever else I could do from the design side.

Which means I used that spreadsheet for years and helped make it worse: more columns, more tabs, more rules, until nothing else fitted. I was one of the users of the thing I ended up replacing, and knowing it from the inside did not stop it from surprising me.

My role

All of it, and that is the point of the case. I ran the research, chose the stack, structured the database, made every interface decision, and built and shipped the app. There was no handoff because there was nobody to hand off to. What I wanted to find out was how far a designer gets on her own now, with no budget and no engineer.

The problem

Most of the people filling it in were doing it from a phone

Foster carers, adoption coordinators, the volunteer who checks the paperwork is complete, the ones who publish and photograph, the people who drive an animal to the vet. Several of them do not have a computer, or do not use one for this. They fill things in from a phone, in the dead minutes of the day, usually with an animal in the other hand.

This is what they were being asked to fill in, on that phone, sideways:

NOMBRENOMBRE POR ADOPTANTEESPECIEFECHA INGRESOTIPO/COLORTAMAÑOSEXOEDAD AL INGRESAREDAD ACTUALCASTRACIÓNTRANSITANTEMAIL DE CONTACTOCÓMO Y DÓNDE APARECIÓCARÁCTERCARÁCTER 2ESTADOFECHA DE ADOPCIÓNTEAM FOTOACTUALIZACIÓN FOTOSV. TRIPLE / SÉXTUPLEV. ANTIRRÁBICADESPARASITACIÓNDESPULGUETRATAMIENTOCOORDINADORAREVISADO SEGUIMIENTOTIPO DE TRÁNSITOADOPTANTEDNI ADOPTANTEMAIL ADOPTANTEDIRECCIÓNTELÉFONO

Thirty-two columns in the animal sheet alone, and it was not the only sheet. Asking someone holding a cat to find the right one is asking them not to bother.

  • The same animal had three different names depending on which tab you were reading.
  • When an animal died in foster the row was usually deleted, so the organization lost its own history one line at a time.
  • Treatment lived in one cell everybody appended to, until the latest news was buried in the middle of it.
  • Every question ended up back in the WhatsApp group, which is where information goes to be answered once and then lost.
Why a spreadsheet stopped being enough

It still works, and that was never the argument. The argument is that Bicho Feliz is a registered organization now and has to be able to behave like one: not lose its own history, not depend on somebody remembering, and not carry a mistake in a cell that nobody catches for a month.

"I can't use Drive." "Google Sheets on my phone is impossible." Both true, and both fair. The people doing the hardest job had the worst tools, and every workaround the rest of us built made that worse, not better.

What I built it with

Free, fast, and mine to change

Before any of the design, a week of trying tools against three requirements. Money was the loudest one and not the only one.

Free, and still free later

Every peso goes to an animal, not to software. I ruled out Lovable and Glide for the same reason: both metered what I could do, and a rescue cannot have its records go read-only because a credit ran out.

Fast enough to be useful

Something in their hands this year, not a perfect thing next year. I would rather ship a working app I can improve than an architecture nobody is using.

The database has to be mine

Supabase is real Postgres underneath. I can open it, query it, restructure it and take it somewhere else. The data belongs to the organization, not to a platform.

Choosing the least limiting tool rather than the most capable one is a product decision, not a technical one. It is the same question as any build-or-buy: what happens to these people on the day this stops being free.

Where the design happened

The design work went into the tables

A design file exists so that somebody who is not you can build the thing correctly, and here that person was me, later the same day. So the work went into the tables instead: what an animal is, what a foster placement is, what an enquiry is, and how they hold on to each other. Get that wrong and no amount of screen design saves you.

The Supabase schema: eight related tables covering animals, their health record, foster placements, volunteers, contacts, neighbourhoods, adoption enquiries and completed adoptions, with the relationships drawn between them

Eight tables. Every screen in the app is a question asked of this.

The one I got wrong first

A checkbox cannot say "nobody has answered this yet"

The health record is a long list of yes-or-no questions. Rabies vaccine. Triple vaccine. Neutered. Dewormed. I built them as checkboxes, which is the obvious choice and, once real volunteers started filling in real records, the wrong one.

Default to false

An unticked box means "no". It also means "nobody has looked at this animal yet", and you cannot tell which.

Default to null

The data is honest, but now a volunteer has to tick and untick a box to record an actual "no". Nobody does that.

Which is why the health record has three answers and not two. “No” and “nobody has looked yet” are different facts, and only one of them is a reason to book a vet. The chronic medication note sits above the list rather than inside it, because it is the thing a new foster carer has to know before anything else on that screen.

The health section of a record: a highlighted note about ongoing medication prescribed by a behaviourist, then neutering, rabies vaccine, triple vaccine, deworming and flea treatment, one of them reading no information rather than yes or no

“No information” is its own answer, and it is the one that gets acted on.

One cell called “temperament”

Foster carers were asked to describe what an animal is like to live with. What came back went into a single cell, and it looked like this:

The real spreadsheet: a column called CARACTER holding fourteen numbered questions and their answers inside a single cell for each animal, with more columns continuing off to the right and six tabs along the bottom

One cell per animal, and the columns keep going to the right. The tabs along the bottom are the other sheets.

Fourteen questions, answered carefully, in prose, in one box. Lovely to read and impossible to do anything with. You cannot filter it, count it, or match an animal to a family with it, so the coordinator read every cell by hand every time somebody asked about a cat.

The behaviour form: chips for whether the animal gets on with cats, dogs, children and older people, yes or no questions about being an only animal and suiting first-time families, and a free text field for the detail

Chips for what can be answered, a box for what cannot.

Not everything got split. Breaking a description into fourteen tick boxes would have thrown away the reason the original cell was worth reading: the qualifications. “Gets on with dogs” is not the same as “gets on with dogs, though she is frightened of them for the first week”.

So the form asks for both. Mark only the clear cases. If it depends, tell us underneath. The chips are what the coordinator filters on. The paragraph is what the family actually needs to read, and it is still there.

That is not data hygiene. It is an animal finding a home sooner, which is the only metric anybody here cares about.

How it actually works

One animal, start to finish

Every screen in the app is a moment in this: what the rescue does with or without software, and the part that stopped being somebody's unpaid evening.

What happensWhat the app doesOn screen

Somebody finds an animal

A message arrives: there is a cat in a garage, a dog on a road. Nothing happens until a coordinator finds somebody with room for it, and that part happens between people, in a call or in the group chat, and always will.

The app holds the shape of that conversation, not the conversation. Who has space, who already has three, who fosters cats and not dogs.

A foster carer takes it in

The record is created the moment the animal arrives, from a phone, usually one-handed.

Two things are required: dog or cat, and a name. Everything else can be filled in later by whoever finds out. The photo is taken here, now. It is not the good photo — that one comes later, from the people who take good photos. This one exists on day one, and an animal with no photo does not get adopted.

The add animal form: name, a photo taken with the phone, species, sex, approximate age and date of intake

It gets looked after

Vaccines, neutering, treatment, weeks of it. The animal moves through states while this happens: in treatment, almost ready, in adoption. Some of them die, and that is a state too.

Treatment used to be one cell everyone appended to, until the latest news was buried in the middle of it. Now it is entries: dated, signed, newest first. Nobody asked for this and nobody expected it, which is why it got the biggest reaction of anything I built. It was never a hard idea. It was a thing a spreadsheet cell cannot do.

The medical history of an animal: dated entries with the name of whoever wrote each one, and a field to add a new note

The questions get answered

Fourteen questions about how the animal actually is to live with. This is the gate: until they are answered, the volunteer who publishes cannot offer the animal to anybody.

So every section counts what it is still missing and says why it matters. The behaviour one reads: there is no information yet, and it is what helps most in finding a family.

An animal record with collapsed sections for data, fostering, behaviour and health, each showing how many fields are still missing

It gets offered

Published on social media, or offered to somebody who wrote in asking for “a cat”. Then a first screening: do they meet the requirements, do they have other animals, have they asked before.

Behind all of it sit the volunteers making sure each step happened. Their card names the eleven fields that are missing rather than saying “incomplete”, and copies them into a message. “Something is missing” is work for her. A named list is work for the person who can fix it.

A management card: the latest medical note, the date of the last review, a count of eleven missing fields, the list of which ones, and a button to copy the list

They talk, and it happens or it does not

The interested person and the foster carer are put in touch. They meet, they arrange it, and either it goes ahead or it does not. Neither outcome is a failure of the process.

Because people are one record now, the app can say this person already fosters, or already adopted, or asked about a different animal last month. If it goes ahead, the adopter, the new name and the contract are recorded. If it does not, that is recorded too, which is the part the spreadsheet forgot.

Ninety days later

An email asks the family how it is going. Whatever comes back gets written down: what was still pending, whether it was done, how the animal settled.

It goes out on its own, grouped by household, worded for the case. I still read and answer every reply by hand. What I wanted automated was the remembering, not the caring.

Two things I did not automate

Drive stayed a link. Those folders hold things like a photo of an adopter's ID, and wiring that in before researching where that data ends up was not a trade worth making. The folder does get created and moved on its own now, which killed a checkbox that used to lie in both directions.

The WhatsApp message is copied, not sent. My plan was to message foster carers one to one from the tracking view. The coordinator told me she would rather paste it into the group, where the rest of the conversation already lives. So the app writes it and she pastes it. Her way is better and it costs me a feature.

What I was actually fixing

Turning invisible failures into visible ones

An animal with two names

Families almost always rename the animal, and from then on there are two names for the same cat: the one the family uses, and the one we called it for the eight months it was in foster. Ask a volunteer about Django and she knows exactly who you mean. Write to the family about Django and they have no idea who that is.

So the adoption flow asks for the new one and the record keeps both. My view leads with the old name, because that is how I find it. The follow-up email uses the new one, because that is who they live with. Same animal, two true names, and the app is the only place they are attached to each other.

Hiding a button only hides the button

Every action in the system is a URL. Hiding a button does not stop anyone from sending the request anyway, and an interface that only pretends to enforce something is the quietest failure of all.

I audited the 23 actions and found several with no check on the server at all. Then I closed access at the database level, which was the real hole: anyone with a session could read and write everything by going straight to the API, including 55 documents and 61 home addresses of adopting families.

The modal for completing an adoption: it congratulates the volunteer, asks who is taking the animal home, and offers a checkbox for confirming the address with a note explaining that nothing needs to be stored

The moment an adoption is confirmed, and the line underneath the checkbox.

The same principle decided what the app keeps. The rescue asks an adopter for an ID and a utility bill so somebody can confirm where the animal is going. The useful thing is not the document. It is that a named person checked it, on a date.

So the checkbox says so out loud: "no hace falta guardar nada: queda registrado que lo verificaste vos." Nothing needs storing. It is recorded that you were the one who checked. What you never collect cannot leak, and that is a design decision long before it is a security one.

Who decides what

The hardest part was deciding who is allowed to do what

In the spreadsheet everyone could edit everything, which is not a permission model, it is the absence of one. Deciding what each person should be able to see and change was the longest and most delicate part of the project, and the one I would have got badly wrong on my own.

Two people broke it. One fosters, coordinates fostering and runs the social media, which are three unrelated things. Another fosters nothing at all and only does the post-adoption follow-up. Any model with two axes had to call one of them an exception, and a model that calls a real person an exception is wrong. It ended with three: the area where someone helps in the real world, the task they use the app for, and what they coordinate, if anything. Nine roles.

The distinction that made it work

Editing is describing the animal. Anyone who finds out something true should be able to do it. Changing the state is a decision with consequences outside the app: it opens an adoption, it sends an email, it can block someone from adopting again.

A fact is something you have. A decision is something someone takes.

It took several rounds to land, and none of them came from reasoning about it at a desk. Each one came from a coordinator telling me what somebody in that role actually needs to do on a Tuesday, which is not a thing you can deduce from an org chart.

A volunteer record with personal details obscured: the areas she helps with in the real world, shown separately from the tasks she uses the app for

Two of the three axes, on one person, where you can see they are not the same thing.

The model is only visible in one place in the product, and it is here: what somebody does for the rescue and what somebody does in the app are listed separately, because for most of these people they genuinely are different lists.

Volunteers, adopters and enquiries used to be three separate lists with the same people scattered across them. They are one record now, so the app can say this person already fosters for you, or already adopted, or asked about a different animal last month.

Removing someone is on this screen too, and it says what it does: cutting access deletes nothing. The records, the history and the adoptions stay, and if she comes back she signs in with the same password. People leave and return in an organization like this. A tool that treats leaving as deletion is a tool that punishes them for it.

The hardest screen

Somebody has to record that an animal died

This is the part the spreadsheet handled worst, for an understandable reason: nobody wants to do it. There is a separate sheet for animals that have died and it does not always get filled in. Animals that died in foster tended to have their row deleted. Ones that died after adoption were marked in red, if word got back at all. None of it was anywhere you could ask a question of.

The person doing it is a volunteer who took an animal into their home and looked after it. So the flow is short, and it says thank you before it asks for anything.

A button on the record opens a modal that thanks the volunteer for taking care of the animal by name, asks for the date, and offers, without requiring it, to leave a note so it is remembered. On save the animal's state changes and it comes off the active list on its own. Nobody has to delete anything, and the record stays.

And the animal does not disappear. There is a memorial, which is where the argument of this whole section ends up: the spreadsheet solved death by deleting the row, and the app solves it by keeping the record and giving it somewhere to be. Nothing is lost to save somebody the discomfort of writing it down.

Underneath it is a status change and a timestamp. Before this there was nowhere to record it at all, and leaving something out is a design decision too, just one nobody signs. Designing for humans who work with technology mostly means noticing which fields cost something to fill in.

The whole flow, start to finish. It says thank you before it asks for anything.

Where it stands

Outgrowing the tool I picked on purpose

FlutterFlow got them a working app and then ran out of room. The next things worth doing were things it would not let me do, starting with deciding how each view behaves on a phone. So I worked out how to rebuild it in code, tried it with Claude, and it held. Supabase never moved. Picking a real Postgres at the start is why this was a rewrite of the interface and not a rescue of the records.

Shipped 5

One app replacing the spreadsheet, live on the organization's own domain, used by 23 people across 92 animals

Post-adoption follow-up going out on its own at ninety days, grouped by household

Nine roles, with a script that prints the whole permission matrix so it can be checked

Access closed at the database, not only in the interface

Documentation of the decisions rather than the functions

Next 2

Everything an adoption needs in one place, so the contract comes out of the app

Resources for foster carers: vets, how to tell a kitten's sex, how to introduce a new cat, what the first two weeks look like

Claude writes the code and I direct it and read what comes back. I am not claiming I became an engineer in a year. What I can do is say what I want precisely, notice when it is wrong, and decide what to do about it, which is what this whole project has been about.

What I took from this

Simple is a thing you assume until you build it

What kept surprising me was how often something I had filed as easy turned out to have a shape. A yes-or-no question in a spreadsheet is a yes-or-no question. The same question in an app, with several people answering at different times and someone else depending on the answer, is three states and a rule about who is allowed to leave it empty. I did not find that by thinking harder up front. I found it by building the thing and watching what it did, and that is the argument for building.

The most expensive bugs do not break anything. The ones that break get fixed the same day, because somebody is standing in front of them. The ones that quietly produce a wrong value keep running for months, and by the time anyone notices, the wrong value has already been copied into a decision.

This does not need to be the most modern app anyone has seen. It needs to work on a phone held by someone with an animal in the other hand, and to be better than a spreadsheet. It already is, and it will stay a work in progress for as long as it is useful to somebody.

Product Design 0 to 1 Low-code to code FlutterFlow Supabase AI-assisted development Database Design Data Modelling Product Strategy
Lucila Di Vanni Frick

Hola, I'm Lu. Senior Product Designer, 8+ years across different industries. I start with the real problem, make complexity understandable, and stay close to the outcome.
More about me →