Files
roi-theme/Shared/Application
FrankZamora 14138e7762 feat(exclusions): Implement component exclusion system (Plan 99.11)
Adds ability to exclude components from specific:
- Categories (by slug or term_id)
- Post/Page IDs
- URL patterns (substring or regex)

Architecture:
- Domain: Value Objects (CategoryExclusion, PostIdExclusion,
  UrlPatternExclusion, ExclusionRuleSet) + Contracts
- Application: EvaluateExclusionsUseCase +
  EvaluateComponentVisibilityUseCase (orchestrator)
- Infrastructure: WordPressExclusionRepository,
  WordPressPageContextProvider, WordPressServerRequestProvider
- Admin: ExclusionFormPartial (reusable UI),
  ExclusionFieldProcessor, JS toggle

The PageVisibilityHelper now uses the orchestrator UseCase that
combines page-type visibility (Plan 99.10) with exclusion rules.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 10:51:00 -06:00
..

Capa de Aplicación (Application Layer)

📋 Propósito

La Capa de Aplicación contiene los Use Cases que orquestan la lógica de negocio de la aplicación. Actúa como punto de entrada coordinando el flujo entre la UI/API y la Capa de Dominio.

Esta capa es el corazón de la arquitectura limpia, encapsulando las reglas de negocio específicas de la aplicación sin conocer detalles de implementación.

🎯 Responsabilidades

QUÉ HACE

  • Orquestar flujo de la aplicación: Coordina la secuencia de operaciones necesarias para completar un caso de uso
  • Coordinar llamadas entre capas: Actúa como intermediario entre la presentación y el dominio
  • Transformar datos (DTOs): Utiliza Data Transfer Objects para encapsular entrada/salida
  • Manejar transacciones: Coordina operaciones que deben ejecutarse de forma atómica
  • Validar entrada: Verifica que los datos de entrada sean válidos antes de procesarlos
  • Gestionar cache: Coordina estrategias de cache para optimizar rendimiento

QUÉ NO HACE

  • NO contiene lógica de negocio: La lógica de dominio vive en la Capa de Dominio
  • NO conoce detalles de implementación: Solo depende de interfaces, nunca de implementaciones concretas
  • NO accede directamente a BD: Usa repositorios a través de interfaces
  • NO maneja detalles de UI: No conoce si la petición viene de REST, CLI o WordPress Admin

📁 Estructura

Application/
├── UseCases/
│   ├── SaveComponent/
│   │   ├── SaveComponentUseCase.php      (Orquestador)
│   │   ├── SaveComponentRequest.php      (DTO entrada)
│   │   └── SaveComponentResponse.php     (DTO salida)
│   ├── GetComponent/
│   │   ├── GetComponentUseCase.php
│   │   ├── GetComponentRequest.php
│   │   └── GetComponentResponse.php
│   ├── SyncSchema/
│   │   ├── SyncSchemaUseCase.php
│   │   ├── SyncSchemaRequest.php
│   │   └── SyncSchemaResponse.php
│   └── DeleteComponent/
│       ├── DeleteComponentUseCase.php
│       ├── DeleteComponentRequest.php
│       └── DeleteComponentResponse.php
└── README.md (este archivo)

🔧 Use Cases Disponibles

1. SaveComponent

Propósito: Guardar o actualizar la configuración de un componente.

Flujo:

  1. Validar datos de entrada
  2. Crear/actualizar entidad Component
  3. Persistir en repositorio
  4. Invalidar cache
  5. Retornar confirmación

Ejemplo de uso:

use ROITheme\Shared\Application\UseCases\SaveComponent\SaveComponentUseCase;
use ROITheme\Shared\Application\UseCases\SaveComponent\SaveComponentRequest;

$useCase = new SaveComponentUseCase($repository, $validator, $cache);
$request = new SaveComponentRequest('top_bar', [
    'background_color' => '#ffffff',
    'text_color' => '#000000',
    'show_logo' => true
]);

$response = $useCase->execute($request);

if ($response->isSuccess()) {
    echo "Componente guardado exitosamente";
} else {
    echo "Error: " . $response->getError();
}

2. GetComponent

Propósito: Obtener la configuración de un componente con cache-first strategy.

Flujo:

  1. Verificar cache
  2. Si existe en cache → retornar
  3. Si no → consultar repositorio
  4. Guardar en cache
  5. Retornar datos

Ejemplo de uso:

use ROITheme\Shared\Application\UseCases\GetComponent\GetComponentUseCase;
use ROITheme\Shared\Application\UseCases\GetComponent\GetComponentRequest;

$useCase = new GetComponentUseCase($repository, $cache);
$request = new GetComponentRequest('top_bar');

$response = $useCase->execute($request);

if ($response->isSuccess()) {
    $data = $response->getData();
    // Procesar datos del componente
} else {
    echo "Error: " . $response->getError();
}

3. SyncSchema

Propósito: Sincronizar múltiples componentes desde un archivo JSON de schemas.

Flujo:

  1. Leer archivo JSON
  2. Validar formato
  3. Procesar cada componente del schema
  4. Identificar cambios (agregados/actualizados/eliminados)
  5. Retornar resumen de cambios

Ejemplo de uso:

use ROITheme\Shared\Application\UseCases\SyncSchema\SyncSchemaUseCase;
use ROITheme\Shared\Application\UseCases\SyncSchema\SyncSchemaRequest;

$useCase = new SyncSchemaUseCase($componentRepo, $defaultsRepo);
$request = new SyncSchemaRequest('/path/to/schemas.json');

$response = $useCase->execute($request);

if ($response->isSuccess()) {
    echo $response->getSummary();
    // Output: "Added: 2, Updated: 3, Deleted: 1"
} else {
    foreach ($response->getErrors() as $error) {
        echo "Error: {$error}\n";
    }
}

4. DeleteComponent

Propósito: Eliminar un componente del sistema.

Flujo:

  1. Verificar que el componente existe
  2. Eliminar de repositorio
  3. Invalidar cache
  4. Retornar confirmación

Ejemplo de uso:

use ROITheme\Shared\Application\UseCases\DeleteComponent\DeleteComponentUseCase;
use ROITheme\Shared\Application\UseCases\DeleteComponent\DeleteComponentRequest;

$useCase = new DeleteComponentUseCase($repository, $cache);
$request = new DeleteComponentRequest('old_component');

$response = $useCase->execute($request);

if ($response->isSuccess()) {
    echo $response->getMessage();
} else {
    echo "Error: " . $response->getError();
}

🏛️ Principios Arquitectónicos

Regla de Dependencias

Application Layer
       ↓ (depende de)
Domain Layer (solo interfaces)
       ↑
NO depende de Infrastructure Layer

La capa de aplicación solo puede depender de la capa de dominio, y únicamente de sus interfaces, nunca de implementaciones concretas.

Dependency Inversion Principle

CORRECTO:

namespace ROITheme\Shared\Application\UseCases\SaveComponent;

use ROITheme\Shared\Domain\Contracts\ComponentRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\ValidationServiceInterface;

final class SaveComponentUseCase
{
    public function __construct(
        private ComponentRepositoryInterface $repository,    // ✅ Interfaz
        private ValidationServiceInterface $validator,       // ✅ Interfaz
        private CacheServiceInterface $cache                 // ✅ Interfaz
    ) {}
}

INCORRECTO:

use ROITheme\Infrastructure\Persistence\WordPress\WordPressComponentRepository;

final class SaveComponentUseCase
{
    public function __construct(
        private WordPressComponentRepository $repository  // ❌ Implementación concreta
    ) {}
}

DTOs Inmutables

Todos los Request y Response utilizan readonly class para garantizar inmutabilidad:

final readonly class SaveComponentRequest
{
    public function __construct(
        private string $componentName,
        private array $data
    ) {}

    // Solo getters, sin setters
    public function getComponentName(): string
    {
        return $this->componentName;
    }
}

Factory Methods en Responses

Los responses utilizan factory methods para encapsular lógica de creación:

final readonly class SaveComponentResponse
{
    // Constructor privado
    private function __construct(
        private bool $success,
        private ?string $error
    ) {}

    // Factory methods públicos
    public static function success(): self
    {
        return new self(true, null);
    }

    public static function failure(string $error): self
    {
        return new self(false, $error);
    }
}

🧪 Testing

Estrategia de Testing

Todos los Use Cases tienen tests unitarios con mocks de dependencias:

namespace ROITheme\Tests\Unit\Application\UseCases;

use PHPUnit\Framework\TestCase;
use ROITheme\Shared\Application\UseCases\SaveComponent\SaveComponentUseCase;
use ROITheme\Shared\Domain\Contracts\ComponentRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\ValidationServiceInterface;

class SaveComponentUseCaseTest extends TestCase
{
    private ComponentRepositoryInterface $repository;
    private ValidationServiceInterface $validator;
    private SaveComponentUseCase $useCase;

    protected function setUp(): void
    {
        $this->repository = $this->createMock(ComponentRepositoryInterface::class);
        $this->validator = $this->createMock(ValidationServiceInterface::class);

        $this->useCase = new SaveComponentUseCase(
            $this->repository,
            $this->validator,
            $this->cache
        );
    }

    public function test_saves_component_successfully(): void
    {
        // Test implementation
    }
}

Ubicación de Tests

tests/Unit/Application/UseCases/
├── SaveComponentUseCaseTest.php
├── GetComponentUseCaseTest.php
├── SyncSchemaUseCaseTest.php
└── DeleteComponentUseCaseTest.php

Cobertura Esperada

  • Objetivo: 95%
  • Mínimo aceptable: 90%

Ejecutar Tests

# Todos los tests de Application
cd _planeacion/roi-theme/_testing-suite
vendor/bin/phpunit tests/Unit/Application

# Test específico
vendor/bin/phpunit tests/Unit/Application/UseCases/SaveComponentUseCaseTest.php

# Con cobertura
vendor/bin/phpunit tests/Unit/Application --coverage-text

# Reporte HTML de cobertura
vendor/bin/phpunit tests/Unit/Application --coverage-html coverage

🔍 Requisitos Técnicos

Todos los archivos de la capa de aplicación deben cumplir:

1. Strict Types

<?php
declare(strict_types=1);  // ✅ Obligatorio en todos los archivos

2. Namespace Correcto

namespace ROITheme\Shared\Application\UseCases\{UseCase};

Patrón: Context-FirstROITheme\Shared\Application\...

3. PHPDoc Completo

/**
 * SaveComponentUseCase - Guardar/actualizar componente
 *
 * RESPONSABILIDAD: Orquestar guardado de componente
 * ...
 *
 * @package ROITheme\Shared\Application\UseCases\SaveComponent
 */
final class SaveComponentUseCase
{
    // ...
}

4. Type Hints Estrictos

// ✅ CORRECTO
public function execute(SaveComponentRequest $request): SaveComponentResponse
{
    // ...
}

// ❌ INCORRECTO
public function execute($request)  // Sin type hints
{
    // ...
}

📚 Referencias

  • Documentación de Fase: _planeacion/roi-theme/_MIGRACION-CLEAN-ARCHITECTURE/Fase-05/FASE-05-PLAN-IMPLEMENTACION.md
  • Clean Architecture: Robert C. Martin
  • Dependency Inversion Principle: SOLID Principles

Checklist de Validación

Para validar que un Use Case cumple con los estándares:

  • Usa declare(strict_types=1)
  • Namespace correcto: ROITheme\Shared\Application\UseCases\{UseCase}
  • PHPDoc completo en clase
  • Solo depende de interfaces del Domain
  • Request es final readonly class
  • Response es final readonly class con factory methods
  • UseCase es final class
  • Constructor injection de dependencias
  • Método execute() con type hints
  • Tests unitarios con >= 90% cobertura
  • Sin dependencias de Infrastructure

🚀 Próximos Pasos

Después de completar la Capa de Aplicación:

  1. Fase 6: Infrastructure Layer (Repositorios, Adaptadores)
  2. Fase 7: API Layer (REST Endpoints)
  3. Fase 8: Integration Tests
  4. Fase 9: Migración gradual del código legacy

Mantenedor: ROI Theme Development Team Última actualización: 2025-01-19 Versión: 1.0.0