# F-079 — Architect: Product list /products does not show brand

## Root cause

The admin `/products` page already renders a `Marca` column with `{p.brand?.name ?? '—'}`, and the `Product` TS type already declares an optional `brand: { id, name, slug }`. However, the backend `PgProductRepository` never populates that field: `PRODUCT_COLUMNS` does not LEFT JOIN `brands_brands`, and `toProduct(row)` only emits `brandId: row.brand_id` without a denormalized `brand` object. So every row in the list renders `—` regardless of whether a brand is assigned.

## Design

Single change set in the repository:

1. **`ProductRow` interface** gains optional fields `brand_name: string | null` and `brand_slug: string | null`.
2. **`PRODUCT_COLUMNS`** is replaced with a subquery (`PRODUCT_BASE_COLUMNS`) that LEFT JOINs `brands_brands` and selects `b.name AS brand_name, b.slug AS brand_slug`. Existing queries (`findById`, `findActiveBySlug`, `listAll`) keep their semantics but read the new column set.
3. **`toProduct(row)`** emits `brand` only when `brand_id IS NOT NULL`:
   ```ts
   brand: row.brand_id
     ? { id: row.brand_id, name: row.brand_name ?? '', slug: row.brand_slug ?? '' }
     : undefined,
   ```
   When `brand_id` is null the frontend falls back to the existing `—` placeholder.

## No new tables, no API contract change.

Frontend is already prepared (the column, the type and the placeholder all exist). After the change:
- Products with a brand: column shows the brand name.
- Products without a brand: column shows `—` (placeholder behavior is unchanged).

## Risk

Low. Pure read-side change. Existing tests on the repository must still pass.

## Acceptance mapping
- "Each row in /products listing shows the brand name" → toProduct now hydrates `brand.name`.
- "Brand column header is present" → already in page.tsx.
- "Empty brand renders as a placeholder" → existing `p.brand?.name ?? '—'` handles it.
- "verify.sh is green" → no test regression expected.