Ir al contenido

Expediente Operador

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

  • Modelo: App\Core\Operators\Operator (app/Core/Operators/Operator.php), tabla operators.
  • 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 por team_id, además de company_id). El modelo relacionado OperatorCandidate vive 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.php solo trae id, 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_status
    • 2018_11_26_180322_add_company_id_field_to_operators_table.php, 2018_11_28_142655_add_team_id_field_to_operators.php — multi-tenencia
    • 2018_12_03_181909_add_viable_at_field_to_operators_table.php — viable_at
    • 2019_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 de code
    • 2020_06_24_125337_add_bank_account_and_payroll_branch_to_operators_table.php — bank_account, payroll_branch
    • 2020_08_07_093332_add_as400_extra_fields_to_operators_table.php — campos sincronizados desde AS400
    • 2022_06_07_203339_add_not_viable_at_to_operators_table.php
    • 2024_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 en checkAndUpdateStatus(): prospect → registered (si is_viable) → authorization (si tiene el archivo alta-imss) → active (si tiene TODAS las aprobaciones de jefes: Operaciones, Liquidaciones, Daños, Satélite, Taller, Capacitación, Factor Humano) → inactive (si tiene el archivo baja-en-el-imss); o not-viable si is_viable === false. Si el status deja de ser active, se borran los business_rule_locks().
  • Documentos/expediente — App\Core\Operators\OperatorFile (app/Core/Operators/OperatorFile.php), tabla operator_files, belongsTo(Operator). Tipos de archivo en App\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 (+ variantes escuela-* para candidatos/escuela). Cada tipo puede requerir fecha ($needDateTypes, p.ej. vigencia de licencia federal), semáforo ($needSemaphoreTypes: antidoping) o calificación ($needQualifiableTypes). OperatorFile expone getUrlAttribute() → /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 en operator_files.custom_date del archivo con type = 'licencia-federal'. OperatorsController::getOperatorLicence() es el ejemplo de lectura de esa fecha.
  • Foto: photo_path en operators, expuesto como photo_url (append) vía ImageUrlTrait/ImageResizeService::saveAndResizeImagesFromBase64(), path físico operators/{id}/images/{photo_path}.
  • Aprobaciones — App\Core\Operators\OperatorApproval, tabla implícita operators_approvals, hasMany desde Operator vía operator_approvals(). Un type por rol de jefe (Role::jefeOperaciones, jefeLiquidaciones, jefeDanos, jefeSatelite, jefeTaller, jefeCapacitacion, jefeFactorHumano), con approved_at y approval_requested_at.
  • Comentarios: CommentableTrait + morphMany(Comment::class, 'commentable'); notificación opcional por WhatsApp vía sendWhatsAppCommentNotification() / App\Notifications\WhatsAppOperatorCommentSent.
  • Relaciones clave: company() (belongsTo, withTrashed), current_unit() (belongsTo App\Core\Unit, FK current_unit_id — el tractor asignado), operations() (hasMany App\Core\Operations\Operation), vouchers() (hasMany App\Core\Vouchers\Voucher), waybills() (hasMany App\Core\Waybills\Waybill), students() (hasMany Operator, FK teacher_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\Trailer tiene operator_id (columna fillable) y public function operator(): BelongsTo (app/Core/Trailer/Trailer.php línea 185) — el remolque referencia al operador que lo trae, no al revés.
  • AS400/sync: As400RelatedModelTrait + SynchronizableTrait (misma tabla polimórfica synchronizations que Remolques). Sync de un operador: OperatorSyncService::syncOperator() (app/AS400/Ein00dat/AS400Sync/OperatorSyncService.php), invocado automáticamente al crear (store(), solo si hay conexión odbc configurada) y manualmente vía GET /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 controller Operators\Candidate\OperatorCandidateController, su propio módulo (escuela-operadores) y permisos operator-candidate en Laratrust. transformCandidate() en OperatorsController convierte 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()).

  • Ruta /operadores/ → resources/js/components/Operators/Operators.vue (resources/js/routes.js línea 58-61), guard beforeEnter: canViewModuleRoute('expedientes-operadores', 'view-operator') (nótese: valida módulo y permiso, distinto del guard simple can() 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ía OperatorFileDate.vue.
  • resources/js/components/Operators/OperatorForm.vue — formulario/detalle de un operador (el “expediente” en sí). Compuesto por secciones bajo resources/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 para operator_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ía DownloadService); 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.js para mapear estados a etiquetas/colores), reportes en services/operators/reports/.

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-testid en estos componentes (no se modifica controlgmg-v2; ningún componente de Operators/ inspeccionado usa ese atributo).
  • El listado (Operators.vue/OperatorRow.vue) usa checkboxes de Element UI (el-checkbox) para filtros con true-label="true"/false-label="" en vez de booleanos nativos — al interactuar por Playwright, verificar el valor real emitido, no asumir true/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.

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.