Ir al contenido

Vales

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

  • Modelo principal: App\Core\Vouchers\Voucher (app/Core/Vouchers/Voucher.php). No declara $table explícita ni guarded/fillable visibles en el archivo (por verificar migración/tabla exacta — probablemente vouchers, no se localizó la migración base en esta pasada). Cast: amount a double. Usa ApprovableTrait (mismo mecanismo de aprobaciones que otros dominios, vía App\Core\Approvals\Approval).
  • Ojo con el modelo “padre”: el módulo “Vales” en realidad gira en torno a App\Core\Vouchers\VoucherPolicy (“póliza de vale”), no solo Voucher. La ruta principal /vales (GET /vouchers/vales) apunta a VoucherPolicyController@index, y el componente raíz del frontend es VoucherPolicies.vue. Voucher (el “vale” individual) cuelga de una VoucherPolicy vía voucher_policy_id — patrón análogo a Liquidaciones/Operation, pero aquí hay dos niveles (póliza → vales individuales) en vez de uno.
  • Columnas clave de Voucher (de los @property del modelo, por verificar contra migración real): id, user_id, voucher_policy_id, voucher_concept_id, operator_id, unit_id, voucher_file_id, number, amount, type, bank_account, reimbursed_at, reimbursed_by, created_at, updated_at, status.
  • Relaciones clave de Voucher:
    • unit() → belongsTo(App\Core\Unit::class)->withTrashed() (Tractor).
    • operator() → belongsTo(App\Core\Operators\Operator::class).
    • policy() → belongsTo(VoucherPolicy::class, 'voucher_policy_id', 'id').
    • voucher_concept() → belongsTo(VoucherConcept::class) (catálogo de conceptos, con posible authorizer_user_id que dispara require_authorization).
    • user() → belongsTo(User) (quien capturó).
    • voucherFile() / supportFiles() / voucherSupportFiles() → archivos adjuntos (VoucherFile, VoucherSupportFile).
    • Atributo calculado require_authorization (appends): true si el concepto tiene authorizer_user_id y aún no tiene todas las aprobaciones de VoucherApprovalTypes::conceptoConAutorizacion.
    • isUnitSituationOk() / validateValidConceptWithUnitSituation(): valida que la “situación” actual del tractor (última posición en unit->last_movement->unit_situation_id) sea compatible con las situaciones permitidas para el concepto del vale (tabla puente UnitSituationByVoucherConcept).
    • getModelTitleAttribute() → 'Vale', getActionUrlAttribute() → /vales?voucher_policy_id=...&voucher_id=... (deep-link usado por notificaciones/comentarios).
  • Otros modelos del dominio en app/Core/Vouchers/: VoucherPolicy, VoucherPolicyService, VoucherPolicyFile, VoucherPolicyComment, VoucherConcept, VoucherFile, VoucherSupportFile, VoucherFileDownload, VoucherApprovalTypes, UnitSituationByVoucherConcept, VoucherRepository, VoucherService.
  • Relación Tractor↔Vale (la que ves en el módulo Tractores): App\Core\Unit::vouchers() → hasMany(Voucher::class) (app/Core/Unit.php línea ~175, a diferencia de operations() que usa withoutGlobalScopes()). El reporte de Tractores (UnitsReport.vue / Unit::scopeWithOperationAndVoucher()) hace withCount(['operations', 'vouchers']) — de ahí sale el alias vouchers_count (columna “# Vales”), y también admite orden por vouchers_count (Unit::scopeOrderByField, case vouchers_count). Existe además scopeWithOperationAndVoucher2 (variante con selects reducidos), por verificar cuál usa exactamente el listado de Tractores en producción.
  • Controladores (namespace App\Http\Controllers\Vouchers, por convención del resto del código — no confirmado archivo por archivo en esta pasada): VoucherPolicyController (CRUD de pólizas, stats, export, pagos/reembolso), VouchersController (borrar vale individual), VoucherApprovalController (aprobar/rechazar/notificar), VoucherConceptsController (catálogo de conceptos), VoucherSupportFileController / VoucherFileController / VoucherPolicyFileController (archivos), VoucherPolicyCommentController (comentarios), ReportsController (reportes por operador/unidad/usuario, charts, export, PDF).

Definidas en routes/web.php dentro de un grupo Route::group(['prefix' => 'vouchers'], ...) (líneas ~374-432), que además hace require base_path('routes/vouchers/vouchers.php') (línea 393) para las rutas de aprobación. Nota: el prefijo del grupo es vouchers pero la ruta visible al usuario es /vales (definida explícitamente dentro del grupo como Route::get('/vales', ...)), no /vouchers/vales — hay que leer con cuidado el prefijo compuesto.

Método Ruta (bajo prefijo vouchers, salvo la marcada) Acción
GET /vales listado principal de pólizas (VoucherPolicyController@index) — página completa, no API
GET vouchers (get) listado paginado de pólizas
PUT vouchers/{voucherPolicy} actualizar póliza
POST vouchers crear póliza
DELETE vouchers eliminar (soft delete) póliza
PATCH vouchers/{id} restaurar póliza
POST vouchers/{voucherPolicy}/payment/ready marcar lista para pago
POST vouchers/{voucherPolicy}/reimburse reembolsar
GET vouchers/stats, vouchers/paginated stats y listado paginado
POST vouchers/{voucherPolicy}/comment comentario en póliza
POST vouchers/upload, GET vouchers/files/{voucherPolicyFile} archivos de póliza
GET vouchers/{voucherPolicy}/download-history historial de descargas
POST vouchers/{voucher}/approve, .../disapprove, .../notify aprobación de vale individual (desde routes/vouchers/vouchers.php)
GET/POST vouchers/unit-situation-by-concept[/{id}] catálogo situación-concepto
DELETE vouchers/{voucher} eliminar vale individual
POST/GET/DELETE vouchers/support-files/upload, vouchers/support/files/... archivos de soporte del vale
POST/GET/DELETE vouchers/files/upload, vouchers/voucher/files/... archivos del vale
CRUD vouchers/concepts/* (paginated, authorizer-users, get, with-trashed, store, update/{id}, delete/{id}, restore/{id}) catálogo de conceptos
GET/POST vouchers/reports/* (items-by-operator/unit/user, vouchers, vouchers-unit, vouchers-user, operators-codes, units-codes, users-codes, chart-info, vouchers-pdf, vouchers-to-download, export, concepts, unit-chart-info, operator-chart-info, export-operator) reportes

No se localizó middleware permission: a nivel de ruta en esta pasada rápida — por verificar si la autorización vive en FormRequests/controladores (patrón visto en Liquidaciones y Remolques).

  • Ruta raíz /vales/ → resources/js/components/Vouchers/VoucherPolicies.vue (listado principal, sin beforeEnter explícito según routes.js).
  • /vales/catalogos/conceptos → Vouchers/Catalog/VoucherConceptPage.vue (guard can('view-vouchers-reports') — nota: usa el permiso de reportes, no uno específico de catálogo; por verificar si es intencional o copy-paste, similar al caso ya detectado en Liquidaciones).
  • /vales/reportes/tractor → Vouchers/Reports/UnitChart.vue, /vales/reportes/operador → Reports/OperatorChart.vue, /vales/reportes/vales → Reports/GeneralReport/VoucherGeneralReport.vue — los tres con guard can('view-vouchers-reports').
  • Componentes relevantes bajo resources/js/components/Vouchers/:
    • VoucherPolicies.vue (listado principal), VoucherPolicyRow.vue (fila de póliza).
    • Forms/VoucherPolicyForm.vue (alta/edición de póliza), Forms/VoucherForm.vue (vale individual dentro de la póliza), Forms/VoucherRow.vue, Forms/VoucherTransferForm.vue (transferencia de vale, por explorar).
    • Catalog/VoucherConceptPage.vue, VoucherConceptForm.vue, VoucherConceptRow.vue, VoucherConceptDetail.vue, VoucherConceptSelector.vue — catálogo de conceptos.
    • Dialogs/VoucherConceptFormDialog.vue.
    • Files/VoucherPolicyFileUpload.vue.
    • Reports/UnitChart.vue, Reports/OperatorChart.vue, Reports/GeneralReport/VoucherGeneralReport.vue con sub-tablas ReportByOperator|ReportByUnit|ReportByUser/ReportTable.vue, ReportSubTable.vue, ReportTableRow.vue, y GeneralReport/ReportChart.vue.
  • No se localizó un voucher-service.js explícito en esta pasada (por verificar en resources/js/services/vouchers/ o similar — no inspeccionado, análogo a operation-service.js en Liquidaciones).
  • Menú lateral (resources/js/components/Nav/menuOptions.js, líneas ~323-362): entrada name: 'Vales', module: 'voucher', path: '/vales', canView: 'view-voucher', ícono /img/vale.svg. Submenús: submodules → “Vales” (/vales), catalogs → “Conceptos” (/vales/catalogos/conceptos, canView: 'create-voucher-concept'), reports → “Tractor” / “Operador” / “Vales” (los tres canView: 'view-vouchers-reports').

Fuente: config/laratrust_seeder.php — resource voucher: 'v,c,r,u,d,delete-paid' (línea ~35/134) y resource voucher-concept: 'c,u,d,a' (línea ~55/136), más vouchers-reports: 'v' (línea ~54/154). Generadas en App\Core\Security\PermissionConst (app/Core/Security/PermissionConst.php):

  • view-voucher, create-voucher, read-voucher, update-voucher, delete-voucher, delete-paid-voucher
  • approve-voucher, reimburse-voucher, add-policy-number-voucher
  • create-voucher-concept, update-voucher-concept, delete-voucher-concept, approve-voucher-concept
  • view-vouchers-reports (gobierna las 3 rutas bajo /vales/reportes/* y también, aparentemente, /vales/catalogos/conceptos)

Roles relevantes en config/laratrust_seeder.php:

  • jefeBancos → voucher: 'apn,v,d,u' (aprobar/notificar, ver, borrar, actualizar).
  • jefeFinanzas → voucher: 're,v,d' (reembolsar, ver, borrar).
  • Un rol no identificado por nombre en esta pasada (bloque ~línea 236-238, junto a operation y operator-candidate) → voucher: 'a,v,d'.
  • solicitanteVales → voucher: 'v,c,r,u,d' (rol “solicitante de vales”, con casi todo el CRUD).
  • autorizadorConceptoVales → voucher: 'v,r', voucher-concept: 'a' (autoriza conceptos, no vales directamente).
  • observadorReportesVales → vouchers-reports: 'v' (solo ve reportes).

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 (mismo estado que Liquidaciones al momento de esta ficha) — nada de lo siguiente fue verificado contra QA real con codegen, es inferencia de código:

  • No hay data-testid en la app (patrón general de controlgmg-v2, no se modifica el repo para agregarlos).
  • El link “Vales” del sidebar (menuOptions.js) NO es de los primeros ítems (aparece después de bloques como Liquidaciones/Operadores en el arreglo menuOptions — no se contó la posición exacta), por lo que es razonable esperar que requiera scroll en el sidebar; debe confirmarse contra QA real, no asumirse.
  • El listado principal tiene DOS niveles de datos (pólizas y vales individuales dentro de cada póliza) — a diferencia de Liquidaciones/Remolques que son listas planas. Antes de escribir selectores para localizar un “vale” específico, hay que entender si VoucherPolicyRow.vue expande inline (patrón el-collapse-transition visto en otros módulos) para mostrar los VoucherRow.vue hijos — no confirmado en esta pasada.
  • No se inspeccionó la estructura interna de VoucherPolicyForm.vue / VoucherForm.vue (labels, inputs, si usan tabla HTML o divs) — no asumir getByLabel sin verificar.
  • La columna “# Vales” (vouchers_count) vive en el módulo Tractores (UnitsReport.vue), igual que “# Liquidaciones” — si un test necesita verificar la relación Tractor↔Vale, navegar a la ruta de Tractores, no a /vales.

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/vales bajo el título “Vales”. No se inspeccionó el contenido a mayor detalle visual — falta una exploración manual más profunda del listado (pólizas y vales individuales), el formulario de alta/edición, y el flujo de aprobación para documentar selectores y datos de prueba concretos (pendiente, análogo a lo hecho para Remolques/Liquidaciones).