# New-Unity ERP — Sweet Works ERP (BYNET Technologies)

## Stack
- Laravel 12, PHP 8.2+ (Laragon local corre 8.3.15), MySQL, Spatie Permissions 6.x, SmartAdmin UI
- Multi-empresa: cada instalación es una empresa independiente
- Modular: app/Modules/{Module}/
- Vistas: app/Modules/{Module}/Resources/Views/ con namespace {module}::
- Migraciones: app/Modules/{Module}/Database/Migrations/ Y database/migrations/
- Seeders: app/Modules/{Module}/Database/Seeders/ Y database/seeders/
- Paquetes clave: barryvdh/laravel-dompdf, maatwebsite/excel, picqer/php-barcode-generator,
  simplesoftwareio/simple-qrcode, spatie/laravel-permission

## Estructura de archivos clave

### Rutas
- Entry point: routes/web.php (carga auth.php, core.php, setup.php; módulos cargan sus propias rutas)
- Rutas de módulos: app/Modules/{Module}/Routes/web.php
- CRÍTICO: rutas estáticas (/create, /import, /template, /export, /toggle-status) SIEMPRE antes
  de wildcards (/{id}, /{model}) — el wildcard captura "create" como ID causando 404 silencioso.
  Ya hubo bugs en suppliers/create y picking por este motivo. Patrón auditado: Purchases v2.2 + Warehouse v2.2.
  NUEVO: brands/create y brands/{id}/edit también se añadieron en Products/Routes v1.5 (05/08/2026).

### HTTP
- Controllers de módulos: app/Modules/{Module}/Http/Controllers/
- Middleware: app/Http/Middleware/Auth_Middleware/
  (EnsureUserIsActive — bloquea login si is_active=0)
- Core controllers (Dashboard, OperationPin): app/Http/Controllers/Core_Controllers/
- Form Requests: app/Modules/{Module}/Http/Requests/ (donde existan)

### Database
- Migraciones Core auth/users/sessions: database/migrations/Auth_Migrations/
- Migraciones Core empresa/config: database/migrations/Core_Migrations/
- Migraciones de módulos: app/Modules/{Module}/Database/Migrations/
- Seeders Core: database/seeders/
- Seeders de módulos: app/Modules/{Module}/Database/Seeders/
- IMPORTANTE: NO mover migraciones sueltas — AppServiceProvider y cada ServiceProvider
  ya saben dónde leerlas. Moverlas es riesgo sin beneficio.

### Providers
- Principal: app/Providers/AppServiceProvider.php
  (Gate::before Super Admin bypass, carga migraciones Core, registra módulos, SMTP, helpers)
- Por módulo: app/Modules/{Module}/Providers/{Module}ServiceProvider.php
  (registra rutas, vistas y migraciones del módulo)

### Vistas
- Layout principal: resources/views/layouts/vertical.blade.php
- Menú lateral: resources/views/layouts/partials/unity-navigation.blade.php
  (chain: vertical.blade.php → app-sidebar → unity-navigation)
  Badges dinámicos via NavigationBadgeComposer (registrado en AppServiceProvider v1.7)
- Campana de notificaciones: resources/views/layouts/partials/app-notifications.blade.php
  (SmartAdmin: notification-header, nav-tabs-clean, tab-notification, custom-scroll, notification-empty-msg)
- Partials de layout: resources/views/layouts/partials/
- Vistas de módulos: app/Modules/{Module}/Resources/Views/
  (acceso vía namespace: purchases::suppliers.create)
- Vistas core/setup: resources/views/setup/
- Páginas de error: resources/views/errors/

### Assets e imágenes
- CSS personalizado: public/css/custom.css
- JS global uppercase: public/js/uppercase-input.js
- Imágenes del sistema: public/images/
- Logos de marcas (validbrands): storage/app/public/brands/
- Fotos de productos: storage/app/public/products/
- Logos de empresa: storage/app/public/companies/
- Acceso público vía symlink: php artisan storage:link
- CRÍTICO Linux case-sensitive: public/modules/billing/js/ debe ser lowercase /js/ no /Js/
  (ese bug ya ocurrió en producción — el fix fue renombrar la carpeta)

## Módulos registrados (orden en AppServiceProvider.php v1.7)
1. AdminServiceProvider     — usuarios, roles, permisos, SMTP, notificaciones internas
2. SalesServiceProvider     — CRM Lite, oportunidades, actividades, cotizaciones, SO, plantillas email
3. ProductsServiceProvider  — productos, grupos, categorías, marcas, clasificación, bundles,
                              industrias (sub-módulo en Catálogos — brand_industries + pivot brand_industry)
4. WarehouseServiceProvider — almacenes, zonas, racks, bins, recepciones, kardex, PEPS, adjust-in/out
5. ProductionServiceProvider — MRP parcial, propuestas de producción
6. BillingServiceProvider   — CFDI 4.0, egresos, complementos de pago, catálogos SAT, Carta Porte
7. PurchasesServiceProvider — proveedores, OC, RFQ, costos
8. FinanceServiceProvider   — listas de precios GEN/MAY, autorización de precios, CxC/AR
9. DeliveriesServiceProvider — picking, despacho, POD, remisión PDF

Módulo en filesystem NO registrado aún: app/Modules/Quality/ (en desarrollo futuro)

## Convenciones críticas

### Headers de versión (OBLIGATORIO en todos los archivos)
```
PHP:   // New-Unity ERP | ruta/archivo | vX.X | DD/MM/YYYY
Blade: {{-- New-Unity ERP | ruta/archivo | vX.X | DD/MM/YYYY --}}
```
Changelog: una línea por versión debajo del header. Incrementar versión en cada cambio.

### UI — SmartAdmin Earth theme
- Íconos: Bootstrap Icons (bi-*) — solo BI, NO FontAwesome en código nuevo
  (fad/fas/fal ya existen en código heredado — no reemplazar, solo usar bi-* en código nuevo)
- Tablas: clases SmartAdmin nativas (st-table, st-responsive) — NO cargar DataTables por CDN
  (SmartAdmin ya incluye DataTables; doble carga rompe la inicialización)
- Theme issue: @section('styles') puede sobreescribir Earth a purple si no se tiene cuidado
- Botones de formulario: en una sola fila al pie del card
- Textos al usuario: ESPAÑOL siempre
- Inputs de búsqueda: type="search" autocomplete="new-password" name="unique_name"
  (el navegador confunde inputs largos con contraseñas — ya ocurrió en productos y cotizaciones)
- Debounce en búsqueda: 300ms, activar desde el 2do carácter (skip term.length === 1)

### Uppercase obligatorio
Todos los campos de texto de datos maestros en MAYÚSCULAS excepto:
email, URLs, contraseñas, description, store_description, notas largas.
- Clase CSS: uppercase-input (delegación en document — funciona en modales Bootstrap dinámicos)
- JS: public/js/uppercase-input.js
- Mutators mb_strtoupper en: Product v1.6, Customer, Supplier, Validbrand v1.3, Group v1.1, Category v1.1
  BrandIndustry v1.0 (nombre e industria en mb_strtoupper vía controller, no mutator)

### Permisos (modelo 2 llaves)
- Llave 1 CRUD: {module}.access / .view / .create / .edit / .delete
- Llave 2 Funcional: warehouse.receive / .picking / .transfer / .adjust / .racks / .dispose
- Permisos anidados: warehouse.reports.stock / .kardex / .trace
  NUEVO: products.industries.access / products.industries.manage (05/08/2026)
- Gate::before en AppServiceProvider: Super Admin bypass global
  (return true si hasRole('Super Admin'), null para los demás)
- NUNCA crear permisos nuevos sin reportarlo y esperar autorización
- NUNCA correr RolesAndPermissionsSeeder en producción (syncPermissions resetea TODO)
- Seeders de permisos por módulo son aditivos (givePermissionTo) — seguros en producción

## Gotchas conocidos

### Post-migrate:fresh
```bash
# Activar usuarios después de migrate:fresh (EnsureUserIsActive bloquea login si is_active=0)
php artisan tinker
# DB::table('users')->update(['is_active' => 1]);
```

### Tablas de catálogo críticas (deben tener datos antes de operar)
- `products_status`: id=1 Activo + id=2 Inactivo (FK 1452 si está vacía al crear productos)
- `currencies`: MXN mínimo (InvoiceController necesita $currency — fue bug 500 en producción)
- Tres tablas de monedas — solo `currencies` es la operacional:
  | currencies      | Products | Tabla operacional principal (InvoiceController, Quote, CostHistory) |
  | currencies_usd  | Billing  | Histórico tipo de cambio USD/MXN (entrada manual diaria en /billing/currency-usd) |
  | core_currencies | Core     | Huérfana — no usada por módulos actuales |
- `brand_industries`: catálogo de sectores de mercado para marcas (NUEVO 05/08/2026)
  Relación: validbrands ↔ brand_industry (pivot) ↔ brand_industries
  La columna `industry_name` en validbrands se conserva para backward compat (NO eliminar)
- Diagnóstico: `php artisan catalogs:audit` para ver conteos en tiempo real

### Orden de rutas
- Estáticas ANTES de wildcards — 404 silencioso si se invierte
- `Route::get('/create', [...])` // PRIMERO
- `Route::get('/{id}', [...])`  // DESPUÉS

### JS embebido en Blade — entidades HTML en RAWTEXT (CRÍTICO)
- NUNCA usar `{{ __('key', ['param' => "' + js_var + '"]) }}` dentro de `<script>`.
- Blade escapa `'` → `&#039;`. En bloques `<script>` (RAWTEXT en HTML5), el parser NO decodifica
  entidades — el motor JS ve literalmente `&#039;` como contenido de string.
- SOLUCIÓN: construir el string en JS puro: `js_var + ' texto fijo'`
- BUG real: finance/price-lists/prices.blade.php v3.33 mostraba `&#039; + products.length + &#039; productos cargados`
  como texto literal. Corregido en v3.34 (05/08/2026).

### Modales secundarios Bootstrap (stacking)
- Para abrir un modal secundario sobre uno primario usar z-index:1065 en el modal externo
  y z-index:1066 en el .modal-dialog.
- Patrón aplicado en classification/index.blade.php v2.0:
  #newIndustryModal (z-1065), #satCodeModal (z-1065), #newProductLineModal (z-1065, ya existía)
- bootstrap.Modal.getOrCreateInstance(element).show() en lugar de $('#id').modal('show')
  (más robusto con múltiples instancias de Bootstrap)

### SAT catalogs (Billing)
- NO tienen seeder (volumen excesivo: sat_c_prod_serv +50,000 registros, sat_c_postal_codes +100,000)
- Se importan desde UI: /billing/sat-catalogs/ usando archivos Excel del SAT
- Quick-create disponible desde modal de Categoría en Clasificación:
  POST /products/classification/sat-codes → ClassificationController::storeQuickSatCode()
  Solo inserta en sat_c_prod_serv — NO llama al PAC, NO valida contra SAT.
- Los que SÍ tienen seeder: BillingCatalogsSeeder (cfdi_uses, payment methods, fiscal regime),
  SatTaxObjectsSeeder (c_ObjetoImp), SatTasaOCuotaSeeder (tasas), SatCancelReasonsSeeder
- PENDIENTE: plantillas Excel SAT faltantes en public/imports/template/ (usuarios no pueden descargar template)

### Spatie
- `php artisan permission:cache-reset` después de seeders en producción cuando cambios no se reflejan
- Gate::before existe — Super Admin bypasses todo (no agregar checks redundantes)

### Billing / PAC
- PAC: BuzónCFDI (pruebas: cfdi33-pruebas.buzoncfdi.mx:1443)
- NO conectar al PAC en create()/edit() — solo en el método timbrar()
- BillingConfigured middleware: retorna JSON 403 para peticiones AJAX

### Queue (producción)
- QUEUE_CONNECTION=database en producción (local usa sync)
- Cron: `* * * * * /usr/bin/php {path}/artisan schedule:run`
- Jobs con sleep: SendArStatementJob tiene sleep(30) en finally (anti-spam de correos)

### Números de negocio
- product_number = consecutivo de negocio — NO usar id de BD ni count()+N
  Usar: max(product_number)+1 con lockForUpdate en transacción
  Valor inicial configurable en products_settings (key: products_starting_number)
- validbrands: es la tabla real de marcas (no la tabla brands que es pivot)

### Linux / Servidor
- Case-sensitive en TODO: rutas de vistas, nombres de clases, carpetas de assets
- Servidor: Hetzner CPX31, AlmaLinux, cPanel usuario elrosal
- Path producción: ~/public_html/erp
- APP_URL local: http://new-unity.test (Laragon)
- Deploy: bash deploy.sh (el archivo sí está en git — `!deploy.sh` en .gitignore)

### Break Glass (usuario de emergencia)
- id=9999, is_hidden=1 (columna en users)
- Invisible en TODAS las consultas (whereNull/where is_hidden != 1 en listados de usuarios)
- NO aparece en roles, ni en asignación de permisos, ni en reportes
- Solo accessible desde tinker directo en servidor — nunca desde UI

### SESSION_LIFETIME y polling
- SESSION_LIFETIME=120 minutos en .env
- El polling de la campana de notificaciones (AJAX cada X segundos) mantiene la sesión viva
  indefinidamente mientras el navegador esté abierto — esto es un FEATURE, no un bug.
  No "arreglar" con sesiones más cortas ni desactivar el polling.

## Instancias activas

| Instancia | URL | Estado |
|-----------|-----|--------|
| El Rosal (producción) | erp.dulceselrosal.com.mx | Operativo, actualizado |
| BYNET | erp.bynet.com.mx | Operativo, Wizard pendiente de completar |

- BYNET: wizard de configuración de empresa NO completado. Faltan catálogos SAT, almacén inicial,
  datos fiscales. NO usar en producción real hasta completar wizard.

## Seeders — orden de ejecución (deploy limpio)
Fuente: database/seeders/DatabaseSeeder.php v2.2

```php
// CORE
1.  App\Modules\Admin\Database\Seeders\CoreModuleSeeder
2.  App\Modules\Admin\Database\Seeders\AdminUserSeeder
3.  App\Modules\Admin\Database\Seeders\BreakGlassSeeder
// ROLES Y PERMISOS
4.  App\Modules\Admin\Database\Seeders\RolesAndPermissionsSeeder  ← SOLO en deploy limpio
5.  App\Modules\Admin\Database\Seeders\AdminPermissionsSeeder
6.  App\Modules\Billing\Database\Seeders\BillingPermissionsSeeder
7.  App\Modules\Finance\Database\Seeders\FinancePermissionsSeeder
8.  App\Modules\Products\Database\Seeders\ProductsPermissionsSeeder  // incluye products.industries.*
9.  App\Modules\Sales\Database\Seeders\SalesPermissionsSeeder
10. App\Modules\Warehouse\Database\Seeders\WarehousePermissionsSeeder
11. App\Modules\Purchases\Database\Seeders\PurchasesPermissionsSeeder
12. App\Modules\Production\Database\Seeders\ProductionPermissionsSeeder
// CATÁLOGOS Y DATOS BASE
13. App\Modules\Products\Database\Seeders\ProductStatusSeeder
14. App\Modules\Products\Database\Seeders\CurrencySeeder           // MXN, USD en tabla currencies
15. App\Modules\Billing\Database\Seeders\BillingCurrencySeeder     // EUR + idempotente MXN/USD
16. App\Modules\Billing\Database\Seeders\BillingCatalogsSeeder
17. App\Modules\Billing\Database\Seeders\BillingFormatsSeeder
18. App\Modules\Billing\Database\Seeders\SatTaxObjectsSeeder       // c_ObjetoImp (01-04)
19. App\Modules\Billing\Database\Seeders\SatTasaOCuotaSeeder       // IVA/IEPS/ISR tasas y retenciones
20. App\Modules\Billing\Database\Seeders\SatCancelReasonsSeeder    // c_MotivoCancelacion (01-04)
21. App\Modules\Sales\Database\Seeders\OpportunityRulesSeeder
22. Database\Seeders\SatUnityKeysSeeder
23. Database\Seeders\ProductLinesSeeder
24. Database\Seeders\BrandsIndustrySeeder
// WarehouseLocationsSeeder — se corre DESPUÉS del wizard (necesita warehouse_id)
```

Post-seeder en producción: `php artisan permission:cache-reset`

**deploy.sh v3.1**: orden de ejecución — seeders de módulos primero (aditivos, seguros),
RolesAndPermissionsSeeder AL FINAL y solo si es deploy limpio. Node/npm incluido automáticamente.

## i18n

- Lang files: lang/en/*.php (extensos, pre-existentes) y lang/es/*.php (creados incrementalmente)
- Middleware SetUserPreferences: `App::setLocale($preferences->locale ?? 'en')` — columna `locale` en `user_preferences`
- Fallback: `config/app.php` fallback_locale='en' — crear solo las claves nuevas en lang/es/ (sparse)
- Módulos con i18n completo: Sales, Products, Billing (vistas interactivas), sidebar (unity-navigation)
- Módulos pendientes: Purchases (parcial: solo suppliers/index), Warehouse, Finance global settings
- SKIP permanente: PDFs/CFDI templates (invoice1-4_pdf, cfdi_xml, orders/pdf*, consignment_cfdi_xml)
- Patrón: `__('file.key')` — funciona en Blade y dentro de `<script>` (Blade compila antes)
  ⚠️ EXCEPCIÓN: NO usar `__()` con parámetros que incluyan variables JS — ver gotcha JS/RAWTEXT arriba.
- DataTables locale-aware:
  `url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/{{ app()->getLocale()=='es'?'es-ES':'en-GB' }}.json'`
- FullCalendar: `locale: '{{ app()->getLocale() }}'`

## Bugs resueltos — sesión 05/08/2026

### BUG 1 — Sub-módulo Industrias (Productos → Catálogos)

**Problema**: Las industrias de marcas eran strings libres en `validbrands.industry_name` (sin catálogo).
El modal "Nuevo Grupo" tenía un dropdown de industria que filtraba marcas, pero el filtro era
comparación de string y fallaba si los strings diferían en mayúsculas/espacios.

**Decisión de arquitectura**: Crear catálogo propio `brand_industries` + pivot `brand_industry`
(many-to-many). La columna `industry_name` en validbrands se conserva (backward compat, no se elimina).

**Archivos creados** (en working tree, pendiente de migrate):
- `app/Modules/Products/Database/Migrations/2026_08_05_000001_create_brand_industries_table.php`
- `app/Modules/Products/Database/Migrations/2026_08_05_000002_create_brand_industry_pivot_table.php`
- `app/Modules/Products/Database/Migrations/2026_08_05_000003_migrate_brand_industry_names_to_pivot.php`
  (migra datos de industry_name → pivot; idempotente; down() limpia ambas tablas)
- `app/Modules/Products/Models/BrandIndustry.php` v1.0
- `app/Modules/Products/Http/Controllers/IndustriesController.php` v1.0
  (index, getAll, store, update, destroy, toggleActive)
- `app/Modules/Products/Resources/Views/catalogs/industries/index.blade.php` v1.0

**Archivos modificados**:
- `Validbrand.php` v1.3 — agrega `industries()` belongsToMany
- `BrandsController.php` v1.3 — CRUD completo con sync pivot; `getBrands()`/`getBrand()` incluyen `industry_ids[]`; `create()`/`edit()` pasan `$industries` y `$brandIndustryIds`
- `brands/create.blade.php` v1.2 — card Industrias con checkboxes (many-to-many)
- `brands/edit.blade.php` v1.2 — card Industrias con checkboxes pre-seleccionados
- `ProductsPermissionsSeeder.php` v1.1 — agrega `products.industries.access` y `products.industries.manage`
- `Products/Routes/web.php` v1.5 — rutas Industrias CRUD + `/brands/create` + `/brands/{id}/edit` (estáticas antes de wildcards)
- `unity-navigation.blade.php` v2.9 — ítem "Industrias" bajo Catálogos (`@can products.industries.access`)
- `classification/index.blade.php` v2.0 — industry select → checkboxes (`#cb_brand_industries`), `cbSelectedIndustries[]`, `renderCbIndustries()`, `openNewIndustryModal()`, `saveNewIndustry()` vía AJAX POST

**Para aplicar en local/producción**:
```bash
php artisan migrate
php artisan db:seed --class="App\Modules\Products\Database\Seeders\ProductsPermissionsSeeder"
php artisan permission:cache-reset
```

### BUG 2 — Botón + Código SAT abre nueva pestaña (categoryModal)

**Problema**: `<a href="{{ route('sat-catalogs.prod-serv.create') }}" target="_blank">` cerraba el
flujo al abandonar la página. El usuario perdía el contexto del modal de categoría.

**Solución**: Botón `<button onclick="openSatCodeModal()">` + modal secundario `#satCodeModal`
(z-index:1065) + endpoint liviano `POST /products/classification/sat-codes`.

**Archivos modificados**:
- `ClassificationController.php` v1.2 — agrega `storeQuickSatCode()`: valida code+description,
  inserta directo en `sat_c_prod_serv`, retorna JSON con code+description para auto-selección en dropdown.
  NO llama al PAC. NO valida contra el SAT.
- `classification/index.blade.php` v2.0 — reemplaza `<a>` por `<button>`, modal `#satCodeModal`,
  `openSatCodeModal()` + `saveQuickSatCode()`: on success → push a `satCodes[]`, `renderSatOptions()`,
  auto-selecciona el nuevo código en `#code`.

### BUG 3 — Texto literal JS en Listas de Precios

**Problema** (root cause): `'{{ __('key', ['count' => "' + products.length + '"]) }}'`
Blade escapa `'` → `&#039;`. En `<script>` (RAWTEXT HTML5), el parser NO decodifica HTML entities.
El JS veía el string literal `&#039; + products.length + &#039; productos cargados`.

**Archivo modificado**:
- `finance/price-lists/prices.blade.php` v3.34 — línea ~526:
  ```javascript
  // ANTES (roto):
  document.getElementById('footerText').textContent = '{{ __('finance.js-products-loaded', ['count' => "' + products.length + '"]) }}';
  // DESPUÉS (correcto):
  document.getElementById('footerText').textContent = products.length + ' productos cargados';
  ```

### BUG 4 — Modal "Nuevo Grupo" no filtra marcas por industria

**Problema**: `filterBrandsByIndustry()` comparaba `b.industry_name === selectedIndustry` (string).
Después del Bug 1, las marcas ya no tienen un string de industria fiable — tienen `industry_ids[]`.

**Solución**: `filterBrandsByIndustry()` ahora usa `b.industry_ids.includes(parseInt(selectedId))`
donde `selectedId` es el `brand_industries.id` del option seleccionado en `#industry_filter`.
`loadIndustries()` ahora llama `/products/catalogs/industries/all` (retorna `[{id, name}]`
en lugar de strings planos). `getValidbrands()` en ClassificationController v1.2 ahora eager-load
`industries` y mapea `industry_ids` array.

**Archivos modificados**: `ClassificationController.php` v1.2, `classification/index.blade.php` v2.0

## Pendientes técnicos activos

### Inmediatos (próxima sesión)
- [ ] Correr `php artisan migrate` en local para crear brand_industries + brand_industry pivot
- [ ] Correr ProductsPermissionsSeeder en local y producción
- [ ] Completar wizard de BYNET (erp.bynet.com.mx): empresa, almacén, catálogos SAT
- [ ] Plantillas Excel SAT en `public/imports/template/` (usuarios no pueden descargar template desde UI)

### En cola (no iniciar sin autorización explícita)
- POS Fase 1 — punto de venta directo
- Contabilidad Fase 0 — estructura de cuentas
- Módulo Help — base de conocimiento in-app
- CxP (Cuentas por Pagar) — complemento de Finance
- Pantalla de bloqueo con PIN — flujo pendiente de definir (NO programar hasta que se defina el flujo completo)

## NO HACER (restricciones permanentes)

- No crear permisos nuevos sin reportarlo y esperar autorización
  EXCEPCIÓN ya autorizada y aplicada: `products.industries.access` y `products.industries.manage`
- No tocar rutas /setup ni de autenticación (login/logout/password)
- No modificar lógica de controladores salvo indicación explícita
- No correr migrate:fresh en servidor de producción
- No commitear *.bat ni *.sh — están en .gitignore permanente
  EXCEPCIÓN: deploy.sh tiene `!deploy.sh` en .gitignore — sí se trackea
- No usar id de BD como número de negocio visible al usuario
- No cargar DataTables por CDN (SmartAdmin ya lo incluye — doble carga rompe inicialización)
- No usar FontAwesome en código nuevo — solo Bootstrap Icons (bi-*)
- No conectar al PAC en create()/edit() de facturas — solo en timbrar()
- No pegar código PHP con -> directamente en PowerShell
  (crea archivos basura en la raíz del proyecto — problema documentado)
- No correr RolesAndPermissionsSeeder en producción (resetea todos los permisos)
- No embeber variables JS en parámetros de `__()` dentro de `<script>` — ver gotcha JS/RAWTEXT
- No iniciar módulos de la cola sin autorización explícita del dueño del proyecto
- No completar ni modificar el wizard de BYNET sin instrucción directa
