Subcontrataciones
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 principal:
App\Core\Outsourcing\OutsourcingRecord(app/Core/Outsourcing/OutsourcingRecord.php), tablaoutsourcing_records. El namespace/dominio en código sí se llama “Outsourcing” de forma consistente (a diferencia de Liquidaciones/Operations) — “Subcontrataciones” es el nombre visible en menú/UI y coincide bastante bien con el código. - Migración de la tabla no localizada por nombre exacto (glob
*outsourcing_records*no encontró archivo — probablemente el nombre de archivo de migración no sigue el patróncreate_outsourcing_records_table, por verificar condatabase/migrations/completo si se necesita el shape exacto). El modelo usa$fillable:user_id, team_id, waybill_id, order_id, carrier_id, status(nota:waybill_idestá en fillable pero la relación realwaybills()eshasMany— sugiere quewaybill_idpodría ser vestigial o usarse en otro contexto; no confirmado).SoftDeletesactivo. - Team scope: vía
BelongsToTeamTrait(patrón estándarBaseModel/trait, no scope custom como Operations). - Estados (
status): constantes en el modelo —NEW_REQUEST(new),PENDING_REQUEST(pending_request),REJECTED(rejected),AUTHORIZED(authorized),REQUEST_PAYED_PENDING_INVOICE(request-paid-pending-invoice),PAID(paid). Traducción vía__('outsourcing/outsourcing.statuses.*')(resources/lang/es/outsourcing/outsourcing.php). - Relaciones clave:
user()→belongsTo(User::class)->withTrashed()(quien capturó/solicitó).order()→belongsTo(App\Core\Purchases\Order::class)— la orden de compra vinculada (verLinkedOrderForm.vuey rutaslink-order-to-outsourcing/remove-order-from-outsourcing).carrier()→belongsTo(Carrier::class)->withTrashed()(App\Core\Outsourcing\Carrier, el transportista/proveedor subcontratado).waybills()→hasMany(App\Core\Waybills\Waybill::class)->withTrashed()— la relación inversa vive enWaybill::outsourcing_record_id(confirma lo hallado documentando Cartas Porte:Waybilltiene FK aoutsourcing_records). Se vincula/desvincula víaLinkWaybillToOutsourcingRequest/RemoveWaybillFromOutsourcingRequest(rutaslink-waybill/remove-waybill).carrier_contract_groups()→hasMany(CarrierContractGroup::class)— agrupawaybillsbajo un mismoCarrierContract(contrato de transportista) para efectos de facturación/cargos.- Otros modelos del dominio:
App\Core\Outsourcing\Carrier(transportistas/proveedores),App\Core\Outsourcing\CarrierContract\CarrierContract(contratos por transportista, con sus propios archivos/aprobaciones/comentarios),CarrierContractGroup. - Traits:
SearchableTrait(búsqueda full-text sobreusers.name,orders.policy_number,carriers.name,waybills.guide_number,waybills.load_relation,waybills.carrier_invoice_number),FileableTrait(archivos adjuntos, base pathoutsourcing),ApprovableTrait(flujo de aprobaciones — verOutsourcingRecordApprovalTypes),MessageCenterNotifiableTrait/UserNotifiableTrait,LoggableTrait(bitácora con mensajes tipo “Fecha de creación/solicitud/autorización/pago/rechazo”). getActionUrlAttribute()→/subcontrataciones?outsourcing_record_id={id}(deep-link usado por Message Center).getModule()→Module::subcontrataciones(integración conApp\Core\Modules\Module).
- Controladores:
App\Http\Controllers\Outsourcing\OutsourcingRecordController(CRUD +getPaginated,linkOrderToOutsourcing,removeOrderFromOutsourcing,sync,exportToExcel),OutsourcingRecordApprovalController(approve/disapprove/notify),OutsourcingRecordCommentController,OutsourcingRecordFileController. Submódulo Transportistas:Carrier\CarrierController,Carrier\CarrierCommentController,Carrier\CarrierFileController,Carrier\CarrierContract\CarrierContractController(+ Approval/Comment controllers propios). - Export:
App\Exports\Outsourcing\OutsourcingRecordExportvía acciónexportToExcel. - Form Requests en
app/Http/Requests/Outsourcing/*(Create/Update/Delete/Get,LinkWaybillToOutsourcingRequest,RemoveWaybillFromOutsourcingRequest, subcarpetasApproval/,Comment/,File/) — la autorización vive ahí, no como middleware de ruta (mismo patrón que Liquidaciones/Remolques).
Backend (routes/web.php líneas ~443-461), agrupadas bajo prefijo outsourcing, cada archivo probablemente ya trae su propio prefijo interno (no confirmado columna por columna):
| Archivo | Contenido |
|---|---|
routes/outsourcing/routes.php |
POST store, GET get-paginated, PUT {outsourcingRecord}, DELETE {outsourcingRecord}, PATCH {outsourcingRecord} (restaurar), POST link-order-to-outsourcing, POST remove-order-from-outsourcing, GET {outsourcingRecord}/sync, POST export |
routes/outsourcing/comments.php |
comentarios del registro (no leído en detalle) |
routes/outsourcing/files.php |
archivos adjuntos (no leído en detalle) |
routes/outsourcing/approvals.php |
POST {outsourcingRecord}/approve, POST {outsourcingRecord}/disapprove, POST {outsourcingRecord}/notify |
routes/outsourcing/carrier/routes.php, carrier/files.php, carrier/comments.php |
CRUD de Transportistas (bajo prefijo anidado outsourcing/carrier) |
routes/outsourcing/carrier-contract/routes.php, .../files.php, .../comments.php, .../approvals.php |
CRUD + aprobaciones de Contratos de Transportista (prefijo outsourcing/carrier-contract) |
No se ven rutas explícitas de link-waybill/remove-waybill en routes.php leído — probablemente estén en files.php o un archivo no revisado; los Form Requests LinkWaybillToOutsourcingRequest/RemoveWaybillFromOutsourcingRequest sí existen. Por verificar el archivo exacto.
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/subcontrataciones→resources/js/components/Outsourcing/OutsourcingRecordPage.vue(beforeEnter: can('view-outsourcing')), chunk webpack"outsourcing". /subcontrataciones/transportistas→resources/js/components/Outsourcing/Carrier/CarrierPage.vue(beforeEnter: can(['view-carrier','view-carrier-contract'])), chunk"carriers".OutsourcingRecordRow.vue— fila de listado (mismo patróninfo-row/divs que Liquidaciones/Remolques, no tabla HTML): póliza de compra (order.policy_number, o “Sin póliza de compra”), nombre del transportista (carrier.name), Importe (total), Ingreso (suma de cargos de waybills,waybillChargesSum), Profit (Ingreso − Importe), Moneda, estatus traducido + barra de progreso (el-progress) conapprovalProgress, botón de comentarios, botón de Message Center (module-name="subcontrataciones").OutsourcingRecordForm.vuecon secciones enOutsourcingRecordFormSections/:OutsourcingRecordGeneralDataForm.vue(datos generales),LinkedOrderForm.vue(vínculo con Orden de Compra),LinkedWaybillForm.vue(vínculo con Cartas Porte — este es el punto de reutilización delWaybillSelectorDialog.vue/WaybillSelectorSection.vuehallado al documentar Cartas Porte),OutsourcingRecordLinkedFilesForm.vue(archivos).OutsourcingRecordLogs.vue(components/Outsourcing/Logs/) — bitácora del registro.- Submódulo Transportistas:
Carrier/CarrierForm.vue+CarrierFormSections/(General, RequiredFiles, OtherFiles),Carrier/CarrierRow.vue,Carrier/CarrierSelector.vue(selector reutilizable de transportista, probablemente usado también enOutsourcingRecordForm/Compras). - Submódulo Contratos de Transportista:
Carrier/CarrierContracts/CarrierContractForm.vue+CarrierContractFormSections/(General, RequiredFiles, OtherFiles, Approvals),CarrierContractMain.vue,CarrierContractRow.vue. - Reutilización cruzada con Compras:
resources/js/components/Purchases/Orders/FormSections/Outsourcing/CarrierContractForm.vueyCarrierContractGroup.vue— el formulario de Orden de Compra embebe UI de Outsourcing (consistente con la relaciónOrder↔OutsourcingRecordmencionada en el enunciado). - Servicios:
resources/js/services/outsourcing/outsourcing-record-service.js,outsourcing-record-approval-service.js,outsourcing-record-comment-service.js,outsourcing-record-file-service.js(no leídos en detalle — por verificar endpoints exactos consumidos). - Menú lateral (
resources/js/components/Nav/menuOptions.js, líneas ~451-470):name: 'Subcontrataciones',module: 'outsourcing',path: '/subcontrataciones',canView: 'view-outsourcing', ícono/img/subcontrataciones.svg. Submódulos: “Subcontrataciones” (/subcontrataciones,view-outsourcing) y “Transportistas” (/subcontrataciones/transportistas,view-carrier-contract). - Traducciones:
resources/lang/es/outsourcing/outsourcing.php(statuses, labels).
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»Fuente: config/laratrust_seeder.php, resource outsourcing: v,r,c,u,d,co,n,a,upload-other-files,link-waybill,remove-waybill → generadas en App\Core\Security\PermissionConst:
view-outsourcing,read-outsourcing,create-outsourcing,update-outsourcing,delete-outsourcingcomment-outsourcing,notify-outsourcing,approve-outsourcingupload-other-files-outsourcing,link-waybill-outsourcing,remove-waybill-outsourcing
Resources relacionados con permisos propios: carrier (v,r,c,u,d,co,upload-other-files,special-update), carrier-contract (v,r,c,u,d,co,n,a,upload-other-files) — controlan el submódulo Transportistas y sus contratos por separado del registro de Outsourcing.
Roles específicos de este módulo detectados en laratrust_seeder.php: Role::solicitanteSubcontrataciones y Role::solicitanteSubcontratacionesCompras, ambos con permisos outsourcing: v,r,c,u,n,co,upload-other-files,link-waybill,remove-waybill (sin d/delete ni approve) más waybill: r y carrier-contract: r — perfiles que solicitan/capturan subcontrataciones pero no pueden aprobarlas ni borrarlas. Por verificar qué rol(es) sí tienen approve-outsourcing (candidatos: admin/owner, algún “jefe” equivalente al patrón de Liquidaciones — no confirmado en esta pasada).
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 — todo lo que sigue es inferencia de código, no verificación en vivo contra QA:
- No hay
data-testiden la app (patrón consistente en todocontrolgmg-v2). - El link “Subcontrataciones” del sidebar está bastante abajo en
menuOptions.js(índice cercano al final del arreglo, después de módulos como Remolques/reportes de sub-remolques) — probablemente requiera scroll en el sidebar, similar a “Remolques”; no asumir visibilidad sin verificar contra QA real. OutsourcingRecordRow.vueusa el mismo patróninfo-row/divs (no tabla HTML) que Liquidaciones y Remolques — aplicaría la misma estrategia degetByText(policy_number o carrier.name)+ heurística de ancestro para ubicar la fila. Ojo: si no hay orden de compra vinculada, el texto de póliza es literalmente “Sin póliza de compra” (útil como fallback de matching, pero también riesgo de colisión con otras filas sin póliza).- El formulario de alta/edición (
OutsourcingRecordForm.vue) tiene varias secciones anidadas (OutsourcingRecordFormSections/*) — no se inspeccionó su estructura interna de campos ni si expande inline (patrónel-collapse-transition) o es modal/página aparte. No asumir asociación label↔input sin verificar. - El vínculo con Cartas Porte (
LinkedWaybillForm.vue) reutilizaWaybillSelectorDialog.vue/WaybillSelectorSection.vue(ya documentado en la ficha de Cartas Porte) — cualquier fragilidad de selector ya detectada ahí aplica también aquí. - El submódulo Transportistas (
/subcontrataciones/transportistas) es una página separada con su propia lista/formulario (CarrierPage.vue,CarrierForm.vue) — un test de “Subcontrataciones” que necesite crear/seleccionar un transportista probablemente necesite navegar ahí primero o usarCarrierSelector.vueembebido, no confirmado cuál.
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/Liquidaciones.
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/subcontrataciones bajo el título “Subcontrataciones”. No se inspeccionó el contenido a mayor detalle visual — falta exploración manual del listado, filtros, formulario de alta/edición, submódulo Transportistas y flujo de aprobación para documentar selectores y datos de prueba concretos (pendiente, análogo a lo hecho para Remolques/Liquidaciones).