Vales
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\Vouchers\Voucher(app/Core/Vouchers/Voucher.php). No declara$tableexplícita niguarded/fillablevisibles en el archivo (por verificar migración/tabla exacta — probablementevouchers, no se localizó la migración base en esta pasada). Cast:amountadouble. UsaApprovableTrait(mismo mecanismo de aprobaciones que otros dominios, víaApp\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 soloVoucher. La ruta principal/vales(GET /vouchers/vales) apunta aVoucherPolicyController@index, y el componente raíz del frontend esVoucherPolicies.vue.Voucher(el “vale” individual) cuelga de unaVoucherPolicyvíavoucher_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@propertydel 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 posibleauthorizer_user_idque dispararequire_authorization).user()→belongsTo(User)(quien capturó).voucherFile()/supportFiles()/voucherSupportFiles()→ archivos adjuntos (VoucherFile,VoucherSupportFile).- Atributo calculado
require_authorization(appends): true si el concepto tieneauthorizer_user_idy aún no tiene todas las aprobaciones deVoucherApprovalTypes::conceptoConAutorizacion. isUnitSituationOk()/validateValidConceptWithUnitSituation(): valida que la “situación” actual del tractor (última posición enunit->last_movement->unit_situation_id) sea compatible con las situaciones permitidas para el concepto del vale (tabla puenteUnitSituationByVoucherConcept).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.phplínea ~175, a diferencia deoperations()que usawithoutGlobalScopes()). El reporte de Tractores (UnitsReport.vue/Unit::scopeWithOperationAndVoucher()) hacewithCount(['operations', 'vouchers'])— de ahí sale el aliasvouchers_count(columna “# Vales”), y también admite orden porvouchers_count(Unit::scopeOrderByField, casevouchers_count). Existe ademásscopeWithOperationAndVoucher2(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).
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/vales/→resources/js/components/Vouchers/VoucherPolicies.vue(listado principal, sinbeforeEnterexplícito segúnroutes.js). /vales/catalogos/conceptos→Vouchers/Catalog/VoucherConceptPage.vue(guardcan('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 guardcan('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.vuecon sub-tablasReportByOperator|ReportByUnit|ReportByUser/ReportTable.vue,ReportSubTable.vue,ReportTableRow.vue, yGeneralReport/ReportChart.vue.
- No se localizó un
voucher-service.jsexplícito en esta pasada (por verificar enresources/js/services/vouchers/o similar — no inspeccionado, análogo aoperation-service.jsen Liquidaciones). - Menú lateral (
resources/js/components/Nav/menuOptions.js, líneas ~323-362): entradaname: '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 trescanView: 'view-vouchers-reports').
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»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-voucherapprove-voucher,reimburse-voucher,add-policy-number-vouchercreate-voucher-concept,update-voucher-concept,delete-voucher-concept,approve-voucher-conceptview-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
operationyoperator-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-testiden la app (patrón general decontrolgmg-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 arreglomenuOptions— 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.vueexpande inline (patrónel-collapse-transitionvisto en otros módulos) para mostrar losVoucherRow.vuehijos — 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 asumirgetByLabelsin 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.
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/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).