Ir al contenido

Subcontrataciones

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

  • Modelo principal: App\Core\Outsourcing\OutsourcingRecord (app/Core/Outsourcing/OutsourcingRecord.php), tabla outsourcing_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ón create_outsourcing_records_table, por verificar con database/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_id está en fillable pero la relación real waybills() es hasMany — sugiere que waybill_id podría ser vestigial o usarse en otro contexto; no confirmado). SoftDeletes activo.
  • Team scope: vía BelongsToTeamTrait (patrón estándar BaseModel/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 (ver LinkedOrderForm.vue y rutas link-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 en Waybill::outsourcing_record_id (confirma lo hallado documentando Cartas Porte: Waybill tiene FK a outsourcing_records). Se vincula/desvincula vía LinkWaybillToOutsourcingRequest/RemoveWaybillFromOutsourcingRequest (rutas link-waybill/remove-waybill).
    • carrier_contract_groups() → hasMany(CarrierContractGroup::class) — agrupa waybills bajo un mismo CarrierContract (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 sobre users.name, orders.policy_number, carriers.name, waybills.guide_number, waybills.load_relation, waybills.carrier_invoice_number), FileableTrait (archivos adjuntos, base path outsourcing), ApprovableTrait (flujo de aprobaciones — ver OutsourcingRecordApprovalTypes), 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 con App\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\OutsourcingRecordExport vía acción exportToExcel.
  • Form Requests en app/Http/Requests/Outsourcing/* (Create/Update/Delete/Get, LinkWaybillToOutsourcingRequest, RemoveWaybillFromOutsourcingRequest, subcarpetas Approval/, 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.

  • 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ón info-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) con approvalProgress, botón de comentarios, botón de Message Center (module-name="subcontrataciones").
  • OutsourcingRecordForm.vue con secciones en OutsourcingRecordFormSections/: 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 del WaybillSelectorDialog.vue/WaybillSelectorSection.vue hallado 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 en OutsourcingRecordForm/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.vue y CarrierContractGroup.vue — el formulario de Orden de Compra embebe UI de Outsourcing (consistente con la relación Order ↔ OutsourcingRecord mencionada 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).

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-outsourcing
  • comment-outsourcing, notify-outsourcing, approve-outsourcing
  • upload-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-testid en la app (patrón consistente en todo controlgmg-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.vue usa el mismo patrón info-row/divs (no tabla HTML) que Liquidaciones y Remolques — aplicaría la misma estrategia de getByText(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ón el-collapse-transition) o es modal/página aparte. No asumir asociación label↔input sin verificar.
  • El vínculo con Cartas Porte (LinkedWaybillForm.vue) reutiliza WaybillSelectorDialog.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 usar CarrierSelector.vue embebido, 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.

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).