Remolques (Trailers)
Generada 2026-08-12. Fuente: exploración manual del navegador (QA) + exploración de código de
W:\www\GMG\controlgmg-v2vía subagentes. Actualiza este archivo cuando el módulo cambie de forma relevante para tests.
Backend
Sección titulada «Backend»- Modelo:
App\Core\Trailer\Trailer(app/Core/Trailer/Trailer.php), tablatrailers. - Fillable clave:
company_id, code, trailer_owner_id, unit_situation_id, origin_city_id, destiny_city_id, operator_id, waybill_id, unit_id, last_situation_at, location, user_id(+priority, migración 2026-08-07). - Llave única (identidad):
(company_id, code, trailer_owner_id)desde la migración2020_06_06_114152_change_unique_key_to_trailers_table.php. - Estado/situación:
unit_situation_id(FK),last_situation_at(timestamp de la situación),hidden(baja AS400, distinto dedeleted_atsoft-delete). - Sync status: vía tabla polimórfica
synchronizations(SynchronizableTrait), no es columna detrailers. has_team_scope: false(config/modules.php) — Remolques NO está team-scoped, solo segmentado porcompany_id.
Bug conocido: GMG-3084 (duplicidad de remolques)
Sección titulada «Bug conocido: GMG-3084 (duplicidad de remolques)»- Causa raíz:
UltimaSituacionRemolque::createOrFindFromAS400()(app/AS400/Ein00dat/Remolque/UltimaSituacionRemolque.php) usafirstOrCreatesobre(company_id, code, trailer_owner_id). Si AS400 reporta un cambio de propietario, se inserta una fila nueva en vez de actualizar la existente — la fila vieja queda “muerta” pero sigue referenciada por salidas/compras/historial. - Impacto medido en prod (jul-2026): 271 salidas y 30 compras cotizadas apuntando a remolques muertos; ~14,692 filas muertas en BD de desarrollo.
- Estado (12-ago-2026): DEV Test, PR listo para Staging desde el 7-ago. Dependencia: GMG-3089.
- Query de regresión ya implementada:
projects/gmg/db/queries/trailers.ts→findTrailerOwnerVariants(companyId, code)(una fila conocida) yfindAllTrailerOwnerVariantConflicts()(barrido generalGROUP BY company_id, code HAVING COUNT(DISTINCT trailer_owner_id) > 1). RequiereGMG_QA_DB_*en.env— no está conectada a ningún spec activo todavía. - Referencias cruzadas confirmadas (documentadas a detalle en las fichas de los módulos afectados):
cartas-porte.md:Waybill::trailer()esbelongsTo(Trailer::class)sinwithTrashed()(a diferencia deunit()/company()/carrier_contract(), que sí lo usan) — pista concreta de por qué una salida puede “perder” su remolque muerto en ciertas consultas. Ademáswaybills.trailer_codees una columna desnormalizada aparte del FKtrailer_id— por verificar cuál de las dos causó el síntoma del caso 178011.compras-cotizadas.md: confirmado queQuotedOrder.trailer_id(yOrderProduct.trailer_id) son FKs numéricas reales haciatrailers.id— el bug no rompe integridad referencial (el id sigue existiendo), solo deja la orden apuntando al remolque “viejo” en vez del vigente.tractores.md:Unit::createOrFindFromAS400()busca por(units.code, company.code)sin dimensión “propietario” — Tractores estructuralmente no debería sufrir este mismo patrón de duplicidad (no confirmado con un caso real, es análisis de código).
Sync con AS400
Sección titulada «Sync con AS400»- Manual (un remolque):
GET /units/trailer/sync/{trailer}→TrailerSyncService::syncTrailerFromAS400Model(). Es el único punto de sync controlable desde un test externo. - Masivo: artisan
bg:as400 trailer→ jobs encolados, gateado por conectividad VPN al AS400 (192.168.10.220) — no disparable de forma realista desde el tester.
Rutas (backend)
Sección titulada «Rutas (backend)»Bajo /units/trailer (grupo auth:api + password_expired, sin middleware de permiso a nivel de ruta — la autorización vive en cada FormRequest):
| Método | Ruta | Acción |
|---|---|---|
| GET | /units/trailer , /units/trailer/paginated |
listado |
| GET | /units/trailer/sync/{trailer} |
sync manual de un remolque |
| GET | /units/trailer/owner, /units/trailer/location |
catálogos de filtro |
| POST | /units/trailer/{trailer}/comment |
comentario |
| GET | /units/trailer/{trailer}/download-history, /{trailer}/kilometer-history |
historiales |
Modular (api/v1): PUT trailers/{trailer}/priority, GET trailers/{trailer}/limited-historical-movements.
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta
/remolques→resources/js/components/Trailer/Reports/TrailerReportPage.vue(guardcan('view-trailer-reports')). TrailerRow.vue— una fila de la lista (código, situación, prioridad, último movimiento, días transcurridos, propietario, ubicación, sync-status, comentarios); el click expande el detalle.Reports/TrailerReportDetail.vue+TrailerAs400Data.vue+TrailerMovementHistory.vue— panel de detalle expandido (no es un accordion inline por fila; es una sección compartida debajo de la tabla que muestra el remolque seleccionado).- Servicio:
resources/js/services/trailer/trailer-service.js—GET /units/trailer,/units/trailer/paginated,/units/trailer/sync/{id},/units/trailer/excel,/units/trailer/location,GET api/v1/trailers/{id}/limited-historical-movements,PUT /units/trailer/executive/{id},PUT api/v1/trailers/{id}/priority.
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»Resource trailer-reports (gobierna el listado principal, pese al nombre) + trailer-sub-reports (sub-reportes KPI). Constantes relevantes: view-trailer-reports, read-trailer-reports, sync-trailer-reports, update-trailer-reports, update-executive-user-trailer-reports, update-priority-trailer-reports, upload-other-files-trailer-reports.
Selectores usados por el tester (riesgo de fragilidad)
Sección titulada «Selectores usados por el tester (riesgo de fragilidad)»No hay data-testid en la app (no se modifica controlgmg-v2). Verificado contra QA real el 2026-08-12 (varios supuestos iniciales fallaron y se corrigieron):
- Login: los labels “E-Mail”/“Contraseña” son texto plano, sin asociación
<label for>/aria-labelledby—getByLabelNO funciona. Se usan textboxes posicionales dentro del<form>(form.getByRole('textbox').nth(0)= email,.nth(1)= contraseña). - Nav a Remolques: el link “Remolques” del sidebar existe (
href="/remolques") pero queda fuera del viewport por defecto y Playwright no logra hacerle scroll-into-view de forma confiable (menú lateral largo). Se navega directo por URL (page.goto('/remolques')) en vez de clickear el link. - Fila de la lista:
getByText(code, { exact: true })+ heurística de ancestro con botón (xpath=ancestor::*[.//button][1]) para localizar la fila y su botón de expandir. Funciona: el wrapper de cada fila (cursor:pointer) contiene el código y, como último hijo, el botón/chevron de expandir. - Campos del panel de detalle: el label y el valor son elementos hermanos, NO un solo nodo de texto —
<strong>Label:</strong><generic>Valor</generic>, ambos hijos de un contenedor común. Localizar así:getByText('Situación:', { exact: true })(única en toda la página — las filas de la lista muestran “Situación” SIN dos puntos) → subir dos niveles (xpath=../..) para obtener el contenedor de todos los campos → dentro de ese contenedor,getByText('Label:', { exact: true })→xpath=following-sibling::*[1]para el valor. Importante: “Propietario:” y “Ubicación:” (con dos puntos) SÍ aparecen también en cada fila de la lista, por eso hace falta escopear al contenedor del panel de detalle vía “Situación:” antes de buscarlos — buscarlos en toda la página da múltiples matches. - Historial: no es una
<table>HTML, songeneric(divs). Se cuenta el número de hijos del contenedor siguiente al heading “Historial” (> 1= hay encabezado + al menos una fila de datos), no se intenta filtrar por fila individual.
Si la marca (markup) de la app cambia, estos selectores pueden romperse silenciosamente. Verificar/ajustar con npm run codegen:gmg contra QA real antes de confiar en un fallo como “bug real” vs “selector desactualizado”.
Dato de prueba verificado manualmente (12-ago-2026)
Sección titulada «Dato de prueba verificado manualmente (12-ago-2026)»Remolque SNLU996394:
- Situación:
TALLER CORTO PLAZO(2 días transcurridos, última situación 2026-08-10 07:49 AM) - Propietario:
SCHNEIDER INTERMODAL - Ubicación:
PATIO SAN LUIS POTOSI - Historial: 1 movimiento (
2026-08-10 07:49 AM→TALLER CORTO PLAZO)
Este es el dato usado por tests/remolques/expand-trailer-detail.spec.ts. Como es un ambiente QA compartido, estos valores pueden cambiar — si el test empieza a fallar, primero confirma manualmente el estado actual del remolque antes de asumir una regresión de la app o del selector.