# 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.