Expediente Clientes
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:
App\Core\Clients\Client(app/Core/Clients/Client.php), tablaclients(inferida por convención — Reliese Model, no se localizó la migración de creación explícita). ExtiendeApp\BaseModel. UsaSoftDeletes(deleted_at). - Columnas clave (via
$fillable/$casts/uso en el modelo):name,abbreviation,rfc,code,company_id(int),city_id(int),credit_days(int),sync_status,affect_accounting(usada enscopeAffectAccounting, valores1/2).nameyabbreviationpasan poriconv('ISO-8859-1','UTF-8', ...)en sus mutators — posible fuente de datos con encoding raro si se prueba con acentos/ñ. - Traits relevantes:
ApprovableTrait(flujo de aprobación, ver abajo),FileableTrait(archivos adjuntos,getFileBasePath()→'client'),SynchronizableTrait+As400RelatedModelTrait(sincronización con AS400/DB2 — hayClientSyncJob,ClientSyncServiceenapp/Jobs/AS400yapp/AS400/Ein00dat/AS400Sync),SearchableTrait(búsqueda full-text-like sobrename,abbreviation,rfc,code, más joins acities,companies,contractsyfile_downloads),MessageCenterNotifiableTrait/UserNotifiableTrait(notificaciones),BusinessRuleLockableTrait/BusinessRuleAppendsTrait(reglas de negocio configurables, verapp/Core/BusinessRules/Modules/Clients/),HasModule→getModule()retornaModule::expediente_clientes(así se llama el módulo internamente, coincide con el nombre visible en UI). $witheager-loaded siempre:company,city.$appends:edit_url,action_url(→/expediente-cliente/?id={id}, usado por notificaciones/links directos),name_with_code("{complete_code} - {name}"),morph_class,complete_code("{companyCode} {cityCode} {code}").- Relaciones clave (confirma el contexto de Trailer/Waybill dado):
waybills()→hasMany(Waybill)(dueño porclient_id),sender_waybills()(sender_client_id),receiver_waybills()(receiver_client_id).trailers()→hasMany(Trailer)(porclient_id),receiver_trailers()(receiver_client_id),sender_trailers()(sender_client_id) — confirma la relación triple conTrailermencionada en el contexto de este dominio.contracts()→hasMany(Contract)(modeloContract, no explorado a fondo en esta pasada — vive en el mismo namespaceApp\Core\Clients, probablementeContract.php), con variantecontracts_with_valid_trips().company()→belongsTo(Company)::withTrashed(),city()→belongsTo(App\Core\LegacyCatalogs\City).
- Controladores:
App\Http\Controllers\Clients\ClientController(CRUD +getPaginated,sync,restore,getDownloadHistory),ClientApprovalController(approve/disapprove/notify),ClientFileController(archivos),ClientCommentController(comentarios). TambiénClients\ReportsController(reportes),Clients\GetTravelReportController/GenerateTravelReportController,Clients\KilometerHistory\GetClientKilometerHistoryController. - Identidad compuesta:
scopeWhereKeys($companyCode, $code, $cityCode)/findByKeys(...)— un cliente se identifica de forma legible por la combinación empresa+ciudad+código (ligado acomplete_code), no solo porid. Útil si el tester necesita “buscar el cliente X” por datos visibles en vez de por id interno. - Export:
app/Exports/Clients/ClientsReportExport.php.
Todas bajo prefijo global client (routes/web.php línea ~296, dentro del grupo auth:api + password_expired), repartidas en varios archivos de routes/client/:
| Archivo | Método | Ruta (bajo /client/...) |
Acción |
|---|---|---|---|
client.php |
GET | `` (raíz) | listado (get) |
client.php |
GET | paginated |
listado paginado |
client.php |
POST | `` (raíz) | crear |
client.php |
GET/POST | travel-report |
reporte de viajes |
client.php |
PUT | {client} |
actualizar |
client.php |
DELETE | {client} |
eliminar (soft delete) |
client.php |
GET | {client}/sync |
forzar sync AS400 |
client.php |
PATCH | {client} |
restaurar |
client.php |
GET | {client}/download-history |
historial de descargas |
client.php |
GET | {client}/kilometer-history |
historial de kilometraje |
client.php |
GET | /{client} |
detalle (show) |
comments.php |
POST | {client}/comment |
agregar comentario |
files.php |
POST | {client}/files |
subir archivo |
files.php |
GET/PUT/DELETE | files/{file} |
descargar/actualizar/borrar archivo |
approvals.php |
POST | {client}/approve, {client}/disapprove, {client}/notify |
flujo de aprobación |
reports.php |
GET | reports/get-clients, reports/get-client-contracts, reports/get-contract-waybills, reports/get-export-waybills, reports/get-chart-info, reports/get-waybills-in-months |
datos para el reporte “Clientes con viajes” |
reports.php |
POST | reports/export |
exportar reporte |
No se detectó middleware permission: a nivel de ruta explícito en estos archivos — la autorización probablemente vive en los controladores/FormRequests (patrón similar a Liquidaciones/Remolques), no verificado línea por línea en esta pasada.
También existen rutas de Contratos (routes/contract/contract.php, files.php, comments.php) — dominio relacionado (Contract belongsTo Client) pero fuera del alcance de esta ficha; documentar aparte si se testea.
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/expediente-cliente/→resources/js/components/Clients/ClientsPage.vue(guardbeforeEnter: can('view-client'), definido enresources/js/routes.js~línea 165). /expediente-cliente/reportes/clientes-con-viajes→Clients/Reports/WaybillsByClient/WaybillsByClientKpi.vue(guardcan('view-client-reports')).- Componentes principales en
resources/js/components/Clients/:ClientsPage.vue— página contenedora del listado.ClientRow.vue— fila individual del listado.ClientForm.vue— formulario de alta/edición, con subcomponentes enFormSections/(ClientGeneralForm.vue,ClientDataForm.vue,ClientFilesForm.vue,ClientOtherFilesForm.vue) yFormElements/(ClientSelector.vue,ClientSelectorRemote.vue— selector reutilizado en otros módulos, p.ej. para elegir cliente en Remolques al asignarsender_client_id/receiver_client_id).ClientApproval.vue— UI del flujo de aprobación (ApprovableTraiten backend).File/ClientFileUpload.vue— subida de archivos del expediente.Contracts/— submódulo de Contratos del cliente:ContractList.vue,ContractRow.vue,ContractForm.vue, conFormSections/(ContractDetail.vue,ContractFilesForm.vue,ContractOtherFilesForm.vue) yFormElements/ContractSelector.vue.Reports/—ClientKilometerHistory.vue/ClientKilometerHistoryDialog.vue(historial de km), yReports/WaybillsByClient/con varios componentes de KPI/tabla/gráfica (WaybillsByClientKpi.vue,WaybillsByClientKpiChartReport.vue,WaybillsByClientKpiTableList.vue,WaybillsByClientKpiSubTable.vuey susRow,WaybillsDialog.vue,WaybillsInMonthsDialog.vue/Table.vue).
- Servicio JS: no se localizó/leyó el archivo de servicio explícitamente en esta pasada (esperado en
resources/js/services/clients/por convención del proyecto, análogo aoperations/operation-service.js) — por verificar. - Menú lateral:
resources/js/components/Nav/menuOptions.jslínea ~163, itemname: 'Expediente Clientes',module: 'client',canView: 'view-client',path: '/expediente-cliente', ícono/img/exp-cliente.svg. Submenú incluye el propio “Expediente Clientes” (path: '/expediente-cliente') y “Clientes con viajes” (path: '/expediente-cliente/reportes/clientes-con-viajes',canView: 'view-client-reports'). El ícono/img/exp-cliente.svgtambién aparece referenciado en otras secciones del menú (líneas ~545+, posiblemente reportes cruzados de otros módulos que enlazan a Clientes) — no explorado en detalle.
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»Fuente: config/laratrust_seeder.php, resource client: 'v,c,r,u,d,co,a,n,cf,uf,df' (visto en al menos dos bloques de roles con el mismo string, ver también variantes reducidas por rol: vendedor → v,c,r,u,co,n,cf; directorGeneralAdjunto → v,r,co,a; directorComercial → v,c,u,r,co,cf,uf,df) → generadas en App\Core\Security\PermissionConst:
view-client,create-client,read-client,update-client,delete-clientcomment-client(co),approve-client(a),notify-client(n)create-file-client(cf),update-file-client(uf),delete-file-client(df)read-client-reports,view-client-reports(gobierna/expediente-cliente/reportes/*)
No se localizó en esta pasada la lista completa de roles que tienen acceso a client (se vieron ejemplos sueltos: vendedor, directorGeneralAdjunto, directorComercial, más los bloques genéricos que probablemente correspondan a admin/owner) — por verificar un listado completo si se necesita para matriz de permisos de testing.
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 — a diferencia de Remolques, que sí tiene tests/remolques/expand-trailer-detail.spec.ts en este mismo repo tester. Expediente Clientes no ha sido explorado con Playwright/codegen contra QA real; todo lo de esta sección es inferencia de código, no verificación en vivo:
- No hay
data-testiden la app (mismo patrón que el resto decontrolgmg-v2; no se modifica el repo para agregarlos). - El item “Expediente Clientes” en el sidebar aparece con ícono propio (
exp-cliente.svg) — su posición exacta dentro delmenuOptionsarray (y por tanto si queda visible sin scroll) no fue determinada en esta pasada; comparar contra el patrón ya visto en Liquidaciones (primer ítem, visible) y Remolques (fuera de viewport). - El listado probablemente use el mismo patrón de filas basadas en
div(no tabla HTML) visto en Remolques/Liquidaciones (ClientRow.vue), pero su estructura interna no fue inspeccionada — no asumir selectores de columna sin verificar. - El formulario
ClientForm.vueestá dividido en secciones (FormSections/*) más complejas que Remolques/Liquidaciones (datos generales, datos adicionales, archivos, otros archivos) — al ser multi-sección, es más probable que use tabs o acordeón; no asumir que todos los campos están visibles simultáneamente sin verificar en vivo. - Existe un flujo de aprobación (
ClientApproval.vue, botones aprobar/desaprobar/notificar) que no existe en Remolques ni Liquidaciones tal cual — sus selectores y estados visuales son completamente desconocidos, hay que explorarlos manualmente. - El selector remoto de cliente (
ClientSelectorRemote.vue) se reutiliza en otros módulos (p.ej. Remolques, al asignarsender_client_id/receiver_client_id) — si un test en Remolques necesita elegir un cliente, este es el componente involucrado; probablemente unel-selectcon búsqueda remota (autocomplete), típicamente frágil paragetByLabel/getByRolesin verificar antes con codegen.
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.
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/expediente-cliente bajo el título “Expediente Cliente”. No se inspeccionó el contenido a mayor detalle visual.