# Estándares de Módulos CD-System

Este documento define los estándares y mejores prácticas para desarrollar y mantener módulos en el sistema CD-System.

## 📋 Tabla de Contenidos

1. [Configuración de Módulos](#configuración-de-módulos)
2. [Estructura de Controladores](#estructura-de-controladores)
3. [Métodos Estándar](#métodos-estándar)
4. [Manejo de Permisos](#manejo-de-permisos)
5. [Estructura de Rutas](#estructura-de-rutas)
6. [JavaScript y Frontend](#javascript-y-frontend)
7. [Checklist de Implementación](#checklist-de-implementación)

---

## 🔧 Configuración de Módulos

### Archivo: `config/cd-system.php`

Cada módulo debe estar registrado en el array `modules`:

```php
'modules' => [
    'products' => [
        'active' => true,  // o 'enabled' => true
        'name' => 'Products',
        'description' => 'Gestión de productos',
        'version' => '1.0.0',
    ],
]
```

**Nota**: Algunos módulos usan `active`, otros `enabled`. **Recomendación**: Estandarizar a `enabled` para consistencia.

---

## 🎮 Estructura de Controladores

### Constructor Estándar

Todos los controladores admin deben seguir esta estructura:

```php
public function __construct()
{
    // 1. Verificar que el módulo esté habilitado
    if (!config('cd-system.modules.{module_name}.enabled', true)) {
        abort(404, '{Module} module is disabled');
    }

    // 2. Middleware de permisos (ver sección de permisos)
    $this->middleware('permission:view_{module}')->only(['index']);
    $this->middleware('permission:create_{module}')->only(['create', 'store']);
    $this->middleware('permission:edit_{module}')->only(['edit', 'update']);
    $this->middleware('permission:delete_{module}')->only(['destroy', 'destroyMultiple']);
    
    // NOTA: El método 'show' NO debe tener middleware de permisos
    // ya que se usa para cargar datos para edición (AJAX)
}
```

---

## 📝 Métodos Estándar

### 1. `manager()` - Vista Principal del Dashboard

```php
public function manager()
{
    // Obtener estadísticas
    $stats = [
        'total_items' => Model::count(),
        'active_items' => Model::where('is_active', true)->count(),
        // ... más estadísticas
    ];

    // Cargar datos necesarios para modales/formularios
    $categories = Category::all();
    $tags = Tag::all();

    return view('modules.{module}.admin.manager', compact('stats', 'categories', 'tags'));
}
```

### 2. `getStats()` - Estadísticas para AJAX

```php
public function getStats()
{
    $stats = [
        'total_items' => Model::count(),
        'active_items' => Model::where('is_active', true)->count(),
        // ... más estadísticas
    ];

    return response()->json($stats);
}
```

### 3. `getData()` o `getItems()` - Datos para DataTables

```php
public function getData()
{
    $items = Model::with(['category', 'tags', 'images'])
        ->orderBy('created_at', 'desc')
        ->get();
    
    return Datatables::of($items)
        ->addColumn('image', function ($item) {
            if ($item->images->count() > 0) {
                $imagePath = $item->images->first()->image_path;
                // Si es una URL completa, devolverla directamente
                if (filter_var($imagePath, FILTER_VALIDATE_URL)) {
                    return $imagePath;
                }
                // Si es una ruta relativa, usar asset()
                return asset($imagePath);
            }
            return null;
        })
        ->addColumn('category', function ($item) {
            return $item->category ? $item->category->name : null;
        })
        ->addColumn('tags', function ($item) {
            return $item->tags->pluck('name')->toArray();
        })
        ->addColumn('status', function ($item) {
            return $item->is_active ? 'active' : 'inactive';
        })
        ->addColumn('created_at', function ($item) {
            return $item->created_at->format('d/m/Y H:i');
        })
        ->addColumn('slug', function ($item) {
            return $item->slug ?? '';
        })
        ->rawColumns(['image'])
        ->make(true);
}
```

### 4. `show($id)` - Obtener Item Individual (AJAX)

**IMPORTANTE**: Este método NO debe tener middleware de permisos en el constructor.

```php
public function show($id)
{
    try {
        $item = Model::with(['category', 'tags', 'images'])->findOrFail($id);

        return response()->json([
            'success' => true,
            'data' => [
                'id'          => $item->id,
                'name'        => $item->name,
                'description' => $item->description,
                'price'       => $item->price,
                'category_id' => $item->category_id,
                'tags'        => $item->tags->pluck('id')->toArray(),  // Convertir a array
                'images'      => $item->images->pluck('image_path')->toArray(),  // Convertir a array
            ]
        ]);
    } catch (\Exception $e) {
        \Log::error('Error in {Controller}@show: ' . $e->getMessage());
        return response()->json([
            'success' => false,
            'message' => 'Error al cargar el item: ' . $e->getMessage()
        ], 500);
    }
}
```

**Razón**: El método `show` se usa para cargar datos en modales de edición vía AJAX. Si tiene middleware de permisos, puede causar errores 403 cuando el usuario tiene `edit_{module}` pero no `view_{module}`.

---

## 🔐 Manejo de Permisos

### Permisos Estándar

Cada módulo debe tener estos permisos definidos:

- `view_{module}` - Ver lista de items
- `create_{module}` - Crear nuevos items
- `edit_{module}` - Editar items existentes
- `delete_{module}` - Eliminar items

### Middleware de Permisos

```php
// En el constructor del controlador:
$this->middleware('permission:view_{module}')->only(['index']);
// show NO debe tener middleware (ver sección de métodos estándar)
$this->middleware('permission:create_{module}')->only(['create', 'store']);
$this->middleware('permission:edit_{module}')->only(['edit', 'update']);
$this->middleware('permission:delete_{module}')->only(['destroy', 'destroyMultiple']);
```

### Excepción: Método `show`

El método `show` **NO debe tener middleware de permisos** porque:
1. Se usa para cargar datos para edición vía AJAX
2. Si el usuario puede acceder a la página principal (requiere `view_{module}`), puede ver datos individuales
3. Evita errores 403 cuando el usuario tiene `edit_{module}` pero no `view_{module}`

---

## 🛣️ Estructura de Rutas

### Archivo: `routes/modules/{module}.php`

```php
Route::middleware(['auth'])->group(function () {
    // Vista unificada con pestañas
    Route::prefix('{module}-settings')->name('{module}-settings.')->group(function () {
        Route::get('/', [Controller::class, 'manager'])->name('index');
        Route::get('/stats', [Controller::class, 'getStats'])->name('stats');
    });

    Route::prefix('{module}')->name('{module}.')->group(function () {
        // Rutas específicas PRIMERO (categories, tags, etc.)
        Route::get('/categories', [CategoryController::class, 'index'])->name('categories_index');
        Route::get('/categories/get-categories', [CategoryController::class, 'getCategories'])->name('categories_data');
        Route::get('/categories/show/{id}', [CategoryController::class, 'show'])->name('show_category');
        
        // Rutas principales
        Route::get('/', [Controller::class, 'index'])->name('index');
        Route::get('/get-{module}', [Controller::class, 'getData'])->name('data');
        Route::get('/create', [Controller::class, 'create'])->name('create');
        Route::post('/', [Controller::class, 'store'])->name('store');
        
        // Rutas con parámetros AL FINAL
        Route::get('/{id}/edit', [Controller::class, 'edit'])->name('edit');
        Route::put('/{id}', [Controller::class, 'update'])->name('update');
        Route::delete('/{id}', [Controller::class, 'destroy'])->name('destroy');
        Route::get('/{id}', [Controller::class, 'show'])->name('show');  // ÚLTIMA
    });
});
```

**Regla de Oro**: Las rutas con parámetros `{id}` o `{slug}` deben ir **AL FINAL**, después de todas las rutas específicas.

---

## 💻 JavaScript y Frontend

### Configuración de Axios

El archivo `resources/views/base/base.blade.php` ya incluye la configuración de axios:

```javascript
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
window.axios.defaults.withCredentials = true;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
```

### Peticiones AJAX para `show`

```javascript
var editItem = function(id) {
    axios.get(`/{module}/${id}`, {
        withCredentials: true,
        headers: {
            'X-Requested-With': 'XMLHttpRequest',
            'Accept': 'application/json'
        }
    })
    .then(function(response) {
        if (!response.data || !response.data.success) {
            throw new Error('Invalid response structure');
        }
        
        const item = response.data.data;
        // Populate form...
    })
    .catch(function(error) {
        console.error('Error loading item:', error);
        Swal.fire({
            text: "Error al cargar los datos del item",
            icon: "error"
        });
    });
};
```

### DataTables - drawCallback

```javascript
drawCallback: function() {
    setTimeout(function() {
        addEventListeners();
        if (typeof handleSelectionCounter === 'function') {
            handleSelectionCounter(table, 'module_name');
        }
        initBulkActions();
    }, 100);
}
```

---

## ✅ Checklist de Implementación

Al crear o actualizar un módulo, verificar:

### Configuración
- [ ] Módulo registrado en `config/cd-system.php` con `enabled` (no `active`)
- [ ] Permisos definidos en la base de datos (`view_{module}`, `create_{module}`, `edit_{module}`, `delete_{module}`)

### Controlador
- [ ] Constructor verifica que el módulo esté habilitado
- [ ] Middleware de permisos configurado correctamente
- [ ] Método `show` **NO tiene middleware de permisos**
- [ ] Métodos `manager()`, `getStats()`, `getData()`, `show()` implementados
- [ ] Método `show` convierte colecciones a arrays con `->toArray()`
- [ ] Manejo de errores con try-catch en métodos críticos

### Rutas
- [ ] Rutas específicas (categories, tags) definidas ANTES de rutas con `{id}`
- [ ] Ruta `/{id}` (show) es la ÚLTIMA en el grupo
- [ ] Rutas frontend con middleware `module.enabled:{module}`

### JavaScript
- [ ] Peticiones AJAX incluyen `withCredentials: true`
- [ ] Headers `X-Requested-With` y `Accept` configurados
- [ ] `drawCallback` verifica existencia de funciones antes de llamarlas
- [ ] Manejo de errores con mensajes claros al usuario

### Testing
- [ ] Crear item funciona
- [ ] Editar item funciona (sin error 403)
- [ ] Eliminar item funciona
- [ ] DataTables carga datos correctamente
- [ ] Estadísticas se actualizan correctamente

---

## 🔍 Módulos Actuales - Estado

### ✅ Products (Completo)
- Configuración: ✅
- Permisos: ✅
- Método show: ✅ (sin middleware)
- JavaScript: ✅

### ⚠️ Blog (Pendiente)
- Configuración: ✅
- Permisos: ⚠️ (comentados)
- Método show: ⚠️ (verificar)
- JavaScript: ⚠️ (verificar)

### ⚠️ Services (Pendiente)
- Configuración: ✅
- Permisos: ❌ (no implementados)
- Método show: ✅
- JavaScript: ⚠️ (verificar)

---

## 📚 Referencias

- [Laravel Permissions (Spatie)](https://spatie.be/docs/laravel-permission)
- [Yajra DataTables](https://yajrabox.com/docs/laravel-datatables)
- [Metronic Documentation](https://preview.keenthemes.com/metronic8/demo1/documentation/getting-started.html)

---

**Última actualización**: 2025-11-19
**Versión del documento**: 1.0.0

