# Estandar de Demo — Adaptacion Porto → CD-System

> Ultima actualizacion: 2026-03-29
> Este documento es la **fuente de verdad** para crear y optimizar demos.
> Reemplaza las inconsistencias entre docs anteriores.

---

## Principio fundamental

```
DEMO = capa visual. MODULO = capa funcional.
El demo NUNCA contiene logica de negocio.
El modulo NUNCA contiene logica visual especifica de un demo.
La adaptacion se logra con CSS + vistas Blade separadas por demo.
```

---

## 1. Anatomia de un demo completo

### 1.1 Archivos requeridos (7 Blade + 1 CSS)

```
resources/views/layout/front/
├── headers/demo-{name}.blade.php           ← Navegacion principal
├── footers/demo-{name}.blade.php           ← Pie de pagina
└── partials/page-header-{name}.blade.php   ← Banner paginas internas (SIN prefijo "demo-")

resources/views/modules/cd-base/frontend/demos/demo-{name}/
├── welcome.blade.php                        ← Homepage
├── about.blade.php                          ← Nosotros
└── contact.blade.php                        ← Contacto

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

### 1.2 Registros en el sistema (4 puntos)

| Registro | Archivo | Que hacer |
|----------|---------|-----------|
| Layout mapping | `app/helpers.php` → `get_demo_layout_mapping()` | Agregar entrada header + footer |
| Skin mapping | `app/helpers.php` → `get_demo_skin_mapping()` | Mapear demo → skin CSS |
| Page headers | `config/page-headers.php` | Agregar partial name |
| Dynamic headers | 10 archivos `dynamic-header.blade.php` | Agregar `@elseif($activeDemo === 'demo-{name}')` |

### 1.3 Assets

```
public/template/css/skins/skin-{name}.css    ← Paleta de colores (CSS custom properties)
public/cd-project/img/demos/{name}/          ← Imagenes del demo (SIN prefijo "demo-")
```

### 1.4 Los 10 dynamic-headers a registrar

| # | Archivo |
|---|---------|
| 1 | `resources/views/modules/services/frontend/partials/dynamic-header.blade.php` |
| 2 | `resources/views/modules/blog/frontend/partials/dynamic-header.blade.php` |
| 3 | `resources/views/modules/gallery/frontend/partials/dynamic-header.blade.php` |
| 4 | `resources/views/modules/projects/frontend/partials/dynamic-header.blade.php` |
| 5 | `resources/views/modules/products/frontend/partials/dynamic-header.blade.php` |
| 6 | `resources/views/modules/team-members/frontend/partials/dynamic-header.blade.php` |
| 7 | `resources/views/modules/references/frontend/partials/dynamic-header.blade.php` |
| 8 | `resources/views/modules/menu/frontend/partials/dynamic-header.blade.php` |
| 9 | `resources/views/modules/cd-base/faqs/frontend/partials/dynamic-header.blade.php` |
| 10 | `resources/views/modules/cd-base/frontend/partials/dynamic-header.blade.php` |

---

## 2. De Porto al CMS — Que se extrae y como

### 2.1 Fuente: estructura de Porto

```
Porto HTML/
├── css/demos/demo-{name}.css          → Se copia a public/template/css/demos/
├── css/skins/skin-{name}.css          → Se copia a public/template/css/skins/
├── img/demos/{name}/                  → Se copia a public/cd-project/img/demos/{name}/
├── demo-{name}.html                   → Fuente para: header + footer + welcome
├── demo-{name}-about-us.html          → Fuente para: about.blade.php
├── demo-{name}-contact.html           → Fuente para: contact.blade.php
└── demo-{name}-{services|blog|...}.html → Referencia para module overrides (futuro)
```

### 2.2 Orden de CSS en Porto vs CD-System

```
PORTO (original):
  vendor → theme.css → theme-elements.css → theme-blog.css → theme-shop.css
  → demo-{name}.css → skin-{name}.css → custom.css

CD-SYSTEM (nuestro):
  vendor → theme.css → theme-elements.css → theme-blog.css → theme-shop.css
  → skin-{name}.css → demo-{name}.css → custom.css
  (Demo CSS carga DESPUES de skin — intencional, demo tiene prioridad)
```

### 2.3 Skin CSS = solo paleta de colores

El skin SOLO define CSS custom properties. NUNCA layout ni componentes:

```css
:root {
    --primary: #HEXVAL;           /* + shades: --primary-100..300, --primary--100..--300 */
    --primary-rgba-0..90;         /* transparencias */
    --secondary: #HEXVAL;         /* mismo patron */
    --tertiary: #HEXVAL;
    --quaternary: #HEXVAL;
    --dark: #HEXVAL;
    --light: #HEXVAL;
    --primary-inverse: #FFF;      /* texto sobre fondo primary */
    --grey: #969696;              /* + --grey-100..1000 */
}
```

### 2.4 Demo CSS = layout + componentes + overrides visuales

Estructura interna del demo CSS:

```css
/* 1. Foundation — body/html backgrounds, line-height base */
/* 2. Header styling — sticky, nav colors, mobile */
/* 3. Typography — SOLO font-size y font-weight (NUNCA font-family) */
/* 4. Components — buttons, cards, sections, forms */
/* 5. Module-specific — services cards, blog grid, etc. */
/* 6. Responsive — breakpoints (@media) */
/* 7. GLOBAL OVERRIDES — dark/light mode remapping (CRITICO) */
/* 8. Container width — forzar max-width si aplica */
```

### 2.5 Regla de tipografia en Demo CSS (CRITICO)

Las font-families las controla el sistema via CSS variables. El demo CSS **NUNCA** debe hardcodear font-family.

```
CORRECTO:
  html.demo-{name} h1 { font-weight: 700; letter-spacing: -0.02em; font-size: 3rem; }
  
INCORRECTO:
  html.demo-{name} h1 { font-family: "Helvetica" !important; }
  html.demo-{name} a { font-family: var(--font-family) !important; font-weight: 300; }
  body { font-family: "Poppins", sans-serif !important; }
```

**Variables del sistema** (definidas en `_styles.blade.php` via `get_dynamic_css_variables()`):
- `--font-family-primary` → body (configurable desde admin)
- `--font-family-secondary` → headings (configurable desde admin)
- `--font-family-tertiary` → texto descriptivo (configurable desde admin)

**Lo que el demo CSS SI puede hacer:**
- Definir aliases internos: `--font-headings: var(--font-family-secondary);`
- Ajustar font-weight por componente (300 para body, 700 para nav)
- Ajustar font-size por componente
- Ajustar letter-spacing
- Ajustar line-height

**Lo que el demo CSS NO puede hacer:**
- Hardcodear font-family ("Helvetica", "Poppins", etc.)
- Pisar font-family con !important en selectores genericos (a, p, span, div)
- Forzar font-weight: 300/400 en .font-weight-bold (pisa clases de Bootstrap)

**Motivo:** Si el demo hardcodea fonts, el admin no puede cambiarlas. El producto deja de ser editable. Cada proyecto debe poder elegir sus fonts desde /admin/site-data > Apariencia > Tipografia.

**Scoping obligatorio:** Todas las reglas del demo deben estar bajo `html.demo-{name}` para aislamiento multi-tenant.

**Seccion 7 (Global Overrides) es la mas importante para demos dark-mode.** Remapea clases de Porto light-mode:

```css
/* Ejemplo para demo dark-mode */
.bg-light { background-color: var(--secondary) !important; }
.text-dark { color: var(--light) !important; }
.card { background-color: var(--dark) !important; border-color: rgba(255,255,255,0.1); }
.form-control { background-color: var(--dark); color: var(--light); border-color: rgba(255,255,255,0.2); }
```

---

## 3. Estandar de Header

### 3.1 Patron definido: dinamico + config-driven CTA

Combinando lo mejor de accounting-1 (nav dinamica + CTA config) e insurance (dropdown intelligence):

```blade
{{-- HEADER ESTANDAR --}}
@php
    $navigation = get_dynamic_navigation('header');
@endphp

<header id="header" ...>
    <div class="header-body">
        <div class="header-container container">
            <div class="header-row">
                {{-- Logo --}}
                <div class="header-column">
                    <div class="header-logo">
                        <a href="{{ url('/') }}">
                            <img alt="{{ config('site.name') }}"
                                 src="{{ asset(config('site.assets.main_logo')) }}">
                        </a>
                    </div>
                </div>

                {{-- Navegacion --}}
                <div class="header-column">
                    <div class="header-nav">
                        <nav class="collapse">
                            <ul class="nav nav-pills" id="mainNav">
                                @foreach($navigation as $key => $item)
                                    @if($item['active'])
                                        <li class="dropdown">
                                            <a class="{{ is_nav_item_active($item['url']) ? 'active' : '' }}"
                                               href="{{ url($item['url']) }}">
                                                {{ $item['title'] }}
                                            </a>
                                        </li>
                                    @endif
                                @endforeach
                            </ul>
                        </nav>
                    </div>

                    {{-- CTA Button (config-driven) --}}
                    @if(config('site.header.cta_button.active'))
                        <a href="{{ config('site.header.cta_button.url', '/contact') }}"
                           class="btn btn-primary"
                           target="{{ config('site.header.cta_button.target', '_self') }}">
                            {{ config('site.header.cta_button.title', 'Contacto') }}
                        </a>
                    @endif
                </div>
            </div>
        </div>
    </div>
</header>
```

### 3.2 Reglas del header

| Regla | Correcto | Incorrecto |
|-------|----------|------------|
| Navegacion | `get_dynamic_navigation('header')` | Hardcodear `@if(is_module_active('services'))` por modulo |
| CTA | `config('site.header.cta_button.*')` | Hardcodear texto ("SOLICITAR COTIZACION") |
| Logo | `config('site.assets.main_logo')` | Path hardcodeado |
| Site name | `config('site.name')` | Texto fijo |
| Active state | `is_nav_item_active($item['url'])` | Comparar con `Request::path()` |

**Excepcion permitida:** Dropdown intelligence (mostrar sub-items de services si hay categorias). Esto se puede agregar al loop como logica extra sin romper el patron dinamico.

---

## 4. Estandar de Footer

### 4.1 Patron definido

```blade
<footer id="footer">
    <div class="container">
        {{-- Logo + descripcion --}}
        <img src="{{ asset(site_asset_url('footer_logo') ?: config('site.assets.main_logo')) }}"
             alt="{{ config('site.name') }}">
        <p>{{ config('site.description') }}</p>

        {{-- Navegacion --}}
        @php $footerNav = get_dynamic_navigation('footer'); @endphp
        @foreach($footerNav as $key => $item)
            @if($item['active'])
                <a href="{{ url($item['url']) }}">{{ $item['title'] }}</a>
            @endif
        @endforeach

        {{-- Contacto (config-driven) --}}
        @if(config('site.contact.address'))
            <p>{{ config('site.contact.address') }}</p>
        @endif
        @if(config('site.contact.phone'))
            <a href="tel:{{ config('site.contact.phone') }}">{{ config('site.contact.phone') }}</a>
        @endif
        @if(config('site.contact.email'))
            <a href="mailto:{{ config('site.contact.email') }}">{{ config('site.contact.email') }}</a>
        @endif

        {{-- Social media --}}
        @foreach(config('site.social_media', []) as $network => $data)
            @if(is_array($data) ? ($data['active'] ?? false) : false)
                <a href="{{ $data['url'] }}" target="_blank">
                    <i class="fab fa-{{ $network }}"></i>
                </a>
            @endif
        @endforeach

        {{-- Newsletter (si modulo activo) --}}
        @if(is_module_active('newsletter'))
            @include('modules.cd-base.newsletter.frontend.partials.footer-form')
        @endif
    </div>

    {{-- Copyright --}}
    <div class="footer-copyright">
        <p>&copy; {{ date('Y') }} {{ config('site.name') }}. {{ config('site.footer.copyright_text', 'All rights reserved.') }}</p>
    </div>
</footer>
```

### 4.2 Reglas del footer

| Regla | Correcto | Incorrecto |
|-------|----------|------------|
| Navegacion | `get_dynamic_navigation('footer')` | Links hardcodeados |
| Contacto | `config('site.contact.*')` | Telefono/email fijo |
| Social media | Loop con filtro `active` | Iconos fijos |
| Contenido seccion | `config('site.description')` | "Insurance Products", "Nuestros Servicios" fijo |
| Newsletter JS | Partial compartido o `fetch()` con CSRF | Duplicar JS en cada footer |
| Logo footer | `site_asset_url('footer_logo')` con fallback | Path fijo |

---

## 5. Estandar de Page-Header

### 5.1 Variables definidas (nombres canonicos)

```blade
{{-- Variables esperadas (usar ESTOS nombres, no otros):
    $pageTitle      (string)           — Titulo de la pagina
    $pageLabel      (string, opcional) — Badge/etiqueta sobre el titulo
    $pageBreadcrumb (array, opcional)  — [{title, url}] o null para default
    $pageSubtitle   (string, opcional) — Subtitulo debajo del titulo
--}}

@php
    $breadcrumb = $pageBreadcrumb ?? [['title' => 'Home', 'url' => url('/')]];
@endphp
```

**Nota:** El nombre canonico es `$pageBreadcrumb`. NO usar `$breadcrumbItems` (inconsistencia anterior).

---

## 6. Estandar de Welcome (Homepage)

### 6.1 Estructura

```blade
@extends('layout.front.master')

@section('title', config('site.name') . ' - ' . config('site.tagline'))
@section('description', config('site.description'))

@section('content')

    {{-- Hero section (propio de cada demo) --}}

    {{-- Secciones de modulos — orden tipico, cada una condicional --}}
    @if(is_module_active('services') && isset($services) && $services->count() > 0)
        {{-- Services section --}}
    @endif

    @if(is_module_active('projects') && isset($featuredProjects) && $featuredProjects->count() > 0)
        {{-- Projects section --}}
    @endif

    @if(is_module_active('products') && isset($featuredProducts) && $featuredProducts->count() > 0)
        {{-- Products section --}}
    @endif

    @if(is_module_active('blog') && isset($featuredPosts) && $featuredPosts->count() > 0)
        {{-- Blog section --}}
    @endif

    @if(is_module_active('gallery') && isset($galleryImages) && $galleryImages->count() > 0)
        {{-- Gallery section --}}
    @endif

    @if(is_module_active('faqs') && isset($featuredFaqs) && $featuredFaqs->count() > 0)
        {{-- FAQs section --}}
    @endif

    @if(is_module_active('team') && isset($teamMembers) && $teamMembers->count() > 0)
        {{-- Team section --}}
    @endif

    @if(is_module_active('references') && isset($featuredReferences) && $featuredReferences->count() > 0)
        {{-- References section --}}
    @endif

    {{-- CTA section (contacto, WhatsApp, etc.) --}}

@endsection
```

### 6.2 Reglas del welcome

| Regla | Correcto | Incorrecto |
|-------|----------|------------|
| Secciones | `@if(is_module_active('x') && isset($col) && $col->count() > 0)` | Mostrar seccion vacia o sin guard |
| Textos hero | `config('site.name')`, `config('site.tagline')`, `config('site.description')` | Texto fijo del demo Porto |
| Imagenes | `filter_var($path, FILTER_VALIDATE_URL) ? $url : asset($path)` | Asumir que siempre es local |
| Links a modulos | `route('frontend.services.index')` | `url('/services')` |
| Orden secciones | Respetar el estilo del demo Porto | Inventar orden diferente |

---

## 7. Estandar de About y Contact

### 7.1 About

```blade
@extends('layout.front.master')
@section('title', 'Nosotros - ' . config('site.name'))

@section('content')
    @include('layout.front.partials.page-header-{name}', [
        'pageTitle' => config('site.modules.about.page_header.title', 'Nosotros'),
        'pageBreadcrumb' => [['title' => 'Home', 'url' => url('/')]]
    ])

    {{-- Contenido propio del demo (mision, vision, equipo, stats) --}}
    {{-- Textos desde config cuando sea posible, imagenes con asset() --}}
@endsection
```

### 7.2 Contact

```blade
@section('content')
    @include('layout.front.partials.page-header-{name}', [...])

    {{-- Formulario con AJAX --}}
    <form id="contactForm">
        @csrf
        {{-- campos: name, email, phone, subject, message --}}
    </form>

    {{-- Sidebar: contacto desde config --}}
    {{ config('site.contact.address') }}
    {{ config('site.contact.phone') }}
    {{ config('site.contact.email') }}
    {{ config('site.contact.hours') }}
@endsection

@section('js')
<script>
    document.getElementById('contactForm').addEventListener('submit', function(e) {
        e.preventDefault();
        // fetch() con FormData, CSRF, alerts
    });
</script>
@endsection
```

---

## 8. Rutas correctas (fuente de verdad)

Inconsistencia anterior entre docs. Estos son los nombres CORRECTOS:

| Modulo | Index | Detail |
|--------|-------|--------|
| Services | `frontend.services.index` | `frontend.services.detail` |
| Blog | `blog.index` | `blog.post` |
| Projects | `frontend.projects.index` | `frontend.projects.detail` |
| Products | `frontend.products.catalogue` | `frontend.products.show` |
| Gallery | `frontend.gallery.index` | - |
| FAQs | `frontend.faqs.index` | - |
| Team | `frontend.team.index` | - |
| References | `frontend.references.index` | - |
| Menu | `frontend.menu.index` | - |
| Contact | `front.contact` | - |
| About | `front.about` | - |
| Home | `front.home` | - |
| Newsletter | `front.newsletter.subscribe` (POST) | - |
| Search | `search` | - |

**Regla:** SIEMPRE usar `route('name')` con nombres de esta tabla. NUNCA usar `url('/path')` para rutas internas de modulos.

---

## 9. Transformaciones HTML → Blade

### 9.1 Siempre aplicar

| Porto HTML | Blade |
|------------|-------|
| `src="img/demos/..."` | `src="{{ asset('cd-project/img/demos/...') }}"` |
| `style="background-image: url(img/...)"` | `style="background-image: url({{ asset('cd-project/img/...') }})"` |
| `href="demo-{name}-about.html"` | `href="{{ route('front.about') }}"` |
| `<form action="php/..."` | `<form id="..." action="{{ route(...) }}"` + `@csrf` |
| Texto estatico del demo | `{{ config('site.*') }}` o `{{ __('texto') }}` |
| `<html lang="en">` | Ya maneja master.blade.php |
| Todo el `<head>` | Ya maneja master.blade.php |
| Vendor CSS/JS includes | Ya maneja master.blade.php |

### 9.2 NO hacer en la extraccion inicial

| Evitar | Por que |
|--------|---------|
| `@if($activeDemo === 'demo-x')` en vistas de modulos | Rompe el principio de separacion demo/modulo |
| Copiar JS de Porto que inicializa plugins | `theme.init.js` lo hace automaticamente via data-attributes |
| Modificar theme.css o theme-elements.css | Son compartidos, el override va en demo-{name}.css |
| Hardcodear nombres de industria | Usar config, no "Insurance Products" ni "Nuestros Servicios" |

---

## 10. Proceso de adaptacion — Paso a paso

### FASE 1: Extraccion (estructura base)

```
Paso 1: Copiar assets
  - Porto css/skins/skin-{name}.css  → public/template/css/skins/
  - Porto css/demos/demo-{name}.css  → public/template/css/demos/
  - Porto img/demos/{name}/          → public/cd-project/img/demos/{name}/
  - Porto js/demos/demo-{name}.js    → public/template/js/demos/ (si existe)

Paso 2: Registrar en sistema
  - helpers.php: get_demo_layout_mapping() + get_demo_skin_mapping()
  - config/page-headers.php
  - Core JSON: database/seeders/products/core/{slug}.json (si es core nuevo)

Paso 3: Crear header
  - Fuente: demo-{name}.html → extraer <header>...</header>
  - Aplicar: navegacion dinamica + CTA config-driven (ver seccion 3)
  - Aplicar: transformaciones HTML→Blade (seccion 9)

Paso 4: Crear footer
  - Fuente: demo-{name}.html → extraer <footer>...</footer>
  - Aplicar: navegacion dinamica + contacto config + social media (ver seccion 4)

Paso 5: Crear page-header
  - Fuente: cualquier pagina interna del demo → extraer <section class="page-header ...">
  - Usar variables canonicas: $pageTitle, $pageLabel, $pageBreadcrumb, $pageSubtitle

Paso 6: Crear welcome
  - Fuente: demo-{name}.html → extraer contenido del <div role="main">
  - Aplicar: secciones condicionales por modulo (ver seccion 6)
  - Hero: dinamizar textos con config, imagenes con asset()

Paso 7: Crear about
  - Fuente: demo-{name}-about-us.html
  - Page-header include + contenido adaptado

Paso 8: Crear contact
  - Fuente: demo-{name}-contact.html
  - Formulario AJAX + sidebar contacto desde config

Paso 9: Registrar en 10 dynamic-headers
  - Agregar @elseif($activeDemo === 'demo-{name}') en cada uno
```

### FASE 2: Optimizacion (CSS global overrides)

```
Paso 10: CSS global overrides
  - Agregar bloque de remapping dark/light mode al final del demo CSS
  - Verificar que todas las clases de Porto se ven bien con el skin elegido
  - Testear todos los modulos activos del core con el demo

Paso 11: Module overrides (si necesario)
  - Crear vistas en: resources/views/modules/{mod}/frontend/demos/demo-{name}/
  - Solo cuando la vista generica del modulo no se adapta bien al demo
  - Fuente: demo-{name}-{services|blog|...}.html de Porto
```

### FASE 3: Validacion

```
Paso 12: Verificar
  [ ] Header: logo, nav, CTA, responsive, sticky
  [ ] Footer: logo, nav, contacto, social, newsletter, copyright
  [ ] Welcome: hero, cada seccion de modulo, CTA final
  [ ] About: page-header, contenido, responsive
  [ ] Contact: page-header, formulario funcional, sidebar
  [ ] CSS: dark/light mode correcto, sin textos invisibles
  [ ] Responsive: mobile, tablet, desktop
  [ ] 10 dynamic-headers: sin error 500 en paginas de modulos
```

---

## 11. Problemas conocidos y como resolverlos

| Problema | Causa | Solucion |
|----------|-------|----------|
| Textos invisibles (blanco sobre blanco) | Falta global override dark/light | Agregar seccion 7 al demo CSS |
| Dropdown de nav no funciona en mobile | Falta `data-bs-toggle="dropdown"` | Verificar atributos Bootstrap 5 |
| Iconos no aparecen | Porto 13 usa `.icon`/`.icons` base class | Agregar a custom.css si falta |
| Newsletter form no envia | Falta CSRF o ruta incorrecta | `@csrf` + `route('front.newsletter.subscribe')` |
| Fonts del demo no cargan | No configurado en cd-system.json | Agregar fonts al core JSON |
| Inline hardcoded `rgba(0,240,255,X)` en modulos | Residuo de accounting-1 refactoring | CSS override con `[style*="rgba(0, 240, 255"]` |
| Demo compartido muestra texto de industria incorrecta | Footer/header con contenido hardcodeado | Reemplazar con `config()` |
| `route('contact')` da error | Nombre incorrecto | Usar `route('front.contact')` |
| `route('frontend.blog.index')` da error | Nombre incorrecto | Usar `route('blog.index')` |
| `route('blog.show')` da error | Nombre incorrecto | Usar `route('blog.post')` |

---

## 12. Checklist rapido — Demo completo

```
ESTRUCTURA (7 archivos)
[ ] Header blade
[ ] Footer blade
[ ] Page-header blade
[ ] Welcome blade
[ ] About blade
[ ] Contact blade
[ ] Demo CSS

REGISTROS (4 puntos)
[ ] helpers.php: layout mapping
[ ] helpers.php: skin mapping
[ ] config/page-headers.php
[ ] 10 dynamic-headers

ASSETS
[ ] Skin CSS copiado
[ ] Demo CSS copiado (con global overrides)
[ ] Imagenes copiadas a cd-project/img/demos/

DINAMIZACION
[ ] Header: nav dinamica + CTA config
[ ] Footer: nav dinamica + contacto config + social config
[ ] Welcome: secciones condicionales por modulo
[ ] About: page-header con variables canonicas
[ ] Contact: form AJAX + contacto config
[ ] CERO texto hardcodeado de industria
[ ] CERO @if($activeDemo === 'demo-x') en modulos

VALIDACION
[ ] Desktop: header, welcome, about, contact, modulos
[ ] Mobile: responsive correcto
[ ] Dark/light: sin textos invisibles
[ ] Newsletter: funciona
[ ] Contacto: funciona
```
