# CLAUDE.md

Guía operativa para agentes de IA que trabajen en este repositorio.

**Para el detalle completo (arquitectura, módulos, modelo de datos, seguridad), leer [ConocimientoControlTower.MD](ConocimientoControlTower.MD).** Este archivo contiene solo lo mínimo para no equivocarse.

---

## Qué es

**Control Tower (CTC)** de ASERCOL S.A.: plataforma web multi-cliente que da visibilidad sobre operaciones de comercio exterior. Clientes externos consultan el estado de sus importaciones y exportaciones; equipos internos las gestionan.

No es la fuente de la verdad aduanera — esa es **Websimex** (ERP externo). El CTC sincroniza desde el ERP, enriquece y presenta según la configuración de cada cliente.

**Stack:** Laravel 8.76.2 · PHP 7.3 · Livewire 2 · Blade · Bootstrap (shell) + Tailwind CDN (módulos nuevos) · MySQL.

---

## Reglas no negociables

### 1. Todo query de negocio filtra por el tenant

`session('GrupoEmpresarial')` es la clave de aislamiento entre clientes. **No hay global scope, trait ni middleware que lo aplique**: es manual en cada consulta. Omitirlo expone los datos de un cliente a otro.

```php
// Tablas propias del CTC
->where('id_grupo_empresarial', session()->get('GrupoEmpresarial'))

// Tablas de Websimex (vía la tabla compania → id_websimex)
->whereIn('ID_IMPORTADOR', $compania->pluck('id_websimex'))
```

El tenant se resuelve por **subdominio**, en un bloque `@php` dentro de `resources/views/login.blade.php`.

Reportes hechos a la medida de un cliente, con importadores fijos en el código: en `mount()` llamar a `$this->exigirImportadores([...])` (trait `App\Http\Livewire\Concerns\FiltraPorGrupoEmpresarial`), que responde 403 fuera de su grupo. Nunca dejar el importador en una propiedad pública sin `#[Locked]`.

### 2. El código debe compilar en PHP 7.3

Prohibido: `match`, propiedades tipadas, *constructor property promotion*, `enum`, nullsafe `?->`, argumentos con nombre, `str_contains`/`str_starts_with` nativos.

### 3. No ejecutar `php artisan migrate`

Solo existen 2 migraciones (`users`, `password_resets`). El esquema real (~230 tablas) se administra fuera del repositorio, directamente en base de datos.

### 4. Verificar qué modelo se importa

Hay modelos duplicados con nombres casi idénticos apuntando a la misma tabla:

| Legacy (`App\`) | Actual (`App\Models\`) | Tabla |
|---|---|---|
| `App\PO` | `App\Models\Po` | `PO` |
| `App\ExPO` | `App\Models\ExPo` | `ex_po` |
| `App\Actividad` | `App\Models\Actividad` | `actividad` |
| `App\Compania` | — | `compania` |
| `App\TipoDocumento` | `App\Models\TipoDocumento` | `tipo_documentos` |

El legacy suele tener `$fillable` y relaciones; el de `Models/` suele ser mínimo. **Leer siempre el bloque `use` del archivo antes de editar** y no mezclar convenciones dentro de un mismo módulo.

### 5. No tocar archivos de respaldo

Copias muertas que no están enrutadas: `*01nov2022.php`, `*20sep2022.php`, `ExPOController_13042023.php`, `MaterialController_19042023.php`, `POController16012026.php`, `expoControllerBK.php`, `ExpoUnidadesInspeccion.php.php`, `layouts/planti1|planti2|planti_ant|planti12032020|planti20sept2022`, ~20 `app/Imports/PoImportAlp*.php`.

### 6. Ningún secreto en el código

Usar `.env` / `config()`. `.env` no está versionado y debe seguir así.
Existe una infracción conocida y documentada: credenciales AWS hardcodeadas en `app/ConexionAWS.php:29`. No replicar ese patrón; si se toca ese archivo, migrar a `config('filesystems.disks.s3')`.

### 7. Importaciones y exportaciones son gemelos

Casi todo módulo existe en dos versiones: sin prefijo (importaciones) y con prefijo `ex`/`Ex` (exportaciones). Antes de dar por terminado un cambio, **verificar si el gemelo lo necesita también**.

---

## Mapa de directorios

| Ruta | Contenido |
|---|---|
| `app/*.php` (126) | Modelos legacy + helpers (`JMetodos`, `JMail`, `ConexionAWS`, `FileAWS`) |
| `app/Models/` (203) | Modelos actuales (+ subcarpetas `Astara/`, `Cti/`, `Tr/`) |
| `app/Http/Livewire/` (153) | **Donde ocurre el desarrollo actual**, 25 áreas temáticas |
| `app/Http/Controllers/` | ~150 controladores legacy + `Api/v1`, `Api/v2`, `Auth`, `Movil` |
| `app/Http/Clases/` | Lógica de negocio suelta (`UnirMundos`, `ValidarCutoff`, …) |
| `app/Services/` | Integraciones externas — solo `ShipsgoService`, es el patrón a replicar |
| `app/Console/Commands/` | 17 comandos, 12 programados (sincronización con el ERP) |
| `app/Exports/` (~60) · `app/Imports/` (~44) | Excel, un importador por cliente |
| `app/JMetodos.php` | Helper global (`autoload.files`) — motor de columnas dinámicas |
| `resources/views/livewire/` | Vistas de los componentes Livewire |
| `resources/views/layouts/` | `planti` (shell), `tailwind` (contenido moderno), + obsoletos |
| `routes/web.php` | 634 rutas, 974 líneas |

---

## Conexiones de base de datos

| Conexión | Base | Rol |
|---|---|---|
| `mysql` *(default)* | `asercolg_po` | BD propia del CTC |
| `websimex` | `comex` | ERP aduanero — **SOLO LECTURA** |
| `websystem` | `asercol` | Estados de hechos / etapas — **SOLO LECTURA** |
| `cti` | `asercolg_bitacora` | Usuarios internos, documentos S3, contenedores |
| `central` | `asercolg_central` | Regalías, declaraciones de exportación |
| `demopo` / `poprueba` | `asercolg_poprueba` | Pruebas |

Escribir en `websimex` o `websystem` rompe el ERP. El flujo es siempre ERP → CTC, mediante los comandos `sync:*`.

`NUMERO_DO` es el identificador que une los cuatro sistemas. Es **string**, nunca castear a int.

---

## Cómo agregar un módulo

Patrón vigente (rama `feature/layout-tailwind`): **componente Livewire de página completa sobre el layout nuevo** (`layouts.app`). La norma visual está en [specs/layout.md](specs/layout.md): tokens CSS, nunca colores fijos ni clases armadas con `session('color')`. El patrón anterior (contenedor `planti` + iframe con `layouts.tailwind`) ya no se usa para módulos nuevos.

1. `php artisan make:livewire Importaciones/MiModulo`
2. En `mount()`, sembrar el contexto del tenant (sin `session('color')`: los colores salen de los tokens):
   ```php
   $this->idGrupoEmpresarial = session()->get('GrupoEmpresarial');
   $compania = Compania::where('id_grupo_empresarial', $this->idGrupoEmpresarial)
                       ->where('inactivo', 0)->get();
   $this->compania = $compania->pluck('id_websimex')->toArray();
   ```
3. En `render()`: `return view('livewire.importaciones.mi-modulo')->layout('layouts.app', ['titulo' => 'Mi módulo']);`. La vista sigue la plantilla de página de layout.md §5, con una sola raíz.
4. Una ruta en `routes/web.php`, con `->middleware('auth')` y la barra inicial (el grupo antepone el namespace de controladores):
   `Route::get('impo/mi-modulo', '\\' . \App\Http\Livewire\Importaciones\MiModulo::class)->middleware('auth');`
5. Añadir el path (`impo/mi-modulo`) a `config('ctc.pantallas_layout')`, para que el menú lo enlace directo y no por `/marco`.
6. **En base de datos**: insertar en `menus` (con `ruta`, `depende`, `orden`, `perfiles`, `estado=1`) y añadir el `id` del menú al JSON `compania.aplicaciones` de cada cliente que deba verlo. Sin esto el módulo no aparece.

Componente de referencia: `app/Http/Livewire/Layout/Muestra.php` (`/layout/muestra`). `Importaciones/OrdenesCompra.php` sigue siendo la referencia de lógica de negocio, pero su vista es heredada (familia `empresa`, ver MigracionLayout.MD L2a).

---

## Convenciones

| Elemento | Convención | Ejemplo |
|---|---|---|
| Ruta resource | `r` + entidad | `rpuertos` |
| Exportaciones | prefijo `ex` / `rex` | `rexcliente`, `ex_po` |
| Búsqueda / índice / edición | `srh` / `idx` / `editar/{id}` | `rexclientesrh` |
| Exportar a Excel | `export` + entidad | `exportmateri` |
| Componente Livewire | PascalCase en `App\Http\Livewire\<Área>` | `Importaciones\OrdenesCompra` |
| Vista Livewire | kebab-case en `views/livewire/<área>` | `importaciones/ordenes-compra` |
| Comando de sincronización | `sync:<entidad>` | `sync:orden-compra` |
| Idioma | **Español** en código, comentarios, tablas y UI | |

---

## Verificación

**No hay tests** — `tests/` solo trae el esqueleto de Laravel. La verificación es manual:

```bash
php -l <archivo>                       # sintaxis
php artisan route:list | grep <ruta>   # la ruta quedó registrada
php artisan config:clear               # tras tocar config/ o .env
```

Luego abrir el módulo en el navegador (Laravel Herd) con un usuario de un grupo empresarial de prueba y **verificar explícitamente que no se filtran datos de otro cliente**.

⚠️ El `.env` apunta a **bases de datos reales compartidas**, no a una BD local. Extremar cuidado con cualquier operación de escritura durante el desarrollo.

---

## Trampas frecuentes

- **`QUEUE_CONNECTION=sync`** — correos y reportes se ejecutan dentro del request. Causa habitual de *timeouts* en reportes grandes.
- **`JSONARRAY_VERIFY_DATA`** es una función MySQL propietaria definida en la BD, no en el repo. Sin ella el menú sale vacío.
- **IDs hardcodeados** de grupos empresariales en los comandos `sync:*`. Un cliente nuevo no se sincroniza hasta añadir su ID.
- **Typos en producción**: `/shipsgo/ifame`, `dasboard`, `ReprteGlobalBrinsa`, `busquedaIncremental/{conatinername}`. Existen tal cual — no "corregirlos" sin revisar quién los referencia.
- **Rutas duplicadas** en `web.php` — mismo verbo y misma URI: `GET ws/insertar`, `GET exportpores`, `GET rexhistorico`. Laravel usa la última definición; las primeras son código muerto.
- **Rutas públicas:** desde el 26/10/2026 solo 11 rutas web no exigen `auth`, a propósito: `/`, `logout`, la API por token (`ws`, `ws/insertar`, `ws/informes`, `ws/informes2`, `dev-api/adama/autenticacion`), `credenciales` y `cambiarclave` (recuperar el token de la API) y `rexpo/notifica/{…}/{tok}` (enlace de correo). Toda ruta nueva lleva `->middleware('auth')`. La autorización por perfil (`Util::acceso()`) sigue comentada en `app/Http/Middleware/Authenticate.php`: el menú oculta opciones, pero con sesión las URLs siguen accesibles para cualquier perfil (MigracionLayout.MD §5.3).

---

## Archivos de referencia

| Para entender… | Archivo |
|---|---|
| Aislamiento entre clientes | `resources/views/login.blade.php` (líneas 1–56) |
| Menú y permisos | `app/Http/Livewire/Plantilla/Sidebar.php`, `app/Http/Controllers/Util.php` |
| Columnas y semáforos configurables | `app/JMetodos.php` |
| Componente Livewire modelo | `app/Http/Livewire/Importaciones/OrdenesCompra.php` |
| Integración con API externa | `app/Services/ShipsgoService.php` |
| Sincronización con el ERP | `app/Console/Commands/OrdenesCompraCommand.php` |
| Procesos automáticos | `app/Console/Kernel.php` |
| Middlewares | `app/Http/Kernel.php` |
| Conexiones de BD | `config/database.php` |
