# CURRENT HANDOVER — Myristica (2026-03-30)

This file is the **single source of truth** for session continuity. Read this first, then reference the other `.md` files for deeper detail if needed.

---

## Quick Reference

| Field | Value |
|-------|-------|
| **Project** | Rental property management webapp + WhatsApp bot for Arcos 1836, CABA |
| **Stack** | Next.js 16.2.1, TypeScript, Tailwind CSS 4, Drizzle ORM, Supabase Postgres, NextAuth v5, Mistral AI |
| **Code root** | `C:/Users/ignac/OneDrive/Documents/claude/myristica/app/` |
| **Repo** | https://github.com/ignaciolagosruiz/myristica (private) |
| **Production** | https://myristica-eight.vercel.app |
| **Login** | `ignacio` / `myristica2026!` (or `eleonora` / `arcos1836`) |
| **Supabase project** | `kszwxklcerptpiacnypn` (us-east-1) |
| **Oracle VM** | `ubuntu@100.87.116.90` (Tailscale), bridge key `/tmp/myristica_bot_key` |
| **Bridge URL** | `https://miopenclaw-vnic.tail9799d2.ts.net:8443` |
| **Bridge API key** | `myristica-evo-2026` |

---

## Architecture

```
WhatsApp Group
     ↕
Baileys 7.0 bridge (Oracle VM :8080, systemd: wa-bridge)
  ~/wa-bridge/index.js
     ↕ HTTP POST (x-api-key header)
Vercel — /api/webhook/whatsapp
     ↕                        ↕
Mistral API              Supabase DB
  mistral-medium-2508       pagos, gastos_servicios, pagos_servicios,
  mistral-ocr-latest        servicios, bot_sessions, contratos,
                            indices_ipc, archivos, configuracion
```

---

## What Was Done Today (2026-03-30)

### WhatsApp Bot Outage — Fixed (commits `e4d9244`, `3624f78`)

**Root cause:** `openclaw-gateway` process (port 18789) was managing the Tailscale Funnel and had taken over port 443, routing it to OpenClaw instead of the bridge. Vercel got `ECONNRESET` on every attempt to call back to the bridge.

**Fix:**
1. Released port 443 from openclaw-gateway: `sudo tailscale funnel --https=443 off`
2. Moved bridge to port **8443** (openclaw-gateway doesn't manage this port): `sudo tailscale funnel --https=8443 --bg http://localhost:8080`
3. Updated `EVOLUTION_API_URL` in Vercel to `https://miopenclaw-vnic.tail9799d2.ts.net:8443`
4. Fixed `sendText` double-read body bug (`res.text()` + `res.json()` on same response)
5. Added `.replace(/\/$/, "")` to `BASE_URL` in `evolution.ts` to strip accidental trailing slash

**Critical infra note:** Do NOT try to reclaim port 443 for the bridge. `openclaw-gateway` will continuously restore the 443 → 18789 rule. Use **port 8443** for the bridge permanently.

**SSH to Oracle:** `ssh -i "C:\Users\ignac\OneDrive\Documents\dev\oracle\id_ed25519" ubuntu@100.87.116.90`

---

## What Was Done (2026-03-26)

### Security Hardening (commits `9846630`, `f9f31ad`)
- **Auth middleware was broken** → renamed `src/proxy.ts` → `src/middleware.ts`, fixed function name
- **No RBAC on API routes** → added `session.user.role !== "admin"` checks to all 12+ write routes
- **Open redirect** in `auth/callback/route.ts` → fixed with URL validation
- **Audit trail spoofing** → removed hardcoded `usuario: "ignacio"` from PagosClient.tsx (4 locations)
- **Security headers** added to `next.config.ts`
- **Input validation** — `isNaN(parseInt(id))` on all `[id]` routes
- Removed dead NextAuth route + unused `next-auth` dependency

### Signout 405 Fix (commit `7682a08`)
- Logout was POSTing to `/api/auth/signout` which redirected as POST to `/login` → 405
- Fixed: `Navbar.tsx` now does client-side `supabase.auth.signOut()` + `router.push("/login")`

### Servicios Payment Sync (commits `840aeaf`, `876cfd6`, `be76264`)
- Created `src/lib/sync-pagos-servicios.ts` — distributes `pagos_servicios` chronologically across `gastos_servicios.montoPagado`
- Integrated into POST and DELETE routes for `/api/admin/pagos-servicios`
- **Excluded inactive services** (Edenor) from distribution; deleted Edenor entirely
- Fixed double-counting (client was computing distribution on top of DB-synced values)
- Fixed payment date tracking (each entry gets the date of the payment that completed it)

### FileUpload Modal (commit `bfd64b5`)
- Replaced clipped inline dropdown with modal dialog via React Portal (`createPortal`)

### Dashboard KPI: Deuda de Servicios (commits `011d5fe`, `d4d55a0`, `c7294d8`)
- New KPI card "Deuda servicios" (amber) with per-service detail modal
- Added to "Total adeudado" banner
- Grid layout changed to `sm:grid-cols-3` (3×2)

### Pagos Data Correction (commit `ba7aac2`)
- Deleted all 12 assumed pagos, inserted correct ones with real payment dates
- Mar 2026 left as impago; Jun 2025+ marked as "parcial" (IPC expected > paid)

### Penalty Calculation Changes (commit `20c9198`)
- **30-day cap REMOVED** — `diasMora` no longer capped at 30
- **Penalty base changed** — now calculated on full `montoEsperadoIpc`, not just the IPC difference
- Formula: `montoEsperadoIpc × 0.003 × diasMora` (diasMora = vencimiento → payment or today)

### Manual Payment Rollback
- Deleted accidental Mar 2026 payment (id 37, $4,500,000) + its historial_cambios entries

---

## Contract & Business Logic

**Contract:** Arcos 1836, CABA — commercial lease
- Period: 01/03/2025 – 28/02/2028
- Base rent: $3,500,000 ARS/month
- IPC adjustment: quarterly (Jun, Sep, Dec, Mar) using INDEC indices
- Penalty: 0.30%/day on full IPC-expected amount, **no cap**
- Verbal agreement: $4,500,000/month from Mar 2026 (keys: `acuerdo_monto`, `acuerdo_desde_periodo` in `configuracion` table)

**Current rent (Mar 2026):** $4,634,413/month (after 4 IPC adjustments)

**Services:**
| Service | Tenant % | Status |
|---------|----------|--------|
| ABL | 33% | Active |
| AYSA | 70% | Active |
| Destapaciones | 100% | Active |
| Edenor | — | **DELETED** (was inactive) |

**Payment distribution:** `syncPagosServicios()` in `src/lib/sync-pagos-servicios.ts` redistributes globally across `gastos_servicios.montoPagado`. Always call after inserting into `pagos_servicios`.

**CRITICAL — contratoId mismatch:**
`contratos.id = 3` in DB, but `pagos` rows have `contrato_id = 1`. Web app queries pagos WITHOUT any contratoId filter. Bot must do the same. DO NOT filter pagos by `contratoId`.

---

## Pagos Data (current, all correct)

| Período | Fecha pago | Esperado (IPC) | Estado |
|---------|-----------|----------------|--------|
| Mar 2025 | 26/03/2025 | $3,500,000 | ok |
| Abr 2025 | 29/04/2025 | $3,500,000 | ok |
| May 2025 | 28/05/2025 | $3,500,000 | ok |
| Jun 2025 | 01/07/2025 | $3,821,102 | parcial |
| Jul 2025 | 04/08/2025 | $3,821,102 | parcial |
| Ago 2025 | 27/08/2025 | $3,821,102 | parcial |
| Sep 2025 | 29/09/2025 | $4,016,200 | parcial |
| Oct 2025 | 28/10/2025 | $4,016,200 | parcial |
| Nov 2025 | 03/12/2025 | $4,016,200 | parcial |
| Dic 2025 | 05/01/2026 | $4,274,285 | parcial |
| Ene 2026 | 03/02/2026 | $4,274,285 | parcial |
| Feb 2026 | 03/03/2026 | $4,274,285 | parcial |
| Mar 2026 | — | $4,634,413 | **impago** |

---

## Known Issues / TODO

1. **Numbered list replies not handled** — When WhatsApp buttons fall back to numbered text lists (1, 2, 3), users replying with "1"/"2"/"3" as plain text are NOT detected. Only native `buttonsResponseMessage`/`listResponseMessage` payloads work. Need to detect plain text "1"/"2"/"3" when `bot_sessions` state is `awaiting_*`.

2. **Cron jobs disabled** — `vercel.json` is empty `{}`. Routes exist at `/api/cron/rent-reminder` and `/api/cron/ipc-reminder` but crons need re-adding.

3. **Luli & Gloria WhatsApp LIDs** — Phone-format entries may not match what Baileys sends. If messages are silently ignored, add `console.log("P:" + senderKey)` before the blocked-sender return, have them message, capture LID from Vercel logs.

4. **No image expiry** — Oracle VM files persist forever. No cleanup.

5. **Next.js middleware deprecation warning** — `src/middleware.ts` still works but shows warning in favor of `proxy.ts`.

6. **Servicios penalties — IMPLEMENTED** — `src/lib/servicios-penalty.ts` calculates penalty per gastos entry. Dashboard shows "Penalidad servicios" KPI. /servicios page shows "Días mora" and "Penalidad" columns. Formula: `corresponde × 0.3% × diasMora` (full paid → stops at fechaPago, partial/unpaid → continues to today).

7. **`servicios/pagos-servicios` UI sync issue** — After deleting a service payment, the page may need a hard refresh to show updated amounts (state may lag behind DB sync).

---

## Critical Gotchas for New Agents

- **Next.js 16.2.1 breaking changes**: `cookies()`/`headers()` are async, `params` is a Promise, `middleware.ts` deprecated. Read `node_modules/next/dist/docs/` first.
- **`.trim()` all `process.env` values** — Vercel CLI adds trailing newlines. Also strip trailing `/` — `EVOLUTION_API_URL` is already protected in code via `.replace(/\/$/, "")`.
- **Bridge URL is port 8443** — `openclaw-gateway` owns port 443 and will reclaim it. `EVOLUTION_API_URL` must be `https://miopenclaw-vnic.tail9799d2.ts.net:8443`.
- **ContratoId mismatch** — `contratos.id=3` but `pagos.contrato_id=1`. Query pagos with NO contratoId filter.
- **WhatsApp bold format** — Convert `**text**` → `*text*` before sending.
- **Edenor is DELETED** — Do not reference it. Active services: ABL (33%), AYSA (70%), Destapaciones (100%).
- **Penalty cap was REMOVED** — There is no 30-day cap anymore.

---

## Key Files (Web App)

| File | Purpose |
|------|---------|
| `src/middleware.ts` | Auth route protection |
| `src/lib/penalties.ts` | `calcularResumen()` — rent debt + penalties |
| `src/lib/servicios-penalty.ts` | `calcularPenalidadServicios()` — service debt penalties |
| `src/lib/ipc.ts` | IPC adjustment calculations |
| `src/lib/sync-pagos-servicios.ts` | Distribute service payments across gastos |
| `src/lib/contract.ts` | Hardcoded contract constants (fallback) |
| `src/lib/config.ts` | `getConfig()`/`setConfig()` — configuracion table |
| `src/app/dashboard/page.tsx` + `DashboardCards.tsx` | Dashboard + KPI cards |
| `src/app/pagos/PagosClient.tsx` | Payment history table + add form |
| `src/app/servicios/ServiciosClient.tsx` | Services view + editable percentages |
| `src/app/api/webhook/whatsapp/route.ts` | Bot webhook entry point |

## Key Files (WhatsApp Bot)

| File | Purpose |
|------|---------|
| `src/lib/bot/evolution.ts` | Bridge API client |
| `src/lib/bot/mistral.ts` | Mistral chat + OCR |
| `src/lib/bot/tools.ts` | Function-calling tool definitions + executors |
| `src/lib/bot/sessions.ts` | bot_sessions CRUD |
| `src/lib/bot/handlers/text.ts` | Text message handler |
| `src/lib/bot/handlers/image.ts` | Image/PDF OCR handler |
| `src/lib/bot/handlers/interactive.ts` | Button/list reply handler |

## Bridge Infrastructure

| Component | Detail |
|-----------|--------|
| Bridge file | `~/wa-bridge/index.js` on Oracle VM |
| Service | `sudo systemctl restart wa-bridge` |
| Media dir | `~/wa-bridge/media/` (public at `/media/<filename>`) |
| Tailscale Funnel | `sudo tailscale funnel --https=8443 --bg http://localhost:8080` (port 443 is taken by openclaw-gateway) |
| Key endpoints | `/status`, `/qr`, `/media/upload`, `/message/sendText/myristica`, `/message/sendButtons/myristica` |
| Bot webhook | `POST /api/webhook/whatsapp` with header `x-api-key: myristica-evo-2026` |
| Allowed senders | Defined in `route.ts` as `ALLOWED_SENDERS` (JIDs including LIDs) |

---

## What's Next

1. **Cron jobs** — re-add rent reminder and IPC adjustment alerts to `vercel.json`
2. **Numbered list replies** — handle plain text "1"/"2"/"3" during bot conversation flow

---

*Updated: 2026-03-30 — WhatsApp bot outage fixed (bridge URL moved to port 8443)*
*Previous session (2026-03-26): see `MiMo_session_2026-03-26.md`*
*For historical context, see `HANDOVER.md` and `HANDOVER_24_MARZO_2026.md`*
