Ir al contenido

Remolques (Trailers)

Generada 2026-08-12. Fuente: exploración manual del navegador (QA) + exploración de código de W:\www\GMG\controlgmg-v2 vía subagentes. Actualiza este archivo cuando el módulo cambie de forma relevante para tests.

  • Modelo: App\Core\Trailer\Trailer (app/Core/Trailer/Trailer.php), tabla trailers.
  • 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ón 2020_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 de deleted_at soft-delete).
  • Sync status: vía tabla polimórfica synchronizations (SynchronizableTrait), no es columna de trailers.
  • has_team_scope: false (config/modules.php) — Remolques NO está team-scoped, solo segmentado por company_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) usa firstOrCreate sobre (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) y findAllTrailerOwnerVariantConflicts() (barrido general GROUP BY company_id, code HAVING COUNT(DISTINCT trailer_owner_id) > 1). Requiere GMG_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() es belongsTo(Trailer::class) sin withTrashed() (a diferencia de unit()/company()/carrier_contract(), que sí lo usan) — pista concreta de por qué una salida puede “perder” su remolque muerto en ciertas consultas. Además waybills.trailer_code es una columna desnormalizada aparte del FK trailer_id — por verificar cuál de las dos causó el síntoma del caso 178011.
    • compras-cotizadas.md: confirmado que QuotedOrder.trailer_id (y OrderProduct.trailer_id) son FKs numéricas reales hacia trailers.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).
  • 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.

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.

  • Ruta /remolques → resources/js/components/Trailer/Reports/TrailerReportPage.vue (guard can('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.

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 — getByLabel NO 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, son generic (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.