# ARQUITECTURA GENERAL — Guía canónica multiproyecto

> **Documento de referencia obligatorio y genérico.**
> Esta guía define las **reglas de arquitectura, nombramiento y patrones de programación** que **TODO** desarrollador (humano o IA) **debe** respetar en **cualquier proyecto** del equipo, sin importar:
>
> - Si el frontend es **Blade puro**, **Vue integrado dentro de Laravel** o **Vue como proyecto separado (SPA)** que consume una API.
> - Si el proyecto es nuevo o es una migración desde un sistema legado.
>
> **La estructura, el nombramiento de variables, archivos, carpetas y módulos es el mismo en todos los casos.** Solo cambia la capa de presentación; el resto de la arquitectura es idéntico.
>
> Cualquier desviación a estas reglas se considera **bug arquitectónico** y debe corregirse antes de hacer commit.

> **Nota sobre este documento:** es la versión **genérica** de la arquitectura. No incluye el sistema ENUM/mappings (`enum_mappings`, `char_mappings`, `MappedQueryBuilder`), que es **exclusivo** de los proyectos en migración V1↔V2 donde la base de datos debe permanecer compatible con ambas versiones. Para esos dos proyectos específicos, ver su `ARQUITECTURA_PROYECTO.md` propio.

---

> **Dos fuentes de verdad, ambas OBLIGATORIAS:**
> - **Datos** → el archivo SQL del esquema ([/asturias_api.sql](/asturias_api.sql)). Ninguna variable, campo ni payload se inventa (§1, §16).
> - **Interfaz** → el sistema de diseño **FSG UI Kit 2.4** en [documentacion/UIKIT/FSG_UI_Kit2.4](documentacion/UIKIT/FSG_UI_Kit2.4). **TODO** planteamiento de interfaz —Blade o Vue— parte de este kit: su marca, tokens, clases y componentes codificados (`CMP-AREA-NOMBRE`).

## 0. Modos de trabajo del frontend

El equipo trabaja con tres modalidades de presentación. **La arquitectura backend y el nombramiento no cambian entre ellas.**

| Modo | Descripción | Ruteo de vistas | Capa HTTP del front |
|------|-------------|-----------------|---------------------|
| **A. Blade puro** | Laravel renderiza HTML con Blade; interactividad mínima o con JS ligero. | Laravel (rutas `web`) | Formularios Blade / fetch puntual |
| **B. Vue integrado en Laravel** | Blade actúa como **montador** de componentes Vue; Laravel sirve las vistas. | Laravel (cada Blade monta un componente) | Pinia + axios (mismo dominio, `withCredentials`) |
| **C. Vue proyecto separado (SPA)** | Frontend Vue independiente que consume una **API** del backend Laravel. | **Vue Router** en el front | Pinia + axios (baseURL a la API, token/cookie) |

**Regla rectora:** elijas el modo que elijas, **el módulo, submódulo, controlador, modelo, form request, resource, rutas y nombres de variables son idénticos**. Lo único que cambia es cómo se pinta la UI y quién resuelve el ruteo de pantallas.

Diferencias puntuales por modo se marcan a lo largo del documento con las etiquetas **[Modo A]**, **[Modo B]**, **[Modo C]**.

---

## 1. Principio rector: la **TABLA** es el punto de partida

Todo parte de la **tabla de base de datos**. Antes de programar **cualquier cosa** se debe:

1. Identificar el **nombre exacto** de la tabla en el **esquema / diccionario de datos del proyecto** (el `.sql`, migraciones o documento de esquema que sea la fuente única de verdad del proyecto).
2. Identificar **todos sus campos** tal cual están escritos en el esquema.
3. **Está estrictamente prohibido inventar campos.**

El nombre de la tabla determina:

- Namespace y nombre del **Controlador**
- Namespace y nombre del **Modelo**
- Carpeta y nombre del **Form Request**
- Carpeta y nombre del **Resource**
- Archivo y prefijos de **Rutas**
- Carpeta de **Vistas Blade** *(Modo A / B)* o de **Vistas Vue** *(Modo C)*
- Nombre del **componente Vue** *(Modo B / C)*
- Nombre del **Pinia Store** *(Modo B / C)*
- Nombre de las **variables, props y campos de formularios** (deben ser idénticos a los del esquema)

---

## 2. Convención de nombramiento de **tablas**

### 2.1 Patrón de prefijo

```
{prefijoModulo}_{submodulo}
```

Cada proyecto define su catálogo de prefijos de módulo (dos–cuatro letras + `_`). El prefijo agrupa la tabla dentro de un módulo funcional. Ejemplos de convención (los prefijos concretos los define cada proyecto):

| Prefijo (ejemplo) | Módulo (ejemplo) | Ejemplo de tabla |
|-------------------|------------------|------------------|
| `seg_` | Seguridad | `seg_perfil` |
| `cat_` | Catálogos | `cat_pais` |
| `{xxx}_` | *(módulo del proyecto)* | `{xxx}_{entidad}` |

> **Regla:** respeta el catálogo de prefijos que ya exista en el proyecto. No inventes prefijos nuevos sin acordarlo con el equipo.

### 2.2 Convención de nombramiento de **campos**

Todos los campos de una tabla **deben** iniciar con el nombre de la entidad (igual al sufijo después del prefijo), salvo las claves primarias y foráneas que usan el patrón `id*`.

**Ejemplo — Tabla `seg_perfil`:**

| Campo | Tipo | Notas |
|-------|------|-------|
| `idPerfil` | PK | Patrón `id` + EntidadPascalCase |
| `perfilAlias` | varchar | Inicia con `perfil` |
| `perfilEstado` | char/varchar | Estado del registro (Activo/Inactivo/Eliminado) |
| `perfilFecha` | datetime | Inicia con `perfil` |
| `idUsuario` | FK | Referencia a tabla externa |

> **Regla:** En backend y frontend las **variables, props y atributos de payload** deben llamarse **EXACTAMENTE** como el campo en la BD. No traducir, no acortar, no usar snake_case, no mezclar inglés.

---

## 3. **Derivación de Artefactos** a partir de la tabla

Tomemos como ejemplo canónico una tabla `hot_preasignacion` (módulo `Hotel`, entidad `Preasignacion`):

| Componente | Convención | Aplica a |
|------------|------------|----------|
| Módulo | `hot_` → `Hotel` | Todos |
| Submódulo / Entidad | `preasignacion` → `Preasignacion` | Todos |
| Controlador | `app/Http/Controllers/Hotel/PreasignacionController.php` | Todos |
| Modelo | `app/Models/Modulos/Hotel/Preasignacion.php` | Todos |
| Archivo de Rutas | `routes/modulos/hotel/preasignacion.php` | Todos |
| Form Request por acción | `app/Http/Requests/Hotel/Preasignacion/{Accion}FormRequest.php` | Todos |
| Resource | `app/Http/Resources/Hotel/Preasignacion/{Tipo}Resource.php` — sin prefijo de entidad: la carpeta ya la identifica | Todos |
| Vista Blade (1 por acción) | `resources/views/modulos/hotel/preasignacion/{accion}.blade.php` | **Modo A / B** |
| Componente Vue (1 por acción) | `resources/js/modulos/hotel/preasignacion/{Accion}.vue` *(Modo B)* o `src/modulos/hotel/preasignacion/{Accion}.vue` *(Modo C)* | **Modo B / C** |
| Sub-componentes locales | `.../hotel/preasignacion/components/{Componente}.vue` | **Modo B / C** |
| Pinia Store | `.../stores/hotel/preasignacionStore.js` | **Modo B / C** |
| Composable (opcional) | `.../composables/hotel/preasignacion/usePreasignacionForm.js` | **Modo B / C** |
| Vista Vue (pantalla ruteada) | `src/modulos/hotel/preasignacion/{Accion}.vue` + entrada en Vue Router | **Modo C** |

> El namespace/carpeta de módulo (`Hotel`) se deriva del prefijo (`hot_`) y la entidad (`Preasignacion`) del sufijo, siempre en PascalCase.

---

## 4. **Rutas backend** — patrón obligatorio

Aplica a los tres modos. En **Modo A/B** son rutas `web`; en **Modo C** son rutas `api` (mismo patrón de URL y permisos).

### 4.1 Archivo dedicado por entidad

Cada entidad tiene **su propio archivo** en `routes/modulos/{modulo}/{submodulo}.php`, incluido desde `routes/web.php` (o `routes/api.php` en Modo C) mediante `require base_path(...)`.

### 4.2 Estructura jerárquica obligatoria

```php
<?php

use App\Http\Controllers\{Modulo}\{Submodulo}Controller;
use Illuminate\Support\Facades\Route;

Route::middleware(['{authMiddleware}', '{seguridadMiddleware}'])->group(function () {
    Route::prefix('{modulo}')->name('{modulo}.')->group(function () {
        Route::prefix('{submodulo}')->name('{submodulo}.')->group(function () {
            Route::get('/{accion}', [
                {Submodulo}Controller::class,
                '{accion}'
            ])->name('{accion}')
              ->middleware('permiso:{modulo}.{submodulo}.{accion}');
        });
    });
});
```

> Los nombres concretos de los middlewares de autenticación/seguridad (`ApiAuth`, `auth`, `sanctum`, `seguridad*`, etc.) los define cada proyecto. El **patrón** (grupo con auth + seguridad global, y `permiso:...` por ruta) es obligatorio.

### 4.3 Ejemplo concreto

```php
<?php

use App\Http\Controllers\Hotel\PreasignacionController;
use Illuminate\Support\Facades\Route;

Route::middleware(['ApiAuth', 'seguridadFSG'])->group(function () {
    Route::prefix('hotel')->name('hotel.')->group(function () {
        Route::prefix('preasignacion')->name('preasignacion.')->group(function () {

            Route::get('/', [PreasignacionController::class, 'index'])
                ->name('index')
                ->middleware('permiso:hotel.preasignacion.index');

            Route::post('/listar', [PreasignacionController::class, 'listar'])
                ->name('listar')
                ->middleware('permiso:hotel.preasignacion.listar');

            Route::post('/guardar', [PreasignacionController::class, 'guardar'])
                ->name('guardar')
                ->middleware('permiso:hotel.preasignacion.guardar');

            Route::get('/detalle/{id}', [PreasignacionController::class, 'detalle'])
                ->name('detalle')
                ->middleware('permiso:hotel.preasignacion.detalle');

            Route::post('/cambiar-estado', [PreasignacionController::class, 'cambiarEstado'])
                ->name('cambiarEstado')
                ->middleware('permiso:hotel.preasignacion.cambiarEstado');
        });
    });
});
```

### 4.4 Reglas de rutas (no negociables)

1. **URL**: siempre con el patrón `/{modulo}/{submodulo}/{accion}`.
2. **Middleware global**: auth + seguridad envuelven TODO el grupo.
3. **Middleware por ruta**: `permiso:{modulo}.{submodulo}.{accion}`.
4. **`->name('{accion}')`**: el nombre coincide **exactamente** con el método del controlador y con el sufijo del permiso.
5. La **acción** en la URL puede ser kebab-case (`cambiar-estado`) pero el **método** del controlador y el **nombre de la ruta** siempre van en **camelCase** (`cambiarEstado`).
6. **No mezclar verbos HTTP arbitrariamente**: `GET` para lecturas/vistas; `POST` para mutaciones y listados con filtros complejos; `PUT`/`DELETE` cuando aporten semánticamente.
7. **Incluir el archivo** en `routes/web.php` (o `routes/api.php`) mediante `require base_path('routes/modulos/{modulo}/{submodulo}.php');` con su bloque de comentario `|--- Submodulo ---|`.
8. **[Modo C]** En SPA separada, las rutas backend suelen ser `api` (sin `index` que renderice Blade). La acción `index` de pantalla la resuelve Vue Router en el front; el resto de acciones (`listar`, `guardar`, ...) son idénticas.

---

## 5. **Controlador** — patrón obligatorio

### 5.1 Ubicación y namespace

```
app/Http/Controllers/{Modulo}/{Submodulo}Controller.php
```

```php
namespace App\Http\Controllers\{Modulo};

use App\Http\Controllers\Controller;
```

### 5.2 Reglas del controlador

1. **Un controlador por entidad/tabla** (no agrupar varias entidades).
2. **Una acción = un método público** cuyo nombre **coincide exactamente** con el último segmento de la ruta y con el `name`/`permiso` declarado.
3. **Validación**: SIEMPRE mediante un **Form Request** dedicado por acción (`{Accion}FormRequest`). No usar `$request->validate()` inline salvo casos triviales bien justificados.
4. **Acceso a BD**: el controlador **NO** ejecuta queries directas (`Model::where(...)->update(...)`). Toda interacción con la BD se delega a **métodos estáticos o de instancia del modelo** (`Bloqueo::nuevo($data)`, `Bloqueo::editar($data)`, `Bloqueo::cambiarEstado($id, $estado)`, `Bloqueo::lista($filtros)`).

   **4.b Regla de separación de complejidad** (no negociable):
   - El **modelo** expone únicamente métodos **simples** de interacción con BD: `nuevo`, `editar`, `cambiarEstado`, `eliminar`, `lista`, `detalle`, `asignar*`, `desasignar*`, scopes y relaciones. Sus métodos **NO contienen lógica de negocio compleja** — solo persistencia. Regla práctica: si un método del modelo excede ~5 líneas y no es CRUD puro, **mueve la lógica al controlador** (o a un Service dedicado si el proyecto los usa).
   - El **controlador** orquesta la lógica compleja: transacciones, locks pesimistas (`lockForUpdate`), iteraciones de batch, decisiones de flujo (¿crear o editar?), generación de PDF, redirecciones condicionales, bitácora.

5. **Transacciones**: toda mutación va envuelta en `DB::beginTransaction()` + `DB::commit()` / `DB::rollBack()` dentro de un `try { } catch (Throwable $e) { }`.
6. **Bitácora**: cualquier escritura relevante registra el evento correspondiente (ver §12).
7. **Respuesta JSON estandarizada** (Modos B y C, y toda respuesta AJAX del Modo A):
   ```php
   response()->json([
       'success' => true|false,
       'code'    => 200|404|422|500,
       'message' => 'Descripción del resultado',
       'data'    => $resource,
   ], $statusCode);
   ```
8. **No crear métodos que no se utilicen.** Si un endpoint no será consumido, no se crea.

### 5.3 Acciones canónicas

Acciones esperadas para una entidad CRUD estándar. **Usar exactamente estos nombres** salvo justificación de dominio:

| Acción | Método HTTP | Propósito |
|--------|-------------|-----------|
| `index` | GET | **[Modo A/B]** Render Blade de la vista raíz. **[Modo C]** normalmente no existe en backend (lo resuelve Vue Router). |
| `listar` | POST | Listado paginado + filtros + búsqueda + ordenamiento |
| `guardar` | POST | Crear o editar (decide por presencia de `id{Entidad}`) |
| `detalle` | GET | Obtener registro por id |
| `cambiarEstado` | POST | Mutar el campo `{entidad}Estado` (Activo/Inactivo/Eliminado) |
| `eliminar` | POST/DELETE | Soft delete (set `Eliminado`); delega a `cambiarEstado` si aplica |
| `activar` | POST | Reactivar (set `Activo`) |
| `exportarExcel` | POST | Export a Excel |
| `exportarPdf` | POST | Export a PDF |

> Acciones de dominio no-CRUD (p.ej. `confirmar`, `cancelar`, `validarReferencia`) son válidas siempre que **expresen un verbo del negocio**, no detalles de implementación.

### 5.4 Ejemplo canónico — `guardar`

```php
public function guardar(GuardarFormRequest $request)
{
    DB::beginTransaction();
    try {
        $data = $request->validated();

        if (!empty($data['idPreasignacion'])) {
            $preasignacion = Preasignacion::editar($data);
            $idEvento = 41; // editar
        } else {
            $data['idUsuario'] = $request->user()->idUsuario;
            $preasignacion = Preasignacion::nuevo($data);
            $idEvento = 40; // nuevo
        }

        Bitacora::agregar(
            $idEvento,
            $preasignacion->idPreasignacion,
            "Preasignación guardada",
            request()->ip()
        );

        DB::commit();

        return response()->json([
            'success' => true,
            'code'    => 200,
            'message' => 'success',
            'data'    => new PreasignacionListaResource($preasignacion),
        ]);
    } catch (Throwable $e) {
        DB::rollBack();
        registrar_error($e, 'PreasignacionController@guardar');
        return response()->json(['success' => false, 'code' => 500], 500);
    }
}
```

---

## 6. **Modelo** — patrón obligatorio

### 6.1 Ubicación y namespace

```
app/Models/Modulos/{Modulo}/{Submodulo}.php
```

```php
namespace App\Models\Modulos\{Modulo};
```

### 6.2 Estructura mínima del modelo

```php
<?php
namespace App\Models\Modulos\Hotel;

use Illuminate\Database\Eloquent\Model;
use App\Models\Modulos\Reservacion\Usuario;   // ← importar SIEMPRE en el encabezado, nunca FQN inline

class Preasignacion extends Model
{
    protected $table      = 'hot_preasignacion';
    protected $primaryKey = 'idPreasignacion';
    public    $timestamps = false;

    protected $fillable = [
        // SOLO los campos reales del esquema
        'preasignacionFecha',
        'preasignacionEstado',
        'idUsuario',
        // ...
    ];

    /* --------- Relaciones --------- */
    public function usuario()
    {
        return $this->belongsTo(Usuario::class, 'idUsuario');
    }

    /* --------- Métodos de negocio (persistencia simple) --------- */
    public static function lista(array $request = [], array $with = ['usuario'])
    {
        $query = self::query()->with($with);

        if (!empty($request['preasignacionEstado'])) {
            $query->where('preasignacionEstado', $request['preasignacionEstado']);
        }

        if (!empty($request['searchQuery'])) {
            // ...búsqueda
        }

        return $query->paginate($request['perPage'] ?? 10);
    }

    public static function nuevo(array $data): self
    {
        $m = new self();
        $m->fill($data);
        $m->preasignacionEstado = 'Activo';
        $m->save();
        return $m;
    }

    public static function editar(array $data): self
    {
        $m = self::findOrFail($data['idPreasignacion']);
        $m->fill($data);
        $m->save();
        return $m;
    }

    public static function cambiarEstado($idPreasignacion, string $estado): array
    {
        $m = self::find($idPreasignacion);
        if (!$m) return [false, null];
        $m->preasignacionEstado = $estado;
        $m->save();
        return [true, $m];
    }
}
```

### 6.3 Reglas del modelo (no negociables)

1. **Un modelo = una tabla**. Mismo nombre de tabla, mismas variables, mismos campos.
2. `protected $table` y `protected $primaryKey` siempre declarados.
3. `public $timestamps = false;` salvo que la tabla tenga `created_at`/`updated_at`.
4. **Toda inserción/actualización** debe materializarse como un **método estático del modelo** (`nuevo`, `editar`, `cambiarEstado`, `eliminar`, `lista`, etc.). Los controladores **NO** hacen `$model->save()` directo ni queries de mutación.
5. **No crear métodos que no se utilicen.**
6. **Relaciones**: la clase relacionada se **importa con `use` en el encabezado** del archivo y se referencia por su **nombre corto** (`Usuario::class`). **PROHIBIDO** usar el namespace completo (FQN) inline dentro del método (`\App\Models\Modulos\Reservacion\Usuario::class`). El FK siempre como segundo argumento.
   ```php
   // ✅ Correcto — import en el encabezado
   use App\Models\Modulos\Reservacion\Usuario;
   // ...
   public function usuario()
   {
       return $this->belongsTo(Usuario::class, 'idUsuario');
   }

   // ❌ Prohibido — FQN inline
   public function usuario()
   {
       return $this->belongsTo(\App\Models\Modulos\Reservacion\Usuario::class, 'idUsuario');
   }
   ```
   > Regla general de imports: **toda clase externa** (modelos, resources, requests, servicios, facades) se importa con `use` en el encabezado y se usa por su nombre corto. No se escriben FQN inline en el cuerpo de métodos.
7. **Estados**: los campos `{entidad}Estado` se manejan con valores legibles del dominio (`Activo`, `Inactivo`, `Eliminado`, ...). No usar valores mágicos/cortos en el código a menos que el esquema del proyecto lo exija (ese caso es propio de los proyectos de migración, fuera del alcance de este documento genérico).

---

## 7. **Form Requests** — patrón obligatorio

### 7.1 Ubicación y nombramiento

```
app/Http/Requests/{Modulo}/{Submodulo}/{Accion}FormRequest.php
```

**Una clase por acción**, no FormRequests gigantes que validen múltiples acciones.

```
app/Http/Requests/Hotel/Preasignacion/GuardarFormRequest.php
app/Http/Requests/Hotel/Preasignacion/CambiarEstadoFormRequest.php
```

### 7.2 Estructura obligatoria

```php
<?php
namespace App\Http\Requests\Hotel\Preasignacion;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class GuardarFormRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // El control de acceso vive en el middleware 'permiso:...'
    }

    public function prepareForValidation(): void
    {
        // Decodifica IDs si el front los envía codificados (base64), si el proyecto usa esa convención
        if ($this->filled('idPreasignacion')) {
            $this->merge(['idPreasignacion' => base64_decode($this->idPreasignacion)]);
        }
    }

    public function rules(): array
    {
        $rules = [
            'preasignacionFecha' => 'required|date|date_format:Y-m-d',
            'idUsuario'          => 'required|integer|exists:seg_usuario,idUsuario',
            // ...
        ];
        if ($this->filled('idPreasignacion')) {
            $rules['idPreasignacion'] = 'required|integer|exists:hot_preasignacion,idPreasignacion';
        }
        return $rules;
    }

    public function messages(): array
    {
        return [
            'preasignacionFecha.required' => 'La fecha es requerida.',
            // ...mensajes en español
        ];
    }

    protected function failedValidation(Validator $validator)
    {
        throw new HttpResponseException(response()->json([
            'success' => false,
            'status'  => 422,
            'message' => 'Tienes errores de validación',
            'errors'  => $validator->errors(),
        ], 422));
    }
}
```

### 7.3 Reglas (no negociables)

1. **Una clase por acción**, ubicada en la subcarpeta del submódulo.
2. **`authorize()` retorna `true`**: la autorización vive en el middleware `permiso:...`.
3. **`prepareForValidation()`**: decodifica IDs codificados si el proyecto usa esa convención (`btoa(id)` en JS ↔ `base64_decode` en PHP).
4. **`rules()`**: validaciones específicas, usar `exists:tabla,columna` para FKs.
5. **`messages()` en español**.
6. **`failedValidation()`**: respuesta JSON 422 con la misma estructura del resto del sistema.
7. Validaciones cross-field complejas → usar `withValidator($validator)` con `$validator->after(...)`.

---

## 8. **Resources** — patrón obligatorio

### 8.1 Ubicación y nombramiento

```
app/Http/Resources/{Modulo}/{Submodulo}/{Tipo}Resource.php
```

> **Sin prefijo de entidad** en el nombre del archivo: la carpeta `{Modulo}/{Submodulo}/` ya identifica la entidad. El archivo se nombra **únicamente** con el `Tipo` + sufijo `Resource`.

```
app/Http/Resources/Hotel/Preasignacion/ListaResource.php
app/Http/Resources/Hotel/Preasignacion/DetalleResource.php
app/Http/Resources/Hotel/Preasignacion/CatalogoResource.php
```

### 8.2 Regla del nombre `Tipo` ↔ acción del controlador

- **Cuando el Resource corresponde 1:1 a una acción, `Tipo` coincide con el nombre de la acción** en PascalCase:

  | Acción del controlador | Resource |
  |------------------------|----------|
  | `listar` | `ListaResource` |
  | `detalle` | `DetalleResource` |
  | `exportarPdf` | `ExportarPdfResource` *(cuando aplique)* |

- **Cuando es una sub-forma compartida** consumida por varias acciones, `Tipo` describe la **forma de salida**: `CategoriaResource`, `DisponibleResource`, `ResumenResource`, etc.

### 8.3 Aliasing obligatorio ante colisión

```php
use App\Http\Resources\Hotel\Preasignacion\ListaResource as PreasignacionListaResource;
use App\Http\Resources\Hotel\Habitacion\ListaResource    as HabitacionListaResource;
```

### 8.4 Estructura

```php
<?php
namespace App\Http\Resources\Hotel\Preasignacion;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ListaResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'idPreasignacion'     => $this->idPreasignacion,
            'preasignacionFecha'  => carbon_date($this->preasignacionFecha, 'd/m/Y'),
            'preasignacionEstado' => $this->preasignacionEstado,

            'usuario' => $this->whenLoaded('usuario', fn() => [
                'idUsuario'    => $this->usuario->idUsuario,
                'usuarioAlias' => $this->usuario->usuarioAlias ?? '',
            ]),
        ];
    }
}
```

### 8.5 Reglas (no negociables)

1. **Los nombres de los campos en la salida JSON deben coincidir EXACTAMENTE con los campos del esquema.** No renombrar a snake_case ni traducir.
2. **Usar `$this->whenLoaded('relacion')`** para no romper si la relación no fue cargada con `with()`.
3. **Fechas** se formatean con helpers del proyecto (p.ej. `carbon_date($valor, 'd/m/Y')`).
4. **Sin prefijo de entidad en el nombre del archivo/clase.** Nombre = `{Tipo}Resource`.
5. **`Tipo` coincide con el nombre de la acción** cuando el Resource sirve a una sola acción; para sub-formas compartidas, `Tipo` describe la forma.
6. **Aliasing obligatorio** al importar Resources con el mismo `Tipo` de entidades distintas (ver §8.3).

---

## 9. **Vistas** — según modo de trabajo

### 9.A Modo A — Blade puro

```
resources/views/modulos/{modulo}/{submodulo}/{accion}.blade.php
```

**Una vista Blade por acción de pantalla.** La vista contiene el HTML real (formularios, tablas, etc.) y usa los layouts, componentes Blade y parciales del proyecto.

```blade
{{-- resources/views/modulos/hotel/preasignacion/index.blade.php --}}
@extends('layouts.app-layout')

@section('title', 'Preasignaciones')
@section('content')
    {{-- Formulario / tabla renderizados por Blade --}}
    @include('modulos.hotel.preasignacion.partials.tabla')
@endsection
```

**Reglas Modo A:**
1. Una vista Blade por acción de pantalla (`index`, `crear`, `editar`, ...).
2. Reutilizar layouts y componentes Blade del proyecto (`@include`, `<x-...>`), no duplicar markup.
3. Las peticiones AJAX puntuales consumen los mismos endpoints `/{modulo}/{submodulo}/{accion}` y esperan la misma respuesta JSON estándar.

### 9.B Modo B — Vue integrado: Blade como **montador**

```
resources/views/modulos/{modulo}/{submodulo}/{accion}.blade.php
```

**Una vista Blade por acción `GET`.** El Blade es un **montador puro** del componente Vue. La etiqueta sigue siempre `<{modulo}-{submodulo}-{accion}>` (kebab-case).

```blade
{{-- resources/views/modulos/hotel/preasignacion/index.blade.php --}}
@extends('layouts.app-layout')

@section('title', 'Preasignaciones')
@section('content')
    <hotel-preasignacion-index></hotel-preasignacion-index>
@endsection
```

```blade
{{-- Con props inyectadas desde el controlador --}}
@section('content')
    <hotel-preasignacion-sin-habitacion
        :id-tipo-habitacion-inicial="{{ (int) $idTipoHabitacion }}"
        dia-inicial="{{ $dia }}"
        tipo-habitacion-alias="{{ $tipoHabitacionAlias }}"
    ></hotel-preasignacion-sin-habitacion>
@endsection
```

**Reglas Modo B:**
1. Una vista Blade por acción de pantalla.
2. La etiqueta del componente Vue **siempre** es `<{modulo}-{submodulo}-{accion}>` en kebab-case.
3. **Props simples** (escalares): `prop="literal"` o `prop="{{ $valor }}"`. **Props complejas** (arrays/objetos): `:prop="{{ json_encode($datos) }}"`.
4. El Blade **no tiene lógica de presentación** propia — es un montador puro.
5. El componente Vue se registra en `resources/js/app.js` con `app.component("{modulo}-{submodulo}-{accion}", ...)`.

### 9.C Modo C — Vue proyecto separado (SPA)

No hay Blade. Cada acción de pantalla es un **componente/vista Vue ruteado por Vue Router**.

```
src/modulos/{modulo}/{submodulo}/{Accion}.vue
src/router/  → define la ruta /{modulo}/{submodulo}/{accion}
```

**Reglas Modo C:**
1. Una vista Vue por acción de pantalla, con la **misma URL lógica** `/{modulo}/{submodulo}/{accion}` definida en Vue Router.
2. El backend expone **solo API** (`listar`, `guardar`, `detalle`, ...); no renderiza HTML de pantalla.
3. La autenticación viaja por token/cookie según configure el proyecto; el patrón de permisos `permiso:...` sigue vigente en la API.

---

## 10. **Frontend Vue** — patrón obligatorio (Modos B y C)

> **Principio rector:** la estructura del frontend **espeja** la estructura del backend.
> Si el modelo vive en `app/Models/Modulos/Hotel/Preasignacion.php`, su componente Vue, su store Pinia y sus composables viven en la misma ruta lógica (`.../hotel/preasignacion/`).

### 10.1 Componentes Vue — **un archivo por acción de pantalla**

Cada acción que renderiza una pantalla tiene su **propio componente Vue**. El nombre del archivo en **PascalCase** coincide con el método del controlador (Modo B) o con la vista ruteada (Modo C).

```
{raizFront}/modulos/{modulo}/{submodulo}/
├── Index.vue              # acción `index`
├── Editar.vue             # acción `editar`
├── Preasignadas.vue       # acción `preasignadas`
├── SinHabitacion.vue      # acción `sinHabitacion`
└── components/            # sub-componentes locales (no reutilizables fuera)
    ├── PreasignacionLayout.vue
    └── HabitacionBadge.vue
```

> `{raizFront}` = `resources/js` en **Modo B**, o `src` en **Modo C**.

**Reglas (no negociables):**
1. **Un archivo Vue por acción de pantalla**. Nombre = PascalCase del método (`index` → `Index.vue`, `sinHabitacion` → `SinHabitacion.vue`).
2. **[Modo B]** Etiqueta de registro kebab-case `{modulo}-{submodulo}-{accion}`. **[Modo C]** Registro en Vue Router con path `/{modulo}/{submodulo}/{accion}`.
3. **Sub-componentes locales** viven en `components/`.
4. **Componentes reutilizables del sistema** (`BaseInput`, `BaseButton`, `ConfirmModal`, `AlertNotification`, `DynamicTable`, ...) viven en `{raizFront}/components/` y se usan por igual en todos los módulos (ver §12).
5. **Prohibido** crear un solo archivo `{Submodulo}.vue` que contenga todas las pantallas.
6. **REGLA DE REUTILIZACIÓN (no negociable):** todo elemento de UI o comportamiento que se use —o pueda usarse— en **más de una pantalla o módulo** debe extraerse a un **componente genérico separado** en `{raizFront}/components/`, nombrado con prefijo `Base` cuando es un primitivo de captura/acción (`BaseInput.vue`, `BaseButton.vue`, `BaseSelect.vue`, `BaseModal.vue`, ...) o con nombre descriptivo cuando es compuesto (`DynamicTable.vue`, `ConfirmModal.vue`, `AlertNotification.vue`, ...).
   - **Antes de escribir markup nuevo**, verifica si ya existe un componente base que lo cubra. Si existe, **se reutiliza**; si no existe pero el patrón se repetirá, **se crea el componente base primero** y luego se consume.
   - **PROHIBIDO** duplicar inputs, botones, tablas, modales, alertas, badges, tabs, paginadores o cualquier bloque repetible como HTML nativo ad-hoc dentro de una pantalla.
   - Si un sub-componente local (`components/`) empieza a usarse fuera de su submódulo, se **promueve** a `{raizFront}/components/` y se reescribe sin dependencias de dominio (misma regla de promoción que los composables, §10.3).

**[Modo B] Registro en `resources/js/app.js`:**

```js
import HotelPreasignacionIndex from "./modulos/hotel/preasignacion/Index.vue";
import HotelPreasignacionEditar from "./modulos/hotel/preasignacion/Editar.vue";

app.component("hotel-preasignacion-index",  HotelPreasignacionIndex);
app.component("hotel-preasignacion-editar", HotelPreasignacionEditar);
```

> **Convención de import:** constante importada `{Modulo}{Submodulo}{Accion}` en PascalCase; componente registrado `{modulo}-{submodulo}-{accion}` en kebab-case.

### 10.2 Pinia Stores

```
{raizFront}/stores/{modulo}/{submodulo}Store.js
```

**Regla obligatoria:** todas las llamadas HTTP viven en el store. Los componentes **NUNCA** invocan `axios` directamente.

```js
import { defineStore } from "pinia";
import axiosInstance from "@/services/axiosInstance";

export const usePreasignacionStore = defineStore("preasignacion", {
    state: () => ({ data: null, loading: false, error: null }),
    actions: {
        async fetchListarPreasignaciones(payload) {
            this.loading = true;
            try {
                const { data } = await axiosInstance.post("/hotel/preasignacion/listar", payload);
                this.data = data;
                return data;
            } finally {
                this.loading = false;
            }
        },
        async fetchGuardarPreasignacion(payload) { /* ... */ },
        async fetchCambiarEstadoPreasignacion(id, estado = "Eliminado") { /* ... */ },
    },
});
```

**Convención de nombres de actions:** `fetch{Accion}{Entidad}` → `fetchListarPreasignaciones`, `fetchGuardarPreasignacion`. El prefijo `fetch` deja claro que es I/O async contra el backend.

### 10.3 Composables

| Alcance | Ubicación | Ejemplo |
|---------|-----------|---------|
| Genérico reutilizable (raíz) | `{raizFront}/composables/use{Nombre}.js` | `useCrud.js`, `useAlert.js`, `useListPagination.js` |
| Específico de un submódulo | `{raizFront}/composables/{modulo}/{submodulo}/use{Accion}Form.js` | `composables/hotel/preasignacion/usePreasignacionForm.js` |

**Regla de promoción:** si un composable empieza siendo específico y termina usándose en 2+ módulos, se **mueve a la raíz** y se reescribe sin dependencias de dominio.

### 10.4 Reglas frontend (no negociables)

1. **Idioma único: ESPAÑOL.** Archivos, variables, stores, composables, props, payloads y carpetas en español, **idénticos al esquema**. **PROHIBIDO** mezclar inglés.
2. **Variables = campos del esquema.** `preasignacionFecha`, no `fecha_preasignacion`, ni `assignmentDate`.
3. **Espejo backend ↔ frontend.** La ruta lógica es la misma que el modelo/controlador.
4. **URLs de endpoints** siguen `/{modulo}/{submodulo}/{accion}`.
5. **[Modo B]** No usar Vue Router; el ruteo lo maneja Laravel (cada Blade monta un componente). **[Modo C]** Vue Router es el ruteador de pantallas; el backend es solo API.
6. **Configuración axios centralizada** (`axiosInstance`): `withCredentials: true` en Modo B (sesión Laravel), o `baseURL` + interceptor de token en Modo C.
7. **Pinia es la única puerta a `axios`.** Componentes nunca llaman `axios` directamente.
8. **Sin archivos versionados a mano.** Nada de `Componente-1.vue`, `Componente_rediseño.vue`. El control de versiones gestiona historia y branches.

---

## 11. **Bitácora**

Toda operación de escritura relevante registra el evento correspondiente en la bitácora del módulo (si el proyecto la implementa):

```php
Bitacora::agregar(
    $idEvento,            // ID definido en la tabla de eventos del módulo
    $registro->idEntidad, // PK del registro afectado
    'Texto descriptivo',  // Acción realizada
    request()->ip()
);
```

---

## 12. Componentes y composables reutilizables — uso **OBLIGATORIO** (Modos B y C)

Los siguientes componentes son la **base UI canónica**. Cualquier nueva pantalla, CRUD, catálogo o panel **debe** usarlos. **No se permite** recrear modales, alertas, botones, tablas o inputs propios.

> **Principio de componente reutilizable (regla, ver §10.1 #6):** cuando un elemento se repite en el sistema, **no se copia markup**: se construye **un componente genérico separado** (ej. `BaseInput.vue`) y se reutiliza en todas partes. Esta lista es el punto de partida; el catálogo crece conforme aparecen nuevos patrones repetibles, y cada uno se crea como componente propio en `{raizFront}/components/`.

### 12.1 `components/modal/ConfirmModal.vue`

Modal de confirmación (borrado, descartar cambios, cancelaciones, advertencias bloqueantes).

**Props:** `show` (Boolean, req.), `title`, `message`, `cancelText` *(def. "Cancelar")*, `confirmText` *(def. "Continuar")*, `cancelButtonClass`, `confirmButtonClass`, `isLoading`, `showCancel` *(def. true)*, `width` *(def. "500px")*.
**Emits:** `confirm`, `cancel`, `update:show`.

```vue
<ConfirmModal
    :show="confirmState.show"
    @update:show="confirmState.show = $event"
    title="Eliminar registro"
    :message="`¿Eliminar al usuario ${row.usuarioAlias}?`"
    confirm-text="Eliminar"
    @confirm="onConfirmEliminar"
    @cancel="confirmState.show = false"
/>
```

### 12.2 `components/alert/AlertNotification.vue`

Notificación tipo toast con auto-cierre (éxito post-guardado, errores generales 4xx/5xx no-422, avisos).

**Props:** `message`, `type` (`success` | `danger` | `info` | `warning`), `duration` *(ms, def. 4000)*, `customClass`.
**Emits:** `update:message`.

### 12.3 `components/button/BaseButton.vue`

Botón estandarizado. **Prohibido** usar `<button>` puro o estilos ad-hoc en pantallas nuevas.

**Props:** `variant`, `size` (`sm` | `md` | `lg`), `loading`, `disabled`, `fullWidth`, `icon`, `iconPosition` (`left` | `right`), `customClass`, `type`.
**Emits:** `click`.

### 12.4 `components/tables/DynamicTable.vue`

Tabla base con sorting, paginación y slots por celda. **Toda lista** usa este componente.

**Props clave:** `headers` (`[{label, key, sort?, width?, align?}]`), `rows`, `showActions`, `isLoading`, `sortField`, `sortDirection`, `pagination`, `enablePagination`, `selectable`, `textoNoData`.
**Emits:** `sort`, `load-more`, `change-page`, `selectRowIndex`.
**Slots:** `#cell-{key}="{ row }"`.

### 12.5 `components/BaseInput.vue`

Input universal (`text`, `email`, `date`, `select`, `textarea`, `number`, ...). **Toda captura de datos** lo usa.

**Props clave:** `label`, `modelValue`, `type`, `options`, `required`, `error` (`String | String[]`), `placeholder`, `disabled`, `readonly`, `loading`, `size`, `maxLength`, `minLength`, `onlyNumbers`, `noSpecialChars`.
**Emits:** `update:modelValue`.

### 12.6 `composables/useCrud.js`

Composable canónico para pantallas CRUD estándar (listar + crear + editar + eliminar). Encapsula estado (`items`, `loading`, `saving`, `error`, `pagination`, `validationErrors`), acciones (`cargar`, `guardar`, `eliminar`, `cambiarEstado`) e integración con `AlertNotification` / `ConfirmModal` y manejo de 422 (§13).

**Regla:** todo CRUD nuevo **debe** usar `useCrud` salvo justificación de dominio (drag&drop, batch atómico, lógica especializada).

---

## 13. Manejo de `validationErrors` (back ↔ front) — REGLA OBLIGATORIA (Modos B y C)

Los errores 422 emitidos por los Form Requests **deben** llegar a la vista y mostrarse junto al campo (`BaseInput`) o como alerta global (`AlertNotification`).

### 13.1 Backend — FormRequest emite 422 estandarizado

```php
protected function failedValidation(Validator $validator)
{
    throw new HttpResponseException(response()->json([
        'success' => false,
        'status'  => 422,
        'message' => 'Tienes errores de validación',
        'errors'  => $validator->errors(), // { campo: [msg1, msg2], ... }
    ], 422));
}
```

### 13.2 Store (Pinia) — propaga el error

```js
async fetchGuardar(payload) {
    this.saving = true;
    try {
        const { data } = await axiosInstance.post('/modulo/submodulo/guardar', payload);
        return data;
    } catch (error) {
        this.error = error.response?.data?.message;
        throw error;   // SIEMPRE re-lanzar para que el componente capture
    } finally {
        this.saving = false;
    }
}
```

### 13.3 Componente Vue — mapea a `validationErrors` y muestra

```js
const validationErrors = ref({});

const onGuardar = async () => {
    validationErrors.value = {};   // limpiar antes de cada submit
    try {
        await store.fetchGuardar(form.value);
        showSuccess('Guardado correctamente');
    } catch (error) {
        if (error.response?.status === 422) {
            validationErrors.value = error.response.data.errors || {};
        } else {
            showError(error.response?.data?.message || 'Error inesperado');
        }
    }
};
```

```vue
<BaseInput v-model="form.usuarioAlias" label="Usuario"
           :error="validationErrors.usuarioAlias?.[0]" />

<AlertNotification v-if="validationErrors._general" type="danger"
           :message="validationErrors._general[0]" />
```

### 13.4 Reglas duras (no negociables)

1. **Toda pantalla con formulario** declara `const validationErrors = ref({})`.
2. Cada `BaseInput` bindea `:error="validationErrors.{campo}?.[0]"`.
3. El store **siempre re-lanza** la excepción con `throw error;` tras setear `this.error`.
4. El componente **siempre limpia** `validationErrors.value = {}` antes de cada submit.
5. Errores no ligados a un campo (`items` en batch, `_general`) se muestran con `AlertNotification` tipo `danger`.
6. **Prohibido** mostrar errores 422 con `alert()`, `console.error()` o componentes ad-hoc.

> **[Modo A]** En Blade puro, los errores 422 se muestran con `@error('campo')` / `$errors` de Laravel, respetando la misma estructura de respuesta.

---

## 14. **Checklist de creación de un módulo nuevo** (cero → entregable)

Cuando se crea un módulo nuevo desde una tabla del esquema, **seguir EXACTAMENTE este orden**:

1. ✅ Consultar la tabla en el **esquema/diccionario del proyecto** y listar todos sus campos.
2. ✅ Crear **Modelo** en `app/Models/Modulos/{Modulo}/{Submodulo}.php` con `$table`, `$primaryKey`, `$timestamps`, `$fillable`, métodos estáticos **simples** de persistencia (`lista`, `nuevo`, `editar`, `cambiarEstado`, `detalle`) y relaciones. La lógica compleja vive en el controlador (§5.2 #4.b).
3. ✅ Crear **Form Requests** por acción en `app/Http/Requests/{Modulo}/{Submodulo}/`.
4. ✅ Crear **Resources** en `app/Http/Resources/{Modulo}/{Submodulo}/{Tipo}Resource.php` (subcarpeta por entidad, **sin prefijo de entidad** en el archivo).
5. ✅ Crear **Controlador** en `app/Http/Controllers/{Modulo}/{Submodulo}Controller.php`.
6. ✅ Crear archivo de **Rutas** en `routes/modulos/{modulo}/{submodulo}.php` e incluirlo en `routes/web.php` o `routes/api.php`.
7. ✅ **Presentación según modo:**
   - **[Modo A]** Crear una **Vista Blade por acción de pantalla** con el markup real.
   - **[Modo B]** Crear una **Vista Blade montadora por acción** (`<{modulo}-{submodulo}-{accion}>`) + su **Componente Vue**; registrar cada componente en `app.js`.
   - **[Modo C]** Crear una **Vista Vue por acción** + su ruta en **Vue Router**.
8. ✅ **[Modo B/C]** Usar **OBLIGATORIAMENTE** `BaseInput`, `BaseButton`, `DynamicTable`, `ConfirmModal`, `AlertNotification` y `useCrud` cuando aplique (§12).
9. ✅ **[Modo B/C]** Implementar el flujo de `validationErrors` (§13) en todo formulario. **[Modo A]** usar `$errors`/`@error`.
10. ✅ **[Modo B/C]** Crear **Pinia Store** en `{raizFront}/stores/{modulo}/{submodulo}Store.js` (única puerta a axios, re-lanza errores).
11. ✅ **[Modo B/C]** Crear **Composable** específico (si aplica) en `.../composables/{modulo}/{submodulo}/use{Accion}Form.js`.
12. ✅ Registrar los permisos en la tabla de rutas/permisos del proyecto para que `permiso:...` los reconozca.
13. ✅ Probar el flujo completo en navegador.

---

## 15. **Reglas globales no negociables — resumen**

1. **Nunca inventar campos.** Consultar el esquema/diccionario del proyecto.
2. **Nombrar variables exactamente como los campos del esquema** (español, sin snake_case, sin inglés).
3. **Toda mutación DB vive en métodos del modelo.** Los controladores delegan.
4. **Toda escritura va en `DB::beginTransaction()` + `try/catch`.**
5. **Validar con Form Request por acción.** Una clase por acción.
6. **Respuestas JSON con la estructura estandarizada** (`success`, `code`, `message`, `data`).
7. **No crear métodos que no se utilicen.**
8. **No mezclar lógica de varias entidades** en un mismo controlador, modelo o componente.
9. **[Modo B/C] Pinia es la única puerta a `axios`.** Componentes no llaman `axios` directamente.
10. **[Modo B/C] Una Vue por acción de pantalla** (`Index.vue`, `Editar.vue`, ...).
11. **[Modo B/C] Uso obligatorio** de `BaseInput`, `BaseButton`, `DynamicTable`, `ConfirmModal`, `AlertNotification` y `useCrud` (§12). **Todo elemento reutilizable en el sistema se crea como componente genérico separado** en `{raizFront}/components/` (ej. `BaseInput.vue`); prohibido duplicar markup ad-hoc (§10.1 #6).
12. **[Modo B/C] Errores 422** SIEMPRE se propagan al frontend vía `validationErrors` (§13).
13. **Lógica compleja en controlador, persistencia simple en modelo.** Si un método del modelo excede ~5 líneas y no es CRUD puro, mueve la lógica al controlador o a un Service.
14. **La estructura y el nombramiento son idénticos en los tres modos de trabajo.** Solo cambia la capa de presentación.
15. **Imports con `use` en el encabezado, nunca FQN inline.** En modelos, controladores, requests y resources, toda clase externa (incluidas relaciones Eloquent) se importa con `use` y se referencia por su nombre corto. Prohibido `\App\...\Clase::class` dentro del cuerpo de métodos (§6.3 #6).

---

## 16. Tabla resumen — qué cambia entre modos

| Aspecto | Modo A (Blade puro) | Modo B (Vue integrado) | Modo C (Vue separado) |
|---------|--------------------|-----------------------|-----------------------|
| Nombramiento tabla → artefactos | ✅ idéntico | ✅ idéntico | ✅ idéntico |
| Controlador / Modelo / FormRequest / Resource | ✅ idéntico | ✅ idéntico | ✅ idéntico |
| Rutas backend | `web` | `web` | `api` |
| Ruteo de pantallas | Laravel | Laravel (Blade monta Vue) | Vue Router |
| Vista de pantalla | Blade con markup | Blade montadora + Vue | Vue |
| Capa HTTP del front | Formularios / fetch | Pinia + axios (`withCredentials`) | Pinia + axios (baseURL API) |
| Componentes base + `useCrud` | — | ✅ obligatorio | ✅ obligatorio |
| Manejo de errores 422 | `$errors` / `@error` | `validationErrors` | `validationErrors` |

---

**Aplicabilidad:** todos los proyectos del equipo (Laravel + Blade / Vue integrado / Vue SPA).
**Fuera de alcance:** sistema ENUM/mappings (`enum_mappings`, `char_mappings`, `MappedQueryBuilder`), exclusivo de proyectos en migración V1↔V2 con esquema compartido.
