# Guía: Adaptar un nuevo demo al sistema

> Basado en 11 productos validados: personal-brand, construction-2, restaurant-bar, corporative, marketing-1, creative-agency-2, architecture-2, law-firm-2, construction, insurance, standard-website
> Actualizado: 2026-04-05

---

## Fórmula del Producto

```
PRODUCTO = TEMPLATE + CSS_OVERRIDES + ADMIN_ALIGNMENT + PROVISION + SEEDS
```

## Paso 0: Proceso end-to-end (secuencia de trabajo)

**Lección clave (producto 4):** NO parchear blades incrementalmente. Reescribir desde Porto HTML.

### Secuencia correcta:

```
1. PREPARAR
   ├── Crear DB fresca: CREATE DATABASE bp-{slug}
   ├── Provisionar: php artisan bewpro:new EMAIL "Nombre" {core} --skip-assets
   ├── Copiar TODOS los assets de Porto: rsync img/demos/{name}/ → cd-project/img/demos/{name}/
   ├── Verificar JS de Porto: copiar js/demos/demo-{name}.js (verificar versión 13.0.0)
   └── Insertar registro en tabla demos (site_config + cd_system_config en inglés)

2. BLADES (el trabajo principal)
   ├── Abrir Porto HTML en browser para referencia visual
   ├── Reescribir welcome.blade.php desde Porto HTML (NO parchear el existente)
   │   └── Cada texto → config('site.welcome.*', 'texto Porto como default')
   │   └── Cada imagen decorativa → asset('cd-project/img/demos/{name}/...')
   │   └── Cada dato dinámico → @if(is_module_active()) + $variable del controller
   ├── Reescribir about.blade.php desde Porto HTML
   └── Reescribir contact.blade.php desde Porto HTML

3. CSS
   ├── Verificar skin --secondary → ¿funciona como fondo de sección?
   ├── Si NO → agregar module theme overrides en demo-{name}.css
   └── Scope: html.demo-{name} (clase automática desde master.blade.php)

4. ADMIN PANEL
   ├── Listar TODOS los config() keys de las 3 blades
   ├── Verificar campo por campo en site-data admin
   ├── Agregar campos faltantes con condicional $isCorporate / $isHospitality / $isPersonal
   ├── Agregar image uploads con preview + dimensiones recomendadas
   └── Agregar validaciones en SiteDataController

5. SEEDEAR
   ├── Config keys: INSERT INTO settings con textos Porto como valores
   ├── Módulos: services, projects, gallery, faqs con contenido genérico en inglés
   └── References: logos de clientes (si el demo los usa)

6. VERIFICAR
   ├── Todas las rutas 200
   ├── Todas las imágenes cargan (no broken)
   ├── Nav en inglés, locale=en
   ├── Admin panel muestra solo campos del demo activo
   ├── Comparar visualmente sección por sección vs Porto
   └── Mobile: responsive, CTA en hamburger menu

7. DOCUMENTAR
   ├── Crear checklist-{slug}.md
   ├── Actualizar estado-productos.md
   ├── Actualizar README.md (tabla de productos validados)
   └── Actualizar guía con lecciones aprendidas
```

### Errores a evitar:
- **NO parchear la blade existente** — reescribirla desde Porto HTML
- **NO empezar sin copiar assets** — faltan imágenes → broken layout
- **NO hardcodear textos** — todo via config() con default de Porto
- **NO olvidar la clase CSS en `<html>`** — master.blade.php la agrega automáticamente
- **NO crear campos de admin sin verificar que la blade los use** — genera confusión
- **NO dejar config keys sin seedear en DB** — el admin muestra campos vacíos

---

## Paso 1: Archivos a crear

### 1.1 Blades del demo (mínimo 3, máximo 4+)

```
resources/views/modules/cd-base/frontend/demos/demo-{name}/
├── welcome.blade.php    ← homepage
├── about.blade.php      ← page about/story
├── contact.blade.php    ← page contacto
└── {modulo}.blade.php   ← opcional (ej: menu.blade.php para restaurant)
```

**Regla:** Cada `config('site.*')` en la blade DEBE tener un campo correspondiente en el admin panel.

### 1.2 Header + Footer

```
resources/views/layout/front/headers/demo-{name}.blade.php
resources/views/layout/front/footers/demo-{name}.blade.php
```

### 1.3 Page Header Partial (shared por todos los módulos)

```
resources/views/layout/front/partials/page-header-{name}.blade.php
```

**Acepta:** `$pageTitle`, `$pageLabel`, `$pageBreadcrumb`, `$pageSubtitle`, `$backgroundImage`, `$anchorId`

**REGLA CRÍTICA:** El estilo del page-header partial DEBE derivarse del page header
de about.blade.php y contact.blade.php del demo original de Porto. NUNCA inventar un estilo
nuevo. About, Contact y todos los módulos deben verse idénticos en su page header.

**Proceso correcto:**
1. Observar cómo Porto hace el page header en about.html y contact.html del demo
2. Extraer las clases/estilos comunes (fondo, colores de texto, efectos)
3. Replicarlos en el partial compartido
4. Actualizar about.blade.php y contact.blade.php para usar el MISMO estilo (o el partial directamente)

### 1.4 Admin Partials (3 archivos por demo — refactorizado 2026-04-05)

```
resources/views/admin/site-data/welcome/demo-{name}.blade.php  ← Campos Welcome
resources/views/admin/site-data/about/demo-{name}.blade.php    ← Campos About (si tiene campos únicos)
resources/views/admin/site-data/contact/demo-{name}.blade.php  ← Campos Contact (si tiene campos únicos)
```

NO tocar `index.blade.php` (451 líneas). `@includeFirst` resuelve automáticamente.
Cada partial tiene campos numerados en el MISMO ORDEN visual que la página.
Si el demo no tiene partial propio, usa `_default.blade.php`.

### 1.5 Config Seed (auto-seed — refactorizado 2026-04-05)

```
database/seeders/products/core/seeds/config-{core-slug}.json
```

Se mergea AUTOMÁTICAMENTE durante `bewpro:new`. NO requiere seedeo manual.
Formato: `{ "welcome": {...}, "about": {...}, "contact": {...} }`

### 1.6 Demo CSS

```
public/template/css/demos/demo-{name}.css
```

---

## Paso 2: Registros en el sistema

### 2.1 config/demos.php

```php
'demo-{name}' => [
    'label' => 'Display Name',
    'type' => 'personal|corporate|hospitality',  // controla admin panel
    'features' => [...],  // controla campos visibles
    'about_style' => 'personal|corporate|story',
],
```

**Tipos y su efecto en el admin:**

| Tipo | Labels | Idioma admin | Campos especiales |
|------|--------|:---:|---|
| `personal` | Mi Marca / Sobre Mí | ES 1ra persona | pilares, stats, tabs |
| `corporate` | La Marca / Empresa | ES 3ra persona | services, projects, counters, split, team |
| `hospitality` | Brand / Our Story | EN | offer, founder, story images, contact images |

### 2.2 Features disponibles

| Feature | Efecto en admin panel | Productos que la usan |
|---------|----------------------|----------------------|
| `carousel` | Link a banner editor | Todos |
| `about_inline` | Story section en welcome | corporate, hospitality |
| `pilares` | Tabs + CTAs + stats en welcome | personal |
| `stats_welcome` | Estadísticas numéricas | personal |
| `services_preview` | Badge/heading servicios | corporate |
| `projects_preview` | Badge/heading proyectos | corporate, personal |
| `gallery_preview` | Badge/heading galería | personal, hospitality |
| `blog_preview` | Badge/heading blog | personal, hospitality |
| `counters` | 4 contadores en about | corporate |
| `team` | Sección equipo en about | corporate |
| `testimonials` | Sección testimonios en about | corporate |
| `split_section` | Sección split en welcome | corporate |
| `offer_section` | "What we offer" en welcome | hospitality |
| `founder_quote` | Cita + foto en welcome | hospitality |
| `menu_preview` | Preview del menú en welcome | hospitality |

### 2.3 app/helpers.php

Agregar mapping en `get_demo_skin_mapping()`:
```php
'demo-{name}' => 'skin-{name}',
```

### 2.4 config/page-headers.php

Registrar el tipo de header por módulo.

### 2.5 Dynamic headers de TODOS los módulos

Agregar `@elseif($activeDemo === 'demo-{name}')` en cada dynamic-header:

```
resources/views/modules/blog/frontend/partials/dynamic-header.blade.php
resources/views/modules/gallery/frontend/partials/dynamic-header.blade.php
resources/views/modules/cd-base/faqs/frontend/partials/dynamic-header.blade.php
resources/views/modules/services/frontend/partials/dynamic-header.blade.php
resources/views/modules/projects/frontend/partials/dynamic-header.blade.php
resources/views/modules/team-members/frontend/partials/dynamic-header.blade.php
resources/views/modules/products/frontend/partials/dynamic-header.blade.php
resources/views/modules/references/frontend/partials/dynamic-header.blade.php
resources/views/modules/cd-base/frontend/partials/dynamic-header.blade.php
```

---

## Paso 3: CSS — Module Theme Overrides

### ¿Cuándo se necesitan?

Si el skin del demo tiene `--secondary` que NO funciona como fondo de sección
(ej: verde, rosa, amarillo), se necesitan overrides.

### 3.1 Skin: variables requeridas

Además de las variables estándar del skin (`--primary`, `--secondary`, etc.), agregar:

```css
:root {
    --primary-rgb: R, G, B;     /* ← OBLIGATORIO: para rgba() en inline styles de módulos */
    --secondary-rgb: R, G, B;   /* ← Recomendado */
}
```

**Por qué:** Los módulos usan `rgba(var(--primary-rgb, 0,240,255), 0.08)` con fallback cyan.
Sin `--primary-rgb` en el skin, los icon circles y decoraciones muestran cyan en vez del color del demo.

### 3.2 Overrides completos (20 reglas — validado con marketing-1)

```css
/* === 1. Section backgrounds → Neutral claro ===
   Módulos usan style="background-color: var(--secondary)" */
html.demo-{name} .main section[style*="--secondary"]:not(.page-header) {
    background-color: #f8f8f6 !important;
}

/* === 2. Card/Panel backgrounds → Blanco con borde sutil ===
   Módulos usan style="background-color: var(--dark)" */
html.demo-{name} .main section div[style*="background-color: var(--dark)"],
html.demo-{name} .main section aside [style*="background-color: var(--dark)"] {
    background-color: #fff !important;
    border: 1px solid #e8e5df !important;
    box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);
}

/* === 3. Cards por clase → Blanco === */
html.demo-{name} .main section[style*="--secondary"] .blog-card,
html.demo-{name} .main section[style*="--secondary"] .service-card,
html.demo-{name} .main section[style*="--secondary"] .project-card {
    background-color: #fff !important;
    border: 1px solid #e8e5df !important;
    box-shadow: 0 2px 15px rgba(0, 0, 0, 0.05);
}

/* === 4. Text light→dark ===
   Headings/body usan color: var(--light) en dark bg → revertir */
html.demo-{name} .main section[style*="--secondary"] [style*="color: var(--light)"] {
    color: #212529 !important;
}

/* === 5. Muted text → legible en fondo claro === */
html.demo-{name} .main section[style*="--secondary"] [style*="color: var(--quaternary)"] {
    color: #777 !important;
}

/* === 6. Cyan borders (herencia digital-agency) → Neutro cálido === */
html.demo-{name} .main [style*="rgba(0, 240, 255"] {
    border-color: #e8e5df !important;
}
html.demo-{name} .main [style*="background-color: rgba(0, 240, 255"] {
    background-color: #f8f8f6 !important;
}

/* === 7. Gallery filter pills === */
html.demo-{name} .main .btn-outline-light {
    color: #555 !important;
    border-color: #ccc !important;
    background: #fff !important;
}

/* === 8. FAQs module === */
html.demo-{name} .faq-item { background: #fff !important; border: 1px solid #e8e5df !important; }
html.demo-{name} .faq-question { background: #f8f8f6 !important; }
html.demo-{name} .faq-question h5 { color: #212529 !important; }
html.demo-{name} .faq-answer { background: #fff !important; }
html.demo-{name} .faq-content { color: #555 !important; }
html.demo-{name} .category-nav-item { color: #555 !important; border: 1px solid #e8e5df !important; }
html.demo-{name} .category-nav-item.active { background: var(--primary) !important; color: #fff !important; }

/* === 9. Sidebar nav links → dark text === */
html.demo-{name} .main section[style*="--secondary"] .nav-link { color: #212529 !important; }

/* === 10. Pagination → legible en fondo claro === */
html.demo-{name} .main section[style*="--secondary"] .pagination .page-link {
    color: #212529; background-color: #fff; border-color: #e8e5df;
}
html.demo-{name} .main section[style*="--secondary"] .pagination .page-item.active .page-link {
    background-color: var(--primary); border-color: var(--primary); color: #fff;
}

/* === 11. Empty state gradients → neutro === */
html.demo-{name} .main [style*="linear-gradient(135deg, var(--dark)"] {
    background: #fff !important;
}

/* === 12. Blog card overrides === */
html.demo-{name} .main .blog-card .card-body { background-color: #fff; }
html.demo-{name} .main .blog-card .card-title a { color: #212529 !important; }
html.demo-{name} .main .blog-card .card-title a:hover { color: var(--primary) !important; }

/* === 13. Inner divs with var(--secondary) bg ===
   Card-headers, containers con bg --secondary que NO son <section> */
html.demo-{name} .main section div[style*="background-color: var(--secondary)"] {
    background-color: #f3f2ee !important;
    border-color: #e8e5df !important;
}

/* === 14. Category/Tag badges ===
   Badges con var(--secondary) bg + var(--primary) text = contraste pobre en light theme */
html.demo-{name} .main .badge[style*="background-color: var(--secondary)"] {
    background-color: #f0ede8 !important;
    border: 1px solid #e0dbd3;
}
html.demo-{name} .main .badge[style*="background-color: var(--secondary)"][style*="color: var(--primary)"] {
    color: DARK_PRIMARY_HEX !important;  /* ← versión más oscura del primary */
}

/* === 15. Service category badge === */
html.demo-{name} .main .service-badge[style*="background-color: var(--secondary)"] {
    background-color: rgba(PRIMARY_R, PRIMARY_G, PRIMARY_B, 0.12) !important;
}

/* === 16. btn-outline-secondary (filter pills en services) === */
html.demo-{name} .main .btn-outline-secondary {
    color: #555 !important; border-color: #ccc !important; background: #fff !important;
}
html.demo-{name} .main .btn-outline-secondary:hover {
    color: var(--primary) !important; border-color: var(--primary) !important;
}

/* === 17. Service icon circles (fallback cyan → skin primary) === */
html.demo-{name} .main .service-card .rounded-circle[style*="primary-rgb"] {
    background: rgba(PRIMARY_R, PRIMARY_G, PRIMARY_B, 0.10) !important;
    border: 1px solid rgba(PRIMARY_R, PRIMARY_G, PRIMARY_B, 0.20) !important;
}

/* === 18. Projects sidebar filter text === */
html.demo-{name} .main .projects-sidebar-sticky .card-body a { color: #555; }
html.demo-{name} .main .projects-sidebar-sticky .card-header h5 { color: #212529 !important; }

/* === 19. Project card internal borders === */
html.demo-{name} .main .project-card [style*="border-top: 1px solid rgba(240"] {
    border-top: 1px solid #e8e5df !important;
}

/* === 20. Mobile filter button === */
html.demo-{name} .main .projects-filter-btn {
    color: #555 !important; border-color: #ccc !important;
}
```

### Regla de oro: NUNCA poner `@if($activeDemo === 'demo-{name}')` en blades de módulos compartidos.

---

## Paso 4: Admin Panel Alignment

### Checklist obligatorio:

1. Listar TODOS los `config('site.*')` de las 3-4 blades del demo
2. Verificar que cada key tenga campo en el admin panel (site-data)
3. Si falta → agregar campo con `@if($isHospitality)` / `@if(demo_has_feature('X'))`
4. Si sobra → ocultar con condicional
5. Las imágenes hardcodeadas en blades → hacer configurables via `config()` + file upload en admin
6. Verificar que los form names mapeen correctamente al controller save logic

### Imágenes editables — patrón:

**Blade:**
```blade
@php $img = config('site.welcome.story_image_1', 'default.jpg'); @endphp
<img src="{{ filter_var($img, FILTER_VALIDATE_URL) ? $img : asset($img) }}" />
```

**Admin:**
```blade
<input type="hidden" name="welcome[story_image_1]" value="{{ $currentImg }}" />
<input type="file" name="story_image_1_file" accept="image/*" />
<span class="form-text">524 x 886 px — portrait</span>
```

**Controller:**
```php
if ($request->hasFile('story_image_1_file')) {
    $uploadResult = ProjectAssetService::uploadAsset(...);
    $configToSave['welcome']['story_image_1'] = $uploadResult['secure_url'];
}
```

**DB seed:**
```sql
INSERT INTO settings (`key`, value) VALUES
('site.welcome.story_image_1', 'cd-project/img/demos/{name}/generic/image.jpg');
```

---

## Paso 5: Provision & Seeds

### Core JSON
```
database/seeders/products/core/{product-slug}.json
```

### Seeds por módulo
```
database/seeders/products/core/seeds/
├── blog-{product-slug}.json
├── faqs-{product-slug}.json
├── services-{product-slug}.json      (si tiene services)
├── testimonials-{product-slug}.json  (si tiene testimonials)
└── menu-{product-slug}.json          (si tiene menu)
```

### Catalog mapping
Agregar variantes en `database/seeders/products/catalog.json`:
```json
"variante-slug": ["core-slug", "Display Name"]
```

---

## Paso 6: Checklist final

```
docs/bewpro2.0/product-readiness/checklist-{product-slug}.md
```

Seguir estructura de `checklist-restaurant-bar.md`:
1. Template Visual (tabla de archivos)
2. CSS Module Theme Overrides (si aplica)
3. Admin Panel Alignment (condicionales, campos visibles/ocultos, imágenes)
4. Page Header unificado
5. Módulos activos (tabla con nav header/footer)
6. Provision y Seeds
7. Config Keys en DB
8. Diferencias con otros productos

---

## Paso 7: Tabla `demos` en DB

La tabla `demos` tiene `site_config` (JSON) y `cd_system_config` (JSON) que **overridean** los settings individuales. Todo cambio de config debe reflejarse en ambos lugares:

### `cd_system_config` — controla:
- Módulos activos + navegación (header/footer por módulo)
- Locale (`modules.translations.default_locale`)
- Fonts (`theme.fonts.primary/secondary/tertiary`)

### `site_config` — controla:
- Footer navegación (`footer.navegacion_principal`) — orden, títulos, URLs, active
- Header CTA (`header.cta_button`) — active, title, url, target
- About/Welcome/Contact config keys
- Assets (logos, backgrounds)

### Prioridad de config:
```
config/site.php (base) → tabla demos (override) → tabla settings (override final)
```

**Gotcha:** Si un key existe como empty string `''` en demos/settings, `config()` retorna `''` (no null), y el fallback de `config('key', 'default')` NO se activa. Usar `config('key') ?: 'default'` para manejar empty strings.

---

## Paso 8: Navegación (header + footer)

### Header nav
Generada por `get_dynamic_navigation('header')` en `app/helpers.php`:
1. Lee `site.footer.navegacion_principal` (sí, usa footer config para ambos)
2. Filtra por `$item['active']` + `is_module_active()` + `navigation.header`
3. Soporta títulos multiidioma: `['Carta', 'Menu']` (índice 0=es, 1=en)

### Footer nav
El footer blade de cada demo tiene links **semi-hardcodeados** por módulo. Para agregar un módulo al footer:
```blade
@if(is_module_active('gallery'))
<li>
    <i class="fas fa-angle-right text-color-default"></i>
    <a href="{{ route('gallery') }}" class="link-hover-style-1 ms-1"> Gallery</a>
</li>
@endif
```

### Mobile nav
El header mobile (hamburger) usa el mismo `<x-header-nav>` pero necesita el CTA como botón extra:
```blade
@if(config('site.header.cta_button.active'))
<div class="d-block d-lg-none mt-3 pt-3 border-top border-color-grey">
    <a href="{{ config('site.header.cta_button.url') }}" target="{{ config('site.header.cta_button.target', '_blank') }}" class="btn btn-primary w-100 py-3">
        <span>{{ config('site.header.cta_button.title') }}</span>
    </a>
</div>
@endif
```

---

## Paso 9: Idioma y traducciones

### Activar idioma del proyecto:
- Tabla `settings`: `cd-system.modules.translations.active = 1`, `default_locale = en`
- Tabla `demos`: `cd_system_config.modules.translations.active = true`

### `__()` translations:
- Los módulos compartidos (blog, faqs, gallery) usan `__('Texto en español')` como keys
- Archivo: `resources/lang/en.json` — mapea español→inglés
- Si falta una key, se muestra el texto español como fallback
- Agregar keys faltantes al JSON, NO cambiar las blades compartidas

### Config texts vs `__()`:
- `config('site.about.badge')` → editable desde admin (texto guardado en DB)
- `__('Sobre el Blog')` → traducción del sistema (fija por idioma, no editable por usuario)

---

## Paso 10: Imágenes — aspect ratio y responsive

### Forzar proporciones con CSS (no depender del tamaño del upload):
```css
html.demo-{name} #section .col-class > img {
    aspect-ratio: 262 / 443;
    object-fit: cover;
    width: 100%;
}
```

### Dimensiones recomendadas (basadas en template Porto x2):
Obtener de `file:///path/demo-{name}.html` los placeholders (ej: 262x443) y duplicar.
Mostrar como hint en el admin: `<span class="form-text">524 x 886 px — portrait</span>`

---

## Paso 11: Deploy y Cloudflare

### Archivos estáticos (CSS, JS, imágenes):
- Cloudflare cachea agresivamente (`cf-cache-status: HIT`, `max-age: 14400`)
- Después de cambiar CSS/JS: **Purge cache** desde Cloudflare dashboard
- Para urgencias: agregar query param `?v=2` al CSS link

### DB:
- Exportar local: `mysqldump -u root "bp-{project}" > dump.sql`
- Importar en producción via phpMyAdmin o SSH
- Ejecutar `php artisan cache:clear` en producción después de importar

### Config cache en producción:
- `SiteConfigService` cachea por 24h con key `md5(DB_DATABASE)`
- `Cache::flush()` o `SiteConfigService::clearCache()` para forzar refresh

---

## Matriz de decisión rápida

| Si el demo tiene... | Entonces necesita... |
|---------------------|---------------------|
| Skin con --secondary claro/brillante | CSS module theme overrides (Paso 3) |
| Módulo propio (ej: menu) | Blade extra en demos/{name}/ |
| Imágenes decorativas en blades | Config keys + admin uploads + aspect-ratio CSS (Paso 10) |
| Parallax page headers | Partial custom + background configurable via `page_header_background` |
| Tipografía especial | Reglas en demo CSS (font-family, weight, kerning) |
| Idioma inglés | Translations module active + `en.json` keys (Paso 9) |
| Links externos (delivery, booking) | `target="_blank"` auto-detect en controller (Paso 8) |
| Footer links custom | Editar footer blade del demo (semi-hardcoded por módulo) |
| Mobile CTA | Agregar botón en nav collapse del header (Paso 8) |
| Deploy a producción | DB export + Cloudflare purge + cache clear (Paso 11) |
| JS de demo específico | Copiar `js/demos/demo-{name}.js` de Porto (verificar versión 13.0.0) |
| JS de animaciones extras | Si Porto carga `examples.animations.js`, incluir en `@section('scripts')` |
| Imágenes decorativas (SVG) | Copiar de Porto, usar `asset()` con path fijo (no config — son parte del diseño) |

---

## Lecciones aprendidas (5 productos validados)

### Producto 1 (personal-brand): Pilares + stats como campos únicos del tipo `personal`
### Producto 2 (construction-2): CSS overrides como patrón replicable para cualquier skin con --secondary claro
### Producto 3 (restaurant-bar): Admin panel 1:1 con blade, imágenes editables con upload, locale inglés
### Producto 4 (corporative): Reescribir blade desde Porto HTML como base (no parchear incrementalmente). Verificar versión de JS (12.x vs 13.0.0). Copiar TODOS los assets de Porto antes de empezar. Seedear config keys con textos exactos de Porto como defaults.
### Producto 5 (marketing-1): Lecciones CSS críticas:
- **Page header consistency:** About, Contact y módulos DEBEN compartir el mismo estilo de page header. Derivar SIEMPRE del Porto HTML original, nunca inventar un estilo nuevo para los módulos.
- **`--primary-rgb` en skin:** OBLIGATORIO. Sin esto, los módulos muestran cyan en icon circles (fallback de `rgba(var(--primary-rgb, 0,240,255))`).
- **20 reglas de CSS override (no 8):** Las 8 reglas originales dejaban huecos: inner divs con `--secondary`, badges con contraste pobre, `btn-outline-secondary`, sidebar card-headers, project card borders, mobile filter buttons. Ahora documentadas las 20 completas.
- **Attribute selectors son frágiles:** `[style*="rgba(0, 240, 255"]` NO matchea `rgba(var(--primary-rgb, 0,240,255))` — son strings literales distintos. El skin `--primary-rgb` es la solución limpia; los CSS overrides por `[style*="primary-rgb"]` son el safety net.

### Producto 6 (creative-agency-2): Dark theme + admin alignment + seeds. Lecciones críticas:
- **Dark theme CSS overrides:** Las 20 reglas son INVERSAS al light theme. Section bg → dark (#1a1a1a), cards → slightly lighter (#1e1e1e), muted text → gris claro (#999). Usar `demo-creative-agency-2.css` como modelo para dark themes.
- **Admin panel: NO mezclar campos de otros demos.** `@if($isCorporate)` mostraba campos de marketing-1 para creative-agency-2. Solución: `@if($isCorporate && !demo_has_feature('stats_welcome'))` para excluir.
- **Admin panel: ORDEN = orden visual de la página.** Numerar secciones ("1. Hero", "2. Concepto", etc.) para que el usuario pueda contrastar admin y sitio lado a lado.
- **`bewpro:new` NO seedea config keys de welcome/about.** Las blades usan `config('site.welcome.*', 'default Porto')` — los defaults se muestran, pero los campos del admin aparecen vacíos. Hay que seedear con `SiteConfigService::setMany()` después de provisionar.
- **About counters: formato ARRAY JSON, no keys individuales.** El admin guarda `site.about.counters = [{value, label, append}, ...]`. La blade debe leer `config('site.about.counters')` con `json_decode`, NO `config('site.about.counter_1_value')`.
- **Services seed: columna = `service_category_id`**, no `category_id`. El seed JSON debe usar el formato con `service_categories[]` + `services[]`, no un array plano.
- **Verificar que servicios se insertaron después de provisionar.** `bewpro:new` puede fallar silenciosamente si el formato del seed JSON no coincide con el seeder.

### Producto 7 (architecture-2): Capa 1 completa + admin per-demo + variables del controller. Lecciones:
- **Capa 1 NO es opcional.** Reescribir welcome/about/contact desde Porto HTML es EL TRABAJO PRINCIPAL. Sin esto, la homepage no se parece a Porto. Las capas 2 (CSS) y 3 (seeds) son necesarias pero no suficientes.
- **Variables del HomepageController:** Usar nombres EXACTOS del `compact()`. Son: `$companyLogos` (NO `$clientLogos`), `$featuredReferences` (NO `$testimonials` en la view), `$featuredFaqs`, `$teamMembers`, etc. Leer `HomepageController.php` antes de escribir la blade.
- **Iconos SVG vs FontAwesome:** Algunos demos (architecture-2) usan archivos SVG como iconos de servicios (`<img src="{{ asset($service->icon) }}" data-icon>`), otros usan clases FA (`<i class="{{ $service->icon }}">`). El campo `icon` en la tabla services debe contener el formato que la blade espera.
- **Feature flag por demo para admin panel:** Cada demo con secciones únicas necesita su propio feature flag (`architecture_welcome`, `stats_welcome`, etc.) y bloque condicional. ADEMÁS, los bloques de otros demos deben excluir el nuevo con `!demo_has_feature('nuevo_flag')`.
- **Seedear en UNA sola pasada:** Hacer múltiples `SiteConfigService::setMany()` con keys parecidos pero distintos genera duplicados vacíos (`process_step1_title` vs `process_1_title`). Definir todos los keys de la blade ANTES de seedear y hacerlo en un solo `setMany()`.
- **Config keys vacíos = fallback NO funciona:** Si `config('site.welcome.key')` retorna `''` (empty string en DB), el fallback de `config('key', 'default')` NO se activa. Limpiar keys vacíos con DELETE o usar `config('key') ?: 'default'` en la blade.

### Producto 8 (law-firm-2): Admin exclusion cascading + services SVG recurrente. Lecciones:
- **Admin exclusion es CASCADING:** No alcanza con excluir en el bloque corporate genérico. Hay que excluir en CADA bloque que no corresponda: CTA pie de contacto (`!$isHospitality && !lawfirm_welcome`), Office Photos (`$isCorporate && !lawfirm_welcome`), Culture/Values (`$isCorporate && !lawfirm_welcome && !architecture_welcome`), Counters genéricos (`counters && !lawfirm_welcome`). Cada demo nuevo agrega una condición más.
- **Services SVG es un patrón recurrente:** architecture-2 y law-firm-2 ambos usan SVG paths como iconos. El seed JSON DEBE tener SVG paths cuando la blade usa `<img data-icon>`. Verificar SIEMPRE cómo renderiza la blade ANTES de seedear servicios.
- **About counters individuales vs array:** law-firm-2 usa `config('site.about.counter_1_value')` (keys individuales), creative-agency-2 usa `config('site.about.counters')` (array JSON). Depende de la blade. El admin de counters genéricos usa formato array. Si la blade usa keys individuales, necesita campos admin propios.
- **Tabs del admin afectan a MÚLTIPLES demos:** Un cambio en tab About o Contact puede romper la vista de otro demo. Siempre verificar con `!demo_has_feature()` que los bloques excluyan lo que no corresponde.

### Producto 9 (construction): Config key naming mismatch + strip_tags. Lecciones:
- **Config keys de blade ≠ keys que seedeás.** Siempre EXTRAER los keys exactos de la blade con `grep config('site.welcome.` ANTES de seedear. No asumir nombres — ej: blade usa `hero_title_line1` pero se seedeó como `hero_title`.
- **`strip_tags()` antes de `Str::limit()`:** Si la descripción del servicio tiene HTML (`<p><strong>...`), usar `Str::limit(strip_tags($service->description))` en la blade, no `Str::limit($service->description)`.
- **`$service->icon` no `$service->icon_svg`:** Verificar el nombre exacto de la columna en la tabla antes de usarla en la blade. La tabla services tiene `icon`, no `icon_svg`.
- **Propiedad inexistente = blade rota silenciosamente:** Si se referencia `$service->icon_svg` y no existe, Eloquent retorna null sin error. La blade no muestra icono sin dar error visible.

### Producto 10 (insurance): Admin partials 3 tabs + bewpro:skin + config-{core}.json completo. Lecciones:
- **Admin partials: SIEMPRE crear para los 3 tabs.** No solo welcome — sin about/contact partial el demo usa `_default` que muestra campos de otros demos. Checklist: welcome/ ✓ about/ ✓ contact/ ✓
- **`bewpro:skin` borra `--primary-rgb`:** El comando de generación de skins no incluye `--primary-rgb`. Si se regenera el skin, hay que re-agregarlo manualmente. Oportunidad de mejora: modificar el comando para incluir `--primary-rgb` y `--secondary-rgb` automáticamente.
- **config-{core}.json debe cubrir los 3 tabs:** No solo welcome — incluir about y contact. Extraer DESPUÉS de seedear todos los keys en la DB. El script de extracción solo captura keys que ya tienen valor.
- **4 cores comparten 1 demo:** insurance-advisor, financial-wealth, nutritionist, concierge todos usan demo-insurance. Los admin partials y CSS overrides se comparten, pero cada core tiene su propio config-{core}.json con valores diferentes. Solo se creó config-insurance-advisor.json — los otros 3 cores deberían tener el suyo.
