WhatsApp-native bookkeeping for LATAM micro-businesses, including barbershops, corner stores, and small shops. Send a voice note in Spanish describing sales and expenses, and Cuenta Clara extracts structured transactions, confirms them through interactive WhatsApp cards, and sends a daily P&L summary at 8 pm.
Built for the Vercel Zero to Agent hackathon, ChatSDK Agents track.
- Voice or text - the owner sends a WhatsApp message, such as "I sold 3 haircuts at 200 each"
- AI extraction - Claude Sonnet 4.6 via AI Gateway parses the message into a structured transaction with type, items, total, and confidence
- Confirmation card - Cuenta Clara replies with an interactive card showing the itemized transaction, with Delete and View summary buttons
- Daily close - at 8 pm Tegucigalpa, a cron job sends each user a P&L summary card with sales, expenses, and margin
┌─────────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────┐
│ WhatsApp │───▶│ Kapso │───▶│ Cuenta │───▶│ Neon DB │
│ voice/text │ │ transcribe │ │ extract │ │ persist tx │
└─────────────┘ └────────────┘ └──────────┘ └──────────────┘
│
▼
┌──────────────┐
│ Confirmation │
│ Card │
└──────────────┘
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) + TypeScript |
| Chat | Chat SDK + @luicho/kapso-chat-sdk (WhatsApp via Kapso) |
| AI | AI SDK 6 + Vercel AI Gateway, Claude Sonnet 4.6 |
| Database | Postgres (Supabase) via Drizzle ORM |
| State | Redis (Vercel KV / Upstash) via @chat-adapter/state-redis |
| Cron | Vercel Cron (0 2 * * * = 20:00 Tegucigalpa) |
| Linter | Biome |
| Deploy | Vercel |
app/
├── api/
│ ├── cron/daily-summary/route.ts # 8pm P&L summary cron
│ └── webhooks/whatsapp/route.ts # WhatsApp webhook endpoint
├── globals.css # Landing page styles
├── layout.tsx # Root layout (es-HN)
└── page.tsx # Landing page
lib/
├── db/
│ ├── index.ts # Supabase + Drizzle connection
│ └── schema.ts # users, transactions tables
├── accounting.ts # DB queries, P&L, formatting
├── env.ts # Zod env validation + type augmentation
├── extraction.ts # AI extraction (Zod + Claude)
└── cuenta-clara-bot.ts # Chat SDK bot (Redis state), handlers, cards
drizzle/ # Generated SQL migrations
scripts/
└── seed.ts # Seed demo user
| Input | Handler |
|---|---|
| Voice note | Kapso transcribes → AI extracts → confirmation card |
| Text message | AI extracts → confirmation card |
/resumen or resumen |
Returns today's P&L card |
| Button: Delete | Soft-deletes the transaction |
| Button: View summary | Returns today's P&L card |
- Node.js 20+
- pnpm
- A Neon Postgres database
- Redis (e.g. Vercel KV or Upstash)
- A Kapso account with a connected WhatsApp number
- A Vercel account (for AI Gateway + deploy)
# Install dependencies
pnpm install
# Copy environment variables
cp .env.example .env.local
# Fill in DATABASE_URL, KV_URL, KAPSO_API_KEY, KAPSO_PHONE_NUMBER_ID, etc.
# Generate and run database migration
pnpm db:generate
pnpm db:migrate
# Seed demo user
pnpm db:seed
# Start dev server
pnpm dev| Script | Description |
|---|---|
pnpm dev |
Start Next.js dev server |
pnpm build |
Production build |
pnpm typecheck |
Run TypeScript type checking |
pnpm lint |
Run Biome linter |
pnpm format |
Auto-format with Biome |
pnpm db:generate |
Generate Drizzle migrations |
pnpm db:migrate |
Run pending migrations |
pnpm db:push |
Push schema directly (dev) |
pnpm db:studio |
Open Drizzle Studio |
pnpm db:seed |
Seed demo user |
generateText+Output.object()with Zod - voice transcripts are messy; Zod.refine()validates item totals match, and a 2-attempt retry loop handles transient failures- Idempotency on
source_message_id- Kapso may redeliver; dedupe at both app-level (check before insert) and DB-level (UNIQUE+onConflictDoNothing) - Confirmation card as the hero - voice in, roughly 1.5s, clean interactive card. That's the demo moment
MIT