Cartas Porte (Waybills)
Generada 2026-08-13. Fuente: exploración de código de controlgmg-v2 + observación manual del navegador (QA).
Backend
Sección titulada «Backend»- Modelo:
App\Core\Waybills\Waybill(app/Core/Waybills/Waybill.php), tablawaybills. El nombre interno en código es “Waybill” en todo el árbol (app/Core/Waybills/,App\Http\Controllers\Waybills\*, rutasroutes/waybill/*, servicio JS); “Carta Porte” (singular, sin plural en la UI) es el nombre visible en menú/URL (/carta-porte). fillable: sologuide_number(el resto de columnas se asignan por otros medios, p.ej. mass-assignment protegido o servicios dedicados comoWaybillService).SoftDeletesactivo.casts:guide_numberaint, 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/scopeInactivetratan Documentada y Cancelada como “inactivas”. - Relaciones clave:
unit()→belongsTo(Unit::class)->withTrashed()(tractor).trailer()→belongsTo(Trailer::class)(sinwithTrashed(), a diferencia deunit(),company()yuser()que sí lo usan — ver sección del bug abajo).client(),receiver_client(),sender_client()→ todasbelongsTo(Client::class)con distinto FK.operator(),company()(withTrashed()),exchange_rate(),office(),outsourcing_record(),carrier_contract()(withTrashed()).charges()→hasMany(WaybillCharge::class)(importa parascopeValidWaybills, que exige cargos confielden['guitx1','guitx5']y monto > 0).- Auto-referencia por sustitución:
substitute_waybill()(belongsTo,substitute_waybill_id— la carta porte que reemplaza a esta) yreplaced_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 incluyenwaybills.guide_number,waybills.load_relation,waybills.trailer_code(columna plana/desnormalizada, no join),waybills.client_referral,waybills.date,waybills.as400_user, másclients.code/clients.nameví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\WaybillyWaybillCharge(ensrc/, namespaceSrc\) 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ódulosrc/ClientRecords/Tripsreemplaza o complementa aapp/Core/Waybills. - Sync AS400:
app/AS400/Ein00dat/AS400Sync/WaybillSyncService.phpyapp/Jobs/AS400/WaybillSyncJob.php— confirma que Cartas Porte se sincroniza desde AS400 igual que Remolques (mismo mecanismo mencionado en el bug GMG-3084).
Relación con el bug GMG-3084
Sección titulada «Relación con el bug GMG-3084»Waybill::trailer()esbelongsTo(Trailer::class)sinwithTrashed()— a diferencia deunit(),company()ycarrier_contract()en el mismo modelo, que sí usanwithTrashed(). Esto es consistente con el síntoma del bug: si el remolque referenciado porwaybill.trailer_idqueda “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 (columnatrailers.code, que es el número de caja visible al usuario, p.ej. 594376).- Nótese que
Waybilltambién tiene una columna plana desnormalizadawaybills.trailer_code(verSearchableTraitarriba) — separada de la relacióntrailer_id → trailers.id. Por verificar: si el bug del caso 178011 (mostrar “560” en vez de “594376”) viene de que la UI leetrailer_id/el registro relacionado roto en vez de usartrailer_code, o sitrailer_codetambié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_idal remolque vivo (179869) vía UI (probablemente el selector de remolque enWaybillForm.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
Waybillcuyotrailer_idapunta a unTrailerconwaybill_id(la FK inversa, verapp/Core/Trailer/Trailer.phplíneas ~51/92/109) que ya no es el remolque vivo actual del mismo número de caja. Verremolques.mdpara el detalle completo del bug de sync AS400 (duplicación de remolques, campowaybill_idenTrailer, 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 /waybilllistado ( get)GET /waybill/paginatedlistado paginado POST /waybill/exportexportar POST /waybillcrear PUT /waybill/{waybill}actualizar DELETE /waybill/{waybill}eliminar (soft delete) GET /waybill/{waybill}/syncforzar sync AS400 de esa carta porte GET /waybill/{waybill}/download-historyhistorial de descargas GET /waybill/{waybill}/upload-historyhistorial 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 testWaybillAddCommentTestcuya ruta es/waybill/%s/comment).- No se observó middleware
permission:explícito a nivel de ruta enwaybill.php— igual que Liquidaciones y Remolques, la autorización probablemente vive en FormRequests o en el controlador (WaybillController), no confirmado en detalle.
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/carta-porte/→resources/js/components/Waybills/WaybillsPage.vue, guardbeforeEnter: can('view-waybill')(definido enresources/js/routes/waybill.js, importado enresources/js/routes.jscomoWaybillRoutes). - Reportes bajo
/carta-porte/reportes/*(todos con guardcan('view-waybills-reports')):reportes/tractor→Waybills/Reports/Unit/FirstReport/Chartreportes/operador→Waybills/Reports/Operator/FirstReport/Chartreportes/remolque→Waybills/Reports/Trailer/FirstReport/Chartreportes/cliente→Waybills/Reports/Client/FirstReport/Chartreportes/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.vueyFormSections/Shared/WaybillSelectorDialog.vue/WaybillSelectorSection.vue(selector de carta porte reutilizable, usado por ej. desde Outsourcing segúnoutsourcingpermissionlink-waybill/remove-waybillvisto enlaratrust_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.
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»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 conupload-waybill,upload-instructions,upload-against-receipt, otro conupload-pod,upload-other,upload-against-receipt, otro conupload-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 desdecodel resourcewaybill). - Resource separado
waybills-reports→'v'únicamente, gobiernaview-waybills-reports(guard de las rutas/carta-porte/reportes/*). - Related: resource
outsourcingincluyelink-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}Waybillvisto en Liquidaciones/Remolques y en el uso decommentWaybill; por verificar contra el archivo generadoapp/Core/Security/PermissionConst.phpsi 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-testiden la app (patrón consistente en todocontrolgmg-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 deWaybillForm.vue, no confirmado en vivo. Esto es más complejo que los módulos ya documentados y merece una pasada decodegendedicada 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}(viaWaybill::getActionUrlAttribute()) es útil para que un test navegue directo a una carta porte específica sin tener que buscarla en el listado — por confirmar siWaybillsPage.vueefectivamente lee ese query param al montar. - Antes de escribir cualquier spec para este módulo, correr
npm run codegen:gmgcontra QA real para verificar selectores, igual que se hizo para Remolques.
Datos observados en QA (2026-08-12)
Sección titulada «Datos observados en QA (2026-08-12)»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).