# Proceso de adaptacion de un demo Porto al CMS modular
> CD-System | Guia paso a paso para extraer e integrar demos
> Revision: 2026-03-20 | Actualizado con lecciones de accounting-1 y digital-agency-2

---

## Contexto

Porto provee demos como HTMLs estaticos. Cada demo tiene sus propias paginas:
- `demo-{name}.html` — homepage
- `demo-{name}-about.html` (o about-us, who-we-are)
- `demo-{name}-contact.html` (o contact-us)
- `demo-{name}-services.html` (o expertise, practice-areas, etc.)
- `demo-{name}-services-detail.html`
- `demo-{name}-projects.html` (o cases, portfolio, work)
- `demo-{name}-projects-detail.html`
- `demo-{name}-blog.html` (o news, insights)
- `demo-{name}-blog-post.html`
- `demo-{name}-team.html` (o staff, attorneys, agents)

Nuestro CMS modular necesita:
1. Extraer SOLO la estetica (colores, layout, componentes)
2. Conectarla al dinamismo del sistema (datos de DB, modulos activos, config)
3. NO hardcodear contenido en las vistas

---

## Source: archivos Porto

```
/Users/cokecolombres/Downloads/themeforest-L3kTPMt7-porto-responsive-html5-template/HTML/
├── demo-{name}.html                    ← Homepage
├── demo-{name}-about*.html             ← About page
├── demo-{name}-contact*.html           ← Contact page
├── demo-{name}-services*.html          ← Services index
├── demo-{name}-services-detail*.html   ← Service detail
├── demo-{name}-projects*.html          ← Projects/portfolio
├── demo-{name}-projects-detail*.html   ← Project detail
├── demo-{name}-blog*.html              ← Blog index
├── demo-{name}-blog-post*.html         ← Blog post detail
├── demo-{name}-team*.html              ← Team members
└── css/demos/demo-{name}.css           ← CSS del demo
    css/skins/skin-{name}.css           ← Skin del demo
```

---

## FASE 1: Extraccion (de Porto HTML → archivos del CMS)

### 1.1 Skin CSS
- **Source**: `css/skins/skin-{name}.css` del ZIP de Porto
- **Target**: `public/template/css/skins/skin-{name}.css`
- **Accion**: copiar tal cual. Verificar que todas las variables estan definidas:
  - `--primary`, `--secondary`, `--tertiary`, `--quaternary`
  - `--dark`, `--light`
  - Variaciones (-100, -200, -300, --100, --200, --300)
  - RGBAs (rgba-0 a rgba-90)
  - Inversas (`--primary-inverse`, etc.), grises (`--grey` a `--grey-1000`)

### 1.2 Demo CSS
- **Source**: `css/demos/demo-{name}.css` del ZIP de Porto
- **Target**: `public/template/css/demos/demo-{name}.css`
- **Accion**: copiar y agregar al final:
  - Container width override si corresponde
  - Global overrides si el demo es dark mode (ver seccion "Global Overrides")

### 1.3 Header
- **Source**: el `<header>` del HTML de Porto (cualquier pagina del demo)
- **Target**: `resources/views/layout/front/headers/demo-{name}.blade.php`
- **Accion**:
  1. Extraer SOLO el bloque `<header>...</header>` del HTML
  2. Reemplazar links hardcodeados con `get_dynamic_navigation('header')`
  3. Reemplazar logo con `{{ asset(config('site.assets.main_logo')) }}`
  4. Agregar boton WhatsApp: `@if(config('site.social_media.whatsapp.active'))`
  5. Agregar CTA button: `@if(config('site.header.cta_button.active'))`
  6. Container: usar `container container-xl-custom`

### 1.4 Footer
- **Source**: el `<footer>` del HTML de Porto
- **Target**: `resources/views/layout/front/footers/demo-{name}.blade.php`
- **Accion**:
  1. Extraer SOLO el bloque `<footer>...</footer>`
  2. Reemplazar links con `get_dynamic_navigation('footer')`
  3. Reemplazar datos de contacto con `config('site.contact.*')`
  4. Reemplazar redes sociales con loop de `config('site.social_media')`
  5. Agregar formulario newsletter funcional (ver template en accounting-1)
  6. Agregar copyright dinamico con `config('site.author')`
  7. Descripcion: usar `config('site.footer.description', config('site.description'))`
  8. Container: usar `container container-xl-custom`

### 1.5 Page header partial
- **Source**: observar el patron de page-header en las paginas internas del demo
- **Target**: `resources/views/layout/front/partials/page-header-{name}.blade.php`
- **Variables que DEBE aceptar**:

```blade
@php
    $pageTitle = $pageTitle ?? 'Pagina';
    $pageLabel = $pageLabel ?? null;          // Badge/etiqueta (opcional)
    $pageBreadcrumb = $pageBreadcrumb ?? [
        ['label' => 'Inicio', 'url' => route('front.homepage')],
        ['label' => $pageTitle, 'url' => null],
    ];
    $pageSubtitle = $pageSubtitle ?? null;
@endphp
```

- Container: usar `container container-xl-custom`
- NO hardcodear colores — usar variables CSS del skin

### 1.6 Welcome (homepage)
- **Source**: `demo-{name}.html`
- **Target**: `resources/views/modules/cd-base/frontend/demos/demo-{name}/welcome.blade.php`
- **Accion**:
  1. `@extends('layout.front.master')` + `@section('content')`
  2. Extraer el layout visual (hero, secciones, estructura)
  3. Dinamizar CADA seccion con datos de modulos:

```blade
@if(is_module_active('services') && isset($services) && $services->count() > 0)
    {{-- Seccion de servicios --}}
    @foreach($services->take(3) as $service)
        {{-- Card del demo --}}
    @endforeach
@endif

@if(is_module_active('projects') && isset($featuredProjects) && $featuredProjects->count() > 0)
    {{-- Seccion de portfolio --}}
@endif

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

@if(is_module_active('blog') && isset($featuredPosts) && $featuredPosts->count() > 0)
    {{-- Seccion de blog --}}
@endif
```

  4. Datos del sitio: `config('site.name')`, `config('site.tagline')`, `config('site.description')`
  5. Container: usar `container container-xl-custom`

### 1.7 About page
- **Source**: `demo-{name}-about*.html`
- **Target**: `resources/views/modules/cd-base/frontend/demos/demo-{name}/about.blade.php`
- **Accion**:
  1. `@include('layout.front.partials.page-header-{name}', [...])`
  2. Extraer el layout del body
  3. Usar `config('site.*')` para datos dinamicos — NUNCA hardcodear nombre de empresa
  4. Container: usar `container container-xl-custom`

### 1.8 Contact page
- **Source**: `demo-{name}-contact*.html`
- **Target**: `resources/views/modules/cd-base/frontend/demos/demo-{name}/contact.blade.php`
- **Accion**:
  1. `@include('layout.front.partials.page-header-{name}', [...])`
  2. Formulario funcional (ver template JS abajo)
  3. Sidebar con datos de `config('site.contact.*')`
  4. Container: usar `container container-xl-custom`

---

## FASE 2: Registro en dynamic-headers (10 archivos)

Para cada modulo, agregar el caso del nuevo demo:

```blade
@elseif(get_active_demo() == 'demo-{name}')
    @include('layout.front.partials.page-header-{name}', [
        'pageTitle' => $pageTitle,
        'pageLabel' => 'LABEL',
        'pageBreadcrumb' => $breadcrumbItems,
        'pageSubtitle' => $pageSubtitle ?? null,
    ])
```

Los 10 archivos:
1. `modules/services/frontend/partials/dynamic-header.blade.php`
2. `modules/projects/frontend/partials/dynamic-header.blade.php`
3. `modules/blog/frontend/partials/dynamic-header.blade.php`
4. `modules/cd-base/faqs/frontend/partials/dynamic-header.blade.php`
5. `modules/gallery/frontend/partials/dynamic-header.blade.php`
6. `modules/products/frontend/partials/dynamic-header.blade.php`
7. `modules/team-members/frontend/partials/dynamic-header.blade.php`
8. `modules/references/frontend/partials/dynamic-header.blade.php`
9. `modules/cd-base/frontend/partials/dynamic-header.blade.php` (contact)
10. `modules/menu/frontend/partials/dynamic-header.blade.php`

**Agregar la variable de deteccion al bloque @php si no existe:**
```blade
$isNuevoDemo = ($activeDemo === 'demo-{name}');
```

---

## FASE 3: Verificacion

### Checklist de rutas

- [ ] `/` — Homepage renderiza con modulos activos
- [ ] `/about` — Page-header correcto + contenido
- [ ] `/contact` — Page-header correcto + formulario funciona + envio AJAX OK
- [ ] `/services` — Lista dinamica de servicios de DB
- [ ] `/services/{slug}` — Detalle con page-header del demo
- [ ] `/projects` — Lista dinamica de proyectos
- [ ] `/projects/{slug}` — Detalle con page-header del demo
- [ ] `/faqs` — FAQs dinamicas de DB
- [ ] `/blog` — Posts de DB (si blog esta activo)
- [ ] Header muestra links dinamicos de modulos activos
- [ ] Footer muestra nav + contacto + newsletter

### Checklist visual

- [ ] Sin gaps blancos entre secciones
- [ ] Container width consistente en todas las paginas
- [ ] Responsive funciona (mobile, tablet, desktop)
- [ ] Page-headers identicos en todas las paginas internas
- [ ] Colores del skin aplicados correctamente

### Checklist tecnico

- [ ] No hay errores en `storage/logs/laravel.log`
- [ ] Todas las rutas usan prefijo correcto (`front.contact`, `front.about`, `frontend.services.*`)
- [ ] NO usar `frontend.services.show` → la ruta correcta es `frontend.services.detail`
- [ ] NO usar `route('contact')` → la ruta correcta es `route('front.contact')`
- [ ] NO usar `route('about')` → la ruta correcta es `route('front.about')`
- [ ] `config()` maneja valores null y tipos mixtos (string/array) sin errores

---

## Errores comunes y como evitarlos

### Rutas rotas
**Problema**: Porto usa `route('contact')` pero en CD-System la ruta es `route('front.contact')`
**Solucion**: siempre verificar los nombres de ruta correctos:

| Pagina | Ruta correcta |
|--------|---------------|
| Homepage | `route('front.homepage')` |
| About | `route('front.about')` |
| Contact | `route('front.contact')` |
| Contact form POST | `route('front.contact.store')` |
| Services index | `route('frontend.services.index')` |
| Service detail | `route('frontend.services.detail', $slug)` |
| Projects index | `route('frontend.projects.index')` |
| Project detail | `route('frontend.projects.show', $slug)` |
| Projects portfolio | `route('frontend.projects.portfolio')` |
| FAQs index | `route('faqs.index')` |
| Blog index | `route('frontend.blog.index')` |
| Blog post | `route('frontend.blog.show', $slug)` |
| Newsletter subscribe | `route('front.newsletter.subscribe')` |

### Config que puede ser string o array
**Problema**: `config('site.contact.hours')` puede ser string ("Lun-Vie 9-18") o array (["Lun-Vie 9-18", "Sab-Dom Cerrado"])
**Solucion**: siempre manejar ambos tipos:

```blade
@php $hours = config('site.contact.hours'); @endphp
@if($hours && is_array($hours))
    @foreach($hours as $hour)
        <li>{{ $hour }}</li>
    @endforeach
@elseif($hours && is_string($hours))
    <li>{{ $hours }}</li>
@else
    <li>Consultar horarios</li>
@endif
```

### Redes sociales inactivas
**Problema**: el loop de redes sociales puede incluir redes con `active: false`
**Solucion**: siempre filtrar:

```blade
@php
    $activeSocialMedia = collect(config('site.social_media'))
        ->filter(fn($social) => $social['active'] === true);
@endphp
@foreach($activeSocialMedia as $key => $social)
    <a href="{{ $social['url'] }}"><i class="{{ $social['icon'] }}"></i></a>
@endforeach
```

### Assets del footer (logo)
**Problema**: el logo del footer puede tener un path diferente al del header
**Solucion**: usar el helper `site_asset_url()`:

```blade
<img src="{{ site_asset_url('footer_logo') }}" alt="{{ config('site.assets.main_logo_alt') }}" />
```

---

## Template de formulario de contacto (copiar en cada demo)

```blade
<form id="contactForm" action="{{ route('front.contact.store') }}" method="POST">
    @csrf
    <div class="row">
        <div class="form-group col-md-6">
            <label>{{ __('Nombre Completo') }}</label>
            <input type="text" class="form-control" name="name" required>
        </div>
        <div class="form-group col-md-6">
            <label>{{ __('Email') }}</label>
            <input type="email" class="form-control" name="email" required>
        </div>
    </div>
    <div class="row">
        <div class="form-group col-md-6">
            <label>{{ __('Telefono') }}</label>
            <input type="text" class="form-control" name="phone">
        </div>
        <div class="form-group col-md-6">
            <label>{{ __('Asunto') }}</label>
            <select class="form-control" name="subject" required>
                <option value="">{{ __('Selecciona...') }}</option>
                <option value="Consulta General">{{ __('Consulta General') }}</option>
                <option value="Solicitar Cotizacion">{{ __('Solicitar Cotizacion') }}</option>
                <option value="Soporte Tecnico">{{ __('Soporte Tecnico') }}</option>
                <option value="Otro">{{ __('Otro') }}</option>
            </select>
        </div>
    </div>
    <div class="form-group">
        <label>{{ __('Mensaje') }}</label>
        <textarea class="form-control" name="message" rows="6" required
                  placeholder="{{ __('Contanos sobre tu proyecto...') }}"></textarea>
    </div>
    <button type="submit" class="btn btn-primary">{{ __('Enviar Mensaje') }}</button>
</form>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const form = document.getElementById('contactForm');
    if (!form) return;

    form.addEventListener('submit', function (e) {
        e.preventDefault();
        const btn = form.querySelector('button[type="submit"]');
        const originalText = btn.textContent;
        btn.textContent = 'Enviando...';
        btn.disabled = true;

        const formData = {};
        new FormData(form).forEach((value, key) => formData[key] = value);

        fetch(form.action, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('input[name="_token"]').value,
                'Accept': 'application/json'
            },
            body: JSON.stringify(formData)
        })
        .then(r => r.json())
        .then(data => {
            if (data.success) {
                form.reset();
                alert('Mensaje enviado correctamente');
            } else {
                throw new Error(data.message || 'Error');
            }
        })
        .catch(err => alert(err.message || 'Error al enviar'))
        .finally(() => { btn.textContent = originalText; btn.disabled = false; });
    });
});
</script>
```

---

## Template de newsletter (copiar en cada footer)

```blade
<form id="newsletterForm" action="{{ route('front.newsletter.subscribe') }}" method="POST">
    @csrf
    <div class="input-group">
        <input class="form-control" placeholder="{{ __('Tu email') }}"
               name="newsletterEmail" id="newsletterEmail" type="email">
        <button class="btn btn-primary" type="submit">
            <i class="fas fa-arrow-right"></i>
        </button>
    </div>
</form>

<script>
(function () {
    const form = document.getElementById('newsletterForm');
    if (!form) return;
    const input = document.getElementById('newsletterEmail');
    const csrf = document.querySelector('input[name="_token"]').value;
    let sending = false;

    form.addEventListener('submit', function (e) {
        e.preventDefault();
        if (sending) return;
        sending = true;

        fetch(form.action, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf, 'Accept': 'application/json' },
            body: JSON.stringify({ newsletterEmail: input.value })
        })
        .then(r => { if (!r.ok) throw r; return r.json(); })
        .then(data => { if (data.success) { form.reset(); } })
        .catch(() => {})
        .finally(() => { sending = false; });
    });
})();
</script>
```

---

## Variables disponibles del HomepageController

Estas variables se pasan automaticamente a TODOS los welcome.blade.php:

| Variable | Contenido | Limite |
|----------|-----------|--------|
| `$services` | Servicios activos | hasta 6 |
| `$featuredProjects` | Proyectos activos | hasta 6 |
| `$featuredFaqs` | FAQs destacadas (o primeras 5) | hasta 5 |
| `$featuredPosts` / `$recentPosts` | Posts de blog | hasta 3 |
| `$galleryImages` | Imagenes de galeria | hasta 8 |
| `$teamMembers` | Miembros del equipo | todos |
| `$featuredReferences` | Referencias/testimonios | destacados |
| `$carouselImages` | Imagenes del carousel/hero | todas |
| `$featuredProducts` | Productos destacados | hasta 3 |
| `$seoData` | Meta tags SEO | - |

---

## Mapeo de nombres Porto → CD-System

| Porto | CD-System Module |
|-------|-----------------|
| services, expertise, practice-areas, departments, insurances, classes | `services` |
| projects, cases, portfolio, work, case-study | `projects` |
| blog, news, insights, resources, sermons | `blog` |
| team, staff, attorneys, agents, doctors, instructors | `team` |
| gallery, portfolio (visual) | `gallery` |
| menu | `menu` |
| products, rooms-rates, special-offers | `products` |
| contact, contact-us, book-now, appointment | `contact` |
| about, about-us, who-we-are, company | `about` |
| faq (usualmente seccion dentro de about) | `faqs` |
| testimonials, references | `references` |

---

## Demos completados como referencia

### demo-accounting-1 (Bold Edge / Dark Mode)

```
Porto source:
    demo-accounting-1.html, -about, -contact, -services, -services-details,
    -projects, -projects-details, -news, -news-post, -process

Archivos generados:
    skin-accounting-1.css              ← Paleta OLED Energy (customizada para Compania Digital)
    demo-accounting-1.css              ← Estilos + global overrides dark mode (~1400 lineas)
    page-header-accounting-1.blade     ← Gradient dark, badge cyan, titulo white
    headers/demo-accounting-1.blade    ← Nav dinamica, sticky con backdrop-filter
    footers/demo-accounting-1.blade    ← Dark, newsletter cyan, contacto dinamico
    demos/demo-accounting-1/welcome    ← Secciones dinamicas ($services, $projects, $faqs)
    demos/demo-accounting-1/about      ← Page-header partial + contenido config()
    demos/demo-accounting-1/contact    ← Page-header partial + formulario funcional
    + 10 registros en dynamic-headers

Caracteristicas del demo:
    - Dark mode first (requiere global overrides extensos)
    - Container: 1440px (xl-custom)
    - Skin customizado (no el original de Porto)
```

### demo-digital-agency-2 (Dark with Animated Circles)

```
Porto source:
    demo-digital-agency-2.html, -about-us, -contact-us, -our-services,
    -our-services-detail, -our-work, -our-work-detail, -our-blog, -our-blog-post
    (tambien variante dark: demo-digital-agency-2-dark-*.html)

Archivos generados:
    skin-digital-agency-2.css          ← Paleta original Porto (teal #3fc2c2 + navy #060928)
    demo-digital-agency-2.css          ← Estilos Porto originales (814 lineas) — ya dark by design
    page-header-digital-agency-2.blade ← Dark con circulos animados + breadcrumb
    headers/demo-digital-agency-2.blade ← Nav inteligente con dropdowns por modulo (ya existia, limpio)
    footers/demo-digital-agency-2.blade ← Newsletter + contacto dinamico (ya existia, limpio)
    demos/demo-digital-agency-2/welcome ← Genericizado (era BewPro-only, ahora usa config + modulos)
    demos/demo-digital-agency-2/about   ← Genericizado (era BewPro-only, ahora usa config)
    demos/demo-digital-agency-2/contact ← Genericizado (subjects actualizados, horarios type-safe)
    + 10 registros en dynamic-headers

Caracteristicas del demo:
    - Dark by design (no necesita global overrides como accounting-1)
    - Container: 1440px (xl-custom)
    - Circulos animados decorativos (custom-circle-*)
    - Tipografia: Poppins + Lora
    - Fontello icons custom

Lecciones aprendidas:
    - route('contact') no existe → usar route('front.contact')
    - route('about') no existe → usar route('front.about')
    - config('site.contact.hours') puede ser string o array → manejar ambos
    - Welcome tenia contenido BewPro hardcodeado → reescribir generico
    - Header y footer ya estaban limpios → no fue necesario rehacer
```

---

## Que sigue: demos pendientes de adaptar a Nivel A

| Prioridad | Demo | Cores | Header/Footer | Welcome | Page-header partial | Dynamic-headers |
|-----------|------|-------|:-------------:|:-------:|:-------------------:|:---------------:|
| ✅ | accounting-1 | 2 | Limpio | Dinamico | Creado | 10/10 |
| ✅ | digital-agency-2 | 5 | Limpio | Dinamico | Creado | 10/10 |
| 3 | insurance | 4 | Verificar | Verificar | Crear | Verificar |
| 4 | business-consulting | 2 | Verificar | Verificar | Crear | Verificar |
| 5 | law-firm-2 | 1 | Verificar | Verificar | Crear | Verificar |
| 6 | construction | 1 | Verificar | Verificar | Crear | Verificar |
| 7 | accounting-2 | 1 | Verificar | Verificar | Crear | Verificar |
| 8 | architecture-2 | 1 | Verificar | Verificar | Crear | Verificar |
| 9 | photography-3 | 1 | Verificar | Verificar | Crear | Verificar |
| 10 | restaurant | 1 | Verificar | Verificar | Crear | Verificar |
| 11-16 | rest | 1 c/u | Verificar | Verificar | Crear | Verificar |

---

*Proceso validado con 2 demos completos (accounting-1, digital-agency-2) — 2026-03-20*
