# Troubleshooting: Formulário de Associados — Imagens não persistem / Modal não abre

## Cronologia

1. **Problema reportado:** Áreas de input de imagem (logo, cover, galeria) no form de Associados estavam quebradas.
2. **Investigação:** Mapeamento do fluxo completo — view `_form.blade.php` → controller `InstituicaoCmsController` → modelo `AssociadoInstituicao`.
3. **Correções aplicadas:** 5 bugs corrigidos em 3 arquivos.
4. **Problema residual:** Modal de crop da galeria não aparecia na viewport.
5. **Correção final:** Modal movido para `<body>` via JS + backdrop removido.

---

## Bugs encontrados e corrigidos

### 1. `dd()` esquecido em `handleLogoFromGallery`

**Arquivo:** `V2/Modules/Institucional/Associados/Controllers/InstituicaoCmsController.php`

**Problema:** Um `dd([...])` de debug foi commitado na primeira linha do método `handleLogoFromGallery()`. Como este método é chamando **incondicionalmente** tanto em `store()` quanto em `update()`, **qualquer tentativa de salvar** uma instituição morria ali — independente de ter logo ou não.

**Correção:** Remover o `dd()`.

```diff
-   dd([
-       'method_called' => true,
-       'logo_id_raw' => $logoId,
-       ...
-   ]);
```

---

### 2. `create()` não passava `$instituicao` para a view

**Arquivo:** `InstituicaoCmsController.php`

**Problema:** O método `create()` retornava a view com apenas `$allTags` e `$unidadesRegionais`. A view `_form.blade.php` acessava `$inst->metadata`, `$inst->name`, etc. — com `$inst` nulo, lançava **fatal error** (`Call to a member function metadata on null`), impedindo a página de criar de renderizar.

**Correção:** Passar uma instância vazia do modelo para a view.

```diff
  public function create()
  {
      $allTags = AssociadoTag::orderBy('name')->get();
      $unidadesRegionais = UnidadeRegional::active()->ordered()->get();
+     $instituicao = new AssociadoInstituicao();
      return view('v2-associados::cms.instituicoes.create', compact('instituicao', 'allTags', 'unidadesRegionais'));
  }
```

---

### 3. `handleCoverFromGallery` não persistia `metadata['cover_image_id']`

**Arquivo:** `InstituicaoCmsController.php`

**Problema:** O método `handleLogoFromGallery` salvava corretamente `metadata['logo_id']`, mas `handleCoverFromGallery` salvava apenas `cover_image` (coluna) sem atualizar `metadata['cover_image_id']`. Isso fazia o campo hidden `cover_image_id_sync` ficar sempre vazio no form de edição.

**Correção:** Adicionar persistência do ID no metadata.

```diff
  if ($media) {
      $instituicao->cover_image = $media->file_path;
+     $metadata = $instituicao->metadata ?? [];
+     $metadata['cover_image_id'] = $coverId;
+     $instituicao->metadata = $metadata;
  }
```

---

### 4. Galeria (`media-group-input`) fora do `<form>` no edit

**Arquivo:** `V2/Modules/Institucional/Associados/Views/cms/instituicoes/edit.blade.php`

**Problema:** O bloco da galeria de imagens estava **fora** da tag `<form>` — o `</form>` fechava na linha 141, e a galeria começava na linha 143. Os hidden inputs `convenios_gallery_ids[]` nunca eram submetidos, e o método `syncGalleryMediaIds()` recebia array vazio, **deletando todas as imagens** a cada atualização.

**Correção:** Mover o bloco da galeria para dentro do `<form>`, antes dos botões de submit.

```
Antes:
  <form> ... </form>
  <div class="card mt-4"><!-- GALERIA FORA --></div>

Depois:
  <form>
    ... <div class="card mt-4"><!-- GALERIA DENTRO --></div>
    <button>Salvar</button>
  </form>
```

---

### 5. Modal de crop da galeria fora da viewport

**Arquivo:** `V2/Services/Media/Views/components/media-group-input.blade.php`

**Problema:** O modal de crop estava aninhado dentro de elementos com `transform` (ou outro contexto de empilhamento), fazendo com que `position: fixed` do Bootstrap não posicionasse corretamente o modal na viewport. O modal aparecia "no meio da página" mas fora da área visível, atrás do backdrop escuro.

**Causa técnica:** `position: fixed` é relativo à viewport **apenas** quando nenhum ancestral possui `transform`, `filter` ou `perspective` não-`none`. Quando um ancestral tem essas propriedades, o elemento fixed passa a ser relativo àquele ancestral.

**Correções:**

1. **Remover backdrop** — `backdrop: false` elimina a sobreposição escura que escondia o modal.

```diff
- cropModal = new bootstrap.Modal($cropModal[0], { backdrop: 'static' });
+ cropModal = new bootstrap.Modal($cropModal[0], { backdrop: false, keyboard: false });
```

2. **Mover modal para `<body>` ao abrir** — Garante que nenhum ancestral interfira no `position: fixed`.

```javascript
if ($cropModal.parent()[0] !== document.body) {
    $(document.body).append($cropModal);
}
```

3. Adicionado antes de `cropModal.show()`.

---

## Fluxo de salvamento (pós-correção)

```
Usuário preenche form
  → submit POST /nova ou PUT /editar/{id}
    → InstituicaoCmsController@store / @update
      → validate() ← valida campos, incluindo logo_id e cover_image_id
      → preenche $instituicao com dados do request
      → handleLogoFromGallery()
          → busca GalleryMedia::find($logoId)
          → seta $instituicao->logo = media->file_path
          → seta $instituicao->metadata['logo_id'] = $logoId
      → handleCoverFromGallery()
          → mesmo fluxo para cover_image_id
      → $instituicao->save() ← persiste tudo (incluindo metadata JSON)
      → syncTagsFromArray(), syncUnidadeRegional(), syncGalleryMediaIds()
      → activityLogger->log()
      → redirect com flash message
```

---

## Verificação Horizontal: Outros Módulos V2

Realizada em 30/07/2026. Foram inspecionados os módulos abaixo contra os 5 padrões de bug identificados no Associados.

### Legenda
- ✅ **OK** — Padrão não encontrado ou devidamente tratado
- ⚠️ **ATENÇÃO** — Padrão encontrado, requer correção
- 🔘 **N/A** — Não se aplica (módulo não possui o recurso)

### Resumo por Bug

| Bug | SobreNós | Unid.Regional | Notícias | Parceiros | Agenda | LndSctEditor |
|-----|----------|---------------|----------|-----------|--------|--------------|
| #1 dd() esquecido | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| #2 create() sem model | ✅ | ✅ | ✅ | ✅ | ✅ | 🔘 |
| #3 metadata inconsistente | ✅ | ✅ | ✅ | ✅ | ✅ | 🔘 |
| #4 galeria fora do form | 🔘 | ⚠️ | ✅ | 🔘 | 🔘 | 🔘 |
| #5 modal de crop | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |

### Detalhamento

#### 1. Sobre Nós (`V2/Modules/Institucional/SobreNos/`)
- **Controllers:** `SobreNosCmsController` (editor de seções), `BoardMemberController` (CRUD membros)
- **Bug #1:** Nenhum `dd()`, `dump()`, `var_dump()` ou `print_r()` encontrado. ✅
- **Bug #2:** `BoardMemberController@create()` passa apenas `$bienniums`. A view `_form.blade.php` faz `$member = isset($member) ? $member : null;` e `$isEdit = !is_null($member);`, usando `$isEdit ? $member->name : ''` — **null-safe**. ✅
- **Bug #3:** `BoardMemberController::fillMember()` chama `resolvePhotoFromMedia()` que salva `$member->photo = $media->file_path` e `$member->photo_id` já é o ID vindo do request. Não usa metadata para isso. Relação direta com `GalleryMedia`. ✅
- **Bug #4:** Não possui galeria. Apenas campo `photo_id` (media-input). 🔘
- **Bug #5:** O `media-group-input` (usado em outros módulos) já tem o fix aplicado. Modal de crop movido para `<body>`. ✅

#### 2. Unidades Regionais (`V2/Modules/Institucional/UnidadeRegional/`)
- **Controller:** `UnidadeRegionalCmsController`
- **Bug #1:** Nenhum `dd()` encontrado. ✅
- **Bug #2:** `create()` retorna view sem `$unidade`. Mas `_form.blade.php` usa `$unidade = $unidade ?? null` e `$unidade->name ?? ''` — **null-safe** em todos os acessos. ✅
- **Bug #3:** `handleLogoFromGallery()` e `handleCoverFromGallery()` **ambos** persistem corretamente no metadata. ✅
- **Bug #4:** ⚠️ **GALERIA FORA DO `<form>`** — Em `edit.blade.php`, a galeria (`media-group-input`) está na coluna lateral direita (`col-lg-4`, linha 184), **FORA** do `<form>` que está na coluna principal (`col-lg-8`, linhas 77-124). Os hidden inputs `gallery_media_ids[]` nunca são submetidos. O `update()` chama `syncGalleryMediaIds($unidade, $request->input('gallery_media_ids', []))` — com array vazio, **todas as imagens da galeria são deletadas**. Mesmo padrão do Bug #4 do Associados.
- **Bug #5:** O `media-group-input` já tem o fix. ✅

#### 3. Notícias (`V2/Modules/Publicacoes/Noticias/`)
- **Controller:** `NoticiasCmsController`
- **Bug #1:** Nenhum `dd()` encontrado. ✅
- **Bug #2:** `create()` não passa `$news`. Mas `_editor-content.blade.php` é incluída com `['news' => null]` e a view faz `$isEditing = isset($news) && $news->exists;` — quando `$news` é null, `$isEditing` é false. Todos os acessos usam `$news->title ?? ''`. ✅
- **Bug #3:** `cover_image_id` é coluna direta no modelo `News`. Não há duplicidade em metadata. ✅
- **Bug #4:** Galeria (`media-group-input`) está **dentro** do `<form>` em `_editor-content.blade.php` (linhas 329-347). ✅
- **Bug #5:** O `media-group-input` já tem o fix. ✅

#### 4. Parceiros (`V2/Modules/Publicacoes/Parceiros/`)
- **Controller:** `ParceirosCmsController`
- **Bug #1:** Nenhum `dd()` encontrado. ✅
- **Bug #2:** `create()` retorna view sem `$parceiro`. `create.blade.php` inclui `_form` com `['parceiro' => null]`. `_form.blade.php` faz `$isEditing = isset($parceiro) && $parceiro->exists;` e usa `$parceiro->name ?? ''`. **null-safe**. ✅
- **Bug #3:** `logo_id` é coluna direta. `Parceiro` tem relação `belongsTo(GalleryMedia::class, 'logo_id')`. Sem metadata duplicado. ✅
- **Bug #4:** Não possui galeria (apenas `media-input` para logo, dentro do form). 🔘
- **Bug #5:** O `media-group-input` já tem o fix. ✅

#### 5. Agenda (`V2/Modules/Publicacoes/Agenda/`)
- **Controller:** `EventosCmsController`
- **Bug #1:** Nenhum `dd()` encontrado. ✅
- **Bug #2:** `create()` retorna view sem `$evento`. `create.blade.php` inclui `_form` com `['evento' => null]`. `_form.blade.php` faz `$evento = isset($evento) ? $evento : null` e `$isEditing = !is_null($evento) && $evento->exists;`. Usa `$evento->title ?? ''`. **null-safe**. ✅
- **Bug #3:** `image_id` é coluna direta. Sem metadata inconsistente. ✅
- **Bug #4:** Não possui galeria. Apenas `media-input` para `image_id`, dentro do form. 🔘
- **Bug #5:** O `media-group-input` já tem o fix. ✅

#### 6. Landing Sections Editor (`V2/Services/LandingSectionsEditor/`)
- **Natureza:** Serviço de edição de seções, não é CRUD com formulários persistentes. Opera via AJAX com preview em cache.
- **Bug #1:** Nenhum `dd()` encontrado. ✅
- **Bug #2:** Não possui método `create()` com formulário de modelo. 🔘
- **Bug #3:** Não gerencia metadata de modelos. 🔘
- **Bug #4:** Não possui formulário com galeria. 🔘
- **Bug #5:** Não usa `media-group-input`. O modal de asset picker (`asset-picker-modal`) está no `<body>` via `@push('modals')`. ✅

---

## Ações Realizadas

### 🔴 Crítico — Unidade Regional e Associados: Galeria fora do `<form>` + grid duplicado (Bug #4)

**Arquivos:**
- `V2/Modules/Institucional/UnidadeRegional/Views/cms/edit.blade.php`
- `V2/Modules/Institucional/Associados/Views/cms/instituicoes/edit.blade.php`

**Problema 1:** A galeria (`media-group-input`) estava **fora** da tag `<form>` principal (na UnidadeRegional ficava na sidebar; no Associados estava dentro do form mas também tinha grid duplicado). Os hidden inputs `gallery_media_ids[]` nunca eram submetidos corretamente.

**Problema 2:** Ambos os módulos tinham um grid customizado de thumbnails com `.delete-gallery-item` ABAIXO do `@include('media-group-input')`, fazendo cada imagem aparecer **duas vezes**: uma no preview grid do componente e outra no grid customizado.

**Correção aplicada em 30/07/2026:**

1. **UnidadeRegional: Card da galeria movido para dentro do `<form>`** — estava na sidebar, fora do form.
2. **Ambos: Grid customizado removido** — eliminando a duplicação. O `media-group-input` já renderiza o preview grid com as imagens existentes (linhas 50-64 do componente).
3. **Ambos: Delete forms individuais removidos** — os formulários `destroy-gallery-{id}` (POST para exclusão imediata) foram removidos porque o `media-group-input` usa o fluxo: remover hidden input → submit → `syncGalleryMediaIds()` limpa o banco.
4. **Ambos: JS handler `.delete-gallery-item` removido** — órfão após a remoção do grid customizado.

**Diff resumido:**
```
Antes:
  <div class="col-lg-8">
    <form> ... </form>
  </div>
  <div class="col-lg-4">
    <div class="card"><!-- GALERIA FORA --></div>
    <div class="card-body">
      @include('media-group-input')  ← grid 1
      <div class="row g-2">           ← grid 2 (DUPLICADO)
        @foreach($media as $item)
          <img ...>
          <button class="delete-gallery-item">✕</button>
        @endforeach
      </div>
      @foreach($media as $item)       ← delete forms
        <form id="destroy-gallery-{{ $item->id }}">...</form>
      @endforeach
    </div>
  </div>

Depois:
  <div class="col-lg-8">
    <form>
      <div class="card">
        @include('media-group-input')  ← grid ÚNICO
      </div>
      <button>Salvar</button>
    </form>
    <!-- Sem delete forms, sem grid duplicado -->
  </div>
```

---

### 🔴 Crítico — Unidade Regional: Coluna `gallery_media_id` com tipo UUID incompatível (Bug #6)

**Descoberto em:** 30/07/2026 (pós-correção do Bug #4)

**Arquivo:** `V2/Modules/Institucional/UnidadeRegional/Migrations/2026_07_23_000003_create_v2_unidade_regional_media_table.php`

**Problema:** A migration original definiu `gallery_media_id` como `uuid`, mas a coluna que ela referencia — `v2_gallery_media.id` — é `increments` (unsigned integer). A tabela `v2_associado_instituicao_gallery` usou corretamente `string('gallery_media_id', 36)`, mas a `v2_unidade_regional_media` usou `uuid`, gerando incompatibilidade.

**Sintoma:** Após mover a galeria para dentro do `<form>` (Bug #4), os hidden inputs `gallery_media_ids[]` passaram a ser submetidos com valores inteiros (ex: `95`, `96`). O método `syncGalleryMediaIds()` gerava a query:
```sql
DELETE FROM v2_unidade_regional_media
WHERE gallery_media_id NOT IN (95, 96)
```
PostgreSQL rejeitava porque `95` não é um UUID válido:
```
SQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for type uuid: "95"
```

**Correção aplicada (3 frentes):**

1. **Migration corretiva** — `2026_07_30_000001_fix_gallery_media_id_type.php`:
   - Remove coluna `gallery_media_id` (uuid, sem dados válidos pois o tipo impedia inserção)
   - Recria como `unsignedInteger` (compatível com `v2_gallery_media.id`)

2. **Validação de input (`syncGalleryMediaIds`)**:
   - Filtra `$mediaIds` para aceitar apenas inteiros positivos
   - Converte todos os valores com `intval()` antes de usar na query

3. **Regra de validação no `update()`**:
   - Adicionado `'gallery_media_ids.*' => 'nullable|integer'` na validação do request

**Lições:** Migrations devem usar o mesmo tipo da coluna referenciada. Toda FK deve ter o tipo exato da PK que referência. `uuid` só deve ser usado quando a PK de destino também é uuid.

---

## Prevenção

1. **Nunca commitar `dd()`/`dump()`/`var_dump()`** — revisar antes do commit.
2. **Sempre passar uma instância do modelo para views de criação** — mesmo que vazia, para evitar `Call to a member function on null`.
3. **Manter consistência entre métodos** — se `handleLogoFromGallery` persiste metadata, `handleCoverFromGallery` deve fazer o mesmo.
4. **Componentes de modal devem sempre estar no `<body>`** — jamais aninhados dentro de `<form>` ou elementos com `transform`. Usar `@push('modals')` ou mover via JS.
5. **Elementos com `position: fixed` não podem ter ancestrais com `transform`/`filter`/`perspective`** — verificar a árvore DOM ao diagnosticar modais "invisíveis".
6. **Galeria (`media-group-input`) deve SEMPRE estar dentro do `<form>`** — ou sincronizar via AJAX. Verificar todo `edit.blade.php` que usa o componente.
7. **FK deve ter o mesmo tipo da PK referenciada** — se `v2_gallery_media.id` é `increments` (unsigned integer), a FK deve ser `unsignedInteger`, não `uuid`. Verificar migrations de tabelas que referenciam `v2_gallery_media.id`.

---

## Encerramento da Sessão — 30/07/2026

### Resumo de todas as correções

| # | Bug | Módulo(s) | Arquivo(s) | Tipo |
|---|-----|-----------|------------|------|
| 1 | `dd()` esquecido em `handleLogoFromGallery` | Associados | `InstituicaoCmsController.php` | 🔴 Produção |
| 2 | `create()` não passava `$instituicao` para view | Associados | `InstituicaoCmsController.php` | 🔴 Produção |
| 3 | `handleCoverFromGallery` não persistia `metadata['cover_image_id']` | Associados | `InstituicaoCmsController.php` | 🔴 Produção |
| 4 | Galeria (`media-group-input`) fora do `<form>` + grid duplicado | **Associados, UnidadeRegional** | `edit.blade.php` (ambos) | 🔴 Produção |
| 5 | Modal de crop fora da viewport | **Global** (componente) | `media-group-input.blade.php` | 🔴 Produção |
| 6 | Coluna `gallery_media_id` com tipo `uuid` incompatível | UnidadeRegional | Migration + Controller | 🔴 Produção |
| 7 | Grid duplicado + delete forms órfãos + JS handler órfão | **Associados** (descoberto na varredura) | `instituicoes/edit.blade.php` | 🔴 Produção |

### Arquivos modificados nesta sessão

| Arquivo | Ação |
|---------|------|
| `V2/Modules/Institucional/UnidadeRegional/Views/cms/edit.blade.php` | Galeria movida para dentro do form; grid duplicado removido; delete forms e JS handler removidos |
| `V2/Modules/Institucional/Associados/Views/cms/instituicoes/edit.blade.php` | Grid duplicado removido; delete forms e JS handler removidos |
| `V2/Modules/Institucional/UnidadeRegional/Migrations/2026_07_30_000001_fix_gallery_media_id_type.php` | **Nova migration** — corrige tipo `uuid` → `unsignedInteger` |
| `V2/Modules/Institucional/UnidadeRegional/Controllers/UnidadeRegionalCmsController.php` | Validação de inteiros em `syncGalleryMediaIds()`; regra `integer` no `update()` |
| `V2/Modules/Institucional/Associados/Controllers/InstituicaoCmsController.php` | Mesma validação de inteiros em `syncGalleryMediaIds()` (defense-in-depth) |
| `wiki/v2/troubleshoot-associados-form.md` | Documentação completa de todos os bugs e correções |

### Estado final dos módulos V2

| Módulo | Bug #1 | Bug #2 | Bug #3 | Bug #4 | Bug #5 | Bug #6 | Bug #7 |
|--------|:------:|:------:|:------:|:------:|:------:|:------:|:------:|
| Associados | ✅ | ✅ | ✅ | ✅ | ✅ | 🔘 | ✅ |
| UnidadeRegional | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🔘 |
| Sobre Nós | ✅ | ✅ | ✅ | 🔘 | ✅ | 🔘 | 🔘 |
| Notícias | ✅ | ✅ | ✅ | ✅ | ✅ | 🔘 | 🔘 |
| Parceiros | ✅ | ✅ | ✅ | 🔘 | ✅ | 🔘 | 🔘 |
| Agenda | ✅ | ✅ | ✅ | 🔘 | ✅ | 🔘 | 🔘 |
| LndSctEditor | ✅ | 🔘 | 🔘 | 🔘 | ✅ | 🔘 | 🔘 |
| AssocieSe | ✅ | 🔘 | 🔘 | 🔘 | ✅ | 🔘 | 🔘 |

### Padrão oficial para galerias no CMS V2

Ao implementar uma galeria de imagens em qualquer módulo V2, siga este padrão:

```blade
{{-- Dentro do <form>, antes dos botões de submit --}}
<div class="card mb-4">
    <div class="card-header">
        <h5><i class="bi bi-images me-1 text-primary"></i> Galeria</h5>
        <span class="badge bg-secondary">{{ $model->gallery->count() }}</span>
    </div>
    <div class="card-body">
        @php
            $galleryIds = $model->gallery->pluck('gallery_media_id')->filter()->values()->toArray();
            $galleryUrls = [];
            foreach ($model->gallery as $item) {
                $galleryUrls[] = $item->galleryMedia
                    ? $storageSvc->url($item->galleryMedia->file_path)
                    : $storageSvc->url($item->image);
            }
        @endphp
        @include('v2-service-media::components.media-group-input', [
            'name' => 'gallery', 'label' => '',
            'accept' => 'image/*', 'ownerModule' => 'meu-modulo',
            'existingIds' => $galleryIds, 'existingUrls' => $galleryUrls,
        ])
        {{-- NÃO adicionar grid customizado abaixo — o componente já renderiza previews --}}
        {{-- NÃO adicionar delete forms avulsos — o syncGalleryMediaIds() gerencia --}}
    </div>
</div>
```

### Controller — método `syncGalleryMediaIds` (defense-in-depth)

Sempre filtrar os IDs recebidos antes de usar na query:

```php
protected function syncGalleryMediaIds(Model $model, array $mediaIds)
{
    $mediaIds = array_values(array_filter($mediaIds, function ($id) {
        return is_numeric($id) && (int) $id > 0;
    }));
    $mediaIds = array_map('intval', $mediaIds);
    // ... resto do sync
}
```

---

## Comandos úteis

```bash
# Limpar cache de views (obrigatório após alterar .blade.php)
docker exec <container> php artisan view:clear

# Limpar cache de rotas
docker exec <container> php artisan route:clear

# Ver logs do módulo
docker exec <container> grep "UnidadeRegional\|unidade.regional" storage/logs/laravel-$(date +%Y-%m-%d).log

# Logs em tempo real
docker exec <container> tail -f storage/logs/laravel-$(date +%Y-%m-%d).log
```
