Cuenta
Her paper invoice, come to life: every screen, behavior, state and check.
The journey
Judge path: Count, Deliver, Invoice and Ledger on the deployed URL with mock data, under three minutes, using Corazon Cocina.
Click a screen in the diagram, or use the tabs below, to see its behavior, states and checks.
PIN Gate
- The La Condesa wordmark, one six-digit field, one button. On a phone the field opens the numeric keypad.
- Wrong: "That's not it." in serif under the field.
- Right: remembered on that device for 30 days, then lands on Count.
Attempt limiting
- 5 wrong attempts from one IP within 15 minutes lock that IP for 15 minutes.
- 20 wrong attempts from anywhere within 15 minutes lock the PIN screen for everyone for 15 minutes.
- While locked, every attempt (even the right PIN) is refused with "Too many tries. Wait 15 minutes." in the same quiet serif line.
- Every failed attempt is logged with its IP and time, never the digits tried.
Checks
- A wrong PIN shows "That's not it." and stays on the PIN screen.
- Six wrong attempts in a row: the sixth is refused with "Too many tries. Wait 15 minutes."
- The right PIN during a lockout is still refused; after 15 minutes it works again.
- The attempted PIN never appears in any log.
- Reopening on the same device within 30 days goes straight to Count.
- No screen or data request works without a valid PIN session.
Count
- Cafe picker, preselected to the last cafe she counted. One cafe at a time.
- Rows: every product on the menu (the same for every cafe), each with the product name, a stepper (− number +), and in grey under the name "last counted Oct 9: 3".
- Save is pinned to the bottom. After saving, the grey lines update to today, the steppers clear, and "Saved" shows for 2 seconds.
- Past counts (added in 5-build; she keeps a paper book of old counts today): one link under the cafe picker opens a plain read-only list for the selected cafe, newest day first, each day's date with the product and number for each row saved that day. No editing, no charts. A cafe never counted shows "No counts yet."
States
- All rows blank: Save is disabled.
- Unsaved entries and she switches cafe: a one-time prompt.
- Unsaved entries and she leaves via the bottom nav: the same prompt.
- Never counted: "not counted yet".
Checks
- Picking any cafe shows every menu product with blank steppers and that cafe's grey last-count line.
- Entering two numbers and saving updates only those two grey lines to today; the other rows keep their old dates.
- Save is disabled when every row is blank.
- Switching cafes with unsaved entries prompts once.
- Saving a count changes no invoice, and creating an invoice changes no count.
- After two saves on different days, Past counts shows both dates, newest first, with the right numbers under each.
Deliver
- Cafe picker · Date (the invoice date and delivery date, default today) · Invoice # (pre-filled suggestion).
- One line per product on the menu, the same for every cafe: name, price, a blank qty field, a live line total, and the grey shelf count (reference only, never pre-fills).
- A running total in integer cents. Create invoice is disabled until some qty is above 0.
| Code | Account | Oct 17, 2026 |
|---|---|---|
| 1 | Corazon Cocina (SB) | 110172026 |
| 2 | Little Heart Cafecito | 210172026 |
| 3 | Alma | 310172026 |
| 4 | Corazon SUR (Carpinteria) | 410172026 |
- The suggestion follows the date and cafe until she types her own number; then it's left alone.
- A duplicate shows "Already used on Oct 3 for Corazon SB" and won't save.
- A same-day repeat gets a suffix she types herself (
110172026-2). The app never invents one.
Prices, special orders & snapshots
- The unit price on each line is editable, pre-filled from the product or the saved copy. It changes only that invoice.
- "+ Add special order" adds a line with a description, price and qty. It's not linked to a product, prints like any line in the order added, never creates a product, never joins the menu, and never shows in Count. The description is required, the price is 0 or more, and the qty is at least 1. Built in Block 4 if the menu lines are done, otherwise Block 6; either way it ships before Handoff.
- Every invoice saves a copy of each line's description, unit price and quantity, and of the cafe's name, address, phone, email and logo. Totals come from the lines. Past invoices and Ledger totals never change when a product or cafe changes later, and Edit shows the saved copy, not today's prices.
Edit
- The same screen, loaded with an existing invoice: quantities, add or remove lines, date, number. Totals recompute. Saving returns to the Invoice screen.
- Changing the cafe isn't offered on Edit (decided in slice 7). The cafe shows as text with "To bill a different cafe, void this invoice and create a new one."
- A void invoice can't be edited or emailed. Print and Download PDF still work, so she keeps the record.
Checks
- Corazon Cocina loads the menu with prices and its grey shelf counts; every qty is blank. Every cafe sees the same products and prices.
- Line and invoice totals update immediately and are correct to the cent.
- Corazon SB on Oct 17, 2026 suggests
110172026, which changes with the date or cafe until she types over it. - A duplicate number blocks the save with "Already used on [date] for [cafe]".
- Create invoice is disabled with no quantities.
- Changing a line's unit price changes that invoice only; the product's price is unchanged on the next new delivery.
- A special order line with a description, price and qty prints on the invoice; a missing description, negative price or qty of 0 blocks the save.
- Editing one quantity updates the total on the Invoice screen and in the Ledger.
Invoice
- The Canva form: wordmark, INVOICE, "Invoice for:" block with the logo, Invoice # and Date (printed as
10/17/2026, learner-accepted in 5-build), DESCRIPTION / QTY / PRICE / TOTAL, a dotted rule, INVOICE TOTAL $, the blush contact block, "Order received by:", and the Net 30 line. - Pages (revised Oct 18, decided by Eileen): up to 8 lines per Letter page. Lines 1 to 8 go on page 1 and the rest continue on page 2 (page 3 if ever needed). Every page repeats the wordmark, INVOICE, invoice number, date, "Invoice for:" and cafe name, plus the column headings. Page 1 alone carries the cafe's phone, address and logo. The total, "Order received by", the contact block and the Net 30 line appear once, on the last page. A multi-page invoice shows "Page 1 of 2" under the date; a one-page invoice shows no page number, exactly like the paper form.
- Type: INVOICE in Limelight · body text in a Times-first serif stack, with Tinos next (added in 5-build: metric-compatible with Times New Roman, so the PDF, rendered on a server with no system fonts, breaks lines exactly where her printout does) · the wordmark, footer title and column headers are Canva PNGs (DK Otago's license forbids web use, so the font never enters the repo; the images go in
design/before Block 3). - A grey status line: "Last sent Oct 17, 8:12pm" · "Last sent Oct 17, 8:12pm · last try failed" after a failed later attempt · "Not sent" if no send ever succeeded · or nothing if never emailed.
- Navigation (phone first, added in 5-build): the Invoice screen keeps the bottom nav and has a "← Back to Ledger" link at the top. Every sheet (Email pop-up, confirms, Mark paid) closes with Cancel, a tap outside it, or the phone's back button and returns her to the same screen; Back never jumps to the previous page while a sheet is open.
- Actions: Print (browser dialog, one Letter page per 8 lines, no app chrome; cancelling leaves her on the Invoice screen, state intact) · Download PDF (the same file the email attaches, between Print and Send email; served as an attachment, so it downloads, to the share sheet on iOS or saved on Android, and the app tab never navigates to the PDF) · Send email (opens the Email pop-up; reads Send again after the first successful send) · Edit · Void (one confirm, "Void" stamp, the invoice stays visible, number never reused, no hard delete anywhere).
- A void invoice can't be edited or emailed; Print and Download PDF still work.
Checks
- The printout laid next to the Canva reference matches its layout, fonts, header, footer block, contact details and Net 30 line, with only the delivered lines.
- Corazon invoices show the heart; Little Heart and Alma leave that space empty.
- The printout has no nav, buttons or browser-styled UI. Up to 8 lines fit one Letter page; a 9-line invoice prints, downloads and emails as 2 pages, with the header and page numbers on both and the total and footer on page 2 only.
- Download PDF saves
Invoice-[number].pdf, identical to the email attachment. - At 390px, after Download PDF the Invoice screen is still showing with its buttons.
- Cancel and the phone's back button both close the Email pop-up and leave her on the invoice.
- Void asks once, stamps "Void", and the number stays taken.
Email Invoice
- To: the invoice's saved billing email, filled in when the pop-up opens (no waiting on a second request), editable for this send only (it doesn't change the customer record). She never types the recipient; her only required action is one tap on Send email.
- Subject: "La Condesa Baking Company Invoice 110172026", fixed
- Body: the fixed template with cafe, total and due date, not editable, collapsed to its first two lines with Show full email. The template is edited in code until Handoff.
- "Invoice-110172026.pdf attached"
- Cancel · Send email (black, one tap), pinned to the bottom so they're always visible without scrolling, on phone and laptop. After a send: Send again, with a grey "Last sent Oct 17, 8:12pm" line above it.
- Success: the pop-up closes, "Last sent [date, time]" shows on the invoice in grey, the button reads Send again, and the Ledger row shows the envelope with "Emailed [date]".
- Failure (provider error, network down, or the PDF didn't render): the pop-up stays open with "Couldn't send. The invoice is saved. Try again or print it." plus a Try again button. The invoice shows "Not sent", and she can send it again later. Never a false "Sent".
- No email saved: the To field is empty with the cursor in it, "No email saved for this invoice. Type one to send.", and Send email stays disabled until there's an address.
From invoices@lacondesabakingcompany.com · Reply-To her Gmail · the PDF matches the printed invoice. Until the domain verifies, sends go from Resend's onboarding sender, which only delivers to the account owner's inbox; the sender and Reply-To live in env, so the switch is two values and a redeploy (5-build, slice 8) · the invoice saves before sending · Send again keeps the same number and subject.
Checks
- On camera, Send delivers a real email with
Invoice-[number].pdfattached to the mock cafe's address (Resend's test inbox,delivered@resend.dev). Resend shows it Delivered, and the logged message ID matches it. - A cafe with no email on file shows the message and blocks Send email until one is typed.
- A forced failure shows the failure line and "Not sent", never a "Last sent" line.
- On a 390×640 phone and a laptop, Cancel and Send email are visible without scrolling, even with the full email shown.
- An invoice with no saved email opens with the cursor in To, "No email saved for this invoice. Type one to send.", and Send email disabled.
- No button or label anywhere says "Resend". After a send, the Invoice page, the pop-up and the Ledger say "Last sent …", "Send again" and "Emailed …".
Ledger
- Open: the count of unpaid, non-void invoices
- Outstanding: the sum of unpaid, non-void invoices
- This month: non-void invoices dated this month, paid or not
All + one chip per cafe, defaulting to All · newest delivery date first
Tapping a row (outside its button) opens that invoice.
| State | Looks like |
|---|---|
| Unpaid | number · cafe · delivery date · due (date + 30 days) · total · Unpaid button |
| Overdue | unpaid and past due: due date in blush + "overdue 9 days" + a thin blush left rule, nothing louder |
| Paid | muted grey · "Paid Oct 20" + reference underneath if entered · out of Open/Outstanding, stays in This month if dated this month |
| Void | crossed out (a line through the number and total) · "Void" stamp · out of all stats · can't be marked paid |
| Emailed | the envelope with a grey "Emailed Oct 17" label; hover (laptop) shows the full time, screen readers hear the same words · "Not sent" if no send succeeded and the last one failed |
Mark paid and undo
- Tap Unpaid: a sheet with the paid date (default today), an optional reference, and Mark paid.
- Tap "Paid Oct 20": the same sheet with Mark unpaid and one confirm. The row returns to unpaid and the totals recompute. She can flip it any number of times.
States
- Empty, or a filter with no matches: "No invoices yet."; stats read 0 / $0.00 / $0.00
Checks
- The judge invoice appears at the top as Unpaid, due 30 days later, with all three stats updated.
- Mark paid mutes the row, shows "Paid [date]" with the reference, and updates Open and Outstanding but not This month.
- Mark unpaid restores it and the totals.
- An unpaid invoice dated more than 30 days ago shows the blush due date, "overdue N days" and the left rule.
- A void invoice appears crossed out with "Void" and counts in no stat.
- The Corazon SB chip filters correctly.
Customers
| Product | Price |
|---|---|
| Strawberry Tres Leches Cake | $4.75 |
| Coconut Tres Leches Cake | $4.75 |
| Coconut Rice Pudding | $3.75 |
| Pixie Tangerine/Orange Flan | $4.00 |
| Mexican Wedding Cookies | $3.50 |
- Each cafe: name, location code, phone, address, billing email, terms (Net 30) and logo if any.
- No editing in this version: changes are seed-file edits until Handoff (Block 8).
Checks
- The menu appears once with every product and price, and all four cafes appear with their seeded details and logo where present.
Every screen
- A small "?" in the top corner of every page, the PIN screen included, opens a plain-words page.
- One short section each for Count, Deliver, Invoice page, Email pop-up, Ledger and Customers: what the screen is for and what each button does, using the buttons' exact names.
- No icon without words: every icon comes with words she can read, such as the envelope with "Emailed Oct 17". On a laptop, hovering shows the full detail, and screen readers hear the same words.
- Button names: the send button reads Send email the first time and Send again after a successful send, on the Invoice page, in the Email pop-up, in the Ledger and on the How it works page.
Operational requirements
- One JSON line per server event: time, route, outcome, duration, plus the message ID or error for sends.
- Never logs emails, addresses, phones, invoice bodies, the PIN or env vars. Only the invoice number and cafe name.
- The host's function logs are the viewer.
/health: database status and app version (cold start vs outage).- Every request failure shows a one-line message, never a blank screen, and logs to the console with the route name.
Boundary
Building now
- The six-digit PIN gate with attempt limiting · Count with blank steppers, partial save, the grey last count and a read-only Past counts list · Deliver with the menu, grey shelf counts, live totals in cents, the suggested and unique invoice number, special order lines and Edit · the Invoice matching the Canva form, with seeded logos, Print, Download PDF, Void and the status line · Email with the Email pop-up, a real send with the PDF, success, failure and Send again · the How it works page behind a "?" on every page · the Ledger with stats, filters, overdue, Mark paid and its undo, void rows and the "Emailed [date]" label · read-only Customers · structured logging, /health and client error lines · mock seed data (four accounts, five products) · deployed to a public URL
Deferred to Handoff (Block 8)
- Editing Products and Customers in the app: an admin UI the judge path doesn't need. Seed edits cover it for now.
- The email template editor: one code file for now.
- The full printed/emailed history line, and the "(revised)" / "Edited after sending" wording: a second send is just Send again for now.
- Changing the cafe on an existing invoice: not built (decided in slice 7). Void and recreate covers it.
- Real data seeding, the production PIN, and Eileen's own acceptance run: the project finish line. It happens in the same production deployment and database, after the video and the tagged submission, in the order in
checklist.md > Block 8: Handoff.
Later
- Stripe · multi-user · low-stock alerts · a bake list · logo upload
- Overdue reminders (stub, added in 5-build; not built now): a daily Vercel Cron job finds unpaid, non-void, past-due invoices. Ask-first by default: Eileen gets one email listing them, with a single signed link that sends the reminders in one tap. The reminder reuses the invoice PDF with a short Net 30 body, and the Ledger row shows "Reminded [date]". Fully automatic per cafe is a later toggle.
- Export the Ledger as Excel (.xlsx) (stub, added in 5-build; not built now): respects the cafe filter and a date range. Columns: invoice number, cafe, delivery date, due date, total, status, paid date, reference.
- A separate demo deployment with mock data, only when a second customer wants a look. Until then there's one deployment, and Handoff makes it Eileen's.
- Accounts receivable aging report (stub, added in 5-build; not built now): unpaid invoices grouped by how overdue (current, 1 to 30, 31 to 60, 61 to 90, over 90 days), per cafe and in total. Exportable as PDF or Excel, for Eileen's accountant or for following up on overdue bills.
Never
- Ingredients or recipe costing · accounting or tax · model-generated prices, totals, dates, numbers or email text · inventory decrement · real data in the repo · hard delete · app-invented number suffixes
Build rules for spec & build
One file, one responsibility, and no god files. Every code file starts with a one-sentence Job: comment; if it needs a second sentence, the file gets split. Flag any code file over about 200 lines at each checkpoint.
Open questions
- Cafe switch on Edit. Decided in slice 7: not included. The cafe is locked on Edit; to bill a different cafe, she voids the invoice and creates a new one.