GMG-Utils (Videos, Links, Tareas)
Generada 2026-08-13. Fuente: exploración de código de controlgmg-v2 + observación manual del navegador (QA).
Estas tres utilidades viven como ítems independientes del sidebar (no hay un menú padre “GMG-Utils” en el código — es una agrupación informal usada para esta documentación). Cada una tiene su propio modelo, grupo de rutas y página Vue, sin relación funcional entre sí.
Backend
Sección titulada «Backend»- Modelo:
App\Core\Videos\Video(app/Core/Videos/Video.php), extiendeBaseModel, implementaHasModule. Tablavideos(no localizada la migración explícita en esta pasada — por verificar). - Columnas relevantes:
user_id,title,notes,is_public(bool),status.fillableexplícito.SoftDeletesactivo. - Estados (
status): constantesPENDING(pending),PROCESSING(processing),CONVERTED(converted) — sugiere un pipeline de conversión/transcodificación de video en background, con scopesscopePending/scopeProcessing/scopeConvertedy setterssetPending/setProcessing/setConverted. - Relaciones:
user()(belongsTo),videotags()(belongsToManyconApp\Core\Catalogs\Videotag— catálogo de etiquetas, gestionado enroutes/catalogs/videotag.php),roles()(belongsToMany(Role)vía tabla pivoterole_video— visibilidad por rol),logs()(hasMany(VideoLog)— historial, posiblemente de reproducción/edición),video_file()(morphOne(File)contype = 'video'). - Traits:
FileableTrait,SearchableTrait(busca portitle,notes,users.name),CommentableTrait,HasModuleRelationshipTrait,MessageCenterNotifiableTrait,UserNotifiableTrait. getModelTitleAttribute()→'Video';getActionUrlAttribute()→/videos?video_id={id}(deep-link usado por Message Center/notificaciones).getModule()→Module::videos.- No se localizó
App\Http\Controllers\Videos\VideoControlleren detalle (solo inferido de rutas) — por verificar lógica de negocio del pipeline de conversión.
Prefijo /videos (routes/web.php línea ~436), requiere require base_path('routes/videos/videos.php') + files.php + other-files.php:
| Método | Ruta | Acción |
|---|---|---|
| GET | /videos/get-videos |
listado |
| POST | /videos/store |
crear |
| PUT | /videos/update/{video} |
actualizar |
| DELETE | /videos/{video} |
eliminar (soft delete) |
| PATCH | /videos/{video} |
restaurar |
| POST | /videos/create-log |
crear log |
| PUT | /videos/update-log |
actualizar log |
| PUT | /videos/finish-log |
finalizar log |
| POST | /videos/{video}/comment |
comentario |
| POST/GET/PUT/DELETE | /videos/{video}/files/* (routes/videos/files.php) |
archivo principal del video (nota: hay una línea de ruta files comentada — revisar si el alta de archivo usa {video}/files directo) |
| POST/GET/PUT/DELETE | /videos/{video}/other-files/* (routes/videos/other-files.php) |
archivos adicionales |
Catálogo relacionado: routes/catalogs/videotag.php (CRUD de Videotag, prefijo /videotag, controlador Catalogs\VideotagController).
No se observó middleware permission: a nivel de grupo de ruta (mismo patrón que Liquidaciones/Remolques) — por verificar si VideoController valida permisos en el propio método o vía FormRequest.
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/videos→resources/js/routes.jslínea ~310-313, componenteresources/js/components/GmgVideos/VideosPage.vue(chunkgmg-videos). Ojo: el directorio esGmgVideos, noVideos— a diferencia de Liquidaciones (Operations), no sigue el nombre del modelo/dominio backend (Video) sino un prefijoGmg*compartido con Links. - Sin
beforeEnteren la definición de ruta (a diferencia de/gmg-links) — el guard de vista, si existe, se resuelve dentro del propio componente/menú. - Otros componentes en
resources/js/components/GmgVideos/:VideoForm.vue(alta/edición),VideosTableRow.vue(fila de listado),VideoPlayer.vue(reproductor embebido — consistente con el pipeline de conversión del backend),FormSections/LinkedOtherFilesForm.vue,File/VideoFileUpload.vue,File/LinkedFilesUpload.vue. - Menú lateral (
resources/js/components/Nav/menuOptions.jslínea ~580-585):name: 'Videos', ícono/img/videos.svg,path: '/videos',canView: ''(cadena vacía — sin gate de permiso en el menú, a diferencia de Liquidaciones que usacanView: 'view-operation'). - No se localizó el servicio JS (
*-service.js) en esta pasada — por verificar nombre exacto (probablementeresources/js/services/videos/video-service.jso similar, análogo al patrón de Operations).
Backend
Sección titulada «Backend»- Modelo:
App\Core\Links\Link(app/Core/Links/Link.php), extiendeModel(noBaseModel). Tablalinks(migración no localizada explícitamente). fillable:name,link,is_public(cast a boolean).SoftDeletesactivo.- Traits:
SearchableTrait(busca porlinks.name),FileableTrait. - Relaciones:
roles()→morphToMany(Role::class, 'roleable', 'roleables', ...)— visibilidad por rol vía tabla polimórficaroleables(patrón distinto al de Video, que usa pivote dedicadorole_video). commonWith(): cargafilesyroles(soloid).- Es el modelo más simple de los tres (sin estados/status, sin
Module/HasModule, sin comentarios ni notificaciones) — coherente con ser un simple repositorio de enlaces con archivos adjuntos opcionales. - Controlador:
Links\LinkController(namespace inferido de rutas, no inspeccionado a detalle).
Prefijo /links (routes/web.php línea ~133-135), require routes/links/links.php + files.php. Nota: hay dos bloques de rutas casi duplicados en links.php (líneas 2-5 y 10-13) — uno con verbos GET /get-links, POST /store, PUT /update/{link}, DELETE /{link}, y otro con GET '', POST '', PUT {link}, DELETE {link} (rutas RESTful “planas” superpuestas a las nombradas). Por verificar cuál usa realmente el frontend (probablemente la primera, estilo /get-links y /store, siguiendo el patrón visto en Videos/Tasks) — posible código muerto o transición de API.
| Método | Ruta | Acción |
|---|---|---|
| GET | /links/get-links (o /links) |
listado |
| POST | /links/store (o /links) |
crear |
| PUT | /links/update/{link} (o /links/{link}) |
actualizar |
| DELETE | /links/{link} |
eliminar (soft delete) |
| POST/GET/PUT/DELETE | /links/{link}/files/* (routes/links/files.php) |
archivos adjuntos |
beforeEnter: can('view-links') está en el frontend (routes.js), no confirmado middleware de permiso a nivel de ruta backend.
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/gmg-links→resources/js/routes.jslínea ~315-318, componenteresources/js/components/GmgLinks/LinksPage.vue(chunkgmg-links), con guardbeforeEnter: can('view-links')— único de las tres utilidades con guard explícito de ruta. - Otros componentes:
LinkForm.vue(alta/edición),LinksTableRow.vue(fila de listado — probablemente corresponde a las columnas Nombre + visibilidad observadas en QA),FormSections/LinkOtherFilesForm.vue,File/LinkFileUpload.vue. - Menú lateral (línea ~586-591):
name: 'Links', ícono/img/links.svg,path: '/gmg-links',canView: ''(vacío en el menú — el gate real está en elbeforeEnterde la ruta, no en la visibilidad del ítem de menú; posible inconsistencia: el ítem de menú podría mostrarse a usuarios sin permiso, que luego serían redirigidos al navegar).
Datos observados en QA (2026-08-12)
Sección titulada «Datos observados en QA (2026-08-12)»- URL:
https://qa.controlgmg.com/gmg-links— título de página “Gmg Links”. - 38 registros totales.
- Columnas visibles: Nombre (ej. “CELULAR EN CONMUTADOR GDL…”, “LIQUIDACIONES-TABULADOR INTERMODAL GDL/MEX/MTY”) y visibilidad (Público/Privado — corresponde al campo
is_publicdel modelo). - Filtro: “Tiene archivos relacionados” (corresponde a la relación
filesvíaFileableTrait). - Botón “Agregar” (alta, abre presumiblemente
LinkForm.vue). - No se inspeccionó el formulario de alta/edición ni el detalle de un registro individual — pendiente.
Backend
Sección titulada «Backend»- Modelo:
App\Core\Tasks\Task(app/Core/Tasks/Task.php), extiendeBaseModel, implementaHasMedia(Spatie MediaLibrary) yHasModule. Es el modelo más complejo de los tres, con lógica de negocio sustancial (aprobaciones, extensiones, actividades). fillable:user_id,subject,due_date,detail,priority,frequency.SoftDeletesactivo.- Estados (
status):NEW_TASK(new),IN_PROGRESS(in_progress),COMPLETED(completed),EXPIRED(expired). ScopescopeNewInProgress(). - Frecuencias (
frequency):UNIQUE(unica),MONTHLY(mensual),ANNUAL(anual) — sugiere tareas recurrentes, no solo únicas. - Prioridad (
priority): entero 1-5 mapeado porgetPriorityTextAttribute()a “Muy baja”/“Baja”/“Normal”/“Alta”/“Muy Alta”. - Relaciones:
user()(creador,withTrashed),task_activities()(hasMany(TaskActivity)— subtareas/actividades con su propio flujo de aprobación víaapprovals),informed_users()/responsible_users()(belongsToMany(User)vía pivote con columnatype=informed/responsible— dos roles de usuario distintos por tarea),business_rule_extensions(víaBusinessRuleExtensionTrait— solicitudes de prórroga de fecha límite, congetHasActiveExtensionAttribute()/getIsLockedAttribute()para bloquear tareas vencidas sin extensión activa). - Traits adicionales:
ApprovableTrait,LoggableTrait(historiallogs, con helpersaddCreatedLog/addCompletedLog),MessageCenterNotifiableTrait,UserNotifiableTrait. - Eventos de dominio (constantes):
TASK_CREATED,TASK_MODIFIED,TASK_ACTIVITY_CREATED,TASK_FINISHED,TASK_FILE_ADDED/MODIFIED/DELETED— mapean a notificaciones del Message Center (patrón igual a Operations en Liquidaciones). getModelTitleAttribute()→'Tarea';getActionUrlAttribute()→/tareas?id={id}.getModule()→Module::tareas.- Controlador:
Tasks\TaskController(namespace inferido, no inspeccionado a detalle),Tasks\TaskFileController,Tasks\TaskCommentController.
Prefijo /tasks (routes/web.php línea ~314-317), require routes/tasks/tasks.php + files.php, más POST /tasks/{task}/comment inline:
| Método | Ruta | Acción |
|---|---|---|
| GET | /tasks/ |
listado paginado |
| GET | /tasks/search-tasks |
búsqueda |
| POST | /tasks/store |
crear |
| POST | /tasks/store-activity |
crear actividad/subtarea |
| PUT | /tasks/update/{task} |
actualizar |
| POST | /tasks/{task}/image |
agregar imagen (media) |
| DELETE | /tasks/{task} |
eliminar (soft delete) |
| DELETE | /tasks/delete-activity/{activity} |
eliminar actividad |
| PATCH | /tasks/{task} |
restaurar |
| POST | /tasks/approve-activity/{activity} |
aprobar actividad |
| POST | /tasks/revert-approval-activity/{activity} |
revertir aprobación |
| POST | /tasks/complete/{task} |
marcar completada |
| GET | /tasks/stats/{type} |
estadísticas |
| POST | /tasks/{task}/create-extension |
solicitar extensión de fecha |
| GET | /tasks/{task}/download-history |
historial de descargas |
| POST | /tasks/{task}/comment |
comentario |
| POST/GET/PUT/DELETE | /tasks/{task}/files/* (routes/tasks/files.php) |
archivos adjuntos |
Sin middleware permission: visible a nivel de grupo (mismo patrón general del repo).
Frontend (Vue 2)
Sección titulada «Frontend (Vue 2)»- Ruta raíz
/tareas→resources/js/routes.jslínea ~319-322, componenteresources/js/components/Tasks/TasksPage.vue(chunkbill— nombre de chunk no descriptivo, posible copy-paste; ojo: a diferencia de Videos/Links no usa el prefijoGmg*, sinoTasksdirectamente, igual que el modelo backend). - Componentes en
resources/js/components/Tasks/:TaskForm.vue,TaskRow.vue,TaskSelector.vue(selector reusable en otros módulos),Activities/ActivityForm.vue+ActivityRow.vue,FormSections/TaskDetailForm.vue,TaskGeneralForm.vue,TaskActivitiesForm.vue,TaskExtensionsForm.vue,TaskFilesForm.vue— el formulario está descompuesto en secciones, reflejando la riqueza del modelo (detalle, actividades, extensiones, archivos). - No se inspeccionó
menuOptions.jspara la entrada de “Tareas” en esta pasada (se confirmaron Videos y Links, pero no se buscó explícitamente la línea de Tareas) — por verificar elcanViewexacto del ítem de menú de Tareas.
Permisos (Laratrust)
Sección titulada «Permisos (Laratrust)»Fuente: config/laratrust_seeder.php (definición de recursos) + App\Core\Security\PermissionConst:
- Video — recurso
video:c,r,u,d,download,view-logs→create-video,read-video,update-video,delete-video,download-video,view-logs-video. Recurso apartevideotag:c,r,u,d→create-videotag,read-videotag,update-videotag,delete-videotag(catálogo de etiquetas). Roles dedicados:Role::capturadorVideos(video: c,r,u+videotag: c,r,u),Role::visualizadorVideos(video: r,videotag: r). No hayview-videoen la lista de constantes — el listado probablemente se controla solo porread-video, y el ítem de menú tienecanView: ''(sin gate), consistente con ausencia de un permiso “view” dedicado. - Links — recurso
links:v,c,u,d,upload-other-files→view-links,create-links,update-links,delete-links,upload-other-files-links. Roles dedicados:Role::manejadorLinks(v,c,u,d,upload-other-files— control total) yRole::observadorLinks(v— solo lectura).view-linkssí existe y se usa activamente en elbeforeEnterde la ruta frontend. - Tareas — recurso
task:c,r,u,d,v,a,su→create-task,read-task,update-task,delete-task,view-task,approve-task,super-update-task. Recurso apartetask_activity:c,u,d,su,a→create-task_activity,update-task_activity,delete-task_activity,super-update-task_activity,approve-task_activity. Hay además permisos separados para un submódulo “task-maintenances-preventives” (create/update/delete/view-tasks-maintenances-preventives) que no corresponde a la Tarea genérica observada en QA — posible variante especializada, no explorada.
En resumen: Links es la única de las tres con un permiso de vista (view-links) efectivamente enforced en el routing frontend; Videos y Tareas tienen permisos CRUD granulares definidos en el backend, pero el gate de menú (canView) no se confirmó como estricto para ninguna (vacío para Videos/Links en el menú; Tareas no verificado).
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 ninguna de las tres utilidades todavía (mismo estado que Liquidaciones antes de esta pasada) — todo lo anterior es inferencia de código, no verificación de selectores en vivo. Puntos a tener en cuenta antes de escribir specs:
- No hay
data-testiden el repo (patrón consistente en todocontrolgmg-v2); no se debe modificar el código para agregarlos. - Videos y Links comparten prefijo de directorio
GmgVideos/GmgLinksen el frontend pero no siguen el nombre del modelo backend (Video/Link) ni un namespace común real — al buscar componentes por convención de nombres, no asumirresources/js/components/Videos/niLinks/(no existen); sonGmgVideos/yGmgLinks/. - Tareas sí sigue el nombre del modelo (
Tasks/), a diferencia de las otras dos — inconsistencia de convención dentro del propio dominio “GMG-Utils”. - En Links (
/gmg-links) se observaron en QA una tabla con columnas Nombre + visibilidad (Público/Privado), filtro “Tiene archivos relacionados” y botón “Agregar” — el componente de fila esLinksTableRow.vue, pero su estructura interna (si usa<table>HTML real o divs, como en Liquidaciones/Remolques) no fue inspeccionada; no asumir sin verificar connpm run codegen:gmgcontra QA. - Videos y Tareas fueron observados solo por el título de página en esta pasada — no se navegó al listado ni al formulario de alta; columnas, filtros y estructura de fila quedan completamente por verificar.
- El formulario de Tareas está fragmentado en múltiples subcomponentes (
TaskGeneralForm,TaskDetailForm,TaskActivitiesForm,TaskExtensionsForm,TaskFilesForm) — probablemente una UI con pestañas/acordeón; un test de alta de tarea deberá navegar por estas secciones, no asumir un formulario plano de una sola vista. - La duplicidad de rutas backend en
routes/links/links.php(dos definiciones de listar/crear/actualizar con paths distintos:/get-links+/store+/update/{link}vs. RESTful plano''+{link}) no afecta al tester de UI directamente, pero sí importa si algún test intercepta/mockea llamadas de red por URL exacta (page.route) — verificar cuál usa realmente el frontend antes de mockear.
Antes de escribir cualquier spec para Videos, Links o Tareas, correr npm run codegen:gmg contra QA real para verificar selectores y flujo, igual que se hizo (o se planea hacer) para Liquidaciones y Remolques.