# Blueprint brief — commerce-vc1 (shared source of truth for all blueprint authors)

Desenvolvido por **Beesy**.

Every blueprint document MUST use the names, enums, hosts and decisions below. If a document needs something not defined here, it marks it as `PADRÃO PROPOSTO` (proposed default) and lists it in its own "Itens para 10-DECISIONS" section at the end. Never invent facts about legacy systems: read the source code/docs cited, quote paths, and label anything unverified as `A VERIFICAR`.

Language: documents in Brazilian Portuguese; identifiers (tables, columns, enums, classes, routes) in English.

Repo root: `/home/beesy/Projetos/Cursor/suites/falepaco/commerce-vc1`. Blueprint folder: `docs/blueprint/`. Inputs: `docs/sources/` (PRODUCTS-vc1.md = product rules approved by the operator; PLAN-vc1-wholesale.md = first architecture plan; ANALYSIS-vc1-wholesale-pivot.md = legacy analysis; plano-vc1/index.html = visual plan; sales-vc1-mockup = early sales mockup; site-vcone.html = official brand model).

## 1. Business (decided by the operator)

- VC1 sells wholesale voice (SIP trunk, DIDs, channels, minutes landline/mobile Brazil) to resellers (ISPs, call centers, integrators).
- Product 01: reseller buys and resells outside the system. Product 02: reseller sells through commerce-vc1 (sales funnel with its own brand and prices) to end customers. End customers get SIP trunk only (no PBX).
- VC1 -> reseller: always minimum commitment with unit rates (DID, channel, landline minute, mobile minute) by tiers: higher commitment = lower rates. VC1 defines tiers when it creates a **price base** (versioned, with effective date) in the VC1 panel. VC1 also ships **suggested packages** that resellers clone and edit.
- Reseller -> end customer: **package plan** (DIDs, channels, franchise separate landline/mobile OR single pooled, fixed monthly price, overage policy per plan: `charge` or `block`, unused minutes expire monthly) or **commitment plan** (minimum commitment, rates from the reseller tier table, customer adds DIDs/channels freely).
- Everything counts toward consumption: DIDs + channels + minutes. Charged = `max(consumption, commitment)`. Always the contracted tier (no automatic upgrade). Fidelity configurable per plan/tier.
- Reseller minimum with VC1 = own usage + all its customers' VC1 cost.
- Reseller tiers for its customers = VC1 tiers + markup %, editable row by row. Selling below cost is allowed with a **negative margin alert** (no block).
- Rating cadence **30/6** (first 30 s, then 6 s steps). Destinations: landline Brazil (single national price) and mobile Brazil.
- Mid-month DID/channel add: **pro rata** by remaining days.
- **Billing cycle:** calendar month `[first day 00:00, next first day 00:00)` in `America/Sao_Paulo` for everyone. Reseller **due_day** (default **10**) is the invoice due date only, not the cycle boundary.
- Prepaid: every cycle charges `max(consumption, commitment)`; usage debits the wallet in near real time; at cycle close debit `max(0, commitment − consumption_of_cycle)` from the wallet (top-up is cash in the wallet, **not** fulfilment of future minimums). If the wallet cannot cover the close debit, debit to zero and set `blocked_credit` (no negative balance). Enforcement: for prepaid subscriptions TelcoPort sets SipPulse **monthly quota** to available wallet balance (`updateMonthlyQuota`), refreshed on every debit/top-up; CDR import (every 5 min) is the accounting path. Postpaid: credit limit per customer; **alert at 80%** (reseller and VC1), **block at 100%**. Package exposure = monthly fee + extras not included + overage; commitment plans = all unit items (D-BIZ-25).
- Free trial: defined by the reseller per plan (on/off, duration, franchise).
- **Billing mode per reseller**, chosen by VC1 at reseller registration. **There is no split.**
  - `vc1_invoices` ("VC1 fatura"): VC1 charges the end customer at the reseller's price and issues the fiscal document. Contract with the end customer is VC1's; the proposal states that level-1 support and commercial management belong to the reseller. Reseller earns a **monthly payout** against a **commission invoice** issued by the reseller. `paid_margin_cents` = Σ(sell−cost) of customer invoices **fully paid within the cycle window** (negative per-line margins already included in the sum). `shortfall_cents` = max(0, minimum − cost_paid − own_usage). `negative_margin_cents` = |Σ min(sell−cost, 0)| **informational only** (never subtracted again). `net_cents` = `paid_margin_cents` − `shortfall_cents`. If `payout_raw` (= net) **> 0** → commission invoice for that amount; if **< 0** → VC1 invoice to the reseller for |net|; if **= 0** → terminal `settled_zero` (no commission invoice, no reseller invoice). Example: minimum 2,000; customers' cost 1,500 (paid); paid margin 600 → shortfall 500 → net 100. Margin 300 → net −200 → reseller invoice 200.
  - `reseller_invoices` ("revenda fatura"): reseller charges and issues documents to its customers; VC1 charges the reseller `max(customers' cost + own usage, minimum)`. **Requires an STFC license** (Anatel) proven at registration (license number, document, validity, verified by VC1). The system only produces a rated **statement** per customer for the reseller to bill; it does not issue invoices to those customers. Contract with the end customer is the reseller's.
- Open default (not blocking): in `vc1_invoices`, cost of unpaid customer invoices does NOT count toward the reseller minimum until paid (`PADRÃO PROPOSTO`).

## 2. Platform decisions

- Repo `SoftwareBeesy/commerce-vc1` (private), member of suite `falepaco` (anchor `../falepaco-suite`, manifest `.cursorsuite`).
- Fork of `sales-mework360` (`/home/beesy/Projetos/Cursor/suites/mework360/sales-mework360`): Laravel 12, Filament 4, React 18 SPA (Vite, TanStack Query, Tailwind), PostgreSQL 16, Redis 7, Sanctum, DomPDF, Pest, Vitest. Existing proposal funnel (wizard, public link with 6-digit PIN, A4 v2026 sheets, accept, meSign signature, kanban) is reused and made multi-tenant.
- **Filament** = VC1 panel (staff). **React SPA** = reseller panel, end-customer panel and public pages, branding resolved at runtime by `Host`.
- One deployment, multi-tenant by Host. Tenant = reseller. Reseller resolved from `{slug}.vc1.com.br` (and `cliente.vc1.com.br` = generic reseller login that redirects to the reseller host).
- Integration with legacy production **directly**, via ports/adapters (hexagonal). Future swap target: `falepaco-api` hub (suite falepaco-new, today phase 0, docs only) and `falepaco-erp` + Kamailio.
- PROD and LAB on **meCloud** (Proxmox cluster IDC-EVEO; reference `/home/beesy/.cursor/skills/cloud-ops/references/proxmox-mecloud.md`; templates 9001–9010; cloud-init). All Fale Paco will migrate to meCloud over time.
- LAB connects to the **real** legacy (ERP and SipPulse) using **demo accounts only** (one demo WL project + demo SIP subscribers). Lab adapters enforce an allowlist of legacy IDs; any write outside the allowlist is refused. Creating demo accounts in production requires operator approval.
- Payment in `vc1_invoices`: **mePay** (Beesy payment product under construction; no repo found locally) via `PaymentPort`. Payouts to resellers: registered and paid by VC1 finance (manual transfer recorded in the system) until mePay offers payouts (`PADRÃO PROPOSTO`).
- Fiscal: **NFCom model 62** — legal issuer **RLGR** (CNPJ 57.675.896/0001-26) or reseller CNPJ when applicable; **technical path only via mePay** (`FiscalPort` → mePay → third-party issuer). VC1 never calls SyncNFe/Omie directly (ADR-013, 2026-11-04). Status `issued` only with 44-digit SEFAZ key from mePay webhook (or poll fallback). NFCom API on mePay: **to be created** (mePay today has NFS-e via Omie/ErpGateway only).
- DNS: zone `vc1.com.br` on **meDNS** (`/home/beesy/Projetos/Cursor/suites/mecloud/mecloud-meDNS`, PowerDNS + MariaDB Galera, ns1–ns4 Eveo+BRS). Wildcard TLS via ACME DNS-01 against meDNS/PowerDNS API.
- Signature: **meSign** (`/home/beesy/Projetos/Cursor/suites/mework360/mesign`, `https://mesign.mework360.com.br`, webhook `POST /api/v1/webhooks/paco-assign` with HMAC header `X-Paco-Signature`, events `envelope.completed`, `envelope.archived`).
- Git: branch `front/*` + PR; never push to main; Conventional Commits. No deploy to production without the operator.

## 3. Canonical names

### Ports (connectors)

| Port | Adapter (PROD) | Target |
|------|----------------|--------|
| `ProvisioningPort` | `LegacyErpProvisioningAdapter` | easy_cicerone `api/white_label` (`/home/beesy/Projetos/Cursor/suites/falepaco/easy_cicerone`): create project, customer org, trunk (`cadastro_total_tronco`), DIDs, `generate_login_link`, `alterar_status_boqueio` (sic) |
| `TelcoPort` | `SipPulseSoapAdapter` | SipPulse 4.2 SOAP `172.16.49.222:8080` (VPN): subscriber, DID, callLimit (channels), `updateMonthlyQuota` (prepaid cap), `blockOutgoingCalls` / unblock, `rotateSipCredentials` (password update; exact SOAP `A VERIFICAR`) |
| `CdrPort` | `SipPulseReportsAdapter` | MySQL `sippulse_reports.cdrs_report`, read-only |
| `PaymentPort` | `MePayBillingAdapter` | mePay (`/v1/invoices/adhoc`, `/charges`, webhooks `invoice.paid`) |
| `FiscalPort` | `MePayFiscalAdapter` | mePay → third-party NFCom issuer (API TBD on mePay) |
| `SignaturePort` | `MeSignAdapter` | meSign envelopes |
| `DnsPort` | `MeDnsAdapter` | meDNS PowerDNS API (vanity domains later; wildcard covers `{slug}`) |
| `NotificationPort` | `SmtpMailAdapter`, `SmsAdapter` | e-mail/SMS |

Every adapter has a `Fake*Adapter` for tests and a `LabGuarded*` decorator for LAB (allowlist).

### PostgreSQL schemas

`identity`, `tenancy`, `pricing`, `catalog`, `sales`, `customers`, `telco`, `rating`, `billing`, `fiscal`, `integration`, `audit`. Conventions (copied from falepaco-erp `/home/beesy/Projetos/Cursor/suites/falepaco-new/falepaco-erp/docs/DATABASE.md`): UUID PKs (`gen_random_uuid()`), money in `*_cents bigint`, `legacy_id`/`legacy_*` columns for legacy references, `created_at/updated_at`, `deleted_at` for business entities, schema-qualified tables.

### Core entities (table names)

`tenancy.resellers`, `tenancy.reseller_domains`, `tenancy.reseller_branding`, `tenancy.stfc_licenses`; `pricing.price_bases`, `pricing.price_base_versions`, `pricing.vc1_tiers`, `pricing.reseller_tier_tables`, `pricing.reseller_tier_rows`; `catalog.plans`, `catalog.plan_versions`, `catalog.plan_quotas`, `catalog.suggested_packages`; `sales.proposals`, `sales.proposal_events`, `sales.contracts`, `sales.signature_envelopes`; `customers.customers`, `customers.contacts`, `customers.subscriptions`, `customers.portability_requests`, `customers.portability_files`; `telco.sip_accounts`, `telco.dids`, `telco.channel_allocations`, `telco.block_events`; `rating.cdr_imports`, `rating.usage_records`, `rating.rated_calls`, `rating.cycles`; `billing.invoices`, `billing.invoice_lines`, `billing.statements`, `billing.payments`, `billing.credit_wallets`, `billing.credit_ledger`, `billing.credit_limits`, `billing.limit_events`, `billing.payouts`, `billing.commission_invoices`; `fiscal.fiscal_documents`, `fiscal.fiscal_attempts`; `integration.outbox`, `integration.inbox_webhooks`, `integration.connector_calls`, `integration.legacy_id_map`, `integration.reconciliation_runs`, `integration.lab_allowlist`; `audit.audit_log`; `identity.users`, `identity.roles`, `identity.user_roles`, `identity.api_tokens`.

### Enums

- `reseller.status`: `draft`, `onboarding`, `active`, `suspended`, `terminated`
- `reseller.billing_mode`: `vc1_invoices`, `reseller_invoices`
- `customer.status`: `lead`, `trial`, `active`, `suspended`, `cancelled`
- `subscription.status`: `trial`, `active`, `blocked_credit`, `blocked_limit`, `blocked_overdue`, `blocked_manual`, `cancelled`
- `plan.kind`: `package`, `commitment`; `plan.franchise_mode`: `separate`, `pooled`; `plan.overage_policy`: `charge`, `block`; `payment_mode`: `prepaid`, `postpaid`
- `proposal.stage`: `draft`, `sent`, `viewed`, `trial_active`, `negotiating`, `accepted`, `pending_signature`, `contracted`, `expired`, `lost`, `cancelled`; kanban columns `lead`, `negotiating`, `won`, `lost`
- `invoice.party`: `reseller`, `customer`; `invoice.status`: `draft`, `issued`, `paid`, `overdue`, `cancelled`, `written_off`
- `payout.status`: `calculating`, `pending_commission_invoice`, `approved`, `paid`, `netted_to_invoice`, `settled_zero`
- `fiscal_document.status`: `pending`, `imported`, `authorized`, `rejected`, `cancelled`
- `destination`: `landline_br`, `mobile_br`, `other` (`other` blocked by default — `PADRÃO PROPOSTO`)

### Roles

VC1: `vc1_admin`, `vc1_finance`, `vc1_ops`, `vc1_support`. Reseller: `reseller_owner`, `reseller_admin`, `reseller_finance`, `reseller_seller`. End customer: `customer_admin`, `customer_user`.

## 4. Hosts

| Env | Host | Use |
|-----|------|-----|
| PROD | `vc1.com.br`, `www` | Institutional site (separate repo Site-VC1, out of scope) |
| PROD | `admin.vc1.com.br` | VC1 panel (Filament) |
| PROD | `cliente.vc1.com.br` | Generic reseller login, redirects to reseller host |
| PROD | `{slug}.vc1.com.br` | Reseller panel (`/painel`), end-customer panel (`/app`), public proposal (`/p/{code}`) |
| PROD | `sales.vc1.com.br` | Short links to proposals (redirect to the reseller host) |
| PROD | `api.vc1.com.br` | Public API (reseller tokens) and inbound webhooks |
| LAB | `admin.lab.vc1.com.br`, `cliente.lab.vc1.com.br`, `{slug}.lab.vc1.com.br`, `api.lab.vc1.com.br` | Same layout |
| — | SIP domain | `A VERIFICAR` (per-reseller `sip.{slug}.vc1.com.br` vs single domain) |

Reserved slugs: `admin`, `cliente`, `api`, `sales`, `lab`, `www`, `sip`, `mail`, `status`, `docs`, `app`, `static`.

## 5. Legacy facts (with sources)

- easy_cicerone: WP + Slim + MySQL, PHP 5.5.9, host `177.11.49.9` (`erp.falepaco.com.br`); new VM 140 on Proxmox sda691 under migration. Tenant = `projetos_clientes`; end customers in `organizacoes.id_projeto_cliente`; trunks in `projetos_clientes_tronco`. API `api/white_label`. Activation `AtivacaoTronco.php` creates SipPulse subscriber with prefixes 750/850/950 without Fusion. Legacy trunk billing cron `geracao_faturas_whitelabel_tronco.php` must be **skipped** for VC1 projects (needs a flag; small change in easy_cicerone, operator approval).
- SipPulse 4.2 / OpenSIPS 1.11.8: SOAP `177.11.49.222:8080` (VPN `172.16.49.222:8080`), proxy `sip.falepaco.com.br` -> `177.11.49.223`; CDR via `acc-import.py` into `sippulse_reports` (~52k calls/day). Docs: `/home/beesy/Projetos/Cursor/suites/falepaco/falepaco-sippulse-old`.
- Payment gateways today: Pagar.me/Vindi per project in the ERP (not used by commerce-vc1 in `vc1_invoices`).
- Security debts NOT to copy: totals computed in the browser (wl_label/VC1-Intelbras), TLS verification disabled, token in URL query, public portability document folder (LGPD).
- Panels today: `wl_label` (Laravel 8, `/tronco-sip/*`, `/admin`), `painel_wl` (Laravel 5.7, PBX-centric: ramais, URA, filas — out of the VC1 product).
- Bastion `177.11.49.26:19639` (Perninha). Proxmox BRS nodes sda802/sda691/sda933/sda565. PBS `backup.beesy.com.br`.

## 6. Epics (names fixed)

E0 Fundação · E1 Produto 01 atacado (+ STFC upload/validação) · E2 Planos + funil (Produto 02) · E3 Tarifação, fatura revenda, NFCom VC1→revenda, statements · E4 Crédito e limites · E5 VC1 fatura o cliente final (mePay, NFCom cliente, repasse) · E6 Troca de motor (falepaco-api / falepaco-erp / Kamailio).

**Go-live (FECHADA):** E0–E4 em `reseller_invoices` only, **1 pilot reseller**; `vc1_invoices` from E5 onward.
