# PPTG Dashboard — manual

Generated from `app/manual/manual.json` (schema 1, updated 2026-10-16, build 69f7733). Machine-readable: https://dash.portapottytogo.com/app/manual/manual.json

The office tool of Porta Potty To Go (PPTG): orders, invoices, collections, recurring billing, customers, routes and reports for two markets. GoHighLevel (GHL) is the accounting book (contacts, invoices, payments); Stripe holds the saved cards; the dashboard is the collector (cobrador) and the screen people work in.

- **Sign in:** Google sign-in with a company account. The role (owner, manager, office, finance, field) decides which tiles you see. Preview mode (access key) is NOT a sandbox: it talks to the real server and real GHL / Stripe data — for safe tests, mock the endpoints or use the robot contact only.
- **Views:** Phone view (tiles) everywhere; Office view (left navigation, dense tables, side panel) on desktop. The header's View switch (Auto / Phone / Office) is remembered per user and device; Auto = Office at 1200 px and wider; phones (< 640 px) see no switch.
- **Markets:** West Palm — PPTG (`wpb`, GHL location `6N89N8vbtVWNcj2VpNak`); Orlando — partner (`orl`, GHL location `7Ismp3jGLVSOZ2737Cfo`)

## Screens

### Home (Inicio) {#launcher}

- **Where:** #/home (Office) · the tile grid (Phone)
- **What it is for:** The start page: greeting, today's numbers (today's stops, open quotes, invoices due / overdue = open invoices at or past their due date, won this month = paid invoices issued this month), the Ask or dictate box, and the tiles your role may open.
- **How to get there:** Sign in. The PPTG logo or the back arrow on any screen returns here.
- **What you see:** Number chips (a source that could not be read shows '?'); All / West Palm / Orlando buttons and a 'By market —' line (a market that failed says 'unavailable'); 'Recurring drafts awaiting activation' line when there are any (opens Invoices); Ask or dictate (the Assistant); Tiles: Sales & Billing, Operations, Tools & Reports, and the Manual link
  - **All / West Palm / Orlando** — Re-filters the numbers by market. (read only)
  - **Recurring drafts awaiting activation** — Opens Invoices, where they are activated. (read only)
  - Note: The number chips are not links (in the Office view the Home tiles are).

### New Order (Nueva orden) {#new-order}

- **Where:** tile New Order · Office: #/orders
- **What it is for:** Build an order for a customer and turn it into an invoice (factura) or a quote (cotización): customer, service address, lines from the catalog, delivery date, notes, Job/PO, tax.
- **How to get there:** Tile 'New Order', '+ New' in Office, 'New order' on a customer profile, or a draft's 'Edit draft'.
- **What you see:** 1 Customer: search by phone / name / email, or create one ('Save customer'); Billing market badge (decided by the service address; Override needs a reason); 2 Service address (site address, ZIP prices the zone, optional place name); 'Schedule pickup' and 'Adds to an active site' options; 3 Order: the catalog by category, quantity, price; 'Prices from …' tells how old the prices are; Payment: our pay page by default (saves the card), 'Enter card now', 'Use saved card'; 'Send GHL's link instead' is the secondary path; Send as QUOTE, Preview, Send as INVOICE, Save invoice draft, Save quote draft
  - **Send as INVOICE** — The confirm sheet (lines, tax, total, where it goes); Confirm creates the invoice in GHL, makes our pay page and sends it by email (and text where enabled). It also books the delivery on the Google Calendar (and a pickup when ticked), and for recurring lines drafts an inactive GHL recurring template (or enrolls the site in billing when mixedOrders is on). (contacts the customer, asks first)
  - **Payment: Enter card now** — Creates the invoice and opens our pay page in staff mode to type the card; the screen waits until it is paid. (MONEY, asks first)
  - **Payment: Use saved card** — Creates the invoice and charges the contact's saved card. (MONEY, asks first)
  - **Send as QUOTE** — Creates the estimate (cotización) and sends it by email / text. (contacts the customer, asks first)
  - **Preview** — How the customer will see it. Read only. (read only)
  - **Save invoice draft (Guardar borrador)** — Creates (or updates, when editing a draft) a GHL draft invoice. Nothing is sent. (writes GHL)
  - **Save quote draft** — Creates a GHL estimate as a draft; send it from Quotes. (writes GHL)
  - **Save customer / Save contact** — Creates the GHL contact, or saves its edits from the customer strip. (writes GHL)
  - **Override (market)** — Bills the order in the other market. A reason is required and goes on the invoice. (read only)
  - **Create recurring template (draft) — on the result screen** — Drafts an inactive GHL recurring invoice template for the recurring lines, after an in-app confirm. (writes GHL, asks first)
  - Note: Leaving an order with unsaved lines asks 'Discard this order?' first.
  - Note: The market follows the SERVICE address, not the billing address.
  - Note: A county with no tax configured in that market blocks invoicing (fail loud) — add the tax in GHL first.
  - Note: Tax exempt needs a reason (and the certificate on the contact).
  - Note: 'Prices from … (<age>)' next to 'Refresh catalogue' tells how old the prices are; older than 2 days is flagged (the owner can update them in Admin).
  - Note: Every customer invoice email also goes to the owner as a '[Copy]' (CONFIG.invoiceOwnerCopy; when no GHL contact has that email, CONFIG.ownerContactId). A copy that was not sent is said on the result screen and in a toast.

### Customers (Clientes) {#customers}

- **Where:** tile Customers · #/customers/<market>:<contactId>
- **What it is for:** Find any customer and see everything: contact, sites, invoices, payments, notes (History), recurring billing; edit the contact.
- **How to get there:** Tile 'Customers', or click any customer name anywhere (every name is a link).
- **What you see:** Search, '+ New customer'; The profile: phone, email, card on file, last invoice; Sites (service addresses): '+ Add site', Remove; Invoices with the shared invoice menu; Orders (GHL opportunities); History: invoices, payments, refunds, voids, notes, orders, recurring billing — searchable; add an internal note
  - **+ New customer** — Creates the GHL contact (read back). (writes GHL)
  - **Edit** — Edits the GHL contact (CC emails, place reference, tax exemption); every save is read back. (writes GHL)
  - **+ Add site / Save site** — Saves a service address on the contact's sites (read back). (writes GHL)
  - **Remove (site)** — Removes a saved site; click twice ("Confirm remove?"). (writes GHL, asks first)
  - **New order / New invoice** — Opens New Order with this exact contact. (read only)
  - **Add note** — Writes an internal note on the contact (read back). (writes GHL)
  - **Recurring… (Sites card)** — Opens this customer's recurring templates (active and draft, in that market) with 'Cancel schedule' on each: an in-app confirm ('Keep it' does nothing), then GHL stops making invoices from it — re-read to confirm it shows cancelled, and a 'RECURRING CANCELLED' note goes on the customer's History. Invoices already made stay as they are. There is no Pause (no proxy action yet). (writes GHL, asks first)
  - Note: 'Back' returns to the screen the name was clicked on.

### Collections (Cobranza) {#collections}

- **Where:** tile Collections
- **What it is for:** The past-due queue for both markets, worst first, with the reminder ladder: the screen suggests the next step, a human clicks.
- **How to get there:** Tile 'Collections' (its badge counts past-due invoices in both markets).
- **What you see:** Per-market totals and a market filter; Each invoice: days over, last reminder, the NEXT step; Send reminder (the ladder dialog), Call, Promised to pay, and the shared invoice menu
  - **Send reminder** — Opens the reminder dialog with the ladder's step and a preview; Send emails / texts the customer and records a note. (contacts the customer, asks first)
  - **Promised to pay** — Hides the invoice from the queue until the promised date (recorded as a note). Asks the date in an in-app date box; Cancel records nothing. (writes GHL)
  - Note: Nothing auto-sends: GHL's own reminders are switched off on purpose.
  - Note: A market that failed to load says 'unavailable', never 0.
  - Note: When the invoice has no stored pay link, the reminder dialog sends GHL's own invoice email / text instead.
  - Note: Call is a phone link (tel:).

### Billing (Cobro recurrente) {#billing}

- **Where:** tile Billing
- **What it is for:** The recurring-billing engine (billing.php): which sites are enrolled, what the next cycle charges, holds that need approval, the daily run, and the activity timeline.
- **How to get there:** Tile 'Billing'.
- **What you see:** Recurring charging ON / OFF (the kill switch), last run, enrolled / on hold / charged so far, daily limit; Enrolled sites per market with the per-cycle amount and a market filter; Holds (first cycle, amount changed, over a cap); Waiting for a saved card; Activity timeline by name (raw ids in the tooltip and in 'Show raw log')
  - **Turn charging ON / OFF** — The kill switch of automatic charges. (MONEY, asks first)
  - **Authorize charge (a hold)** — Charges the saved card NOW for that held cycle. (MONEY, asks first)
  - **Preview today** — Shows what today's run would charge. Read only. (read only)
  - **Enroll (waiting for a saved card)** — Enrolls the site in recurring billing once its card is saved. (MONEY, asks first)
  - **Remove (waiting for a saved card) / Unenroll** — Stops future automatic charges for that site (the card stays saved; no money moves). (read only, asks first)
  - Note: The first cycle of every site is always held for approval.
  - Note: Charges stop at the gates (tolerance, jump vs. last cycle, per-charge cap, daily total).
  - Note: The engine sends no receipt of its own: GHL sends it when the payment is recorded.

### Invoices (Facturas) {#payments}

- **Where:** tile Invoices (data-nav payments) · Office: #/invoices
- **What it is for:** Every invoice in both markets: search, filter, sort, and the invoice menu (details, links, send, preview, record payment, refund, edit, void).
- **How to get there:** Tile 'Invoices'.
- **What you see:** Search (number, name, company, email, phone; address and notes once the invoice's Details were opened); Filters: market, past due, status, dates, amount, county, source; 'Recurring drafts awaiting activation' card when there are any; Rows with status, market and the invoice menu
  - **Details** — The invoice as GHL has it. 'Edit line items' (draft, sent, overdue, partially paid: qty, price, name, description, add / remove lines) saves to GHL and, for a sent invoice, asks whether to resend; 'Edit full order' opens a draft in New Order. (writes GHL)
  - **Open payment link / Copy link** — Our pay page link (refreshed when it expired), or GHL's link for invoices made in GHL. (read only)
  - **Send invoice / Send receipt** — The channel dialog (email / text) with a preview; Send contacts the customer. On a draft it first asks: our pay page or GHL's standard email (GHL's is not previewed). (contacts the customer, asks first)
  - **Preview** — How the customer will see it (our templates). Read only. (read only)
  - **Send pay page (saves card)** — Makes our pay page (pays the invoice and saves the card), then the email / text choice. A DRAFT is finalized in GHL first (it becomes SENT) — cancelling the choice leaves it sent, nothing reaches the customer. (contacts the customer, asks first)
  - **Charge saved card** — Charges the card on file for the amount due (only shown when the contact has a card). (MONEY, asks first)
  - **Record payment** — Records a cash / check / Zelle payment in GHL (no card is charged); open invoices only; click twice. (writes GHL, asks first, flag `recordPayments`)
  - **Refund** — Full or partial refund to the card (owner only), also on partially paid invoices; 'Paid outside the card' records the refund only; click twice. A full refund also voids the invoice. (MONEY, asks first, flag `refunds`)
  - **Edit draft** — Opens a draft in New Order, fully editable; 'Save changes' updates the same draft. (writes GHL)
  - **Edit invoice (sent, unpaid)** — Opens a sent / overdue / partially paid invoice in New Order; 'Save changes to invoice #N' updates the SAME invoice (number, dates, status kept; an 'Updated <date>: …' line in its notes and on the customer's History); never below what was already paid. Then 'Resend updated invoice' sends one message with a fresh pay link — the old link stops working. If GHL refuses the update: 'Replace invoice' (new draft, old one voided). (writes GHL, flag `editSentInvoices`)
  - **Void** — Two steps: confirm, then a required reason (saved on the customer). Disabled on paid invoices — refund instead. (writes GHL, asks first)
  - **Send reminder** — A payment reminder with the pay link (email, text where enabled), recorded as a note. (contacts the customer, asks first)
  - **Open customer** — The customer's profile. (read only)
  - **Activate (recurring drafts card)** — Activates a drafted GHL recurring invoice schedule, charged to the customer's card each cycle. (MONEY, asks first)
  - **Recurring… (invoice menu)** — Opens this customer's recurring templates (active and draft, in that market) with 'Cancel schedule' on each: an in-app confirm ('Keep it' does nothing), then GHL stops making invoices from it — re-read to confirm it shows cancelled, and a 'RECURRING CANCELLED' note goes on the customer's History. Invoices already made stay as they are. There is no Pause (no proxy action yet). Shown when the customer has a live template. (writes GHL, asks first)
  - Note: The SAME invoice menu is in Invoices, Collections, Customers, Office and the New Order result screen.
  - Note: The customer-facing title is 'PPTG Order — <customer> · Job/PO <po>'; internal stamps (zone, county) ride a 'Dashboard ref:' line in the terms.
  - Note: Paying through GHL's link does not save the card; our pay page does.
  - Note: Cancel an activated recurring schedule: 'Recurring…' on the customer card or the invoice menu → 'Cancel schedule'. Pause is not in the dashboard (no proxy action) — pause it in GHL.

### Quotes & Follow-Up (Cotizaciones) {#quotes}

- **Where:** tile Quotes & Follow-Up
- **What it is for:** Open estimates (cotizaciones) per market: edit drafts, send them, and turn an accepted quote into an invoice.
- **How to get there:** Tile 'Quotes & Follow-Up'.
- **What you see:** Open quotes per market with status; Row buttons: Edit (drafts), Send to customer / Resend, Create invoice (accepted); '+ New quote' (opens New Order)
  - **Edit → Save draft** — Edits a draft quote in GHL. (writes GHL)
  - **Send to customer / Resend** — Sends the estimate to the customer. (contacts the customer, asks first)
  - **Create invoice (accepted quote)** — Creates the real invoice (due on receipt) with the quote's lines and the county tax, then sends OUR pay page (pays and saves the card; stored on the invoice) by email + text. If the pay page cannot be made it sends GHL's link and says so. One request every 3 s, so it takes about half a minute. (contacts the customer, asks first)
  - **+ New quote** — Opens a blank New Order. (read only)
  - Note: There is no automatic follow-up: a human resends.

### Schedule Pickup (Recoger unidades) {#pickup}

- **Where:** tile Schedule Pickup
- **What it is for:** Request a pickup for an active rental: the request is written on the customer as a note for dispatch. It does NOT book the calendar, stop billing or remove the route stop — dispatch does those.
- **How to get there:** Tile 'Schedule Pickup'.
- **What you see:** Active rentals in both markets; Search by name or phone; 'Request pickup' per customer
  - **Request pickup** — One in-app dialog: the date (or 'ASAP', required) and an optional dispatch note, then 'Save pickup note' writes 'PICKUP REQUESTED — <date> — <note>' on the contact. Cancel saves nothing. It does NOT book the calendar and does NOT stop billing (the dialog says so). (writes GHL)
  - Note: After the request: book the pickup on the calendar, stop the billing ('Recurring…' on the customer card → Cancel schedule, or Billing → Unenroll) and adjust the route.

### Routes (Rutas) {#routes}

- **Where:** tile Routes
- **What it is for:** A read-only live view of today's route from HelloTracks (no HelloTracks login needed): stops by date, address search and a map.
- **How to get there:** Tile 'Routes' (the only tile the field role sees).
- **What you see:** Today's Route: date picker, Today, Refresh; Address Search; Map
  - Note: Target stops per driver and day: about 20-26 on a local route, 15-20 on a far route.

### Calendar (Calendario) {#calendar}

- **Where:** tile Calendar
- **What it is for:** Deliveries, pickups and services on the Google calendars the routing sync reads.
- **How to get there:** Tile 'Calendar'.
- **What you see:** 'Connect Google Calendar' (first time); WPB / ORL switch; Events by calendar and day; Create / edit an event (events the dashboard or you created; others open in Google Calendar)
  - **Connect Google Calendar** — Connects the Google account (OAuth). (read only)
  - **Create event / Save** — Writes the Google calendar right away (the routing bot syncs it the next afternoon). (writes Google Calendar)
  - **Delete (this occurrence / the event)** — Deletes from the Google calendar. (writes Google Calendar, asks first)
  - Note: Same-day changes are manual for the dispatcher; Orlando is not synced by the bot.
  - Note: Only events the dashboard created, or that you created, can be edited here.

### Week Organizer (Organizador semanal) {#week-organizer}

- **Where:** tile Week Organizer
- **What it is for:** A read-only weekly view from HelloTracks: seven day columns with the stop count and up to 25 delivery / pickup / service chips per day.
- **How to get there:** Tile 'Week Organizer'.
- **What you see:** Prev / This week / Next; Seven day columns with a stop count; DEL / PU / SVC chips
  - Note: Moving stops between days or drivers is done in HelloTracks, not here.

### Price Lookup (Precios) {#price}

- **Where:** tile Price Lookup
- **What it is for:** The rate card by ZIP and service (the same catalog New Order uses).
- **How to get there:** Tile 'Price Lookup'.
- **What you see:** A service address with suggestions (the ZIP is taken from it) -> zone; Prices per service for that zone
  - Note: Trailers cost the same in every zone.
  - Note: Price increases apply to new orders only.

### Reports (Reportes) {#reports}

- **Where:** tile Reports
- **What it is for:** Revenue by month: totals by market, zone and status, a separate sales-tax-by-county table, CSV exports.
- **How to get there:** Tile 'Reports'.
- **What you see:** Prev / This month / Next, Refresh; 'By market' lines (WPB / ORL), zone and status totals; 'Sales tax by county' table (taxable, tax, lines) with its own Export CSV; Export CSV of the invoices
  - **Export CSV** — Downloads exactly what the month view shows (market column WPB / ORL). (read only)
  - **Export CSV (tax by county)** — Downloads the county tax table. (read only)
  - Note: What Orlando billed: Reports, pick the month, read the ORL line in 'By market' (or the CSV, market = ORL).
  - Note: Drafts and voided invoices are excluded.
  - Note: A past month reads until the start of THAT month (one request every 3 s, so a far month takes a few seconds). A market that could not be read fully is said in red above the totals ('Orlando: incomplete — …; these totals are LOW') — refresh, or check that market in GHL.

### Assistant (Asistente) {#assistant}

- **Where:** tile Assistant · the Ask or dictate box
- **What it is for:** A read-only beta: ask in words (prices by ZIP, customer lookups, invoice status) or dictate; it answers, it takes no action.
- **How to get there:** Tile 'Assistant' or the Ask or dictate box on Home.
- **What you see:** Chat; Microphone (dictation); With the assistantDrafts flag: draft cards and customer candidates that open in New Order
  - **Open draft in New Order** — Fills New Order with the dictated lines; nothing is sent. (read only, flag `assistantDrafts`)
  - Note: The Assistant never sends or charges anything by itself.
  - Note: With assistantDrafts on, 'Open in New Order' asks 'Discard this order?' first when New Order has unsaved lines; Cancel keeps that order.

### Admin {#admin}

- **Where:** tile Admin (owner)
- **What it is for:** Prices (catalog) per market, config summary and live health checks (owner).
- **How to get there:** Tile 'Admin' (owner only).
- **What you see:** Prices (catalog): each market's last update ('West Palm: N products, M prices · from <time> (<age>)'); 'Update prices now' (owner); Config: host, market, service-worker build; Pricing data summary; Health checks: GHL proxy per market, routes proxy (HelloTracks); Diagnostics (collapsed): billing version and file hashes (click to copy)
  - **Update prices now** — Re-reads every product and price from GHL for West Palm, then Orlando (3 s apart) and says the result per market (✓ / ✗ previous prices stay / ? check). Nothing is sent or charged; New Order reads the fresh prices on its next visit. (read only, asks first)
  - **Re-run checks** — Runs the health checks again. (read only)

### Office view (Vista oficina) {#office}

- **Where:** ?view=office (only while the officeMode flag is on, and for roles with Customers / Invoices / Collections / Billing / New Order) · #/home #/customers #/invoices #/orders #/collections #/billing #/payments
- **What it is for:** The desktop layout of the same dashboard: left navigation, global search, dense Customers / Invoices tables with a side panel, + New, bookmarkable routes.
- **How to get there:** View = Office in the header (desktop, when the officeMode flag is on), or ?view=office.
- **What you see:** Left nav: Home, Customers, Orders / Sites (opens New Order), Invoices, Collections, Recurring billing, Payments (invoices with a payment received); Global search (name, company, phone, email, address, invoice #); Invoices title row: summary tiles Drafts / Open / Overdue / Paid (30 days) — a click filters the list to exactly that tile (search and dates cleared); again = all; Market chips; + New: New order / New invoice (both open New Order), Record payment (jumps to open invoices), New customer; Clickable Home KPI tiles; Invoices table: row icons ✉ (send) / $ (record payment) / ⋯ (the shared menu), bulk Send reminder (N), Export CSV; Invoice side panel: the shared invoice menu + tabs Summary (status, customer, dates, pay link, amounts, open balance, addresses) · Lines (the line editor) · Payments · Activity (the customer's notes about THIS invoice) · Notes (what the customer reads); Manual link at the bottom of the left nav; Customer side panel: tabs Summary · Invoices · Payments · Notes · Contacts; Colors (brand): Deep Blue sidebar, Brand Blue on the active item, ONE green button per screen (+ New)
  - **+ New** — Starts a new order / invoice / customer (asks before discarding an unsaved order). (read only)
  - **Send reminder (N) — Invoices table** — Runs the Collections reminder dialog for each selected open invoice. (contacts the customer, asks first)
  - **Export CSV — Invoices table** — Downloads the table. (read only)
  - Note: Office spaces its server requests 3 seconds apart (the host blocks bursts).
  - Note: Keys: / search, n + New, Esc closes the panel.
  - Note: The Payments / Activity tabs show only notes that name THIS invoice (inv=, num=, 'invoice #N', the proxy's payment / refund notes) — a note that mentions the invoice some other way is on the customer's Notes tab.

### Pay page (Página de pago) {#pay-page}

- **Where:** app/pay.html?t=<token>
- **What it is for:** OUR customer-facing pay page (Stripe): pays one invoice and saves the card for future services (with consent).
- **How to get there:** The link in the invoice email / text, 'Open payment link', 'Copy link'.
- **What you see:** Invoice number and amount; Card / Apple Pay / Google Pay; After paying: 'Send receipt' by email or text; Staff mode (opened by New Order 'Enter card now'): 'Enter the customer's card', no wallets; after a partial charge, 'Charge the remaining … to another card'
  - **Pay now** — Charges the card at once (the consent text is shown above it), records the payment in GHL, saves the card. (MONEY)
  - **Send receipt** — Emails or texts the receipt. (contacts the customer)
  - Note: One live link per invoice: a newer link replaces the older one ('use the link in the most recent email or text').
  - Note: The page re-checks the invoice before charging (paid, void, amount changed -> refused).
  - Note: The code keeps Stripe's Link in customer mode while the decision log says card only — architect to confirm.
  - Note: The invoice / reminder / receipt EMAILS and the pay page have the website look (D70): blue band with the white logo and the invoice number, the amount, a green PAY NOW button, a Deep Blue footer with the main phone line.

## How to …

### Send an invoice that saves the card {#flow-send-invoice-saves-card}

1. **new-order** — Find the customer (or create one), set the service address, add the lines (The market badge follows the address.)
2. **new-order** — Leave 'Send GHL's link instead' unticked (the default is our pay page)
3. **new-order** — Send as INVOICE, then Confirm (The confirm sheet shows lines, tax, total and the channels.)

Outcome: The invoice exists in GHL as sent; the customer gets our pay page link; paying saves the card. Check it in: The result screen (Pay link = our pay page) and Invoices (status sent).

### Send our pay page for an invoice that already exists {#flow-send-pay-page-existing}

1. **payments** — Find the invoice
2. **payments** — Send pay page (saves card), choose email / text, Send (A draft is finalized first; nothing reaches the customer before Send.)

Outcome: The customer gets our pay page link. Check it in: Invoices row (status), the customer's History (note).

### Record a cash / check / Zelle payment {#flow-record-cash-zelle}

1. **payments** — Record payment on the invoice (Behind the recordPayments flag.)
2. **payments** — Amount, method, date -> Record (click twice)

Outcome: The payment is recorded in GHL (no card charged); the invoice shows paid or the new balance. Check it in: Invoices row ('Paid … of …') and the customer's History.

### Refund a card payment {#flow-refund}

1. **payments** — Refund on a paid invoice (owner only) (Behind the refunds flag.)
2. **payments** — Full or partial amount, reason -> Refund (A full refund also voids the invoice.)

Outcome: Stripe refunds the card; a note records it. Check it in: The toast, the invoice status, Stripe.

### Charge a saved card {#flow-charge-saved-card}

1. **payments** — 'Charge <amount> to <card>' on an open invoice (Only shown when the contact has a card.)
2. **payments** — Confirm (The server charges the exact amount due.)

Outcome: The invoice is paid by the card on file. Check it in: The toast (PI id) and the invoice status.

### Edit a draft invoice {#flow-edit-draft}

1. **payments** — Edit draft (Opens in New Order with everything loaded.)
2. **new-order** — Change lines / notes / address -> Save changes to draft #N (Updates the same draft; sending it replaces the draft.)

Outcome: The same draft, updated. Check it in: Invoices (draft row).

### Bill an order in the other market {#flow-change-market}

1. **new-order** — Override next to the market badge, pick the market
2. **new-order** — Type the reason (Required; it goes on the invoice.)

Outcome: The invoice is created in the chosen market with 'Market override: … — <reason>' in its terms. Check it in: The invoice terms and the market badge.

### Find a customer and its history {#flow-find-customer-history}

1. **customers** — Search by name, phone or email (or click the name anywhere)
2. **customers** — Read History (invoices, payments, notes, orders, recurring) (Searchable.)

Outcome: Everything about the customer in one place. Check it in: The profile.

### Preview before sending {#flow-preview-before-send}

1. **payments** — Preview on the invoice (Read only, our templates.)

Outcome: You see the email / text the customer would get. Check it in: The preview dialog.

### See what Orlando billed in a month {#flow-orlando-billed}

1. **reports** — Pick the month
2. **reports** — Read the ORL line in 'By market', or Export CSV (market = ORL)

Outcome: Orlando's billed total for the month (see the Reports known gap for past months). Check it in: Reports.

### Work the past-due queue {#flow-collect-past-due}

1. **collections** — Start at the top (worst first), read NEXT
2. **collections** — Send reminder (the ladder dialog) or Call / Promised to pay (Every action is recorded as a note.)

Outcome: Each past-due invoice has its next step done and recorded. Check it in: Collections (last reminder) and the customer's History.

### Change an invoice that was already sent (add a line) {#flow-edit-sent-invoice}

1. **payments** — Edit invoice on the sent invoice (Behind the editSentInvoices flag; not for paid invoices.)
2. **new-order** — Add / change lines, notes, Job/PO -> Save changes to invoice #N (Nothing reaches the customer yet.)
3. **new-order** — Resend updated invoice (One message with a new pay link for the new amount.)

Outcome: The same invoice with the new lines and total; the customer has one link that pays the new amount. Check it in: The invoice's notes ('Updated …') and the customer's History.

### Stop a customer's recurring billing (early pickup, cancelled service) {#flow-cancel-recurring}

1. **customers** — Open the customer → Sites card → Recurring… (Or 'Recurring…' on one of their invoices.)
2. **customers** — Cancel schedule on the template → confirm (House rule: the period already started is charged in full — no proration, no refunds.)

Outcome: GHL makes no more invoices from that template; already-made invoices are unchanged (void or refund them separately). Check it in: The dialog re-reads GHL and no longer lists it; a 'RECURRING CANCELLED' note on the customer's History.

## Rules the dashboard follows

- The market (West Palm / Orlando) is decided by the SERVICE address: its ZIP's zone first, then the county list; overriding needs a reason that goes on the invoice. _(docs/DESIGN-market-attribution.md)_
- Base prices are the SEFL column of the master catalog (v20); zones are applied on top by the agents, never in ServiceCore. _(DECISIONES.md · Precios (2026-09-19))_
- Trailers cost the same in every zone (no zone factor). _(DECISIONES.md · Precios (2026-08-28))_
- Trailers rent by Day/Event (up to 4 days), 7 days or 28 days; one extra day is another Day/Event, two or more extra days = quote the week. No extension rate. _(DECISIONES.md · Precios (2026-08-28))_
- Price increases apply to new orders only; existing customers keep their price until decided otherwise. _(DECISIONES.md · Precios (2026-09-26))_
- 'Deluxe' is not a separate unit: it is a standard unit in better condition, reserved for events. VIP is only the VIP with flush. _(DECISIONES.md · Producto y flota (2026-09-26))_
- Trailer fleet: 2, 3, 4, 6, 8 and 10 stations; 5, 7 and 9 exist on invoices but deliver 6, 8 or 10 at the same price — never a smaller one. _(DECISIONES.md · Producto y flota (2026-09-29))_
- The catalog never mentions third-party providers. _(DECISIONES.md · Producto y flota (2026-09-18))_
- Recurring services bill every 28 days, no proration (event vs. construction rule); billing comparisons use billingStart / billingEnd, never the issue date. _(DECISIONES.md · Datos y herramientas (2026-09-19, 2026-09-26))_
- The dashboard is the collector; GHL is the accounting book. GHL autopay is not used (paying one invoice there does not save the card). _(DECISIONES.md · Datos y herramientas (2026-09-19))_
- The billing engine always holds the first cycle of every site for approval, and stops a charge at its gates (tolerance, jump vs. last cycle, per-charge cap, daily total). _(DECISIONES.md · Datos y herramientas (2026-09-19))_
- Dashboard payments are by card only (Apple Pay and Google Pay included); Link and PayPal are off on purpose. _(DECISIONES.md · Datos y herramientas)_
- Sales tax is the county of the service address (Florida, destination based); a county with no tax configured in the billing market blocks invoicing until it is added in GHL. _(app/county-taxes.json)_
- Tax exempt needs a reason, and for a customer the Florida certificate on the contact; an expired certificate is said on screen. _(D43 / D46)_
- Invoices are due on receipt (house rule). _(New Order / Quotes)_
- No discounts: price is price. _(DECISIONES.md · Agentes de voz y SMS (2026-09-23))_
- Nothing reaches a customer or moves money without a human click in the dashboard; GHL's automatic reminders are off. _(PPTG-AGENT-BRIEF.md)_
- One live pay link per invoice: a newer link replaces the older one, and the pay page refuses an invoice that is paid, void or whose amount changed. _(reviews/ARCH-D61-PHP-2026-10-10.md)_

## Feature flags (app/config.js)

- `officeMode` = **ON** — Shows the View switch (Auto / Phone / Office) in the header and the Office layout on desktop. ON since D73 (owner, 2026-10-13). Who changes it: Owner decision, Code changes app/config.js in a PR.
- `recordPayments` = **OFF** — 'Record payment' (cash / check / Zelle) on open invoices. Who changes it: Owner decision, Code in a PR.
- `refunds` = **OFF** — 'Refund' on paid invoices (owner only on the server too). Who changes it: Owner decision, Code in a PR.
- `mixedOrders` = **OFF** — Once / Recurring per line and the billing cycle in New Order. Who changes it: Owner decision, Code in a PR.
- `taxExempt` = **ON** — The 'No tax on this invoice' option with a required reason. Who changes it: Owner decision, Code in a PR.
- `invoiceCc` = **ON** — CC emails on an invoice (saved back to the contact). Who changes it: Owner decision, Code in a PR.
- `assistantDrafts` = **OFF** — The Assistant can hand a dictated order to New Order. Who changes it: Owner decision, Code in a PR.
- `editSentInvoices` = **ON** — 'Edit invoice' on sent / overdue / partially paid invoices (edit in New Order, Save updates the same invoice, then 'Resend updated invoice' with a fresh pay link). ON since batch 3 (proxy.php e1821ce4 live); the architect checks it live with the robot after the deploy. Who changes it: Owner decision (the architect runs the robot live check right after), Code in a PR.

## Server actions (names and purposes only)

- `askAssistant` — The Assistant (answers; may return a draft order). (read only)
- `bulkUpsertProducts` — Catalog maintenance (admin).
- `calAuthUrl` — Connect Google Calendar. (read only)
- `calStatus` — Is Google Calendar connected. (read only)
- `cancelInvoiceSchedule` — Cancel a recurring schedule.
- `createAppointment` — Book a GHL appointment.
- `createCalEvent` — Create a calendar event.
- `createContact` — Create a contact.
- `createContactNote` — Write a note on a contact (History).
- `createEstimate` — Create a quote.
- `createInvoice` — Create an invoice.
- `createOpportunity` — Create an opportunity.
- `createSchedule` — Create a recurring invoice template.
- `deleteCalOccurrence` — Delete one occurrence of an event.
- `deleteInvoiceDraft` — Delete a draft invoice.
- `echo` — Health check. (read only)
- `findContactByPhone` — Find a GHL contact by phone. (read only)
- `getCalendars` — GHL calendars. (read only)
- `getCatalog` — The price catalog both markets use (cached server side). (read only)
- `getContact` — Read one contact. (read only)
- `getInvoice` — Read one invoice. (read only)
- `getOpportunities` — Read pipeline opportunities. (read only)
- `listCalEvents` — Read calendar events. (read only)
- `listContactNotes` — Read a contact's notes. (read only)
- `listEstimates` — List quotes (estimates). (read only)
- `listInvoices` — List invoices of a market (paged). (read only)
- `listProducts` — GHL products. (read only)
- `listSchedules` — GHL recurring invoice schedules. (read only)
- `listUsers` — GHL users (the invoicing user). (read only)
- `purgeProducts` — Catalog maintenance (admin).
- `recordPayment` — Record a cash / check / Zelle payment in GHL. (money / message)
- `refundInvoice` — Refund a card payment (owner only). (money / message)
- `scheduleInvoiceSchedule` — Activate a recurring schedule. (money / message)
- `searchContacts` — Search / page GHL contacts. (read only)
- `sendEmail` — Email a contact (our templates). (money / message)
- `sendEstimate` — Send a quote to the customer. (money / message)
- `sendInvoice` — Send / finalize an invoice through GHL. (money / message)
- `sendSms` — Text a contact (our templates). (money / message)
- `signOut` — End this session.
- `signOutAll` — End every session of the user.
- `stripeCardStatus` — Does the contact have a saved card. (read only)
- `stripeChargeCard` — Charge the saved card for the amount due (human click). (money / message)
- `stripePayConfirm` — The pay page reports a payment; recorded in GHL. (money / message)
- `stripePayExchange` — The pay page's read of its link (re-checks the invoice). (read only)
- `stripePayToken` — Make (or reuse) our pay page link for an invoice.
- `text2payInvoice` — Create an invoice as a draft (with its GHL link).
- `updateCalEvent` — Update a calendar event.
- `updateContact` — Update a contact (read back by the caller).
- `updateEstimate` — Update a quote.
- `updateInvoice` — Update an invoice (full object PUT).
- `voidInvoice` — Void an invoice.
- `whoAmI` — Who is signed in (role). (read only)

## Files

- `app/index.html` — The dashboard (all screens, one page). (owner: code)
- `app/config.js` — Settings and feature flags. (owner: code)
- `app/office.js` — The Office view. (owner: code)
- `app/order.js` — The order builder (catalog, lines). (owner: code)
- `app/pricing.js / app/prices.json` — Zone prices from the master catalog. (owner: code)
- `app/zones.js` — Markets and the ZIP -> zone -> market rules. (owner: code)
- `app/county-taxes.json` — County sales tax per market (tax ids). (owner: code)
- `app/customers/` — The Customers hub. (owner: code)
- `app/pay.html` — Our pay page. (owner: code)
- `app/manual/` — This manual (manual.json is the source). (owner: code)
- `app/sw.js` — The service worker (one version bump per change). (owner: code)
- `app/api/proxy.php` — The server proxy to GHL and Stripe. (owner: architect)
- `app/api/billing.php` — The recurring-billing engine. (owner: architect)
- `app/api/secrets.php` — Keys (never in git, never printed). (owner: owner)

## Never

- Never send, charge, refund or void without a human click in the dashboard.
- Use only the robot contact for probes and tests; never a real customer.
- Never paste or print secrets (tokens, keys, secrets.php).
- Never re-enable GHL's automatic reminders.
- Never make more than one server request every 3 seconds in automation (the host blocks bursts).
- Never edit PHP from the dashboard side (proxy.php and billing.php belong to the architect; the owner uploads them).
- Never merge or deploy without the owner.
- Never promise a price or a discount that the catalog does not have.
- Never treat preview / access-key mode as a sandbox: it reaches the real server.

## Glossary (English / Español)

- **Invoice** (Factura) — A GHL invoice; the dashboard sends and collects it.
- **Draft** (Borrador) — An invoice that exists but was not sent.
- **Quote / Estimate** (Cotización) — A price proposal; becomes an invoice when accepted.
- **Pay page** (Página de pago) — Our Stripe page that pays an invoice and saves the card.
- **Market** (Mercado) — West Palm (PPTG) or Orlando (partner), each a GHL sub-account.
- **Service address / Site** (Dirección de servicio / Sitio) — Where the units are; decides market, zone and tax.
- **Zone** (Zona) — The price band of a ZIP (SEFL is the base).
- **Cycle** (Ciclo) — One 28-day billing period of a recurring service.
- **Collections** (Cobranza) — Following up past-due invoices.
- **Past due** (Vencida) — An open invoice after its due date.
- **Void** (Anular) — Cancel an unpaid invoice (it stays as a record).
- **Refund** (Reembolso) — Return money to the card.
- **Job/PO** (Trabajo / Orden de compra) — The customer's job or purchase-order number, printed on the invoice.
- **Robot contact** (Contacto robot) — The test contact used for every probe.
