---
id: ARCH-OVERVIEW
tipo: arquitetura
onda: 1
status: concluido
unidades: [ARCH-OVERVIEW]
fontes:
  - V2/README.md
depende_de: []
consumidores: [CORE-BOOT, MOD-INDEX, GRP-API, GRP-ATOSOFICIAIS, GRP-CONEXOES, GRP-INFRAESTRUTURA, GRP-LAYOUTS, GRP-PUBLICACOES, GRP-RELACIONAMENTOS, GRP-SUPPORT, GRP-UTILITARIOS, GRP-_EXEMPLOS]
criticidade: alta
dono: BootNavigation
---

# Arquitetura: visão geral do V2

> **Legado × alvo.** Este documento descreve o V2 (Laravel 5.5). A reconstrução usa
> `src/core|services|layout|modules` (PHP 8.4/Laravel 12), com descoberta + manifesto em
> cache e fronteira física entre camadas. Preserve o contrato de pastas/nomes de view;
> troque a implementação. Ver [`pilha-alvo-public-v7.md`](../09_reimplementacao/pilha-alvo-public-v7.md).

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | O repositório passa a servir o portal público e o CMS a partir de uma única aplicação Laravel 5.5, em que toda rota, view e migration nascem dentro de `V2/Modules/<Grupo>/<Modulo>/`, `V2/Core/` ou `V2/Services/`. |
| Entrada | Requisição HTTP (portal, `/cms`, `/api/v2/...`), comando `php artisan` e classes `ModuleProvider` descobertas em disco. |
| Saída | Rotas registradas, views nos namespaces `v2-*` e `v2-service-*`, migrations carregadas, menu lateral do CMS montado por grupo. |
| Efeitos colaterais | Varredura de diretórios a cada boot; consulta a `v2_module_flags` na montagem da sidebar e na checagem `module.active`; seed silencioso do índice de busca para `BasicPage` no console. |
| Particularidade central | O contrato de pastas **é** o mecanismo de integração: não há arquivo de registro de módulos; o nome do diretório vira namespace de classe, namespace de view, prefixo de API e chave de permissão. |
| Como se integra | `V2\Core\Providers\V2ServiceProvider` (registrado em `config/app.php:185`), `V2\Core\Contracts\ModuleProviderInterface`, helper `v2_can()` e diretiva Blade `@v2can`. |
| Se removido, quebra | Não há outra fonte de rotas: sem o provider, nenhuma rota do portal ou do CMS é registrada (`V2/Core/Providers/V2ServiceProvider.php:85-137`). |

## 1. Efeito

O V2 é uma aplicação Laravel monolítica modularizada por convenção de diretório. O que ele garante, de forma observável:

1. **Toda pasta em `V2/Modules/<Grupo>/<Modulo>` é um módulo** e é percorrida no boot; se tiver `ModuleProvider`, entra no menu e na checagem de permissão; se tiver `Migrations/`, `Views/`, `Routes/`, esses artefatos são carregados automaticamente (`V2/Core/Providers/V2ServiceProvider.php:92-130`).
2. **Toda pasta em `V2/Services/<Servico>` é um pacote compartilhado** com migrations, views e rotas próprias, publicado no namespace `v2-service-<slug>` e na API `api/v2/<slug>` (`V2/Core/Providers/V2ServiceProvider.php:168-191,203-231`).
3. **O Core fornece rotas globais** (login/logout, dashboard, busca, tags, fallback de página) antes de qualquer módulo (`V2/Core/Providers/V2ServiceProvider.php:85,256-281`).
4. **O portal só responde em `/{slug}` quando nada mais responde**, porque o catch-all é registrado depois dos módulos (`V2/Core/Routes/fallback.php:7-21`).
5. **O CMS só é acessível a usuários Sentinel com ao menos um papel**; a checagem fina é por módulo, usando o nome do diretório como chave (`V2/Core/Middleware/CmsAuthenticate.php:21-44`, `V2/Core/Middleware/V2PermissionMiddleware.php:35-99`).

O que **não** acontece: não existe `routes/web.php` na raiz do projeto nem `RouteServiceProvider` em `app/Providers/`; o único carregador de rotas é o `V2ServiceProvider`. Isso foi confirmado pela ausência de `routes/` e pela lista `app/Providers/` conter apenas `AppServiceProvider`, `AuthServiceProvider` e `EventServiceProvider`.

### 1.1 Camadas

| Camada | Caminho | Papel | Evidência |
|---|---|---|---|
| Infraestrutura | `V2/Core/` | Middlewares, contratos, rotas, migrations base, helpers, serviços de permissão/escopo regional. | `V2/README.md:9` |
| Domínio | `V2/Modules/<Grupo>/<Modulo>/` | Regras de negócio isoladas; controllers `Cms`/`Portal`/`Api`, models, views, rotas. | `V2/README.md:10,17-19` |
| Compartilhado | `V2/Services/<Servico>/` | Macro-componentes com backend + Blade + migrations + rotas. | `V2/README.md:11`; `V2/Core/Providers/V2ServiceProvider.php:168-191` |
| Layouts globais | `V2/layouts/cms`, `V2/layouts/portal` | Namespaces `v2-cms` e `v2-portal`, registrados no boot. | `V2/Core/Providers/V2ServiceProvider.php:38-43` |
| Dados iniciais | `V2/Seeds/` | Seeders de páginas e conteúdo; não são descobertos automaticamente. | `V2/Core/Providers/V2ServiceProvider.php:145-165` |

### 1.2 Divisão de contexto dentro do módulo

O README fixa três contextos e uma convenção de nomes de controller:

| Contexto | Sufixo do controller | Pasta de view convencionada | Rota |
|---|---|---|---|
| CMS | `...CmsController` | `Views/cms/` | `/cms/<slug>` |
| Portal | `...PortalController` | `Views/portal/` | `/...` |
| API | `...ApiController` | — | `/api/v2/<slug-do-modulo>` |

Fontes: `V2/README.md:17-19,23-25`; `V2/Modules/README.md:16-18`. A pasta `Views/cms/` é convenção, não imposição: o provider apenas registra `Views/` inteiro sob `v2-<modulo-minusculo>` (`V2/Core/Providers/V2ServiceProvider.php:121-124`), então o caminho `cms.index` é escolhido pelo controller.

## 2. Abordagem

### 2.1 Fluxo de uma requisição

```mermaid
flowchart TD
  A[HTTP request] --> B[Kernel global: CspMiddleware, HtmlMinify]
  B --> C[Grupo web]
  C --> D{Rota registrada?}
  D -- sim --> E[CmsAuthenticate? module.active?]
  E --> F[Controller do módulo]
  D -- não --> G[Fallback GET /{slug}]
  G --> H[BasicPagePortalController@runtime]
  H -- página inexistente --> I[404 Laravel]
  F --> J[View v2-<modulo>::...]
  J --> K[v2-cms::layouts.app ou v2-portal::app-layout]
```

O registro das rotas, porém, acontece **antes** da requisição: o provider percorre o disco no `boot()` e monta a tabela de rotas na ordem Core → módulos → fallback → serviços (`V2/Core/Providers/V2ServiceProvider.php:85,92-130,137,168-191`).

### 2.2 Mecânica passo a passo do desenho

1. `register()` mescla `config/v2.php` na chave `v2` e carrega `V2/Core/Helpers/helpers.php` com `require_once`; também registra `PermissionService` (apenas se `ActivityLoggerService` existir) e `ModuleFlagService` como singletons (`V2/Core/Providers/V2ServiceProvider.php:345-364`).
2. `boot()` registra as diretivas `@v2can`/`@endv2can`, os namespaces `v2-cms` e `v2-portal`, o `SidebarComposer` e o composer global do portal (`:30-64`).
3. Carrega as migrations do Core e as rotas do Core (`:79-85`).
4. Para cada grupo (diretório de `V2/Modules`) e cada módulo (subdiretório), instancia `ModuleProvider`, injeta `group`, carrega migrations/views/rotas (`:92-130`).
5. Publica o mapa de módulos em `v2.modules.configs`, registra o fallback e atualiza os índices de nomes de rota (`:133-142`).
6. No console, sincroniza `BasicPage` não indexadas com `v2_public_search_index` (`:145-165`).
7. Por último, percorre `V2/Services` e registra migrations, views e rotas de serviço (`:168-191`).

### 2.3 Trechos de código pivô

```php
// V2/Core/Providers/V2ServiceProvider.php:100-124 — o diretório é a identidade do módulo.
$moduleName = basename($modulePath);
$moduleNamespace = strtolower($moduleName);

$providerClass = "V2\\Modules\\{$groupName}\\{$moduleName}\\ModuleProvider";
if (class_exists($providerClass)) {
    $provider = new $providerClass();
    if ($provider instanceof \V2\Core\Contracts\ModuleProviderInterface) {
        $config = $provider->getConfig();
        $config['group'] = $groupName;   // grupo vem do disco, não do getConfig()
        $moduleConfigs[$moduleName] = $config;
    }
}
// ...
$this->loadViewsFrom($viewPath, 'v2-' . $moduleNamespace);
```

Mostra que `group` é derivado do diretório físico e sobrescreve qualquer valor devolvido por `getConfig()`, e que o namespace de view é o nome do módulo em minúsculas — `ExemploAPI` vira `v2-exemploapi`, não `v2-exemplo-api`.

```php
// V2/README.md:9-11 — as três pastas que compõem o sistema.
- **`/Core`**: Infraestrutura vital. Contém middlewares, controladores base, provedores de serviço e rotas globais.
- **`/Modules`**: Onde as funcionalidades de negócio residem. Cada pasta aqui é um domínio isolado (Ex: Noticias, Licitações).
- **`/Services`**: Macro-componentes e lógica compartilhada que cruzam múltiplos módulos (Ex: Auditoria, Uploads).
```

É o contrato declarado; o código confirma as três varreduras no boot e acrescenta `V2/layouts` e `V2/Core/Migrations` como fontes adicionais de views/migrations.

```php
// V2/Core/Helpers/helpers.php:12-27 — v2_can(): permissão por módulo, com bypass de suporte.
function v2_can(string $moduleKey, string $action = 'read'): bool
{
    if (!\Sentinel::check()) {
        return false;
    }

    $user = \Sentinel::getUser();

    // Suporte sempre passa (email centralizado via config/v2.php)
    if ($user->email === config('v2.support.email')) {
        return true;
    }

    $service = app(\V2\Core\Services\PermissionService::class);
    return $service->hasPermission($user, $moduleKey, $action);
}
```

É o contrato global de autorização fina usado por controllers e views; a chave é o **nome do módulo**, não o slug da URI.

```php
// V2/Core/Contracts/ModuleProviderInterface.php:6-14 — único contrato obrigatório de módulo.
interface ModuleProviderInterface
{
    /**
     * Retorna a configuração do módulo para o CMS.
     */
    public function getConfig(): array;
}
```

Um módulo sem `ModuleProvider` continua tendo rotas e views carregadas, mas não aparece no menu nem entra no mapa de permissões — logo, não é filtrável por RBAC (ver §3).

```php
// V2/Core/Contracts/RegionalOwnable.php:12-38 — o Core não importa models de módulo.
interface RegionalOwnable
{
    public function scopeByUnidadeRegional($query, $unidadeId);
    public function belongsToUnidadeRegional($unidadeId): bool;
    public function getUnidadeRegionalId();
}
```

Contrato opcional para escopo regional: qualquer model de módulo pode ser filtrado por `RegionalScopeService` sem que o Core conheça a classe.

### 2.4 Decisões e restrições

- **Convenção sobre configuração.** Não há `modules.json`; o sistema descobre módulos por `File::directories()` (`:92,96`). Custo: leitura de diretórios a cada boot; benefício: criar uma pasta basta para o módulo existir.
- **Ordem de boot explícita.** Core → módulos → fallback → serviços (`:85,137,191`). O comentário no código declara a intenção de o fallback nunca bloquear rotas específicas (`:135-136`).
- **`group` vem do disco.** Mesmo que um `getConfig()` declare `'group' => 'X'`, o provider sobrescreve com o nome do diretório (`:109`). Isso garante que mover a pasta muda o grupo e, com ele, o bloqueio de Support/API.
- **Compatibilidade Laravel 5.5 / PHP 7.4.** `composer.json:7,15` fixa `laravel/framework: 5.5.*` e `php >=7.4.0`; o código usa `Route::redirect`, `refreshNameLookups` e `Sentinel` (V1) em vez de recursos modernos.

## 3. Particularidades

### 3.1 O que só existe aqui

- **Identidade tríplice pelo nome da pasta**: `V2\Modules\{Grupo}\{Modulo}\Controllers` (namespace PHP), `v2-{modulo-minusculo}` (view), `api/v2/{modulo-minusculo}` (API). Um rename de pasta quebra as três ao mesmo tempo.
- **Injeção de grupo no boot** (`V2/Core/Providers/V2ServiceProvider.php:109`) é o único lugar que define a que grupo um módulo pertence para efeito de menu e RBAC.
- **Fallback como serviço do Core**, não do módulo `BasicPage`: o arquivo está em `V2/Core/Routes/fallback.php` e referencia o controller com FQCN (`:17-21`), justamente para não depender do namespace do grupo.
- **Seed de busca só no console** (`:145-165`): a indexação de `BasicPage` em `v2_public_search_index` não roda em requisição web, e qualquer exceção é engolida com `Log::warning`.

### 3.2 Invariantes

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| RN-ARCH-01 | Toda pasta de `V2/Modules` é grupo; toda subpasta é módulo. | Pasta criada sem `ModuleProvider` já ganha rotas/views; sem provider não entra no menu. | `V2/Core/Providers/V2ServiceProvider.php:92-130` |
| RN-ARCH-02 | `group` é sempre o nome do diretório do grupo. | `getConfig()['group']` é ignorado. | `V2/Core/Providers/V2ServiceProvider.php:109` |
| RN-ARCH-03 | O fallback é o último registro de rota de módulo/portal. | `GET /{slug}` não sombreia `/noticias` nem `/busca`. | `V2/Core/Providers/V2ServiceProvider.php:135-137`; `V2/Core/Routes/fallback.php:7-21` |
| RN-ARCH-04 | Serviços são registrados depois do fallback. | Suas rotas (`cms/...`, `api/v2/<slug>`) não colidem com `/{slug}`, mas dependem dessa ordem. | `V2/Core/Providers/V2ServiceProvider.php:168-191` |
| RN-ARCH-05 | `v2.modules.configs` é a fonte do menu e do RBAC. | Módulo sem provider não é ocultado nem bloqueado por permissão. | `V2/Core/Providers/V2ServiceProvider.php:133`; `V2/Core/Middleware/V2PermissionMiddleware.php:35` |

### 3.3 Casos-limite

- **Dois módulos com o mesmo nome em grupos diferentes**: `$moduleConfigs[$moduleName]` (`:110`) e `v2-<nome>` (`:123`) colidem; no menu a última varredura sobrescreve a entrada e, nas views, o primeiro path registrado vence (o segundo fica inalcançável). Hoje não há nomes duplicados em `V2/Modules/*/*`.
- **Módulo com nome composto**: `StructuralLandingEditor` produz namespace de view `v2-structurallandingeditor` e prefixo `api/v2/structurallandingeditor`; não há conversão para `snake_case` ou `kebab-case`. [C] `V2/Core/Providers/V2ServiceProvider.php:100,123,310`
- **`ModuleProvider` que lança exceção**: não há `try/catch` em volta de `new $providerClass()`/`getConfig()` (`:104-110`); o boot inteiro falha. [C]
- **Console sem as tabelas de busca**: as três condições (`class_exists`, `runningInConsole`, `Schema::hasTable` duas vezes) evitam erro de boot em banco vazio (`:146-150`).
- **Serviço cujo nome termina em `Service`**: o slug de API remove o sufixo; `MailService` vira `api/v2/mail`, `Gallery` vira `api/v2/gallery` (`:239-251`). [C]

### 3.4 Divergências README × código

| Afirmação documentada | Código | Consequência |
|---|---|---|
| "UUIDv7" (`V2/Modules/README.md:13`) | `v2_gen_uuid()` gera UUID **v4** com bits de versão `0100` e variante `10` (`V2/Core/Migrations/2026_06_01_000001_create_v2_uuid_function.php:24-33`) | Não se pode assumir ordenação temporal por PK. |
| "Views prefixadas pelo namespace `v2-[modulo]::` (Ex: `v2-noticias::cms.index`)" (`V2/README.md:25`) | `'v2-' . strtolower($moduleName)` (`V2/Core/Providers/V2ServiceProvider.php:100,123`) | Para `ExemploAPI` o namespace real é `v2-exemploapi`; `v2-exemplo-api` não existe. |
| "Namespace: Use `V2\Modules\[NomeModulo]`" (`V2/Modules/README.md:25`) | `V2\Modules\{group}\{module}` (`V2/Core/Providers/V2ServiceProvider.php:103,296`) | Não há suporte a módulo solto na raiz de `Modules/`. |
| "Copie o Módulo `/Exemplo`" (`V2/Modules/README.md:24`; `V2/README.md:28`) | O caminho real é `V2/Modules/_Exemplos/ExemploAPI` | O grupo `_Exemplos` também é carregado como módulo e tem restrição de acesso. |
| "Toda view V2 nasce com suporte a eMAG 3.1 e WCAG 2.1 AAA" (`V2/README.md:21`) | É convenção de autoria; não há validação automática no boot | Acessibilidade não é garantida por mecanismo. |
| "performance assíncrona" (`V2/README.md:3`) | Rotas e serviços não usam fila no boot; apenas cache HTTP (`page.cache`, `public.cache`) | Assíncrono é objetivo declarado, não contrato verificável. |

### 3.5 V1 residual

O V1 não foi removido; ele permanece como dependência estrutural:

| Resíduo V1 | Onde | Efeito no V2 |
|---|---|---|
| Sentinel (autenticação/papéis) | `V2/Core/Middleware/CmsAuthenticate.php:7,21,29`; `V2/Core/Helpers/helpers.php:14,18` | Todo o CMS depende de `Sentinel::check()`/`getUser()`; o papel `super-admin` é V1. |
| Tabelas Sentinel criadas pelas migrations V2 | `V2/Core/Migrations/2026_06_01_000003_create_sentinel_tables.php` | O V2 nasce sobre o schema de autenticação do V1. |
| Middlewares de framework em `App\Http\Middleware` | `app/Http/Kernel.php:17-41` (`EncryptCookies`, `VerifyCsrfToken`, `TrimStrings`, `TrustProxies`) | O grupo `web` do V2 depende deles. |
| Seeds em `database/seeds` | `database/DatabaseSeeder.php` | Fora da descoberta automática do V2; executados manualmente. |
| `super-admin` como bypass | `V2/Core/ViewComposers/SidebarComposer.php:53,60`; `V2/Core/Middleware/V2PermissionMiddleware.php:70` | O RBAC V2 é contornado por um papel V1. |
| Comentários "PHP 7.2 compatível" | `V2/Modules/_Exemplos/ExemploAPI/Controllers/ExemploAPICmsController.php:16` | Divergência com `composer.json:7` (`>=7.4`). |

`REFERENCE/` contém HTMLs de design e não participa do runtime; está fora do escopo do corpus conforme `_contrato/TAXONOMIA.md` §11.

## 4. Integrabilidade

### 4.1 Contratos globais que todo módulo herda

| Contrato | Forma | Precondição | Consumidor |
|---|---|---|---|
| Provider | `implements V2\Core\Contracts\ModuleProviderInterface::getConfig(): array` | Classe no namespace `V2\Modules\<Grupo>\<Modulo>` | `V2ServiceProvider` (`:103-110`) |
| Permissão | `v2_can('<Modulo>', 'read'|'write')` | Usuário Sentinel autenticado | Controllers/views |
| Diretiva Blade | `@v2can('<Modulo>', 'write') ... @endv2can` | Diretiva registrada no boot (`:30-35`) | Views CMS |
| View namespace | `v2-<modulo-minusculo>::<pasta>.<arquivo>` | Pasta `Views/` existente | Controllers e `@include` |
| Rotas | `Routes/{web,cms,api}.php` | Arquivo existente | `mapModuleRoutes` (`:287-313`) |
| Migrations | `Migrations/` | Diretório existente | `loadMigrationsFrom` (`:114-118`) |

### 4.2 Receitas de uso

**Criar um módulo mínimo** (adaptado de `V2/Modules/README.md:22-27` e do provider):

```text
V2/Modules/MeuGrupo/MeuModulo/
├── ModuleProvider.php          # implements ModuleProviderInterface
├── Controllers/MeuModuloCmsController.php
├── Routes/cms.php              # Route::get('/', 'MeuModuloCmsController@index')->name('v2.meumodulo.index')
└── Views/cms/index.blade.php   # @extends('v2-cms::layouts.app'); usada como v2-meumodulo::cms.index
```

Efeito esperado: menu em `MeuGrupo`, rota `GET /cms/...` protegida por `CmsAuthenticate` + `module.active:MeuModulo`, view resolvida sem registro manual.

**Consumir um serviço compartilhado** (`V2/Services/README.md:24-34`):

```php
$logger = app(\V2\Services\ActivityLogger\ActivityLoggerService::class);
$logger->log($model, 'updated', ['campo' => 'valor']);
```

```blade
@component('v2-service-activitylogger::cms.timeline', ['model' => $item]) @endcomponent
```

### 4.3 Anti-uso

1. **Registrar rota fora de `Routes/`**: não é carregada. O provider só inclui `web.php`, `cms.php`, `api.php` do módulo (`:289-292`).
2. **Confiar em `getConfig()['group']`**: o valor é sobrescrito pelo nome do diretório (`:109`); um módulo colocado em `Modules/API` fica invisível na sidebar mesmo que declare outro grupo (`V2/Core/ViewComposers/SidebarComposer.php:36-40`).
3. **Assumir que o nome do diretório é o slug da rota**: o provider não cria prefixo nem nome de rota para CMS; quem declara `Route::group(['prefix' => 'cms/...'])` é o arquivo de rotas (ver `V2/Modules/_Exemplos/ExemploAPI/Routes/cms.php:20-27`).

## 5. Inventário

| Arquivo | Papel | Efeito | Fonte |
|---|---|---|---|
| `V2/README.md` | Contrato de arquitetura declarado | Define Core/Modules/Services, contextos e convenções de nome | `V2/README.md:1-28` |
| `V2/Core/Providers/V2ServiceProvider.php` | Motor de boot | Descobre módulos/serviços, registra views, migrations e rotas | `V2/Core/Providers/V2ServiceProvider.php:27-364` |
| `V2/Core/Contracts/ModuleProviderInterface.php` | Contrato de menu | Torna o módulo visível/filtrável | `:6-14` |
| `V2/Core/Contracts/RegionalOwnable.php` | Contrato de escopo regional | Permite filtro genérico por unidade | `:12-38` |
| `V2/Core/Helpers/helpers.php` | Funções globais | `v2_can`, `v2_is_regional_operator`, `v2_uuid` etc. | `:12-27` |
| `V2/Modules/README.md` | Anatomia de módulo | Checklist de criação | `V2/Modules/README.md:5-32` |
| `V2/Services/README.md` | Anatomia de serviço | Namespaces `v2-service-*` | `V2/Services/README.md:1-37` |

## 6. Riscos de reimplementação

1. **Ordem de boot** é o contrato mais frágil: core → módulos → fallback → serviços. Reimplementar com descoberta paralela ou fallback antecipado muda o roteamento observável.
2. **`strtolower` sem normalização** cria namespaces previsíveis apenas para nomes simples; qualquer reimplementação que “melhore” para kebab-case quebra views existentes.
3. **RBAC acoplado ao nome do diretório** (`$moduleConfigs[$moduleName]`) implica migração de permissões ao renomear módulos.
4. **Bypass duplo de `super-admin` e do e-mail de suporte** precisa ser reproduzido; caso contrário, o suporte perde acesso a todos os módulos.
5. **`group` derivado do disco** deve ser preservado; um registro explícito de grupos mudaria o comportamento de módulos movidos.

## 7. Lacunas

- **LG-ARCH-01** — Não há inventário de quem consome `v2.modules.configs` além de `SidebarComposer` e `V2PermissionMiddleware`; busca textual cobre o repositório, mas código gerado em runtime não é verificável. Onde procurar: `grep -rn "v2.modules.configs"`.
- **LG-ARCH-02** — O README cita `/Services` como “macro-componentes e lógica compartilhada”, mas não define quais serviços são obrigatórios para o portal subir; a ordem de dependência real entre `SEO`, `Search`, `Media` e `Gallery` está fora do escopo deste documento (ver docs `SVC-*`).
- **LG-ARCH-03** — Não foi localizado `RouteServiceProvider`; presume-se que o Kernel do Laravel 5.5 não registre rotas por conta própria. [I] A inferência vem de `app/Providers/` não conter o provider e de `config/app.php:185` registrar apenas o `V2ServiceProvider` para rotas.

## 8. Checklist de paridade

- [ ] `PC-ARCH-01` — Criar pasta `V2/Modules/Teste/GrupoX/ModuloY/Views/cms/index.blade.php` e um `Routes/cms.php`; a view deve resolver em `v2-moduloy::cms.index` sem registro manual. Fonte: `V2ServiceProvider.php:100,121-124`.
- [ ] `PC-ARCH-02` — `ModuleProvider::getConfig()` retornando `'group' => 'Outro'` deve continuar aparecendo no grupo do diretório. Fonte: `V2ServiceProvider.php:109`.
- [ ] `PC-ARCH-03` — `GET /busca` deve ir para `SearchController@portalSearch` mesmo com o fallback existente. Fonte: `V2/Core/Routes/web.php:14`; `fallback.php:19-21`.
- [ ] `PC-ARCH-04` — `GET /slug-inexistente-123` deve retornar 404 após `BasicPagePortalController@runtime` não encontrar página. Fonte: `fallback.php:10-12`.
- [ ] `PC-ARCH-05` — `v2_can('Qualquer', 'read')` com e-mail `config('v2.support.email')` deve retornar `true`. Fonte: `helpers.php:21-23`.
- [ ] `PC-ARCH-06` — Módulo sem `ModuleProvider` deve ter rotas carregadas e **não** aparecer no menu. Fonte: `V2ServiceProvider.php:104-111,133`.

## 9. Evidências consultadas

- `V2/README.md:1-28`
- `V2/Core/README.md:1-20`
- `V2/Modules/README.md:1-32`
- `V2/Services/README.md:1-37`
- `V2/Core/Providers/V2ServiceProvider.php:27-364`
- `V2/Core/Contracts/ModuleProviderInterface.php:1-14`
- `V2/Core/Contracts/RegionalOwnable.php:1-39`
- `V2/Core/Helpers/helpers.php:1-60`
- `V2/Core/Middleware/CmsAuthenticate.php:1-46`
- `V2/Core/Middleware/V2PermissionMiddleware.php:1-120`
- `V2/Core/Middleware/ModuleAccessMiddleware.php:1-67`
- `V2/Core/Services/ModuleFlagService.php:42-95`
- `V2/Core/ViewComposers/SidebarComposer.php:20-101`
- `V2/Core/Routes/{web,cms,api,fallback}.php`
- `V2/Core/Migrations/2026_06_01_000001_create_v2_uuid_function.php:19-36`
- `V2/Modules/_Exemplos/ExemploAPI/{ModuleProvider.php,Routes/cms.php,Controllers/ExemploAPICmsController.php}`
- `config/app.php:171-187`; `config/v2.php:4-11`; `composer.json:7,15`; `app/Http/Kernel.php:17-70`; `app/Console/Kernel.php:14-17`
