Ir al contenido

Cartas Porte (Waybills)

Generada 2026-08-13. Fuente: exploración de código de controlgmg-v2 + observación manual del navegador (QA).

  • Modelo: App\Core\Waybills\Waybill (app/Core/Waybills/Waybill.php), tabla waybills. El nombre interno en código es “Waybill” en todo el árbol (app/Core/Waybills/, App\Http\Controllers\Waybills\*, rutas routes/waybill/*, servicio JS); “Carta Porte” (singular, sin plural en la UI) es el nombre visible en menú/URL (/carta-porte).
  • fillable: solo guide_number (el resto de columnas se asignan por otros medios, p.ej. mass-assignment protegido o servicios dedicados como WaybillService). SoftDeletes activo. casts: guide_number a int, y varios booleanos (has_contract, is_trip, is_valid_trip, is_replacing_another_waybill, is_substituted_by_another_waybill).
  • Estados (status, entero): STATUS_DOCUMENTED (0, “Documentada”), STATUS_IN_TRANSIT (1, “Tránsito”), STATUS_COLLECTED (3, “Recabada”), STATUS_UNDER_REVIEW (4, “Revisión”), STATUS_RECEIVED_PAYMENT (5, “Cobrada”), STATUS_CANCELED (9, “Cancelada”). No hay estado 2 ni 6-8 (huecos históricos). scopeActive/scopeInactive tratan Documentada y Cancelada como “inactivas”.
  • Relaciones clave:
    • unit() → belongsTo(Unit::class)->withTrashed() (tractor).
    • trailer() → belongsTo(Trailer::class) (sin withTrashed(), a diferencia de unit(), company() y user() que sí lo usan — ver sección del bug abajo).
    • client(), receiver_client(), sender_client() → todas belongsTo(Client::class) con distinto FK.
    • operator(), company() (withTrashed()), exchange_rate(), office(), outsourcing_record(), carrier_contract() (withTrashed()).
    • charges() → hasMany(WaybillCharge::class) (importa para scopeValidWaybills, que exige cargos con field en ['guitx1','guitx5'] y monto > 0).
    • Auto-referencia por sustitución: substitute_waybill() (belongsTo, substitute_waybill_id — la carta porte que reemplaza a esta) y replaced_waybill() (hasOne inverso).
    • kilometer() → hasOne(TrailerKmHistory::class).
    • Traits: CommentableTrait, FileableTrait, SearchableTrait, SynchronizableTrait, As400RelatedModelTrait, BusinessRuleLockableTrait, MessageCenterNotifiableTrait, BusinessRuleAppendsTrait, UserNotifiableTrait — confirma que Cartas Porte es un módulo AS400-sincronizado (igual que Remolques) y con reglas de negocio configurables (app/Core/BusinessRules/Modules/Waybill/*: WaybillBusinessRuleService, WaybillBusinessRuleUnlockService, WaybillBusinessRuleFields).
    • getActionUrlAttribute() → /carta-porte?waybill_id={id} (deep link a una carta porte específica, útil para el tester).
    • getFileBasePath() → 'waybill' (carpeta de archivos adjuntos).
  • Búsqueda (SearchableTrait): columnas indexadas incluyen waybills.guide_number, waybills.load_relation, waybills.trailer_code (columna plana/desnormalizada, no join), waybills.client_referral, waybills.date, waybills.as400_user, más clients.code/clients.name vía join. Nótese que hay columnas comentadas (units.code, trailers.code, operators.name, etc.) — sugiere que la búsqueda NO cubre código de unidad/remolque/operador actualmente, solo folio/cliente/fecha/usuario AS400.
  • Filtro principal: scopeFilterWaybills($query, Request $request) — filtra por id, company_id, office_id, user, client, outsourcing_record_id, guide_number (exacto y rango min/max), rango de fechas, status (múltiple), origin/destiny city (múltiple e por oficina), presencia/ausencia de tipos de archivo, sync_status, rango de fecha de archivos, as400_user (like), y reglas de negocio (businessRuleRequestFilters).
  • Otros modelos relacionados: Src\ClientRecords\Trips\Models\Waybill y WaybillCharge (en src/, namespace Src\) parecen ser una representación paralela/moderna en el módulo DDD “Trips” — no confirmado si es el mismo dato o una vista distinta; por verificar si el módulo src/ClientRecords/Trips reemplaza o complementa a app/Core/Waybills.
  • Sync AS400: app/AS400/Ein00dat/AS400Sync/WaybillSyncService.php y app/Jobs/AS400/WaybillSyncJob.php — confirma que Cartas Porte se sincroniza desde AS400 igual que Remolques (mismo mecanismo mencionado en el bug GMG-3084).
  • Waybill::trailer() es belongsTo(Trailer::class) sin withTrashed() — a diferencia de unit(), company() y carrier_contract() en el mismo modelo, que sí usan withTrashed(). Esto es consistente con el síntoma del bug: si el remolque referenciado por waybill.trailer_id queda “muerto” (soft-deleted o huérfano por el bug de sync AS400), la relación Eloquent no lo recupera por defecto, y el frontend puede terminar mostrando datos incompletos o el id interno crudo en vez del código real del remolque (columna trailers.code, que es el número de caja visible al usuario, p.ej. 594376).
  • Nótese que Waybill también tiene una columna plana desnormalizada waybills.trailer_code (ver SearchableTrait arriba) — separada de la relación trailer_id → trailers.id. Por verificar: si el bug del caso 178011 (mostrar “560” en vez de “594376”) viene de que la UI lee trailer_id/el registro relacionado roto en vez de usar trailer_code, o si trailer_code también quedó desincronizado. Esto sería el punto de partida técnico para reproducir el bug en código, no solo en UI.
  • Caso concreto 178011: salida (waybill) rechazada el 24-jul-2026 porque mostraba el id interno del remolque muerto (560) en vez del número de caja real (594376). Se corrigió manualmente re-apuntando trailer_id al remolque vivo (179869) vía UI (probablemente el selector de remolque en WaybillForm.vue/WaybillGeneralForm.vue, no verificado en código qué componente expone ese campo).
  • Alcance: 271 salidas en producción apuntan a remolques muertos por este bug de sync — cada una es un Waybill cuyo trailer_id apunta a un Trailer con waybill_id (la FK inversa, ver app/Core/Trailer/Trailer.php líneas ~51/92/109) que ya no es el remolque vivo actual del mismo número de caja. Ver remolques.md para el detalle completo del bug de sync AS400 (duplicación de remolques, campo waybill_id en Trailer, etc.) — este documento solo cubre el lado Waybill de la relación.
  • Implicación para QA/tester: cualquier prueba sobre el listado/detalle de una carta porte que involucre el campo de remolque debe considerar que el dato puede estar “roto” en registros antiguos de producción (no en QA necesariamente, salvo que se haya replicado el bug) — no asumir que trailer_code/nombre de remolque siempre se resuelve correctamente.

Bajo routes/waybill/ (prefijo /waybill, requerido desde routes/web.php dentro del grupo auth:api + password_expired):

  • routes/waybill/waybill.php — CRUD principal:
    Método Ruta Acción
    GET /waybill listado (get)
    GET /waybill/paginated listado paginado
    POST /waybill/export exportar
    POST /waybill crear
    PUT /waybill/{waybill} actualizar
    DELETE /waybill/{waybill} eliminar (soft delete)
    GET /waybill/{waybill}/sync forzar sync AS400 de esa carta porte
    GET /waybill/{waybill}/download-history historial de descargas
    GET /waybill/{waybill}/upload-history historial de subidas
    GET /waybill/waybill-current-loggers (sin verificar propósito exacto — probablemente usuarios editando en tiempo real)
  • routes/waybill/reports.php, routes/waybill/files.php, routes/waybill/comments.php — existen pero no se leyó su contenido en detalle (por verificar); por nombre: reportes, gestión de archivos adjuntos (WaybillFileController), y comentarios (WaybillCommentController, confirmado por el test WaybillAddCommentTest cuya ruta es /waybill/%s/comment).
  • No se observó middleware permission: explícito a nivel de ruta en waybill.php — igual que Liquidaciones y Remolques, la autorización probablemente vive en FormRequests o en el controlador (WaybillController), no confirmado en detalle.
  • Ruta raíz /carta-porte/ → resources/js/components/Waybills/WaybillsPage.vue, guard beforeEnter: can('view-waybill') (definido en resources/js/routes/waybill.js, importado en resources/js/routes.js como WaybillRoutes).
  • Reportes bajo /carta-porte/reportes/* (todos con guard can('view-waybills-reports')):
    • reportes/tractor → Waybills/Reports/Unit/FirstReport/Chart
    • reportes/operador → Waybills/Reports/Operator/FirstReport/Chart
    • reportes/remolque → Waybills/Reports/Trailer/FirstReport/Chart
    • reportes/cliente → Waybills/Reports/Client/FirstReport/Chart
    • reportes/documentos-faltantes → Waybills/Reports/FileLack/WaybillFileLackReport.vue
  • Componentes principales en resources/js/components/Waybills/: WaybillsPage.vue (listado), WaybillRow.vue (fila), WaybillForm.vue + FormSections/WaybillDataForm.vue, WaybillGeneralForm.vue, WaybillFilesForm.vue (formulario segmentado en secciones, más elaborado que Remolques/Liquidaciones), WaybillSelector.vue y FormSections/Shared/WaybillSelectorDialog.vue/WaybillSelectorSection.vue (selector de carta porte reutilizable, usado por ej. desde Outsourcing según outsourcing permission link-waybill/remove-waybill visto en laratrust_seeder.php).
  • Reportes de “documentos faltantes” tienen su propio chart+tabla: WaybillFileLackChart.vue + WaybillFileLackTable.vue.
  • También existen componentes de reporte “Cartas porte por cliente” bajo resources/js/components/Clients/Reports/WaybillsByClient/ (KPI, dialogs, tablas) — viven en el dominio Clientes, no en Waybills, pero consumen datos de cartas porte.
  • No se inspeccionó resources/js/services/waybill/ (servicio HTTP) en este pase — por verificar.

Fuente: config/laratrust_seeder.php, resource waybill (definido varias veces con distintas variantes según rol, líneas ~46 y ~146 con el set completo; líneas ~414-483 con subconjuntos por rol de subida de archivos):

  • Set completo de acciones: v (view), c (create), r (read), u (update), d (delete), co (comment), más acciones específicas de subida de archivos: upload-waybill, upload-instructions, upload-pod, upload-other, upload-others, upload-invoice, upload-to-review, upload-charge, upload-invoice-complement, special-update, upload-against-receipt.
  • Roles con subconjuntos de solo-lectura (waybill => 'r') y roles con permisos de subida acotados a ciertos tipos de documento (p.ej. un rol solo con upload-waybill,upload-instructions,upload-against-receipt, otro con upload-pod,upload-other,upload-against-receipt, otro con upload-invoice,upload-invoice-complement,...) — sugiere flujos de captura de documentos por rol especializado (agente que sube la carta porte, otro que sube POD, otro que sube factura).
  • Constante usada en tests: PermissionConst::commentWaybill (generada desde co del resource waybill).
  • Resource separado waybills-reports → 'v' únicamente, gobierna view-waybills-reports (guard de las rutas /carta-porte/reportes/*).
  • Related: resource outsourcing incluye link-waybill/remove-waybill — confirma integración entre Outsourcing y Waybills vía el selector (WaybillSelectorDialog.vue).
  • Nombres exactos de constantes en PermissionConst (p.ej. viewWaybill, createWaybill, deleteWaybill, etc.) no se confirmaron uno por uno — se infieren del patrón {acción}Waybill visto en Liquidaciones/Remolques y en el uso de commentWaybill; por verificar contra el archivo generado app/Core/Security/PermissionConst.php si se necesita el nombre literal de un permiso puntual.

Selectores usados por el tester (riesgo de fragilidad)

Sección titulada «Selectores usados por el tester (riesgo de fragilidad)»

No hay specs de Playwright para este módulo todavía — igual que Liquidaciones, y a diferencia de Remolques (que sí tiene tests/remolques/expand-trailer-detail.spec.ts). Todo lo siguiente es inferencia de código, no verificación en vivo contra QA:

  • No hay data-testid en la app (patrón consistente en todo controlgmg-v2; no se debe modificar el repo para agregarlos).
  • El formulario está dividido en secciones (WaybillDataForm.vue, WaybillGeneralForm.vue, WaybillFilesForm.vue) en vez de un único formulario inline como Remolques/Liquidaciones — probablemente implique navegación por tabs/pasos dentro de WaybillForm.vue, no confirmado en vivo. Esto es más complejo que los módulos ya documentados y merece una pasada de codegen dedicada antes de escribir specs.
  • El selector de remolque/carta porte relacionado (WaybillSelectorDialog.vue, WaybillSelectorSection.vue) es un componente de diálogo separado — relevante si se necesita probar el flujo de corrección manual descrito en el bug GMG-3084 (re-apuntar una carta porte a otro remolque), pero su estructura interna (labels, botones) no fue inspeccionada.
  • El deep link /carta-porte?waybill_id={id} (via Waybill::getActionUrlAttribute()) es útil para que un test navegue directo a una carta porte específica sin tener que buscarla en el listado — por confirmar si WaybillsPage.vue efectivamente lee ese query param al montar.
  • Antes de escribir cualquier spec para este módulo, correr npm run codegen:gmg contra QA real para verificar selectores, igual que se hizo para Remolques.

La página carga en https://qa.controlgmg.com/carta-porte bajo el título “Carta Porte”. No se inspeccionó el contenido a mayor detalle visual. Falta una exploración manual más profunda del listado, el formulario multi-sección y el selector de remolque/carta porte para documentar selectores y datos de prueba concretos (pendiente, análogo a lo hecho para Remolques).