# 🐕 IMPLEMENTACIÓN SPECIES MODULE

## **📋 OBJETIVO ESPECÍFICO**

Implementar únicamente la gestión de **Species (Especies)** como tabla raíz independiente del catálogo de animales, siguiendo la arquitectura modular establecida.

---

## **🎯 ANÁLISIS SPECIES MODULE**

### **🔹 ENTIDAD A IMPLEMENTAR:**

**Species (Especies)**
- **Tabla raíz independiente** - Sin dependencias externas
- **Categorías principales**: Perro, Gato, Conejo, Caballo, etc.
- **CRUD básico completo**: Crear, listar, editar y eliminar especies
- **Fundación para Breeds**: Base sobre la cual se construirán las razas

### **🔹 CARACTERÍSTICAS CLAVE:**
- ✅ **Independiente**: No requiere otras entidades para funcionar
- ✅ **Nombres únicos**: Cada especie debe tener un nombre único
- ✅ **Soft deletes**: Eliminación lógica para mantener integridad histórica
- ✅ **Validaciones robustas**: Control de integridad antes de eliminar
- ✅ **API RESTful**: Endpoints estándar para todas las operaciones

### **🔹 REGLAS DE NEGOCIO:**
- ✅ El nombre de la especie es obligatorio y único
- ✅ Una especie puede tener descripción opcional
- ✅ Las especies pueden estar activas o inactivas
- ✅ No se puede eliminar una especie si tiene breeds asociadas (preparación futura)
- ✅ Los nombres deben ser en formato título (primera letra mayúscula)

---

## **🏗️ PLAN DE IMPLEMENTACIÓN PASO A PASO**

### **FASE 1: PREPARACIÓN Y ESTRUCTURA BASE**

#### **PASO 1.1: Crear Estructura de Directorios**

**🔧 COMANDOS PHP ARTISAN:**

```powershell
# 1. Crear directorios base para Species
mkdir app\Interfaces\Animal\Catalog
mkdir app\Repositories\Animal\Catalog  
mkdir app\Services\Animal\Catalog
mkdir app\Http\Controllers\Animal\Catalog
mkdir app\Http\Requests\Animal\Catalog
mkdir app\Exceptions\Animal\Catalog
mkdir app\Validators\Animal\Catalog

# 2. Crear controlador de Species
php artisan make:controller Animal/Catalog/SpeciesController --api

# 3. Crear Form Requests para Species
php artisan make:request Animal/Catalog/StoreSpeciesRequest
php artisan make:request Animal/Catalog/UpdateSpeciesRequest

# 4. Crear excepciones personalizadas para Species
php artisan make:exception Animal/Catalog/SpeciesNotFoundException
php artisan make:exception Animal/Catalog/SpeciesValidationException

# 5. Crear tests para Species
php artisan make:test Animal/SpeciesRepositoryTest --unit
php artisan make:test Animal/SpeciesServiceTest --unit
php artisan make:test Animal/SpeciesValidatorTest --unit
php artisan make:test Animal/SpeciesManagementTest
```

**📁 Estructura resultante para Species:**
```
app/
├── Interfaces/
│   └── Animal/
│       └── Catalog/
│           ├── SpeciesRepositoryInterface.php
│           └── SpeciesServiceInterface.php
├── Repositories/
│   └── Animal/
│       └── Catalog/
│           └── SpeciesRepository.php
├── Services/
│   └── Animal/
│       └── Catalog/
│           └── SpeciesService.php
├── Http/
│   ├── Controllers/
│   │   └── Animal/
│   │       └── Catalog/
│   │           └── SpeciesController.php
│   └── Requests/
│       └── Animal/
│           └── Catalog/
│               ├── StoreSpeciesRequest.php
│               └── UpdateSpeciesRequest.php
├── Exceptions/
│   └── Animal/
│       └── Catalog/
│           ├── SpeciesNotFoundException.php
│           └── SpeciesValidationException.php
└── Validators/
    └── Animal/
        └── Catalog/
            └── SpeciesValidator.php
```

#### **PASO 1.2: Verificar Modelo Species Existente**

**Verificar que el modelo Species.php tenga:**
```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Species extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'name',
        'description',
        'is_active'
    ];

    protected $casts = [
        'is_active' => 'boolean',
        'deleted_at' => 'datetime'
    ];

    // Relación con Breeds (preparación futura)
    public function breeds()
    {
        return $this->hasMany(Breed::class);
    }

    // Relación con Animals (preparación futura) 
    public function animals()
    {
        return $this->hasMany(Animal::class);
    }
}
```

---

### **FASE 2: IMPLEMENTACIÓN COMPLETA DE SPECIES**

#### **PASO 2.1: Interfaces de Species**

**SpeciesRepositoryInterface.php:**
```php
<?php

namespace App\Interfaces\Animal\Catalog;

interface SpeciesRepositoryInterface
{
    public function findAll();
    public function findActive();
    public function findById(int $id);
    public function findByName(string $name);
    public function create(array $data);
    public function update(int $id, array $data);
    public function delete(int $id);
    public function forceDelete(int $id);
    public function restore(int $id);
    public function exists(int $id): bool;
    public function hasBreeds(int $id): bool;
    public function isNameUnique(string $name, ?int $excludeId = null): bool;
}
```

**SpeciesServiceInterface.php:**
```php
<?php

namespace App\Interfaces\Animal\Catalog;

interface SpeciesServiceInterface
{
    public function getAllSpecies(bool $includeInactive = false);
    public function getActiveSpecies();
    public function getSpeciesById(int $id);
    public function searchSpeciesByName(string $name);
    public function createSpecies(array $data);
    public function updateSpecies(int $id, array $data);
    public function deleteSpecies(int $id);
    public function restoreSpecies(int $id);
    public function forceDeleteSpecies(int $id);
    public function validateSpeciesExists(int $id): bool;
    public function canDeleteSpecies(int $id): bool;
}
```

#### **PASO 2.2: Repositorio de Species**

**SpeciesRepository.php:**
```php
<?php

namespace App\Repositories\Animal\Catalog;

use App\Interfaces\Animal\Catalog\SpeciesRepositoryInterface;
use App\Models\Species;
use App\Exceptions\Animal\Catalog\SpeciesNotFoundException;

class SpeciesRepository implements SpeciesRepositoryInterface
{
    public function findAll()
    {
        return Species::withTrashed()->orderBy('name')->get();
    }

    public function findActive()
    {
        return Species::where('is_active', true)->orderBy('name')->get();
    }

    public function findById(int $id)
    {
        $species = Species::withTrashed()->find($id);
        
        if (!$species) {
            throw new SpeciesNotFoundException("Species with ID {$id} not found");
        }
        
        return $species;
    }

    public function findByName(string $name)
    {
        return Species::where('name', 'like', "%{$name}%")
                      ->orderBy('name')
                      ->get();
    }

    public function create(array $data)
    {
        // Formatear nombre (Primera letra mayúscula)
        $data['name'] = ucfirst(strtolower(trim($data['name'])));
        
        return Species::create($data);
    }

    public function update(int $id, array $data)
    {
        $species = $this->findById($id);
        
        // Formatear nombre si se está actualizando
        if (isset($data['name'])) {
            $data['name'] = ucfirst(strtolower(trim($data['name'])));
        }
        
        $species->update($data);
        return $species->fresh();
    }

    public function delete(int $id)
    {
        $species = $this->findById($id);
        return $species->delete(); // Soft delete
    }

    public function forceDelete(int $id)
    {
        $species = $this->findById($id);
        return $species->forceDelete(); // Hard delete
    }

    public function restore(int $id)
    {
        $species = Species::withTrashed()->find($id);
        
        if (!$species) {
            throw new SpeciesNotFoundException("Species with ID {$id} not found");
        }
        
        return $species->restore();
    }

    public function exists(int $id): bool
    {
        return Species::where('id', $id)->exists();
    }

    public function hasBreeds(int $id): bool
    {
        // Preparado para cuando implementemos Breeds
        // return Species::where('id', $id)->has('breeds')->exists();
        return false; // Por ahora siempre false
    }

    public function isNameUnique(string $name, ?int $excludeId = null): bool
    {
        $formattedName = ucfirst(strtolower(trim($name)));
        
        $query = Species::withTrashed()->where('name', $formattedName);
        
        if ($excludeId) {
            $query->where('id', '!=', $excludeId);
        }
        
        return !$query->exists();
    }
}
```

#### **PASO 2.3: Servicio de Species**

**SpeciesService.php:**
```php
<?php

namespace App\Services\Animal\Catalog;

use App\Interfaces\Animal\Catalog\SpeciesServiceInterface;
use App\Interfaces\Animal\Catalog\SpeciesRepositoryInterface;
use App\Validators\Animal\Catalog\SpeciesValidator;
use Illuminate\Support\Facades\DB;

class SpeciesService implements SpeciesServiceInterface
{
    protected $speciesRepository;
    protected $validator;

    public function __construct(
        SpeciesRepositoryInterface $speciesRepository,
        SpeciesValidator $validator
    ) {
        $this->speciesRepository = $speciesRepository;
        $this->validator = $validator;
    }

    public function getAllSpecies(bool $includeInactive = false)
    {
        return $includeInactive 
            ? $this->speciesRepository->findAll()
            : $this->speciesRepository->findActive();
    }

    public function getActiveSpecies()
    {
        return $this->speciesRepository->findActive();
    }

    public function getSpeciesById(int $id)
    {
        return $this->speciesRepository->findById($id);
    }

    public function searchSpeciesByName(string $name)
    {
        $this->validator->validateSearchTerm($name);
        return $this->speciesRepository->findByName($name);
    }

    public function createSpecies(array $data)
    {
        $this->validator->validateForCreation($data);
        
        DB::beginTransaction();
        try {
            $species = $this->speciesRepository->create($data);
            DB::commit();
            
            return $species;
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function updateSpecies(int $id, array $data)
    {
        $this->validator->validateForUpdate($id, $data);
        
        DB::beginTransaction();
        try {
            $species = $this->speciesRepository->update($id, $data);
            DB::commit();
            
            return $species;
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function deleteSpecies(int $id)
    {
        $this->validator->validateForDeletion($id);
        
        DB::beginTransaction();
        try {
            $result = $this->speciesRepository->delete($id);
            DB::commit();
            
            return $result;
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function restoreSpecies(int $id)
    {
        $this->validator->validateForRestore($id);
        
        DB::beginTransaction();
        try {
            $result = $this->speciesRepository->restore($id);
            DB::commit();
            
            return $result;
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function forceDeleteSpecies(int $id)
    {
        $this->validator->validateForForceDelete($id);
        
        DB::beginTransaction();
        try {
            $result = $this->speciesRepository->forceDelete($id);
            DB::commit();
            
            return $result;
        } catch (\Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function validateSpeciesExists(int $id): bool
    {
        return $this->speciesRepository->exists($id);
    }

    public function canDeleteSpecies(int $id): bool
    {
        return !$this->speciesRepository->hasBreeds($id);
    }
}
```

#### **PASO 2.4: Validador de Species**

**SpeciesValidator.php:**
```php
<?php

namespace App\Validators\Animal\Catalog;

use App\Interfaces\Animal\Catalog\SpeciesRepositoryInterface;
use App\Exceptions\Animal\Catalog\SpeciesValidationException;

class SpeciesValidator
{
    protected $speciesRepository;

    public function __construct(SpeciesRepositoryInterface $speciesRepository)
    {
        $this->speciesRepository = $speciesRepository;
    }

    public function validateForCreation(array $data)
    {
        $this->validateRequired($data);
        $this->validateNameFormat($data['name']);
        $this->validateUniqueName($data['name']);
        $this->validateDescriptionLength($data);
    }

    public function validateForUpdate(int $id, array $data)
    {
        $this->validateExists($id);
        
        if (isset($data['name'])) {
            $this->validateNameFormat($data['name']);
            $this->validateUniqueNameForUpdate($id, $data['name']);
        }
        
        if (isset($data['description'])) {
            $this->validateDescriptionLength($data);
        }
    }

    public function validateForDeletion(int $id)
    {
        $this->validateExists($id);
        $this->validateNoRelatedRecords($id);
    }

    public function validateForRestore(int $id)
    {
        // Validar que la especie existe en soft delete
        $species = \App\Models\Species::withTrashed()->find($id);
        
        if (!$species) {
            throw new SpeciesValidationException("Species with ID {$id} not found");
        }
        
        if (!$species->trashed()) {
            throw new SpeciesValidationException("Species with ID {$id} is not deleted");
        }
    }

    public function validateForForceDelete(int $id)
    {
        $this->validateExists($id);
        $this->validateNoRelatedRecords($id);
    }

    public function validateSearchTerm(string $term)
    {
        if (strlen(trim($term)) < 2) {
            throw new SpeciesValidationException('Search term must be at least 2 characters long');
        }
    }

    private function validateRequired(array $data)
    {
        if (empty($data['name']) || trim($data['name']) === '') {
            throw new SpeciesValidationException('Species name is required');
        }
    }

    private function validateNameFormat(string $name)
    {
        $trimmedName = trim($name);
        
        if (strlen($trimmedName) < 2) {
            throw new SpeciesValidationException('Species name must be at least 2 characters long');
        }
        
        if (strlen($trimmedName) > 255) {
            throw new SpeciesValidationException('Species name cannot exceed 255 characters');
        }
        
        if (!preg_match('/^[a-zA-ZáéíóúÁÉÍÓÚñÑ\s]+$/', $trimmedName)) {
            throw new SpeciesValidationException('Species name can only contain letters and spaces');
        }
    }

    private function validateDescriptionLength(array $data)
    {
        if (isset($data['description']) && strlen($data['description']) > 1000) {
            throw new SpeciesValidationException('Description cannot exceed 1000 characters');
        }
    }

    private function validateExists(int $id)
    {
        if (!$this->speciesRepository->exists($id)) {
            throw new SpeciesValidationException("Species with ID {$id} does not exist");
        }
    }

    private function validateUniqueName(string $name)
    {
        if (!$this->speciesRepository->isNameUnique($name)) {
            $formattedName = ucfirst(strtolower(trim($name)));
            throw new SpeciesValidationException("Species name '{$formattedName}' already exists");
        }
    }

    private function validateUniqueNameForUpdate(int $id, string $name)
    {
        if (!$this->speciesRepository->isNameUnique($name, $id)) {
            $formattedName = ucfirst(strtolower(trim($name)));
            throw new SpeciesValidationException("Species name '{$formattedName}' already exists");
        }
    }

    private function validateNoRelatedRecords(int $id)
    {
        if ($this->speciesRepository->hasBreeds($id)) {
            throw new SpeciesValidationException('Cannot delete species with associated breeds');
        }
        
        // Aquí se pueden agregar más validaciones para Animals cuando se implementen
    }
}
```

#### **PASO 2.5: Excepciones de Species**

**SpeciesNotFoundException.php:**
```php
<?php

namespace App\Exceptions\Animal\Catalog;

use Exception;

class SpeciesNotFoundException extends Exception
{
    protected $message = 'Species not found';
    
    public function __construct($message = null, $code = 404, Exception $previous = null)
    {
        $message = $message ?: $this->message;
        parent::__construct($message, $code, $previous);
    }
    
    public function render($request)
    {
        return response()->json([
            'success' => false,
            'error' => 'Species Not Found',
            'message' => $this->getMessage(),
            'code' => $this->getCode()
        ], $this->getCode());
    }
}
```

**SpeciesValidationException.php:**
```php
<?php

namespace App\Exceptions\Animal\Catalog;

use Exception;

class SpeciesValidationException extends Exception
{
    protected $message = 'Species validation failed';
    
    public function __construct($message = null, $code = 422, Exception $previous = null)
    {
        $message = $message ?: $this->message;
        parent::__construct($message, $code, $previous);
    }
    
    public function render($request)
    {
        return response()->json([
            'success' => false,
            'error' => 'Validation Error',
            'message' => $this->getMessage(),
            'code' => $this->getCode()
        ], $this->getCode());
    }
}
```

#### **PASO 2.6: Form Requests de Species**

**StoreSpeciesRequest.php:**
```php
<?php

namespace App\Http\Requests\Animal\Catalog;

use Illuminate\Foundation\Http\FormRequest;

class StoreSpeciesRequest extends FormRequest
{
    public function authorize()
    {
        return true; // Ajustar según políticas de autorización
    }

    public function rules()
    {
        return [
            'name' => [
                'required',
                'string',
                'min:2',
                'max:255',
                'regex:/^[a-zA-ZáéíóúÁÉÍÓÚñÑ\s]+$/',
                'unique:species,name'
            ],
            'description' => 'nullable|string|max:1000',
            'is_active' => 'boolean'
        ];
    }

    public function messages()
    {
        return [
            'name.required' => 'El nombre de la especie es obligatorio',
            'name.min' => 'El nombre debe tener al menos 2 caracteres',
            'name.max' => 'El nombre no puede exceder 255 caracteres',
            'name.regex' => 'El nombre solo puede contener letras y espacios',
            'name.unique' => 'Ya existe una especie con este nombre',
            'description.max' => 'La descripción no puede exceder 1000 caracteres',
            'is_active.boolean' => 'El estado activo debe ser verdadero o falso'
        ];
    }

    protected function prepareForValidation()
    {
        // Formatear el nombre antes de validar
        if ($this->has('name')) {
            $this->merge([
                'name' => ucfirst(strtolower(trim($this->name)))
            ]);
        }
        
        // Establecer is_active por defecto como true
        if (!$this->has('is_active')) {
            $this->merge(['is_active' => true]);
        }
    }
}
```

**UpdateSpeciesRequest.php:**
```php
<?php

namespace App\Http\Requests\Animal\Catalog;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpdateSpeciesRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => [
                'sometimes',
                'required',
                'string',
                'min:2',
                'max:255',
                'regex:/^[a-zA-ZáéíóúÁÉÍÓÚñÑ\s]+$/',
                Rule::unique('species', 'name')->ignore($this->route('species'))
            ],
            'description' => 'sometimes|nullable|string|max:1000',
            'is_active' => 'sometimes|boolean'
        ];
    }

    public function messages()
    {
        return [
            'name.required' => 'El nombre de la especie es obligatorio',
            'name.min' => 'El nombre debe tener al menos 2 caracteres',
            'name.max' => 'El nombre no puede exceder 255 caracteres',
            'name.regex' => 'El nombre solo puede contener letras y espacios',
            'name.unique' => 'Ya existe una especie con este nombre',
            'description.max' => 'La descripción no puede exceder 1000 caracteres',
            'is_active.boolean' => 'El estado activo debe ser verdadero o falso'
        ];
    }

    protected function prepareForValidation()
    {
        // Formatear el nombre antes de validar si se está actualizando
        if ($this->has('name')) {
            $this->merge([
                'name' => ucfirst(strtolower(trim($this->name)))
            ]);
        }
    }
}
```

#### **PASO 2.7: Controlador de Species**

**SpeciesController.php:**
```php
<?php

namespace App\Http\Controllers\Animal\Catalog;

use App\Http\Controllers\Controller;
use App\Interfaces\Animal\Catalog\SpeciesServiceInterface;
use App\Http\Requests\Animal\Catalog\StoreSpeciesRequest;
use App\Http\Requests\Animal\Catalog\UpdateSpeciesRequest;
use App\Exceptions\Animal\Catalog\SpeciesNotFoundException;
use App\Exceptions\Animal\Catalog\SpeciesValidationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class SpeciesController extends Controller
{
    protected $speciesService;

    public function __construct(SpeciesServiceInterface $speciesService)
    {
        $this->speciesService = $speciesService;
    }

    /**
     * Display a listing of species
     */
    public function index(Request $request): JsonResponse
    {
        try {
            $includeInactive = $request->boolean('include_inactive', false);
            $species = $this->speciesService->getAllSpecies($includeInactive);
            
            return response()->json([
                'success' => true,
                'data' => $species,
                'message' => 'Species retrieved successfully',
                'meta' => [
                    'total' => $species->count(),
                    'include_inactive' => $includeInactive
                ]
            ], 200);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error retrieving species',
                'error' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Store a newly created species
     */
    public function store(StoreSpeciesRequest $request): JsonResponse
    {
        try {
            $species = $this->speciesService->createSpecies($request->validated());
            
            return response()->json([
                'success' => true,
                'data' => $species,
                'message' => 'Species created successfully'
            ], 201);
            
        } catch (SpeciesValidationException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'validation'
            ], 422);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error creating species',
                'error' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Display the specified species
     */
    public function show(int $id): JsonResponse
    {
        try {
            $species = $this->speciesService->getSpeciesById($id);
            
            return response()->json([
                'success' => true,
                'data' => $species,
                'message' => 'Species retrieved successfully'
            ], 200);
            
        } catch (SpeciesNotFoundException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'not_found'
            ], 404);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error retrieving species',
                'error' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Update the specified species
     */
    public function update(UpdateSpeciesRequest $request, int $id): JsonResponse
    {
        try {
            $species = $this->speciesService->updateSpecies($id, $request->validated());
            
            return response()->json([
                'success' => true,
                'data' => $species,
                'message' => 'Species updated successfully'
            ], 200);
            
        } catch (SpeciesNotFoundException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'not_found'
            ], 404);
            
        } catch (SpeciesValidationException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'validation'
            ], 422);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error updating species',
                'error' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Remove the specified species (soft delete)
     */
    public function destroy(int $id): JsonResponse
    {
        try {
            $this->speciesService->deleteSpecies($id);
            
            return response()->json([
                'success' => true,
                'message' => 'Species deleted successfully'
            ], 200);
            
        } catch (SpeciesNotFoundException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'not_found'
            ], 404);
            
        } catch (SpeciesValidationException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'validation'
            ], 422);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error deleting species',
                'error' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Search species by name
     */
    public function search(Request $request): JsonResponse
    {
        try {
            $searchTerm = $request->input('q', '');
            
            if (empty($searchTerm)) {
                return response()->json([
                    'success' => false,
                    'message' => 'Search term is required'
                ], 400);
            }
            
            $species = $this->speciesService->searchSpeciesByName($searchTerm);
            
            return response()->json([
                'success' => true,
                'data' => $species,
                'message' => 'Search completed successfully',
                'meta' => [
                    'search_term' => $searchTerm,
                    'results_count' => $species->count()
                ]
            ], 200);
            
        } catch (SpeciesValidationException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'validation'
            ], 422);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error searching species',
                'error' => $e->getMessage()
            ], 500);
        }
    }

    /**
     * Restore a soft deleted species
     */
    public function restore(int $id): JsonResponse
    {
        try {
            $this->speciesService->restoreSpecies($id);
            
            return response()->json([
                'success' => true,
                'message' => 'Species restored successfully'
            ], 200);
            
        } catch (SpeciesValidationException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage(),
                'error_type' => 'validation'
            ], 422);
            
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'message' => 'Error restoring species',
                'error' => $e->getMessage()
            ], 500);
        }
    }
}
```

---

### **FASE 3: CONFIGURACIÓN E INTEGRACIÓN**

#### **PASO 3.1: Inyección de Dependencias**

**AppServiceProvider.php:**
```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Species Module Bindings
        $this->app->bind(
            \App\Interfaces\Animal\Catalog\SpeciesRepositoryInterface::class,
            \App\Repositories\Animal\Catalog\SpeciesRepository::class
        );
        
        $this->app->bind(
            \App\Interfaces\Animal\Catalog\SpeciesServiceInterface::class,
            \App\Services\Animal\Catalog\SpeciesService::class
        );
    }

    public function boot()
    {
        //
    }
}
```

#### **PASO 3.2: Definición de Rutas**

**routes/bully.php:**
```php
<?php

use App\Http\Controllers\Animal\Catalog\SpeciesController;
use Illuminate\Support\Facades\Route;

Route::prefix('animal')->group(function () {
    Route::prefix('catalog')->group(function () {
        // Species Routes
        Route::apiResource('species', SpeciesController::class);
        
        // Additional Species Routes
        Route::get('species/search', [SpeciesController::class, 'search']);
        Route::post('species/{id}/restore', [SpeciesController::class, 'restore']);
    });
});
```

---

### **FASE 4: TESTING ESPECÍFICO SPECIES**

#### **PASO 4.1: Unit Tests**

**SpeciesRepositoryTest.php:**
```php
<?php

namespace Tests\Unit\Animal;

use Tests\TestCase;
use App\Models\Species;
use App\Repositories\Animal\Catalog\SpeciesRepository;
use App\Exceptions\Animal\Catalog\SpeciesNotFoundException;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SpeciesRepositoryTest extends TestCase
{
    use RefreshDatabase;
    
    protected $repository;

    protected function setUp(): void
    {
        parent::setUp();
        $this->repository = new SpeciesRepository();
    }

    public function test_can_create_species()
    {
        $data = [
            'name' => 'perro',
            'description' => 'Canis lupus familiaris',
            'is_active' => true
        ];

        $species = $this->repository->create($data);

        $this->assertInstanceOf(Species::class, $species);
        $this->assertEquals('Perro', $species->name); // Verifica formato
        $this->assertEquals($data['description'], $species->description);
        $this->assertTrue($species->is_active);
    }

    public function test_can_find_all_species()
    {
        Species::factory()->count(3)->create();

        $species = $this->repository->findAll();

        $this->assertCount(3, $species);
    }

    public function test_throws_exception_when_species_not_found()
    {
        $this->expectException(SpeciesNotFoundException::class);
        
        $this->repository->findById(999);
    }

    public function test_can_check_name_uniqueness()
    {
        Species::factory()->create(['name' => 'Perro']);

        $this->assertFalse($this->repository->isNameUnique('perro'));
        $this->assertTrue($this->repository->isNameUnique('Gato'));
    }

    public function test_can_soft_delete_species()
    {
        $species = Species::factory()->create();

        $result = $this->repository->delete($species->id);

        $this->assertTrue($result);
        $this->assertSoftDeleted($species);
    }
}
```

#### **PASO 4.2: Feature Tests**

**SpeciesManagementTest.php:**
```php
<?php

namespace Tests\Feature\Animal;

use Tests\TestCase;
use App\Models\Species;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SpeciesManagementTest extends TestCase
{
    use RefreshDatabase;

    public function test_can_list_all_species()
    {
        Species::factory()->count(3)->create();

        $response = $this->getJson('/api/animal/catalog/species');

        $response->assertStatus(200)
                 ->assertJsonStructure([
                     'success',
                     'data',
                     'message',
                     'meta'
                 ])
                 ->assertJsonCount(3, 'data');
    }

    public function test_can_create_species()
    {
        $speciesData = [
            'name' => 'perro',
            'description' => 'Canis lupus familiaris',
            'is_active' => true
        ];

        $response = $this->postJson('/api/animal/catalog/species', $speciesData);

        $response->assertStatus(201)
                 ->assertJsonFragment(['success' => true])
                 ->assertJsonFragment(['name' => 'Perro']); // Verifica formato
    }

    public function test_cannot_create_duplicate_species()
    {
        Species::factory()->create(['name' => 'Perro']);

        $response = $this->postJson('/api/animal/catalog/species', [
            'name' => 'perro'
        ]);

        $response->assertStatus(422)
                 ->assertJsonFragment(['success' => false]);
    }

    public function test_can_update_species()
    {
        $species = Species::factory()->create(['name' => 'Perro']);

        $response = $this->putJson("/api/animal/catalog/species/{$species->id}", [
            'name' => 'gato'
        ]);

        $response->assertStatus(200)
                 ->assertJsonFragment(['name' => 'Gato']);
    }

    public function test_can_delete_species()
    {
        $species = Species::factory()->create();

        $response = $this->deleteJson("/api/animal/catalog/species/{$species->id}");

        $response->assertStatus(200);
        $this->assertSoftDeleted($species);
    }

    public function test_can_search_species()
    {
        Species::factory()->create(['name' => 'Perro']);
        Species::factory()->create(['name' => 'Gato']);

        $response = $this->getJson('/api/animal/catalog/species/search?q=per');

        $response->assertStatus(200)
                 ->assertJsonCount(1, 'data');
    }
}
```

---

## **📊 CRITERIOS DE ÉXITO SPECIES MODULE**

### **✅ Funcionalidades que deben funcionar perfectamente:**

1. **CRUD Completo:**
   - ✅ Crear especies con validaciones robustas
   - ✅ Listar especies (activas y todas)
   - ✅ Obtener especie por ID
   - ✅ Actualizar especies con validaciones
   - ✅ Eliminar especies (soft delete)
   - ✅ Restaurar especies eliminadas

2. **Validaciones Implementadas:**
   - ✅ Nombres únicos en todo el sistema
   - ✅ Formato de nombre (Primera letra mayúscula)
   - ✅ Solo letras y espacios permitidos
   - ✅ Longitud mínima y máxima
   - ✅ Descripción opcional con límite

3. **Funcionalidades Adicionales:**
   - ✅ Búsqueda por nombre
   - ✅ Filtro por estado activo/inactivo
   - ✅ Soft deletes implementado
   - ✅ Preparado para relaciones futuras

4. **Testing Coverage:**
   - ✅ Unit tests > 90% coverage
   - ✅ Feature tests cubren todos los endpoints
   - ✅ Validaciones testeadas completamente

---

## **🚀 ENDPOINTS RESULTANTES**

```
GET    /api/animal/catalog/species              # Listar especies
POST   /api/animal/catalog/species              # Crear especie
GET    /api/animal/catalog/species/{id}         # Obtener especie
PUT    /api/animal/catalog/species/{id}         # Actualizar especie
DELETE /api/animal/catalog/species/{id}         # Eliminar especie
GET    /api/animal/catalog/species/search       # Buscar especies
POST   /api/animal/catalog/species/{id}/restore # Restaurar especie
```

---

## **🔄 SIGUIENTE PASO**

Una vez completado **Species Module** exitosamente:

1. **Testing completo** y corrección de bugs
2. **Documentación de API** endpoints  
3. **Merge a main** cuando esté perfecto
4. **Crear nueva rama** para **Breeds Module**

**¡Esta implementación nos dará una base sólida y completamente funcional para construir Breeds encima!** 🐕✨
 