# Tenant configuration, brands & permissions (Phase 02)

Everything here lives **inside each tenant database** — fully isolated per
tenant. Baseline data (permissions, role templates, base currencies, a primary
brand) is seeded automatically when a tenant is provisioned, via the
`TenantProvisioned` event → `SeedTenantBaseline` listener.

## Brands

Brands are commercial units **inside** a tenant, never new tenants. The active
brand is resolved per request by the `brand` middleware from the `X-Brand`
header (a brand uuid/slug, or `all` for the consolidated read-only view) and is
always validated against the brands the user may access (`brand_user`, or
`can_access_all_brands`). Consolidated mode is read-only — a brand-scoped
mutation calls `BrandContext::requireBrand()`.

## Roles & permissions

- Stable permission **codes** live in `App\Modules\Identity\Authorization\PermissionRegistry`.
- Roles (`administrador`, `direccion`, `asesor`, `operaciones`, `cobranza`,
  `finanzas`, `coordinador`) are **editable templates** seeded per tenant.
- Server-side enforcement: the `permission:<code>` route middleware, plus a
  `Gate::before` hook so `$user->can('code')` works everywhere. Authorization is
  never "hide the button only".
- `AdminCapabilityGuard` enforces that a tenant always keeps at least one active
  administrator (`config.manage`).

## Folios

`FolioGenerator` produces unique, gap-free folios per `(brand, document_type,
year)` using a `lockForUpdate` transaction, so concurrent requests never collide.
Each brand carries its own prefix, so two brands never share a folio.

## Custom fields

`CustomFieldService` turns definitions (per entity: customer/passenger/booking/
tour/supplier) into validation rules, enforces type/required/visibility, and
**encrypts** values flagged sensitive. Values are stored relationally, one row
per field/entity — never as a JSON blob.

## Status presentations

`StatusService` resolves transitions from **stable internal codes**;
`status_presentations` only changes the visible label/color/icon. Editing a
presentation can never change a transition.

## Templates

`SafeTemplateRenderer` substitutes only allow-listed `{{ variable }}`
placeholders (escaped) and **never** compiles or evaluates the template as code,
so template bodies can never execute arbitrary content.

## New commands

| Command | Purpose |
|---|---|
| `php artisan tenant:seed-baseline {uuid}` | (Re)seed permissions/roles/currencies/brand |
| `php artisan tenant:create-admin {uuid} {email}` | Create an admin user (all-brands) |

## API (admin app)

Authenticated tenant endpoints under `/api/v1/tenant` (Sanctum, `brand`
middleware): `POST /auth/login`, `GET /me`, `GET/POST/PATCH /brands`,
`GET/POST/PATCH /users`, `GET /reports/costs` (permission `costs.view`),
`POST /templates/preview` (permission `config.manage`). The Vue admin SPA
(`resources/js`) consumes `/me` to drive a permission-aware UI and a brand
selector.
