Expediente Operador
Generada 2026-08-12. Fuente: exploración de código de controlgmg-v2 + observación manual del navegador (QA).
Backend
Sección titulada «Backend»- Modelo:
App\Core\Operators\Operator(app/Core/Operators/Operator.php), tablaoperators. - Módulo (Laratrust/config):
Module::expedientes_operadores→config/modules.php:name: 'expedientes-operadores',label: 'Expedientes Operadores',has_team_scope: true(a diferencia de Remolques, este módulo SÍ está segmentado porteam_id, además decompany_id). El modelo relacionadoOperatorCandidatevive bajo un módulo hermano,Module::escuela_operadores(“Escuela Operadores”), también team-scoped. - Tabla base minimalista + ~28 migraciones incrementales.
2018_04_12_204856_create_operators_table.phpsolo traeid, code (unique), name, timestamps. Todo lo demás se fue agregando por separado, entre otras:2018_10_02_091407_add_extra_fields_to_operators_table.php— campos generales (curp, teléfono, email, contacto de emergencia, etc.)2018_10_23_183115_add_soft_deletes_to_operators.php/2019_03_04_112306_add_soft_delete_to_operators_tables.php— soft delete (deleted_at)2018_11_01_115814_add_school_fields_to_operators_table.php— campos de “escuela” (candidatos/maestros)2018_11_22_145113_add_candidate_status_to_operators_table.php—candidate_status2018_11_26_180322_add_company_id_field_to_operators_table.php,2018_11_28_142655_add_team_id_field_to_operators.php— multi-tenencia2018_12_03_181909_add_viable_at_field_to_operators_table.php—viable_at2019_03_05_115700_add_is_teacher_field_to_operators_table.php—is_teacher(maestro de escuela)2019_12_30_144803_change_unique_code_in_operators_table.php— llave única decode2020_06_24_125337_add_bank_account_and_payroll_branch_to_operators_table.php—bank_account,payroll_branch2020_08_07_093332_add_as400_extra_fields_to_operators_table.php— campos sincronizados desde AS4002022_06_07_203339_add_not_viable_at_to_operators_table.php2024_01_22_175019_add_emergency_contact_operators_table.php
- Fillable (modelo):
code, name, curp, cellphone_number, email, emergency_contact, emergency_number, is_viable, status, arrive_way_id, arrive_reason_id, company_id, is_created_in_operations. Otros campos usados en runtime pero no fillable directo (se asignan explícitamente):team_id,photo_path,is_operator,is_candidate,is_teacher,is_re_entry,viable_at,current_unit_id,candidate_status,teacher_operator_id. - Estado (
status), máquina de estados encheckAndUpdateStatus():prospect→registered(siis_viable) →authorization(si tiene el archivoalta-imss) →active(si tiene TODAS las aprobaciones de jefes: Operaciones, Liquidaciones, Daños, Satélite, Taller, Capacitación, Factor Humano) →inactive(si tiene el archivobaja-en-el-imss); onot-viablesiis_viable === false. Si el status deja de seractive, se borran losbusiness_rule_locks(). - Documentos/expediente —
App\Core\Operators\OperatorFile(app/Core/Operators/OperatorFile.php), tablaoperator_files,belongsTo(Operator). Tipos de archivo enApp\Core\Operators\OperatorFileType(app/Core/Operators/OperatorFileType.php), p.ej.:solicitud-gmg, licencia-federal, examen-psicofisico, reactivo-antidoping, examen-antodoping [sic], estudio-socieconomico, antecedentes-penales, r-control, alta-imss, credencial, codigo-de-etica, examen-medico, constancia-capacitacion, ine, curp, comprobante-de-domicilio, evaluacion-del-conductor, historial-imss, motivo-de-baja, baja-en-el-imss, otros, normatividad, contrato-laboral(+ variantesescuela-*para candidatos/escuela). Cada tipo puede requerir fecha ($needDateTypes, p.ej. vigencia de licencia federal), semáforo ($needSemaphoreTypes: antidoping) o calificación ($needQualifiableTypes).OperatorFileexponegetUrlAttribute()→/operators/files/{id}para descarga. - Vigencias/licencia: no hay columna de vigencia en
operators; la fecha de vencimiento de la licencia federal (y de otros documentos con$needDateTypes) vive enoperator_files.custom_datedel archivo contype = 'licencia-federal'.OperatorsController::getOperatorLicence()es el ejemplo de lectura de esa fecha. - Foto:
photo_pathenoperators, expuesto comophoto_url(append) víaImageUrlTrait/ImageResizeService::saveAndResizeImagesFromBase64(), path físicooperators/{id}/images/{photo_path}. - Aprobaciones —
App\Core\Operators\OperatorApproval, tabla implícitaoperators_approvals,hasManydesde Operator víaoperator_approvals(). Untypepor rol de jefe (Role::jefeOperaciones,jefeLiquidaciones,jefeDanos,jefeSatelite,jefeTaller,jefeCapacitacion,jefeFactorHumano), conapproved_atyapproval_requested_at. - Comentarios:
CommentableTrait+morphMany(Comment::class, 'commentable'); notificación opcional por WhatsApp víasendWhatsAppCommentNotification()/App\Notifications\WhatsAppOperatorCommentSent. - Relaciones clave:
company()(belongsTo, withTrashed),current_unit()(belongsToApp\Core\Unit, FKcurrent_unit_id— el tractor asignado),operations()(hasManyApp\Core\Operations\Operation),vouchers()(hasManyApp\Core\Vouchers\Voucher),waybills()(hasManyApp\Core\Waybills\Waybill),students()(hasManyOperator, FKteacher_operator_id— auto-relación maestro/alumno de escuela),arrive_reason()/arrive_way()(catálogos de por qué/cómo llegó el operador),operator_files(),operator_approvals(). - Relación inversa desde Remolques:
App\Core\Trailer\Trailertieneoperator_id(columna fillable) ypublic function operator(): BelongsTo(app/Core/Trailer/Trailer.phplínea 185) — el remolque referencia al operador que lo trae, no al revés. - AS400/sync:
As400RelatedModelTrait+SynchronizableTrait(misma tabla polimórficasynchronizationsque Remolques). Sync de un operador:OperatorSyncService::syncOperator()(app/AS400/Ein00dat/AS400Sync/OperatorSyncService.php), invocado automáticamente al crear (store(), solo si hay conexiónodbcconfigurada) y manualmente víaGET /operators/{operator}/sync. Sync masivo:php artisan bg:as400 operator→App\Jobs\AS400\OperatorSyncJob. - Candidatos/Escuela (dominio hermano, no confundir):
App\Core\Operators\Candidate\OperatorCandidate(app/Core/Operators/Candidate/OperatorCandidate.php) es un modelo/flujo separado para el proceso de captación y escuela de manejo, con su propio controllerOperators\Candidate\OperatorCandidateController, su propio módulo (escuela-operadores) y permisosoperator-candidateen Laratrust.transformCandidate()enOperatorsControllerconvierte un candidato graduado (is_graduated) en operador real.
Todas bajo Route::group(['prefix' => 'operators']) en routes/web.php (líneas ~138-218), dentro del grupo auth:api + password_expired de web.php. El único archivo requireado aparte es el de reportes:
routes/operators/operator-reports.php— incluido en línea 141 (require base_path('routes/operators/operator-reports.php')), reportes/KPIs de operadores.
| Método | Ruta | Acción (controller) |
|---|---|---|
| GET | /operators, /operators/paginated, /operators/with-trashed |
listados |
| GET | /operators/codes |
catálogo de códigos |
| POST | /operators |
crear (OperatorsController@store) |
| PUT | /operators/{operator} |
actualizar |
| DELETE | /operators/{operator} |
borrar (soft delete) |
| PATCH | /operators/{operator} |
restaurar de papelera |
| PUT | /operators/{operator}/set-status |
cambiar status manualmente |
| POST | /operators/{operator}/approve, /notify |
flujo de aprobación por jefes |
| POST | /operators/{operator}/files |
subir documento (OperatorFileController@add) |
| GET/PUT/DELETE | /operators/files/{operatorFile} |
descargar / cambiar fecha / actualizar / borrar documento |
| POST | /operators/{operator}/comment |
comentario (OperatorCommentController@add) |
| POST | /operators/{operator}/reactivate, /reactivate/notify |
reactivación tras baja |
| POST | /operators/{operator}/change-team/{team} |
cambiar de oficina |
| POST | /operators/{operator}/set-teacher |
marcar como maestro de escuela |
| GET | /operators/{operator}/sync |
sync manual con AS400 |
| GET | /operators/{operator}/download-history |
historial de descargas de archivos |
| GET | /operators/{operator}/staff-logs |
bitácora de RH (Staff) |
| POST | /operators/transform/{operatorCandidate} |
graduar candidato → operador |
| POST | /operators/{operator}/export |
exportar a Excel |
| POST/GET | /operators/operator-arrives-detail, /operator-leave-out, /operations-not-dates*, /get-other-files*, /get-operators-kpi5-*, /vouchers-complementary-data |
KPIs/reportes puntuales sobre el listado principal |
| — | /operators/candidate/* (bloque separado, líneas ~187-209) |
CRUD y reportes de candidatos/escuela |
Controller principal: App\Http\Controllers\Operators\OperatorsController (app/Http/Controllers/Operators/OperatorsController.php, ~1060 líneas). Sin middleware permission: a nivel de ruta — la autorización vive en los FormRequests (App\Http\Requests\Operators\*, p.ej. CreateOperatorRequest, UpdateOperatorRequest, DeleteOperatorRequest, ApproveOperatorRequest) y en checks Auth::user()->hasPermission() dentro del controller (p.ej. reports()).
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta
/operadores/→resources/js/components/Operators/Operators.vue(resources/js/routes.jslínea 58-61), guardbeforeEnter: canViewModuleRoute('expedientes-operadores', 'view-operator')(nótese: valida módulo y permiso, distinto del guard simplecan()que usa Remolques). resources/js/components/Operators/OperatorRow.vue— fila de la lista: código, foto (OperatorPicture.vue), estado calculado (getOperatorState()), y semáforos de documentos clave inline (Lic. Federal, Constancia de Capacitación, Psicofísico, Estudio Socioeconómico, Examen Antidoping con círculo de semáforo) víaOperatorFileDate.vue.resources/js/components/Operators/OperatorForm.vue— formulario/detalle de un operador (el “expediente” en sí). Compuesto por secciones bajoresources/js/components/Operators/FormSections/:OperatorGeneralDataForm.vue(datos generales),OperatorRequiredFilesForm.vue(documentos obligatorios),OperatorRegulationsFilesForm.vue(normatividad),OperatorOtherFilesForm.vue(otros archivos),OperatorComplementaryForm.vue,OperatorLeavingForm.vue(baja),OperatorApprovalsForm.vue(aprobaciones de jefes),OperatorAS400Data.vue(datos crudos de AS400),OperatorAttendanceWidget.vue.- Subida de archivos:
resources/js/components/Operators/Files/OperatorFileUpload.vue,OperatorFileUploadList.vue,OperatorRegulationsFileUpload.vue,OperatorFileDate.vue(muestra fecha/vigencia + semáforo). - Otros:
OperatorPicture.vue(foto/avatar),OperatorSelector.vue(selector reusable en otros módulos, p.ej. Remolques podría referenciarlo paraoperator_id),OperatorCommentsModal.vue,OperatorApproval.vue,OperatorTransformForm.vue(graduar candidato). - Reportes/KPIs bajo
/operadores/reportes/(OperatorKpi.vue+OperatorKpi1..6) y escuela bajo/operadores/escuela/(OperatorsSchool.vue,OperatorsSchoolKpi.vue) — fuera del alcance de “expediente” propiamente pero comparten el mismo dominio Vue. - Servicio:
resources/js/services/operators/operator-service.js—GET /operators,/operators/with-trashed,/operators/paginated;POST /operators(Spark.post, crear);PUT /operators/{id}(Spark.put, actualizar);DELETE /operators/{id};PATCH /operators/{id}(restaurar);PUT /operators/{id}/set-status;POST /operators/{id}/notify,/approve,/reactivate,/reactivate/notify,/change-team/{team_id},/set-teacher;GET /operators/{id}/sync,/operators/{id}/staff-logs;GET /operators/excel(export, víaDownloadService);POST /operators/transform/{operator_candidate_id}. Servicios hermanos:operator-file-service.js(documentos),operator-comment-service.js,operator-candidate-service.js(+operator-candidate-status.js,operator-status.jspara mapear estados a etiquetas/colores), reportes enservices/operators/reports/.
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»Resource operator (expediente) + operator-candidate (escuela/candidatos), separados en config/laratrust_seeder.php. Constantes relevantes en App\Core\Security\PermissionConst (app/Core/Security/PermissionConst.php):
- Ver/listar:
viewOperator(view-operator, gobierna la ruta/operadores/),viewOperators(view-operators),viewOperatorsCatalogs,viewOperatorsReports. - CRUD:
createOperator,readOperator,updateOperator,deleteOperator. - Flujo/estado:
approveOperator(aprobación por jefe),notifyOperator(solicitar aprobación/reactivación),reactivateOperator,setAsTeacherOperator,changeTeamOperator,setRegistrationImssOperator,setLeavingImssFileOperator,setLeavingReasonOperator,changeFileCreatedDateOperator(editar fecha de un documento subido),uploadOtherFilesOperator,commentOperator. - Candidatos/escuela (paralelo):
createOperatorCandidate,readOperatorCandidate,updateOperatorCandidate,viewOperatorCandidate,approveOperatorCandidate,approveLeavingOperatorCandidate,approveMechanicalRevisionOperatorCandidate,updateTeacherOperatorCandidate,updateViabilityOperatorCandidate,updateBasicOperatorCandidate,updateDocumentsOperatorCandidate,updateTestsOperatorCandidate,uploadPaymentsOperatorCandidate,editPaymentsOperatorCandidate,setWrittenOffTypeOperatorCandidate,uploadRevisionsOperatorCandidate,commentOperatorCandidate. - Ejemplo de rol (admin) en el seeder:
'operator' => 'v,c,r,u,d,co,set-registration-imss,set-leaving-imss-file,set-leaving-reason,change-team,upload-other-files,set-as-teacher,change-file-created-date'— los roles no-admin (jefes de área) suelen tener solo'v,r,a,co'(ver+leer+aprobar+comentar), sin crear/borrar/editar. - Regenerar constantes tras tocar el seeder:
php artisan helpers:permission-php.
Selectores usados por el tester (riesgo de fragilidad)
Sección titulada «Selectores usados por el tester (riesgo de fragilidad)»Aún NO hay specs de Playwright para este módulo. Por ahora solo Remolques tiene cobertura (tests/remolques/expand-trailer-detail.spec.ts, ver remolques.md); todo lo de esta ficha sobre selectores es deducido del código fuente, no verificado contra el DOM real de QA — hay que correr npm run codegen:gmg contra https://qa.controlgmg.com/operadores antes de escribir el primer spec, siguiendo el mismo patrón de descubrimiento que se usó para Remolques (los supuestos iniciales de ese módulo fallaron varias veces al chocar con el DOM real).
Riesgos previsibles por analogía con Remolques (a confirmar, no asumir):
- Es muy probable que tampoco haya
data-testiden estos componentes (no se modificacontrolgmg-v2; ningún componente deOperators/inspeccionado usa ese atributo). - El listado (
Operators.vue/OperatorRow.vue) usa checkboxes de Element UI (el-checkbox) para filtros contrue-label="true"/false-label=""en vez de booleanos nativos — al interactuar por Playwright, verificar el valor real emitido, no asumirtrue/false. - El detalle es un formulario de varias secciones (
OperatorForm.vue+FormSections/*), no un panel expandible de una sola fila como en Remolques — hay que confirmar en vivo si abre modal, ruta propia, o expande inline. - Las fechas de vigencia de documentos (
OperatorFileDate.vue) probablemente combinan texto + ícono de semáforo (semaphore-circle) de forma similar a como Remolques combina label+valor en nodos hermanos — no asumir un solo nodo de texto sin inspeccionar el DOM real.
Datos observados en QA (2026-08-12)
Sección titulada «Datos observados en QA (2026-08-12)»Página https://qa.controlgmg.com/operadores, título “Expediente Operador” — confirma que en el UI el módulo se presenta como el expediente/dossier de choferes/operadores (documentos, fotos, licencias), aunque internamente el permiso/ruta se llaman operator/operadores (singular en backend, plural en URL) y el módulo Laratrust es expedientes-operadores. No se registraron aún datos de un operador específico (código, nombre, estado) por observación manual — pendiente de una pasada de exploración en vivo dedicada a este módulo, similar a la hecha para el remolque SNLU996394 en remolques.md.