Ir al contenido

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í.

  • Modelo: App\Core\Videos\Video (app/Core/Videos/Video.php), extiende BaseModel, implementa HasModule. Tabla videos (no localizada la migración explícita en esta pasada — por verificar).
  • Columnas relevantes: user_id, title, notes, is_public (bool), status. fillable explícito. SoftDeletes activo.
  • Estados (status): constantes PENDING (pending), PROCESSING (processing), CONVERTED (converted) — sugiere un pipeline de conversión/transcodificación de video en background, con scopes scopePending/scopeProcessing/scopeConverted y setters setPending/setProcessing/setConverted.
  • Relaciones: user() (belongsTo), videotags() (belongsToMany con App\Core\Catalogs\Videotag — catálogo de etiquetas, gestionado en routes/catalogs/videotag.php), roles() (belongsToMany(Role) vía tabla pivote role_video — visibilidad por rol), logs() (hasMany(VideoLog) — historial, posiblemente de reproducción/edición), video_file() (morphOne(File) con type = 'video').
  • Traits: FileableTrait, SearchableTrait (busca por title, 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\VideoController en 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.

  • Ruta raíz /videos → resources/js/routes.js línea ~310-313, componente resources/js/components/GmgVideos/VideosPage.vue (chunk gmg-videos). Ojo: el directorio es GmgVideos, no Videos — a diferencia de Liquidaciones (Operations), no sigue el nombre del modelo/dominio backend (Video) sino un prefijo Gmg* compartido con Links.
  • Sin beforeEnter en 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.js lí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 usa canView: 'view-operation').
  • No se localizó el servicio JS (*-service.js) en esta pasada — por verificar nombre exacto (probablemente resources/js/services/videos/video-service.js o similar, análogo al patrón de Operations).
  • Modelo: App\Core\Links\Link (app/Core/Links/Link.php), extiende Model (no BaseModel). Tabla links (migración no localizada explícitamente).
  • fillable: name, link, is_public (cast a boolean). SoftDeletes activo.
  • Traits: SearchableTrait (busca por links.name), FileableTrait.
  • Relaciones: roles() → morphToMany(Role::class, 'roleable', 'roleables', ...) — visibilidad por rol vía tabla polimórfica roleables (patrón distinto al de Video, que usa pivote dedicado role_video).
  • commonWith(): carga files y roles (solo id).
  • 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.

  • Ruta raíz /gmg-links → resources/js/routes.js línea ~315-318, componente resources/js/components/GmgLinks/LinksPage.vue (chunk gmg-links), con guard beforeEnter: 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 el beforeEnter de 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).
  • 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_public del modelo).
  • Filtro: “Tiene archivos relacionados” (corresponde a la relación files vía FileableTrait).
  • 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.
  • Modelo: App\Core\Tasks\Task (app/Core/Tasks/Task.php), extiende BaseModel, implementa HasMedia (Spatie MediaLibrary) y HasModule. 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. SoftDeletes activo.
  • Estados (status): NEW_TASK (new), IN_PROGRESS (in_progress), COMPLETED (completed), EXPIRED (expired). Scope scopeNewInProgress().
  • Frecuencias (frequency): UNIQUE (unica), MONTHLY (mensual), ANNUAL (anual) — sugiere tareas recurrentes, no solo únicas.
  • Prioridad (priority): entero 1-5 mapeado por getPriorityTextAttribute() 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ía approvals), informed_users() / responsible_users() (belongsToMany(User) vía pivote con columna type = informed/responsible — dos roles de usuario distintos por tarea), business_rule_extensions (vía BusinessRuleExtensionTrait — solicitudes de prórroga de fecha límite, con getHasActiveExtensionAttribute()/getIsLockedAttribute() para bloquear tareas vencidas sin extensión activa).
  • Traits adicionales: ApprovableTrait, LoggableTrait (historial logs, con helpers addCreatedLog/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).

  • Ruta raíz /tareas → resources/js/routes.js línea ~319-322, componente resources/js/components/Tasks/TasksPage.vue (chunk bill — nombre de chunk no descriptivo, posible copy-paste; ojo: a diferencia de Videos/Links no usa el prefijo Gmg*, sino Tasks directamente, 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.js para 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 el canView exacto del ítem de menú de Tareas.

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 aparte videotag: 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 hay view-video en la lista de constantes — el listado probablemente se controla solo por read-video, y el ítem de menú tiene canView: '' (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) y Role::observadorLinks (v — solo lectura). view-links sí existe y se usa activamente en el beforeEnter de 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 aparte task_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-testid en el repo (patrón consistente en todo controlgmg-v2); no se debe modificar el código para agregarlos.
  • Videos y Links comparten prefijo de directorio GmgVideos/GmgLinks en 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 asumir resources/js/components/Videos/ ni Links/ (no existen); son GmgVideos/ y GmgLinks/.
  • 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 es LinksTableRow.vue, pero su estructura interna (si usa <table> HTML real o divs, como en Liquidaciones/Remolques) no fue inspeccionada; no asumir sin verificar con npm run codegen:gmg contra 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.