From 97a9dc03ed7c2f2172347c43f741109544ea9274 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Fri, 29 Nov 2024 11:48:03 -0300
Subject: [PATCH 1/9] Groups wip create groups and manage users

---
 .../app/layout/grupos/grupos.component.html   |  68 ++---
 .../src/app/layout/grupos/grupos.component.ts | 136 +++++++---
 .../src/app/layout/layout.module.ts           |   2 +
 .../create-group-modal.component.html         |  33 +++
 .../create-group-modal.component.ts           |  38 +++
 .../src/app/shared/components/index.ts        |   2 +
 .../manage-group-users-modal.component.html   |  49 ++++
 .../manage-group-users-modal.component.ts     |  74 ++++++
 Frontend Angular 4/src/app/shared/config.ts   |   6 +
 .../src/app/shared/objects/grupo.ts           |  32 +--
 .../app/shared/services/document.service.ts   |   6 +-
 .../src/app/shared/services/file.service.ts   |   1 -
 .../src/app/shared/services/group.service.ts  | 233 ++++++++++++++++++
 .../app/shared/services/haskell.service.ts    |   6 +-
 .../app/shared/services/session.service.ts    |   6 +-
 .../src/app/shared/shared.module.ts           |   4 +
 Frontend Angular 4/src/assets/i18n/en.json    |   7 +-
 Frontend Angular 4/src/assets/i18n/es.json    |   7 +-
 18 files changed, 614 insertions(+), 96 deletions(-)
 create mode 100644 Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/services/group.service.ts

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html
index ceb2c6c..407b509 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
@@ -13,8 +13,20 @@
   <div class="row" style="margin-top: 20px">
     <div class="col-lg-5">
       <div class="card" *ngIf="grupoSeleccionado == undefined">
-        <div class="card-header">
+        <div class="card-header flex justify-between">
           <div *ngIf="grupoSeleccionado == undefined">Grupos</div>
+          <button
+            ngbPopover="{{ 'i18n.action.new' | translate | titleCase }}"
+            triggers="mouseenter:mouseleave"
+            placement="bottom"
+            type="button"
+            class="btn btn-sm btn-secondary"
+            aria-haspopup="true"
+            aria-expanded="false"
+            (click)="openCreateGroupModal(false)"
+          >
+            <i aria-hidden="true" class="fa fa-plus"></i>
+          </button>
         </div>
         <div class="card-block" *ngIf="grupoSeleccionado == undefined">
           <div
@@ -34,7 +46,7 @@
             >
               <i class="fa fa-users matefun-fa-user" aria-hidden="true"></i>
               <p>
-                {{ grupo.grado + "°" + grupo.grupo + " - " + grupo.anio }}
+                {{ grupo.name }}
               </p>
             </div>
           </div>
@@ -64,17 +76,23 @@
                 >
                   <i class="fa fa-arrow-up"></i>
                 </button>
+                <button
+                  class="btn btn-sm btn-secondary pull-right"
+                  style="cursor: pointer; margin-top: -35px; margin-right: 36px"
+                  (click)="openManageGroupUsersModal()"
+                  ngbPopover="{{
+                    'i18n.msg.group.manageGroupUsers' | translate | titleCase
+                  }}"
+                  placement="bottom"
+                  triggers="mouseenter:mouseleave"
+                >
+                  <i class="fa fa-users"></i>
+                </button>
                 <p
                   class="pull-right"
-                  style="margin-top: -34px; margin-right: 60px"
+                  style="margin-top: -34px; margin-right: 93px"
                 >
-                  {{
-                    grupoSeleccionado.grado +
-                      "°" +
-                      grupoSeleccionado.grupo +
-                      " - " +
-                      grupoSeleccionado.anio
-                  }}
+                  {{ grupoSeleccionado.name }}
                 </p>
               </div>
               <div class="card-block">
@@ -83,8 +101,8 @@
                   style="min-height: 100px; overflow-y: scroll"
                 >
                   <div
-                    *ngFor="let alumno of grupoSeleccionado.alumnos"
-                    (click)="seleccionarAlumno(alumno)"
+                    *ngFor="let user of grupoSeleccionado.users"
+                    (click)="seleccionarUser(user)"
                     class="col-sm-3 matefun-group-wrapper"
                   >
                     <i
@@ -92,7 +110,7 @@
                       aria-hidden="true"
                     ></i>
                     <p>
-                      {{ alumno.apellido + ", " + alumno.nombre }}
+                      {{ user.username }}
                     </p>
                   </div>
                 </div>
@@ -121,13 +139,7 @@
                   class="pull-right"
                   style="margin-top: -34px; margin-right: 60px"
                 >
-                  {{
-                    grupoSeleccionado.grado +
-                      "°" +
-                      grupoSeleccionado.grupo +
-                      " - " +
-                      grupoSeleccionado.anio
-                  }}
+                  {{ grupoSeleccionado.name }}
                 </p>
               </div>
               <div class="card-block">
@@ -156,14 +168,14 @@
       <div [ngbNavOutlet]="nav" class="mt-2" *ngIf="grupoSeleccionado"></div>
     </div>
     <div class="col-lg-7">
-      <div class="card" *ngIf="alumnoSeleccionado">
+      <div class="card" *ngIf="selectedUser">
         <div class="card-block">
           <div
             class="row listadoEntregasAlumnoGrupos"
             style="min-height: 100px; overflow-y: scroll"
           >
             <div
-              *ngFor="let entrega of alumnoSeleccionado.archivos"
+              *ngFor="let entrega of selectedUser.archivos"
               (click)="seleccionarEntrega(entrega)"
               class="col-sm-3 col-4 matefun-file-wrapper"
             >
@@ -175,7 +187,7 @@
               <p>{{ entrega.nombre }}</p>
             </div>
             <div
-              *ngIf="alumnoSeleccionado.archivos.length == 0"
+              *ngIf="selectedUser.archivos.length == 0"
               style="width: 100%; text-align: center"
             >
               <i
@@ -187,10 +199,8 @@
                 class="fa fa-file-text"
               ></i>
               <p>
-                No hay entregas del alumno:
-                {{
-                  alumnoSeleccionado.nombre + " " + alumnoSeleccionado.apellido
-                }}
+                No hay entregas del usuario:
+                {{ selectedUser.username }}
               </p>
             </div>
           </div>
@@ -198,9 +208,7 @@
       </div>
       <div
         class="card"
-        *ngIf="
-          alumnoSeleccionado == undefined && archivoSeleccionado == undefined
-        "
+        *ngIf="selectedUser == undefined && archivoSeleccionado == undefined"
       >
         <div class="card-block">
           <div
diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts
index a41d57e..11c633b 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
@@ -1,8 +1,8 @@
-import { Component } from "@angular/core";
+import { ChangeDetectorRef, Component } from "@angular/core";
 import { Router } from "@angular/router";
 
 import { Archivo, Evaluacion } from "../../shared/objects/archivo-types";
-import { Grupo } from "../../shared/objects/grupo";
+import { Group } from "../../shared/objects/grupo";
 import { Usuario } from "../../shared/objects/usuario";
 import { AuthenticationService } from "../../shared/services/authentication.service";
 import { HaskellService } from "../../shared/services/haskell.service";
@@ -10,6 +10,10 @@ import { SessionService } from "../../shared/services/session.service";
 import { NgbPopoverConfig, NgbPopover } from "@ng-bootstrap/ng-bootstrap";
 import { NotificacionService } from "../../shared/services/notificacion.service";
 import { TranslateService } from "@ngx-translate/core";
+import { GroupService } from "../../shared/services/group.service";
+import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
+import { CreateGroupModal } from "../../shared/components/create-group-modal/create-group-modal.component";
+import { ManageGroupUsersModal } from "app/shared/components/manage-group-users-modal/manage-group-users-modal.component";
 
 @Component({
   selector: "grupos",
@@ -17,10 +21,10 @@ import { TranslateService } from "@ngx-translate/core";
 })
 export class GruposComponent {
   archivos: Archivo[] = [];
-  grupos: Grupo[] = [];
-  grupoSeleccionado: Grupo = undefined;
+  grupos: Group[] = [];
+  grupoSeleccionado: Group = undefined;
 
-  alumnoSeleccionado: Usuario = undefined;
+  selectedUser: Usuario = undefined;
 
   archivoSeleccionado: Archivo = undefined;
 
@@ -33,6 +37,8 @@ export class GruposComponent {
   configCodeMirror = JSON.parse(localStorage.getItem("codeMirrorConfig"));
   translateService: any;
 
+  createModalRef: any;
+
   // - - - - - - - - - - - -  Modal show confirm  - - - - - - - - - - - -
   /**
    * Con `true` se renderiza el modal de calificar entrega.
@@ -66,7 +72,10 @@ export class GruposComponent {
     private haskellService: HaskellService,
     private notifService: NotificacionService,
     private sessionService: SessionService,
-    public translate: TranslateService
+    public translate: TranslateService,
+    public groupService: GroupService,
+    private modalService: NgbModal,
+    private changeDetectorRef: ChangeDetectorRef
   ) {
     this.translateService = translate;
     this.directorioActual = {};
@@ -91,6 +100,8 @@ export class GruposComponent {
 
   ngOnInit() {
     // let cedula = this.authService.getUser().cedula; // cedula
+    this.confirmGroupCreation = this.confirmGroupCreation.bind(this);
+    this.confirmGroupUpdated = this.confirmGroupUpdated.bind(this);
     this.loading = true;
     // this.haskellService.getGrupos(cedula).subscribe(
     //   (grupos) => {
@@ -100,6 +111,19 @@ export class GruposComponent {
     //   },
     //   (error) => console.log(error)
     // );
+    this.loadGroups();
+  }
+
+  loadGroups(next?: () => void) {
+    this.groupService.getGroups().subscribe(
+      (grupos) => {
+        this.grupos = grupos.groups;
+        // this.ordenarGrupos();
+        this.loading = false;
+        if (next) next();
+      },
+      (error) => console.log(error)
+    );
   }
 
   showNotification(type: "error" | "success" | "warning", traslation: string) {
@@ -116,9 +140,9 @@ export class GruposComponent {
   }
 
   ordenarArchivos() {
-    this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort(
-      this.ordenarAlph
-    );
+    // this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort(
+    //   this.ordenarAlph
+    // );
   }
 
   //ordeno los archivos del alumno (los archivos entregados.)
@@ -150,9 +174,9 @@ export class GruposComponent {
   }
 
   ordenarAlumnos() {
-    this.grupoSeleccionado.alumnos = this.grupoSeleccionado.alumnos.sort(
-      this.ordenarAlumnosF
-    );
+    // this.grupoSeleccionado.alumnos = this.grupoSeleccionado.alumnos.sort(
+    //   this.ordenarAlumnosF
+    // );
   }
 
   seleccionarGrupo(grupo) {
@@ -160,18 +184,18 @@ export class GruposComponent {
     this.ordenarAlumnos();
     this.ordenarArchivos();
     this.archivoSeleccionado = undefined;
-    this.alumnoSeleccionado = undefined;
+    this.selectedUser = undefined;
   }
 
   desseleccionarGrupo() {
     this.grupoSeleccionado = undefined;
     this.archivoSeleccionado = undefined;
-    this.alumnoSeleccionado = undefined;
+    this.selectedUser = undefined;
   }
 
-  seleccionarAlumno(alumno) {
+  seleccionarUser(alumno) {
     if (!(typeof alumno === "undefined")) {
-      this.alumnoSeleccionado = alumno;
+      this.selectedUser = alumno;
       this.ordenarArchivosAlumno();
       this.archivoSeleccionado = undefined;
     }
@@ -179,13 +203,13 @@ export class GruposComponent {
 
   seleccionarArchivo(archivo) {
     this.archivoSeleccionado = archivo;
-    this.alumnoSeleccionado = undefined;
+    this.selectedUser = undefined;
     this.tipoArchivo = "compartido";
   }
 
   seleccionarEntrega(entrega) {
     this.archivoSeleccionado = entrega;
-    this.alumnoSeleccionado = undefined;
+    this.selectedUser = undefined;
     this.tipoArchivo = "entrega";
   }
 
@@ -195,6 +219,60 @@ export class GruposComponent {
     this.modalQualifyDeliveryOpened = true;
   }
 
+  openCreateGroupModal() {
+    this.createModalRef = this.modalService.open(CreateGroupModal);
+
+    this.createModalRef.componentInstance.modalTitle = "Nuevo Grupo";
+    this.createModalRef.componentInstance.confirmGroupCreation =
+      this.confirmGroupCreation;
+  }
+
+  confirmGroupCreation(nombre: string) {
+    if (nombre == undefined || nombre == "") {
+      this.showNotification("error", "i18n.warning.group.invalidName");
+      return;
+    }
+
+    /** Expresión regular para chequear que el nombre esté empiece con mayúscula */
+    var regex = /^[A-Z]/;
+    if (!regex.test(nombre)) {
+      this.showNotification("error", "i18n.warning.group.capitalLetter");
+      return;
+    }
+
+    this.groupService.createGroup(nombre).subscribe(
+      (data) => {
+        this.loadGroups();
+
+        this.grupoSeleccionado = data.body["group"];
+        this.createModalRef.close();
+      },
+      (error) => {
+        this.notifService.error(error);
+      }
+    );
+  }
+
+  confirmGroupUpdated() {
+    this.loadGroups(() => {
+      this.grupoSeleccionado = this.grupos.find(
+        (grupo) => grupo.id == this.grupoSeleccionado.id
+      );
+    });
+  }
+
+  openManageGroupUsersModal() {
+    this.createModalRef = this.modalService.open(ManageGroupUsersModal);
+
+    this.createModalRef.componentInstance.modalTitle =
+      "Administrar usuarios del grupo";
+    this.createModalRef.componentInstance.groupName =
+      this.grupoSeleccionado.name;
+    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id;
+    this.createModalRef.componentInstance.confirmGroupUpdated =
+      this.confirmGroupUpdated;
+  }
+
   confirmFileQualify(event: CustomEvent<any>) {
     const { descripcion, estado, nota } = event.detail;
 
@@ -234,17 +312,17 @@ export class GruposComponent {
   }
 
   esArchivoGrupo() {
-    if (
-      this.archivoSeleccionado &&
-      this.grupoSeleccionado &&
-      this.grupoSeleccionado.archivos.some(
-        (arch) => arch.id == this.archivoSeleccionado.id
-      )
-    ) {
-      return true;
-    } else {
-      return false;
-    }
+    // if (
+    //   this.archivoSeleccionado &&
+    //   this.grupoSeleccionado &&
+    //   this.grupoSeleccionado.archivos.some(
+    //     (arch) => arch.id == this.archivoSeleccionado.id
+    //   )
+    // ) {
+    //   return true;
+    // } else {
+    //   return false;
+    // }
   }
 
   cargarArchivoCompartido() {
diff --git a/Frontend Angular 4/src/app/layout/layout.module.ts b/Frontend Angular 4/src/app/layout/layout.module.ts
index 5159725..3e4f59a 100755
--- a/Frontend Angular 4/src/app/layout/layout.module.ts	
+++ b/Frontend Angular 4/src/app/layout/layout.module.ts	
@@ -10,6 +10,7 @@ import { AuthenticationService } from "../shared/services/authentication.service
 import { HaskellService } from "../shared/services/haskell.service";
 import { DocumentService } from "../shared/services/document.service";
 import { FileService } from "../shared/services/file.service";
+import { GroupService } from "../shared/services/group.service";
 import { LtCodemirrorModule } from "lt-codemirror";
 import { CodemirrorModule } from "@ctrl/ngx-codemirror";
 import { NotificacionModule } from "../notificacion/notificacion.module";
@@ -34,6 +35,7 @@ import { TitleCaseModule } from "../shared/modules/titlecase.module";
     HaskellService,
     DocumentService,
     FileService,
+    GroupService,
   ],
 })
 export class LayoutModule {}
diff --git a/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html
new file mode 100644
index 0000000..b49e958
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.html	
@@ -0,0 +1,33 @@
+<div class="modal-header">
+  <span class="modal-title font-bold" id="modal-title">{{ modalTitle }}</span>
+  <button
+    aria-label="Cerrar diálogo"
+    (click)="modal.dismiss('Close create group modal')"
+    part="close-button"
+    class="close-button fa fa-close"
+    type="button"
+  ></button>
+</div>
+<div class="modal-body">
+  <div class="flex flex-col space-between">
+    <label for="document-title">Group Name</label>
+    <input
+      type="text"
+      [(ngModel)]="groupName"
+      placeholder="Enter group name"
+      ngbAutofocus
+      id="group-name"
+      class="name-input border-opacity-25 border-black border-2 rounded w-full px-2 py-3 text-base focus-visible:border-focused-blue focus:outline-transparent"
+    />
+  </div>
+  <div class="flex justify-end mt-4">
+    <button
+      (click)="confirmGroupCreation(groupName)"
+      [disabled]="!groupName"
+      class="btn btn-primary"
+      slot="primary"
+    >
+      Crear Grupo
+    </button>
+  </div>
+</div>
diff --git a/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts
new file mode 100644
index 0000000..05cba8d
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/create-group-modal/create-group-modal.component.ts	
@@ -0,0 +1,38 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { MFile } from "../../objects/archivo-types";
+import { Group } from "../../objects/grupo";
+import { FileService } from "../../services/file.service";
+import { GroupService } from "../../services/group.service";
+
+@Component({
+  selector: "app-create-group-modal",
+  templateUrl: "./create-group-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class CreateGroupModal implements OnInit {
+  @Input() modalTitle: string;
+  @Input() groupName: string;
+
+  /**
+   * Se dispara cuando se confirma la creación del archivo en el directorio actual.
+   */
+  @Input() confirmGroupCreation: (title: string) => void;
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupService: GroupService
+  ) {}
+
+  ngOnInit(): void {
+    this.groupName = `${this.groupName || ""}`;
+    this.modalTitle = `${this.modalTitle}`;
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts
index 381d0ed..0612484 100755
--- a/Frontend Angular 4/src/app/shared/components/index.ts	
+++ b/Frontend Angular 4/src/app/shared/components/index.ts	
@@ -8,3 +8,5 @@ export * from "./share-file-modal/share-file-modal.component";
 export * from "./chip-input/chip-input.component";
 export * from "./select/select.component";
 export * from "./add-users-modal/add-users-modal.component";
+export * from "./create-group-modal/create-group-modal.component";
+export * from "./manage-group-users-modal/manage-group-users-modal.component";
diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html
new file mode 100644
index 0000000..7608fa9
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html	
@@ -0,0 +1,49 @@
+<app-add-users-modal
+  [modalTitle]="modalTitle"
+  [(chips)]="chips"
+  [newUserRolesEnum]="newUsersPermissionsEnum"
+  [newUserRole]="newUsersPermission"
+  (close)="modal.dismiss('Close manage user\'s group modal')"
+  [users]="usersCopy"
+  [rolesField]="groupRole"
+  [requiredRole]="'Owner'"
+  [save]="save"
+></app-add-users-modal>
+
+<!-- <div class="modal-header">
+  <span class="modal-title font-bold" id="modal-title">{{ modalTitle }}</span>
+  <button
+    aria-label="Cerrar diálogo"
+    (click)="modal.dismiss('Close share file modal')"
+    part="close-button"
+    class="close-button fa fa-close"
+    type="button"
+  ></button>
+</div>
+<div class="modal-body">
+  <div class="flex flex-col justify-between w-full">
+    <label for="new-users">New Users</label>
+    <div class="flex justify-between w-full items-center">
+      <app-chip-input
+        [(chips)]="chips"
+        [placeholder]="'Add a new user'"
+        [id]="'new-users'"
+        class="w-full"
+      ></app-chip-input>
+      <app-select
+        [options]="newUsersPermissionsEnum"
+        [(selectedOption)]="newUsersPermission"
+      ></app-select>
+    </div>
+  </div>
+  <div class="flex justify-end">
+    <button
+      (click)="confirmFileCreation(documentTitle, currentFolder.id)"
+      [disabled]="!documentTitle || !currentFolder"
+      class="btn btn-primary"
+      slot="primary"
+    >
+      Guardar
+    </button>
+  </div>
+</div> -->
diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts
new file mode 100644
index 0000000..22303ff
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts	
@@ -0,0 +1,74 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+  ChangeDetectorRef,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { User } from "../../objects/archivo-types";
+import { Group } from "../../objects/grupo";
+import { GroupService } from "../../services/group.service";
+
+@Component({
+  selector: "app-manage-group-users-modal",
+  templateUrl: "./manage-group-users-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ManageGroupUsersModal implements OnInit {
+  @Input() modalTitle: string;
+  @Input() groupName: string;
+  @Input() groupId: number;
+  group: Group;
+  newUsersPermissionsEnum: string[] = [
+    "Owner",
+    "Manager",
+    "Moderator",
+    "Member",
+  ];
+  newUsersPermission = this.newUsersPermissionsEnum[3];
+  users: User[] = [];
+  usersCopy: User[] = [];
+  groupRole = "role";
+  /**
+   * Se dispara cuando se quiere administrar los usuarios del grupo
+   */
+  @Input() confirmGroupUpdated: () => void;
+
+  // Hacer get del grupo al inicializar el modal
+  // Capaz mostrar un loader mientras ocurre
+  // Con eso mostrar los usuarios que tienen acceso al grupo y sus permisos
+  // Y usar el group_id para hacer el share
+
+  chips: string[] = [];
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupService: GroupService,
+    private changeDetectorRef: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+    this.groupService.getGroup(this.groupId).subscribe((groupResponse) => {
+      this.group = groupResponse.group;
+      this.users = groupResponse.group.users;
+      this.usersCopy = [...this.users];
+      this.changeDetectorRef.detectChanges();
+    });
+    this.groupName = `${this.groupName}`;
+    this.modalTitle = `${this.modalTitle}`;
+    this.save = this.save.bind(this);
+  }
+
+  save(userData: { id?: number; username: string; role: string }[]): void {
+    this.groupService.updateGroup(this.group.id, userData).subscribe({
+      next: (_) => {
+        this.confirmGroupUpdated();
+        this.modal.close();
+      },
+      error: (error) => console.log(error),
+    });
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts
index f93b27e..1a9848d 100755
--- a/Frontend Angular 4/src/app/shared/config.ts	
+++ b/Frontend Angular 4/src/app/shared/config.ts	
@@ -50,3 +50,9 @@ export const CREATE_DOCUMENT = SERVER + "/api/v2/documents";
 export const UPDATE_DOCUMENT = SERVER + "/api/v2/documents";
 
 export const DESTROY_AUX_DOCUMENT = SERVER + "/api/v2/aux_documents";
+
+export const GET_GROUP = SERVER + "/api/v2/groups/:id";
+export const GET_GROUPS = SERVER + "/api/v2/groups";
+export const CREATE_GROUP = SERVER + "/api/v2/groups";
+export const UPDATE_GROUP = SERVER + "/api/v2/groups";
+export const DELETE_GROUP = SERVER + "/api/v2/groups";
diff --git a/Frontend Angular 4/src/app/shared/objects/grupo.ts b/Frontend Angular 4/src/app/shared/objects/grupo.ts
index 9c9679e..157e4e4 100755
--- a/Frontend Angular 4/src/app/shared/objects/grupo.ts	
+++ b/Frontend Angular 4/src/app/shared/objects/grupo.ts	
@@ -1,27 +1,9 @@
-import { Archivo } from "./archivo-types";
-import { Usuario } from "./usuario";
+import { MFile } from "./archivo-types";
+import { User } from "./archivo-types";
 
-export class Grupo {
-  anio: number;
-  grado: number;
-  grupo: string;
-  liceoId: number;
-  archivos: Archivo[];
-  alumnos: Usuario[];
-
-  constructor(
-    anio: number,
-    grado: number,
-    grupo: string,
-    liceoId: number,
-    archivos: Archivo[],
-    alumnos: Usuario[]
-  ) {
-    this.anio = anio;
-    this.grado = grado;
-    this.grupo = grupo;
-    this.liceoId = liceoId;
-    this.archivos = archivos;
-    this.alumnos = alumnos;
-  }
+export class Group {
+  id: number;
+  name: string;
+  files?: MFile[];
+  users?: User[];
 }
diff --git a/Frontend Angular 4/src/app/shared/services/document.service.ts b/Frontend Angular 4/src/app/shared/services/document.service.ts
index e1df524..9883de7 100644
--- a/Frontend Angular 4/src/app/shared/services/document.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/document.service.ts	
@@ -13,7 +13,7 @@ import {
   MFile,
   MDocument,
 } from "../objects/archivo-types";
-import { Grupo } from "../objects/grupo";
+import { Group } from "../objects/grupo";
 
 import { SERVER } from "../config";
 import { TranslateService } from "@ngx-translate/core";
@@ -262,14 +262,14 @@ export class DocumentService {
   }
 
   // Legacy method, need to check if it's still used
-  getGrupos(cedula: string): Observable<Grupo[]> {
+  getGrupos(cedula: string): Observable<Group[]> {
     let headers = this.getHeaders();
     let params: HttpParams = new HttpParams();
     params = params.set("cedula", cedula);
     let httpOptions = { headers: headers, params: params };
 
     return this.http
-      .get<Grupo[]>(SERVER + "/servicios/grupo", httpOptions)
+      .get<Group[]>(SERVER + "/servicios/grupo", httpOptions)
       .pipe(catchError(this.handleError));
   }
 
diff --git a/Frontend Angular 4/src/app/shared/services/file.service.ts b/Frontend Angular 4/src/app/shared/services/file.service.ts
index b900b57..f1d56c3 100644
--- a/Frontend Angular 4/src/app/shared/services/file.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/file.service.ts	
@@ -8,7 +8,6 @@ import {
   HttpResponse,
 } from "@angular/common/http";
 import { Archivo, Evaluacion, MFile } from "../objects/archivo-types";
-import { Grupo } from "../objects/grupo";
 
 import { SERVER } from "../config";
 import { TranslateService } from "@ngx-translate/core";
diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts
new file mode 100644
index 0000000..bc32858
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/services/group.service.ts	
@@ -0,0 +1,233 @@
+import { throwError as observableThrowError, Observable } from "rxjs";
+import { Injectable } from "@angular/core";
+import { Router } from "@angular/router";
+import {
+  HttpClient,
+  HttpHeaders,
+  HttpParams,
+  HttpResponse,
+} from "@angular/common/http";
+import { Archivo, Evaluacion, MFile } from "../objects/archivo-types";
+import { Group } from "../objects/grupo";
+
+import { SERVER } from "../config";
+import { TranslateService } from "@ngx-translate/core";
+import { AuthenticationService } from "./authentication.service";
+import { catchError } from "rxjs/operators";
+import {
+  CREATE_GROUP,
+  UPDATE_GROUP,
+  DELETE_GROUP,
+  GET_GROUP,
+  GET_GROUPS,
+} from "../config";
+
+@Injectable()
+export class GroupService {
+  translateService: any;
+
+  /**
+   * Creates a new GroupService with the injected HttpClient.
+   * @param {HttpClient} http - The injected HttpClient.
+   * @constructor
+   */
+  constructor(
+    private http: HttpClient,
+    private router: Router,
+    private authService: AuthenticationService,
+    public translate: TranslateService
+  ) {
+    this.translateService = translate;
+  }
+
+  private getHeaders() {
+    return new HttpHeaders({
+      "Content-Type": "application/json",
+      Authorization: "Bearer " + this.authService.getToken(),
+    });
+  }
+
+  private postHeaders() {
+    return new HttpHeaders({
+      "Content-Type": "application/json",
+      Accept: "application/json",
+      "Access-Control-Allow-Headers": "Content-Type",
+    });
+  }
+
+  getGroup(id: number): Observable<{ group: Group }> {
+    return this.http
+      .get<{ group: Group }>(GET_GROUP.replace(/:id/g, String(id)), {
+        headers: this.getHeaders(),
+      })
+      .pipe(catchError(this.handleError));
+  }
+
+  getGroups(): Observable<{ groups: Group[] }> {
+    return this.http
+      .get<{ groups: Group[] }>(GET_GROUPS, {
+        headers: this.getHeaders(),
+      })
+      .pipe(catchError(this.handleError));
+  }
+
+  // getFilesList(options = {}): Observable<{ files: MFile[] }> {
+  //   const siblingsOfId = options["siblingsOfId"];
+  //   return this.http
+  //     .get<{ files: MFile[] }>(GET_FILES_LISTS, {
+  //       headers: this.getHeaders(),
+  //       params: siblingsOfId
+  //         ? new HttpParams().set("siblings_of_id", siblingsOfId)
+  //         : null,
+  //     })
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  fileToArchivo(file: MFile, parent: Archivo = null): Archivo {
+    let archivo = new Archivo();
+    archivo;
+    archivo.id = file.id;
+    archivo.documentId = file.document_id;
+    archivo.nombre = file.title;
+    archivo.contenido = "";
+    archivo.fechaCreacion = file.created_at;
+    archivo.cedulaCreador = file.users[0]?.email;
+    archivo.editable = true;
+    archivo.padreId =
+      file.ancestry === "/"
+        ? -1
+        : parseInt(file.ancestry.slice(0, -1).split("/").pop());
+    archivo.archivos = [];
+    archivo.parent = parent;
+    archivo.archivoOrigenId = null;
+    archivo.directorio = file.directory;
+    archivo.estado = "activo";
+    archivo.eliminado = false;
+    archivo.evaluacion = null;
+    archivo.users = file.users;
+    archivo.role = file.role;
+
+    if (!!file.children) {
+      archivo.archivos = file.children.map<Archivo>((child) => {
+        return this.fileToArchivo(child, archivo);
+      });
+    }
+
+    return archivo;
+  }
+
+  createGroup(name: string): Observable<HttpResponse<Object>> {
+    return this.http.post<Object>(
+      CREATE_GROUP,
+      {
+        group: {
+          name: name,
+        },
+      },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  archivoToFile(archivo: Archivo, parent: MFile = null): MFile {
+    let file = {
+      id: archivo.id,
+      document_id: archivo.documentId,
+      title: archivo.nombre,
+      created_at: archivo.fechaCreacion,
+      parent_id: archivo.padreId,
+      parent: parent,
+      children: [],
+      directory: archivo.directorio,
+      role: archivo.role,
+    };
+
+    file.children = archivo.archivos.map<MFile>((archivoHijo) => {
+      return this.archivoToFile(archivoHijo, file);
+    });
+
+    return file;
+  }
+
+  completeFiles(file: MFile, parent: MFile = null): MFile {
+    file.parent = parent;
+    file.children.forEach((child) => {
+      this.completeFiles(child, file);
+    });
+
+    return file;
+  }
+
+  filterOnlyDirectories(file: MFile, parent: MFile = null): MFile {
+    let newFile = {
+      id: file.id,
+      document_id: file.document_id,
+      title: file.title,
+      created_at: file.created_at,
+      parent_id: file.parent_id,
+      parent: parent,
+      children: [],
+      directory: file.directory,
+    };
+
+    newFile.children = file.children
+      .filter((child) => child.directory)
+      .map((child) => this.filterOnlyDirectories(child, newFile));
+
+    return newFile;
+  }
+
+  updateGroup(group_id, users): Observable<HttpResponse<Object>> {
+    const update_params = {};
+    if (users) {
+      users = users.map((user) => {
+        return { ...user, role: user.role.toLowerCase() };
+      });
+      update_params["users"] = users;
+    }
+
+    return this.http.patch<Object>(
+      `${UPDATE_GROUP}/${group_id}`,
+      { group: update_params },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  // deleteFile(file_id): Observable<HttpResponse<Object>> {
+  //   return this.http.delete<Object>(`${DELETE_FILE}/${file_id}`, {
+  //     observe: "response",
+  //     headers: this.postHeaders(),
+  //   });
+  // }
+
+  // destroyAuxFile(): void {
+  //   this.http.delete<Object>(DESTROY_AUX_DOCUMENT, {
+  //     headers: this.postHeaders(),
+  //   });
+  // }
+
+  /**
+   * Handle HTTP error
+   */
+  private handleError(error: any) {
+    if (error.status == 401) {
+      this.translateService
+        .get("i18n.code")
+        .subscribe((res) => this.router.navigate(["/" + res + "/login"]));
+    }
+    // In a real world app, we might use a remote logging infrastructure
+    // We'd also dig deeper into the error to get a better message
+    let errMsg = error.message
+      ? error.message
+      : error.status
+      ? `${error.status} - ${error.statusText}`
+      : "Server error";
+    console.error(errMsg); // log to console instead
+    return observableThrowError(errMsg);
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/services/haskell.service.ts b/Frontend Angular 4/src/app/shared/services/haskell.service.ts
index 5702c47..7d086ca 100755
--- a/Frontend Angular 4/src/app/shared/services/haskell.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/haskell.service.ts	
@@ -3,7 +3,7 @@ import { Injectable } from "@angular/core";
 import { Router } from "@angular/router";
 import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http";
 import { Archivo, Evaluacion } from "../objects/archivo-types";
-import { Grupo } from "../objects/grupo";
+import { Group } from "../objects/grupo";
 
 import { SERVER } from "../config";
 import { TranslateService } from "@ngx-translate/core";
@@ -145,14 +145,14 @@ export class HaskellService {
       .pipe(catchError(this.handleError));
   }
 
-  getGrupos(cedula: string): Observable<Grupo[]> {
+  getGrupos(cedula: string): Observable<Group[]> {
     let headers = this.getHeaders();
     let params: HttpParams = new HttpParams();
     params = params.set("cedula", cedula);
     let httpOptions = { headers: headers, params: params };
 
     return this.http
-      .get<Grupo[]>(SERVER + "/servicios/grupo", httpOptions)
+      .get<Group[]>(SERVER + "/servicios/grupo", httpOptions)
       .pipe(catchError(this.handleError));
   }
 
diff --git a/Frontend Angular 4/src/app/shared/services/session.service.ts b/Frontend Angular 4/src/app/shared/services/session.service.ts
index 697c1a7..4ce6bfb 100755
--- a/Frontend Angular 4/src/app/shared/services/session.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/session.service.ts	
@@ -1,6 +1,6 @@
 import { Injectable } from "@angular/core";
 import { Archivo, MFile, MDocument } from "../objects/archivo-types";
-import { Grupo } from "../objects/grupo";
+import { Group } from "../objects/grupo";
 
 @Injectable()
 export class SessionService {
@@ -11,7 +11,7 @@ export class SessionService {
   directorioActual: any;
   archivosCompartidos: any;
   file: MFile;
-  grupos: Grupo[];
+  grupos: Group[];
   currentDirectoryDocuments: MDocument[];
 
   public setArchivo(archivo) {
@@ -40,7 +40,7 @@ export class SessionService {
     return this.file;
   }
 
-  public setGrupos(grupos: Grupo[]) {
+  public setGrupos(grupos: Group[]) {
     this.grupos = grupos;
   }
 
diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts
index 73c823e..8fcf224 100644
--- a/Frontend Angular 4/src/app/shared/shared.module.ts	
+++ b/Frontend Angular 4/src/app/shared/shared.module.ts	
@@ -4,8 +4,10 @@ import { TitleCaseModule } from "../shared/modules/titlecase.module";
 import { I18nModule } from "../shared/modules/translate/i18n.module";
 import { CommonModule } from "@angular/common";
 import { CreateFileModal } from "./components";
+import { CreateGroupModal } from "./components";
 import { ImportFileModal } from "./components";
 import { ShareFileModal } from "./components";
+import { ManageGroupUsersModal } from "./components";
 import { FormsModule } from "@angular/forms";
 import { FolderInput } from "./components";
 import { SelectComponent } from "./components";
@@ -30,9 +32,11 @@ import { AddUsersModal } from "./components";
   declarations: [
     MainButtonComponent,
     CreateFileModal,
+    CreateGroupModal,
     FolderInput,
     ImportFileModal,
     ShareFileModal,
+    ManageGroupUsersModal,
     ChipInputComponent,
     SelectComponent,
     AddUsersModal,
diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json
index 75eebe9..af85514 100755
--- a/Frontend Angular 4/src/assets/i18n/en.json	
+++ b/Frontend Angular 4/src/assets/i18n/en.json	
@@ -134,6 +134,9 @@
         "success": "Please check your email",
         "error": "An error occurred while sending the email"
       },
+      "group": {
+        "manageGroupUsers": "Manage group users"
+      },
       "updatePassword": {
         "success": "Your password has been updated, you will be redirected shortly",
         "error": "An error occurred while updating your password",
@@ -159,7 +162,9 @@
         "invalidName": "Invalid file name."
       },
       "group": {
-        "select": "Select a group"
+        "select": "Select a group",
+        "capitalLetter": "Group name must start with upper case.",
+        "invalidName": "Invalid group name."
       }
     },
     "shortcuts": {
diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json
index 55e091b..2d195ce 100755
--- a/Frontend Angular 4/src/assets/i18n/es.json	
+++ b/Frontend Angular 4/src/assets/i18n/es.json	
@@ -134,6 +134,9 @@
         "success": "Favor busque en su correo el email",
         "error": "Error al enviar el email"
       },
+      "group": {
+        "manageGroupUsers": "Administrar usuarios del grupo"
+      },
       "updatePassword": {
         "success": "Su contraseña ha sido actualizada, será redirigido en 5 segundos",
         "error": "Ha ocurrido un error al actualizar su contraseña",
@@ -159,7 +162,9 @@
         "invalidName": "Nombre de archivo inválido."
       },
       "group": {
-        "select": "Seleccione un grupo"
+        "select": "Seleccione un grupo",
+        "capitalLetter": "Nombre del grupo debe iniciar con mayúscula.",
+        "invalidName": "Nombre de grupo inválido."
       }
     },
     "shortcuts": {
-- 
GitLab


From 530419a7561b0ec9548df43131863d2e4570454c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Mon, 2 Dec 2024 18:27:07 -0300
Subject: [PATCH 2/9] group wip destroy grups complete

---
 .../app/layout/grupos/grupos.component.html   | 16 ++++-
 .../src/app/layout/grupos/grupos.component.ts | 59 +++++++++++++++++++
 .../action-confirmation-modal.component.html  | 27 +++++++++
 .../action-confirmation-modal.component.ts    | 15 +++++
 .../destroy-group-modal.component.html        |  6 ++
 .../destroy-group-modal.component.ts          | 53 +++++++++++++++++
 .../src/app/shared/components/index.ts        |  2 +
 .../src/app/shared/objects/grupo.ts           |  1 +
 .../src/app/shared/services/group.service.ts  | 13 ++--
 .../src/app/shared/shared.module.ts           |  5 +-
 Frontend Angular 4/src/app/utils.ts           |  8 +++
 Frontend Angular 4/src/assets/i18n/en.json    |  3 +-
 Frontend Angular 4/src/assets/i18n/es.json    |  3 +-
 13 files changed, 200 insertions(+), 11 deletions(-)
 create mode 100644 Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html
index 407b509..9c374cb 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
@@ -77,6 +77,7 @@
                   <i class="fa fa-arrow-up"></i>
                 </button>
                 <button
+                  *ngIf="roleCanManageUsers()"
                   class="btn btn-sm btn-secondary pull-right"
                   style="cursor: pointer; margin-top: -35px; margin-right: 36px"
                   (click)="openManageGroupUsersModal()"
@@ -88,9 +89,22 @@
                 >
                   <i class="fa fa-users"></i>
                 </button>
+                <button
+                  *ngIf="roleCanDestroyGroup()"
+                  class="btn btn-sm btn-secondary pull-right"
+                  style="cursor: pointer; margin-top: -35px; margin-right: 71px"
+                  (click)="openDestroyGroupModal()"
+                  ngbPopover="{{
+                    'i18n.msg.group.destroyGroupTooltip' | translate | titleCase
+                  }}"
+                  placement="bottom"
+                  triggers="mouseenter:mouseleave"
+                >
+                  <i class="fa fa-close"></i>
+                </button>
                 <p
                   class="pull-right"
-                  style="margin-top: -34px; margin-right: 93px"
+                  style="margin-top: -34px; margin-right: 128px"
                 >
                   {{ grupoSeleccionado.name }}
                 </p>
diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts
index 11c633b..d480280 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
@@ -11,9 +11,11 @@ import { NgbPopoverConfig, NgbPopover } from "@ng-bootstrap/ng-bootstrap";
 import { NotificacionService } from "../../shared/services/notificacion.service";
 import { TranslateService } from "@ngx-translate/core";
 import { GroupService } from "../../shared/services/group.service";
+import { roleCanManageGroupUsers, roleCanDestroyGroup } from "app/utils";
 import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
 import { CreateGroupModal } from "../../shared/components/create-group-modal/create-group-modal.component";
 import { ManageGroupUsersModal } from "app/shared/components/manage-group-users-modal/manage-group-users-modal.component";
+import { DestroyGroupModal } from "app/shared";
 
 @Component({
   selector: "grupos",
@@ -102,6 +104,8 @@ export class GruposComponent {
     // let cedula = this.authService.getUser().cedula; // cedula
     this.confirmGroupCreation = this.confirmGroupCreation.bind(this);
     this.confirmGroupUpdated = this.confirmGroupUpdated.bind(this);
+    this.confirmGroupDestroyed = this.confirmGroupDestroyed.bind(this);
+
     this.loading = true;
     // this.haskellService.getGrupos(cedula).subscribe(
     //   (grupos) => {
@@ -213,6 +217,61 @@ export class GruposComponent {
     this.tipoArchivo = "entrega";
   }
 
+  roleCanManageUsers(): boolean {
+    return (
+      !!this.grupoSeleccionado &&
+      roleCanManageGroupUsers(this.grupoSeleccionado.role)
+    );
+  }
+
+  roleCanDestroyGroup(): boolean {
+    return (
+      !!this.grupoSeleccionado &&
+      roleCanDestroyGroup(this.grupoSeleccionado.role)
+    );
+  }
+
+  openDestroyGroupModal() {
+    if (!this.grupoSeleccionado) {
+      this.showNotification("warning", "i18n.warning.group.noSelected");
+      return;
+    }
+
+    if (!roleCanDestroyGroup(this.grupoSeleccionado.role)) {
+      this.showNotification("error", "i18n.warning.group.noPermission");
+      return;
+    }
+
+    this.createModalRef = this.modalService.open(DestroyGroupModal);
+
+    this.createModalRef.componentInstance.groupName =
+      this.grupoSeleccionado.name;
+    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id;
+    this.createModalRef.componentInstance.confirmGroupDestroyed =
+      this.confirmGroupDestroyed;
+
+    // this.translateService
+    //   .get("i18n.warning.group.destroy", {
+    //     group: this.grupoSeleccionado.name,
+    //   })
+    //   .subscribe((res) => {
+    //     if (confirm(res)) {
+    //       this.groupService.destroyGroup(this.grupoSeleccionado.id).subscribe(
+    //         (data) => {
+    //           this.loadGroups();
+    //           this.grupoSeleccionado = undefined;
+    //         },
+    //         (error) => {
+    //           this.notifService.error(error);
+    //         }
+    //       );
+  }
+
+  confirmGroupDestroyed() {
+    this.desseleccionarGrupo();
+    this.loadGroups();
+  }
+
   mostrarModalCalificarEntrega() {
     // Mostrar el modal
     this.modalQualifyDelivery = true;
diff --git a/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html
new file mode 100644
index 0000000..f80e61c
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.html	
@@ -0,0 +1,27 @@
+<div class="modal-header">
+  <span class="modal-title font-bold" id="modal-title">{{ title }}</span>
+  <button
+    aria-label="Cerrar diálogo"
+    (click)="cancelAction()"
+    part="close-button"
+    class="close-button fa fa-close"
+    type="button"
+  ></button>
+</div>
+<div class="modal-body">
+  <div class="mb-3">
+    {{ message }}
+  </div>
+  <div class="flex justify-end">
+    <button
+      (click)="cancelAction()"
+      class="btn btn-secondary mr-3"
+      slot="secondary"
+    >
+      Cancelar
+    </button>
+    <button (click)="confirmAction()" class="btn btn-primary" slot="primary">
+      Confirmar
+    </button>
+  </div>
+</div>
diff --git a/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts
new file mode 100644
index 0000000..47b26b3
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/action-confirmation-modal/action-confirmation-modal.component.ts	
@@ -0,0 +1,15 @@
+import { Component, ChangeDetectionStrategy, Input } from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+@Component({
+  selector: "app-action-confirmation-modal",
+  templateUrl: "./action-confirmation-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ActionConfirmationModal {
+  @Input() title: string;
+  @Input() message: string;
+  @Input() confirmAction: () => void;
+  @Input() cancelAction: () => void;
+}
diff --git a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html
new file mode 100644
index 0000000..0451b0c
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.html	
@@ -0,0 +1,6 @@
+<app-action-confirmation-modal
+  [title]="title"
+  [message]="message"
+  [confirmAction]="destroyGroup"
+  [cancelAction]="cancel"
+></app-action-confirmation-modal>
diff --git a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts
new file mode 100644
index 0000000..62e1962
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts	
@@ -0,0 +1,53 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { GroupService } from "../../services/group.service";
+
+@Component({
+  selector: "app-destroy-group-modal",
+  templateUrl: "./destroy-group-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DestroyGroupModal implements OnInit {
+  @Input() groupName: string;
+  @Input() groupId: number;
+  /**
+   * Se dispara cuando se quiere administrar los usuarios del grupo
+   */
+  @Input() confirmGroupDestroyed: () => void;
+  title = "Destruir Grupo";
+  message: string;
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupService: GroupService
+  ) {}
+
+  ngOnInit(): void {
+    this.groupName = `${this.groupName}`;
+
+    this.message = `¿Estás seguro de que deseas destruir el grupo ${this.groupName}?`;
+    this.destroyGroup = this.destroyGroup.bind(this);
+    this.cancel = this.cancel.bind(this);
+  }
+
+  destroyGroup(): void {
+    this.groupService.destroyGroup(this.groupId).subscribe({
+      next: (_) => {
+        this.confirmGroupDestroyed();
+        this.modal.close();
+      },
+      error: (error) => console.log(error),
+    });
+  }
+
+  cancel(): void {
+    this.modal.dismiss();
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts
index 0612484..d7aadd0 100755
--- a/Frontend Angular 4/src/app/shared/components/index.ts	
+++ b/Frontend Angular 4/src/app/shared/components/index.ts	
@@ -10,3 +10,5 @@ export * from "./select/select.component";
 export * from "./add-users-modal/add-users-modal.component";
 export * from "./create-group-modal/create-group-modal.component";
 export * from "./manage-group-users-modal/manage-group-users-modal.component";
+export * from "./action-confirmation-modal/action-confirmation-modal.component";
+export * from "./destroy-group-modal/destroy-group-modal.component";
diff --git a/Frontend Angular 4/src/app/shared/objects/grupo.ts b/Frontend Angular 4/src/app/shared/objects/grupo.ts
index 157e4e4..832f220 100755
--- a/Frontend Angular 4/src/app/shared/objects/grupo.ts	
+++ b/Frontend Angular 4/src/app/shared/objects/grupo.ts	
@@ -6,4 +6,5 @@ export class Group {
   name: string;
   files?: MFile[];
   users?: User[];
+  role?: string;
 }
diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts
index bc32858..f1335f8 100644
--- a/Frontend Angular 4/src/app/shared/services/group.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/group.service.ts	
@@ -10,7 +10,6 @@ import {
 import { Archivo, Evaluacion, MFile } from "../objects/archivo-types";
 import { Group } from "../objects/grupo";
 
-import { SERVER } from "../config";
 import { TranslateService } from "@ngx-translate/core";
 import { AuthenticationService } from "./authentication.service";
 import { catchError } from "rxjs/operators";
@@ -198,12 +197,12 @@ export class GroupService {
     );
   }
 
-  // deleteFile(file_id): Observable<HttpResponse<Object>> {
-  //   return this.http.delete<Object>(`${DELETE_FILE}/${file_id}`, {
-  //     observe: "response",
-  //     headers: this.postHeaders(),
-  //   });
-  // }
+  destroyGroup(group_id): Observable<HttpResponse<Object>> {
+    return this.http.delete<Object>(`${DELETE_GROUP}/${group_id}`, {
+      observe: "response",
+      headers: this.postHeaders(),
+    });
+  }
 
   // destroyAuxFile(): void {
   //   this.http.delete<Object>(DESTROY_AUX_DOCUMENT, {
diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts
index 8fcf224..06214de 100644
--- a/Frontend Angular 4/src/app/shared/shared.module.ts	
+++ b/Frontend Angular 4/src/app/shared/shared.module.ts	
@@ -17,7 +17,8 @@ import { MatChipsModule } from "@angular/material/chips";
 import { MatIconModule } from "@angular/material/icon";
 import { NgbDropdownModule } from "@ng-bootstrap/ng-bootstrap";
 import { AddUsersModal } from "./components";
-
+import { ActionConfirmationModal } from "./components";
+import { DestroyGroupModal } from "./components";
 @NgModule({
   imports: [
     TitleCaseModule,
@@ -40,6 +41,8 @@ import { AddUsersModal } from "./components";
     ChipInputComponent,
     SelectComponent,
     AddUsersModal,
+    ActionConfirmationModal,
+    DestroyGroupModal,
   ],
   exports: [MainButtonComponent, CreateFileModal],
 })
diff --git a/Frontend Angular 4/src/app/utils.ts b/Frontend Angular 4/src/app/utils.ts
index 35a774c..f66570f 100644
--- a/Frontend Angular 4/src/app/utils.ts	
+++ b/Frontend Angular 4/src/app/utils.ts	
@@ -41,3 +41,11 @@ export function findInTree<T extends MFile | Archivo>(
 export function roleCanShareFile(role: string): boolean {
   return role === "owner" || role === "manager";
 }
+
+export function roleCanManageGroupUsers(role: string): boolean {
+  return role === "owner" || role === "manager";
+}
+
+export function roleCanDestroyGroup(role: string): boolean {
+  return role === "owner";
+}
diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json
index af85514..246f220 100755
--- a/Frontend Angular 4/src/assets/i18n/en.json	
+++ b/Frontend Angular 4/src/assets/i18n/en.json	
@@ -135,7 +135,8 @@
         "error": "An error occurred while sending the email"
       },
       "group": {
-        "manageGroupUsers": "Manage group users"
+        "manageGroupUsers": "Manage group users",
+        "destroyGroupTooltip": "Destroy Group"
       },
       "updatePassword": {
         "success": "Your password has been updated, you will be redirected shortly",
diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json
index 2d195ce..44ac256 100755
--- a/Frontend Angular 4/src/assets/i18n/es.json	
+++ b/Frontend Angular 4/src/assets/i18n/es.json	
@@ -135,7 +135,8 @@
         "error": "Error al enviar el email"
       },
       "group": {
-        "manageGroupUsers": "Administrar usuarios del grupo"
+        "manageGroupUsers": "Administrar usuarios del grupo",
+        "destroyGroupTooltip": "Destruir Grupo"
       },
       "updatePassword": {
         "success": "Su contraseña ha sido actualizada, será redirigido en 5 segundos",
-- 
GitLab


From 3e153be4b731e0056e0ccac892133c69b26b2d88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Wed, 22 Jan 2025 15:52:17 -0300
Subject: [PATCH 3/9] Feedback wip: can request and return feedback

---
 .../app/layout/grupos/grupos.component.html   | 141 +++++-
 .../src/app/layout/grupos/grupos.component.ts | 449 +++++++++++++++++-
 .../src/app/layout/layout-routing.module.ts   |   5 +
 .../src/app/layout/layout.module.ts           |   4 +
 .../app/layout/matefun/matefun.component.html |  26 +
 .../app/layout/matefun/matefun.component.ts   | 272 +++++++++--
 .../destroy-group-modal.component.ts          |  11 +-
 .../src/app/shared/components/index.ts        |   2 +
 .../request-feedback-modal.component.html     |   6 +
 .../request-feedback-modal.component.ts       |  65 +++
 ...t-group-file-feedback-modal.component.html |   6 +
 ...est-group-file-feedback-modal.component.ts |  54 +++
 .../return-feedback-modal.component.html      |   6 +
 .../return-feedback-modal.component.ts        |  68 +++
 Frontend Angular 4/src/app/shared/config.ts   |  16 +
 .../src/app/shared/objects/archivo-types.ts   |   4 +
 .../app/shared/services/document.service.ts   |  11 +-
 .../src/app/shared/services/file.service.ts   |   4 +
 .../shared/services/group-document.service.ts | 311 ++++++++++++
 .../app/shared/services/group-file.service.ts | 264 ++++++++++
 .../src/app/shared/services/group.service.ts  |  44 ++
 .../src/app/shared/shared.module.ts           |  28 +-
 Frontend Angular 4/src/assets/i18n/en.json    |   4 +-
 Frontend Angular 4/src/assets/i18n/es.json    |   4 +-
 Frontend Angular 4/src/styles/console.css     |   2 +-
 25 files changed, 1712 insertions(+), 95 deletions(-)
 create mode 100644 Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/services/group-document.service.ts
 create mode 100644 Frontend Angular 4/src/app/shared/services/group-file.service.ts

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html
index 9c374cb..74c0e81 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
@@ -60,7 +60,7 @@
         [destroyOnHide]="false"
       >
         <li [ngbNavItem]="1">
-          <a ngbNavLink>Alumnos</a>
+          <a ngbNavLink>Users</a>
           <ng-template ngbNavContent>
             <div class="card">
               <div>
@@ -90,7 +90,6 @@
                   <i class="fa fa-users"></i>
                 </button>
                 <button
-                  *ngIf="roleCanDestroyGroup()"
                   class="btn btn-sm btn-secondary pull-right"
                   style="cursor: pointer; margin-top: -35px; margin-right: 71px"
                   (click)="openDestroyGroupModal()"
@@ -136,11 +135,11 @@
           <a ngbNavLink>Archivos</a>
           <ng-template ngbNavContent>
             <div class="card">
-              <div>
+              <div class="relative pull-right right-0">
                 <button
                   class="btn btn-sm btn-secondary pull-right"
                   style="cursor: pointer; margin-top: -35px; margin-right: 1px"
-                  (click)="desseleccionarGrupo()"
+                  (click)="navBack()"
                   ngbPopover="{{
                     'i18n.action.goBack' | translate | titleCase
                   }}"
@@ -149,9 +148,31 @@
                 >
                   <i class="fa fa-arrow-up"></i>
                 </button>
+                <button
+                  ngbPopover="{{ 'i18n.action.new' | translate | titleCase }}"
+                  triggers="mouseenter:mouseleave"
+                  placement="bottom"
+                  type="button"
+                  data-toggle="dropdown"
+                  class="btn btn-sm btn-secondary pull-right"
+                  style="cursor: pointer; margin-top: -35px; margin-right: 36px"
+                  aria-haspopup="true"
+                  aria-expanded="false"
+                >
+                  <i aria-hidden="true" class="fa fa-plus"></i>
+                </button>
+                <div class="dropdown-menu right-0" style="left: unset">
+                  <a class="dropdown-item" (click)="mkFile(true)">
+                    {{ "i18n.object.file" | translate | titleCase }}
+                  </a>
+                  <div role="separator" class="dropdown-divider"></div>
+                  <a class="dropdown-item" (click)="mkFile(false)">
+                    {{ "i18n.object.folder" | translate | titleCase }}
+                  </a>
+                </div>
                 <p
                   class="pull-right"
-                  style="margin-top: -34px; margin-right: 60px"
+                  style="margin-top: -34px; margin-right: 95px"
                 >
                   {{ grupoSeleccionado.name }}
                 </p>
@@ -162,12 +183,19 @@
                   style="min-height: 100px; overflow-y: scroll"
                 >
                   <div
-                    *ngFor="let archivo of grupoSeleccionado.archivos"
+                    *ngFor="let archivo of directorioActual.archivos"
                     (click)="seleccionarArchivo(archivo)"
                     class="col-sm-3 col-4 matefun-group-wrapper"
                   >
                     <i
+                      *ngIf="archivo.directorio"
+                      class="fa fa-folder matefun-fa-folder"
+                      aria-hidden="true"
+                    ></i>
+                    <i
+                      *ngIf="!archivo.directorio"
                       class="fa fa-file-text matefun-fa-file"
+                      [class.text-blue-400]="archivo.feedbackRequested"
                       aria-hidden="true"
                     ></i>
                     <p>{{ archivo.nombre }}</p>
@@ -182,14 +210,14 @@
       <div [ngbNavOutlet]="nav" class="mt-2" *ngIf="grupoSeleccionado"></div>
     </div>
     <div class="col-lg-7">
-      <div class="card" *ngIf="selectedUser">
+      <div class="card" *ngIf="selectedUser && !archivoSeleccionado">
         <div class="card-block">
           <div
             class="row listadoEntregasAlumnoGrupos"
             style="min-height: 100px; overflow-y: scroll"
           >
             <div
-              *ngFor="let entrega of selectedUser.archivos"
+              *ngFor="let entrega of selectedUserDirectorioActual"
               (click)="seleccionarEntrega(entrega)"
               class="col-sm-3 col-4 matefun-file-wrapper"
             >
@@ -201,7 +229,7 @@
               <p>{{ entrega.nombre }}</p>
             </div>
             <div
-              *ngIf="selectedUser.archivos.length == 0"
+              *ngIf="selectedUser && selectedUserDirectorioActual.length == 0"
               style="width: 100%; text-align: center"
             >
               <i
@@ -253,14 +281,59 @@
             Calificar
           </button>
           <button
-            *ngIf="esArchivoGrupo()"
-            ngbPopover="Cargar/Editar"
+            ngbPopover="{{ 'i18n.action.load' | translate | titleCase }}/{{
+              'i18n.action.edit' | translate | titleCase
+            }}"
+            placement="bottom"
+            triggers="mouseenter:mouseleave"
+            class="btn btn-sm btn-secondary pull-left mr-2"
+            (click)="cargarArchivo()"
+          >
+            <i aria-hidden="true" class="fa fa-pencil"></i>
+          </button>
+          <button
+            ngbPopover="{{ 'i18n.action.delete' | translate | titleCase }}"
             placement="bottom"
             triggers="mouseenter:mouseleave"
             class="btn btn-sm btn-secondary pull-left mr-2"
-            (click)="cargarArchivoCompartido()"
+            (click)="mostrarEliminarDialogo()"
           >
-            <i class="fa fa-pencil"></i>
+            <i aria-hidden="true" class="fa fa-remove"></i>
+          </button>
+          <button
+            ngbPopover="{{ 'i18n.action.move' | translate | titleCase }} {{
+              'i18n.object.file' | translate | titleCase
+            }}"
+            placement="bottom"
+            triggers="mouseenter:mouseleave"
+            class="btn btn-sm btn-secondary pull-left mr-2"
+            (click)="seleccionarDirectorioAMover()"
+          >
+            <i aria-hidden="true" class="fa fa-cut"></i>
+          </button>
+          <button
+            *ngIf="canRequestFeedback()"
+            ngbPopover="{{
+              'i18n.action.requestFeedback' | translate | titleCase
+            }} {{ 'i18n.object.file' | translate | titleCase }}"
+            placement="bottom"
+            triggers="mouseenter:mouseleave"
+            class="btn btn-sm btn-secondary pull-left mr-2"
+            (click)="requestFeedback()"
+          >
+            <i aria-hidden="true" class="fa fa-exchange"></i>
+          </button>
+          <button
+            *ngIf="canReturnFeedback()"
+            ngbPopover="{{
+              'i18n.action.returnFeedback' | translate | titleCase
+            }} {{ 'i18n.object.file' | translate | titleCase }}"
+            placement="bottom"
+            triggers="mouseenter:mouseleave"
+            class="btn btn-sm btn-secondary pull-left mr-2"
+            (click)="returnFeedback()"
+          >
+            <i aria-hidden="true" class="fa fa-exchange"></i>
           </button>
           <div class="pull-left">
             Nombre: {{ archivoSeleccionado?.nombre }} - Creado:
@@ -296,3 +369,45 @@
   *ngIf="modalQualifyDelivery"
 >
 </matefun-modal-calificar-entrega>
+
+<matefun-modal-nuevo-archivo
+  confirmLabel="{{ 'i18n.action.create' | translate | titleCase }}"
+  fileDescriptionLabel="{{ 'i18n.object.descr' | translate | titleCase }}:"
+  fileNameLabel="{{ 'i18n.object.name' | translate | titleCase }}:"
+  header="{{ 'i18n.action.new' | translate | titleCase }}
+  {{
+    (modalTypeIsFile ? 'i18n.object.file' : 'i18n.object.folder')
+      | translate
+      | titleCase
+  }} "
+  [opened]="modalCreateFileOpened"
+  [typeOfFile]="modalTypeIsFile ? 'file' : 'directory'"
+  (close)="modalCreateFile = false"
+  (confirmFileCreation)="
+    modalTypeIsFile
+      ? confirmDocumentCreation($event)
+      : confirmFileCreation($event)
+  "
+  *ngIf="modalCreateFile"
+>
+</matefun-modal-nuevo-archivo>
+
+<matefun-modal-seleccionar-directorio
+  confirmLabel="{{ 'i18n.action.move' | translate | titleCase }} {{
+    'i18n.object.here' | translate
+  }}"
+  [currentDirectory]="currentDirOfFileToMove"
+  [fileIdToMove]="archivoSeleccionado ? archivoSeleccionado.id : -1"
+  fileNameLabel="{{ 'i18n.object.name' | translate | titleCase }}"
+  header="{{ 'i18n.msg.file.move' | translate }}"
+  [initialPath]="currentPath"
+  navigateBackLabel="{{ 'i18n.action.goBack' | translate | titleCase }}"
+  [opened]="modalMoveFileOpened"
+  type-of-modal="move"
+  (close)="modalMoveFile = false"
+  (confirmFileCreation)="confirmFileMove($event)"
+  (navBack)="navigateBackModal($event)"
+  (navTo)="currentDirOfFileToMove = $event.detail"
+  *ngIf="modalMoveFile"
+>
+</matefun-modal-seleccionar-directorio>
diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts
index d480280..02c939e 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
@@ -1,7 +1,11 @@
 import { ChangeDetectorRef, Component } from "@angular/core";
 import { Router } from "@angular/router";
 
-import { Archivo, Evaluacion } from "../../shared/objects/archivo-types";
+import {
+  Archivo,
+  Evaluacion,
+  MDocument,
+} from "../../shared/objects/archivo-types";
 import { Group } from "../../shared/objects/grupo";
 import { Usuario } from "../../shared/objects/usuario";
 import { AuthenticationService } from "../../shared/services/authentication.service";
@@ -11,11 +15,27 @@ import { NgbPopoverConfig, NgbPopover } from "@ng-bootstrap/ng-bootstrap";
 import { NotificacionService } from "../../shared/services/notificacion.service";
 import { TranslateService } from "@ngx-translate/core";
 import { GroupService } from "../../shared/services/group.service";
-import { roleCanManageGroupUsers, roleCanDestroyGroup } from "app/utils";
+import { GroupFileService } from "../../shared/services/group-file.service";
+import { FileService } from "../../shared/services/file.service";
+import { DocumentService } from "../../shared/services/document.service";
+import { GroupDocumentService } from "../../shared/services/group-document.service";
+import {
+  roleCanManageGroupUsers,
+  roleCanDestroyGroup,
+  findInTree,
+} from "app/utils";
 import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
 import { CreateGroupModal } from "../../shared/components/create-group-modal/create-group-modal.component";
 import { ManageGroupUsersModal } from "app/shared/components/manage-group-users-modal/manage-group-users-modal.component";
-import { DestroyGroupModal } from "app/shared";
+import {
+  DestroyGroupModal,
+  RequestFeedbackModal,
+  ReturnFeedbackModal,
+} from "app/shared";
+
+const STARTS_WITH_CAPITAL_LETTER_REGEX = /^[A-Z]/;
+
+const ID_ROOT_DIR = -1;
 
 @Component({
   selector: "grupos",
@@ -23,12 +43,14 @@ import { DestroyGroupModal } from "app/shared";
 })
 export class GruposComponent {
   archivos: Archivo[] = [];
+  selectedUserArchivos: Archivo[] = [];
   grupos: Group[] = [];
   grupoSeleccionado: Group = undefined;
 
   selectedUser: Usuario = undefined;
 
   archivoSeleccionado: Archivo = undefined;
+  selectedFile: Archivo = undefined;
 
   tipoArchivo: string = undefined;
 
@@ -39,8 +61,12 @@ export class GruposComponent {
   configCodeMirror = JSON.parse(localStorage.getItem("codeMirrorConfig"));
   translateService: any;
 
+  selectedUserFileArray: any;
+  selectedUserDirectorioActual: any;
+
   createModalRef: any;
 
+  preview: string = "";
   // - - - - - - - - - - - -  Modal show confirm  - - - - - - - - - - - -
   /**
    * Con `true` se renderiza el modal de calificar entrega.
@@ -68,6 +94,51 @@ export class GruposComponent {
     },
   ];
 
+  // - - - - - - - - - - - - - Modal create file - - - - - - - - - - - - -
+  /**
+   * Con `true` se renderiza el modal de crear un archivo
+   */
+  modalCreateFile = false;
+
+  /**
+   * Con `true` se configura el modal para que se muestre la interfaz de
+   * agregar/borrar archivo. De otro modo, se muestra la interfaz de
+   * agregar/borrar directorio
+   */
+  modalTypeIsFile = true;
+
+  /**
+   * Con `true` se indica que el modal -de crear un archivo- se quiere abrir.
+   * Útil para avisar al modal que anime el dismiss antes de que se elimine del
+   * DOM
+   */
+  modalCreateFileOpened = true;
+
+  /**
+   * Directorio actual sobre el cual será desplegada la lista de directorios
+   * del modal mover archivo.
+   */
+  currentDirOfFileToMove: Archivo;
+
+  /**
+   * Determina la ruta en donde se encuentra ubicado el archivo/directorio
+   * seleccionado actualmente.
+   */
+  currentPath: string = "/";
+
+  // - - - - - - - - - - - - -  Modal move file  - - - - - - - - - - - - -
+  /**
+   * Con `true` se renderiza el modal de mover un archivo
+   */
+  modalMoveFile = false;
+
+  /**
+   * Con `true` se indica que el modal -de mover un archivo- se quiere abrir.
+   * Útil para avisar al modal que anime el dismiss antes de que se elimine del
+   * DOM
+   */
+  modalMoveFileOpened = true;
+
   constructor(
     private router: Router,
     private authService: AuthenticationService,
@@ -76,6 +147,10 @@ export class GruposComponent {
     private sessionService: SessionService,
     public translate: TranslateService,
     public groupService: GroupService,
+    public groupFileService: GroupFileService,
+    public documentService: DocumentService,
+    public groupDocumentService: GroupDocumentService,
+    public fileService: FileService,
     private modalService: NgbModal,
     private changeDetectorRef: ChangeDetectorRef
   ) {
@@ -144,9 +219,12 @@ export class GruposComponent {
   }
 
   ordenarArchivos() {
-    // this.grupoSeleccionado.archivos = this.grupoSeleccionado.archivos.sort(
-    //   this.ordenarAlph
-    // );
+    // var tipo = this.sortFunction;
+    // if (tipo === "tipo") {
+    //   this.ordenarMixto();
+    // } else if (tipo === "fecha") {
+    //   this.ordenarFechaCreacion();
+    // }
   }
 
   //ordeno los archivos del alumno (los archivos entregados.)
@@ -183,37 +261,222 @@ export class GruposComponent {
     // );
   }
 
+  /**
+   * Renderiza en pantalla el modal de agregar archivos o directorios,
+   * dependiendo del valor de `modalTypeIsFile`.
+   * @param modalTypeIsFile Con `true` se indica que el modal a renderizar es el de agregar archivo. De otro modo, se renderiza el de agregar directorio.
+   */
+  mkFile(modalTypeIsFile: boolean) {
+    this.modalTypeIsFile = modalTypeIsFile;
+
+    // Mostrar el modal
+    this.modalCreateFile = true;
+    this.modalCreateFileOpened = true;
+  }
+
   seleccionarGrupo(grupo) {
     this.grupoSeleccionado = grupo;
     this.ordenarAlumnos();
-    this.ordenarArchivos();
-    this.archivoSeleccionado = undefined;
+    this.loadFilesAndFolders(grupo.id);
     this.selectedUser = undefined;
   }
 
+  loadFilesAndFolders(grupoId: number, directorioActualId = null) {
+    this.groupFileService.getGroupFiles(grupoId).subscribe(
+      (files) => {
+        this.tree = this.fileService.fileToArchivo(files["files"]);
+
+        this.archivos = [this.tree];
+        this.loading = false;
+
+        if (directorioActualId) {
+          this.directorioActual = findInTree(this.archivos, directorioActualId);
+        } else {
+          this.directorioActual = this.tree;
+        }
+        this.ordenarArchivos();
+        this.sessionService.setArchivosTree(this.tree);
+
+        this.currentDirOfFileToMove = this.directorioActual;
+      },
+      (error) => console.log(error)
+    );
+  }
+
   desseleccionarGrupo() {
     this.grupoSeleccionado = undefined;
     this.archivoSeleccionado = undefined;
     this.selectedUser = undefined;
   }
 
-  seleccionarUser(alumno) {
-    if (!(typeof alumno === "undefined")) {
-      this.selectedUser = alumno;
-      this.ordenarArchivosAlumno();
-      this.archivoSeleccionado = undefined;
+  seleccionarUser(user) {
+    this.groupFileService
+      .getFeedbackRequestedGroupFiles(this.grupoSeleccionado.id, user.id)
+      .subscribe(
+        (files) => {
+          this.selectedUserFileArray = files["files"].map((file) =>
+            this.fileService.fileToArchivo(file)
+          );
+          this.selectedUserArchivos = [this.selectedUserFileArray];
+          this.loading = false;
+
+          this.selectedUserDirectorioActual = this.selectedUserFileArray;
+          this.ordenarArchivos();
+          this.sessionService.setArchivosTree(this.selectedUserFileArray);
+
+          this.selectedUser = user;
+
+          this.archivoSeleccionado = undefined;
+          this.selectedFile = undefined; // Probando
+        },
+        (error) => console.log(error)
+      );
+  }
+
+  canRequestFeedback() {
+    return (
+      !!this.grupoSeleccionado.id &&
+      !!this.selectedFile &&
+      this.selectedFile.feedbackRequested !== undefined &&
+      !this.selectedFile.feedbackRequested
+    );
+  }
+
+  requestFeedback() {
+    this.openRequestFeedbackModal();
+  }
+
+  canReturnFeedback() {
+    return (
+      !!this.grupoSeleccionado &&
+      !!this.selectedFile &&
+      this.selectedFile.feedbackRequested !== undefined &&
+      this.selectedFile.feedbackRequested &&
+      this.groupService.userCanReturnFeedback(this.grupoSeleccionado.role)
+    );
+  }
+
+  returnFeedback() {
+    this.openReturnFeedbackModal();
+  }
+
+  openReturnFeedbackModal() {
+    if (!this.grupoSeleccionado) {
+      this.showNotification("warning", "i18n.warning.group.noSelected");
+      return;
+    }
+
+    if (!this.selectedFile.fileId) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
     }
+
+    this.createModalRef = this.modalService.open(ReturnFeedbackModal);
+
+    this.createModalRef.componentInstance.groupName = null;
+    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado;
+    this.createModalRef.componentInstance.fileName = this.selectedFile.nombre;
+    this.createModalRef.componentInstance.fileId = this.selectedFile.fileId;
+    this.createModalRef.componentInstance.userId = this.selectedUser.id;
+    this.createModalRef.componentInstance.confirmFeedbackReturned =
+      this.confirmFeedbackReturned;
   }
 
+  confirmFeedbackReturned() {}
+
   seleccionarArchivo(archivo) {
+    this.selectedFile = archivo;
+    if (archivo.directorio) {
+      this.directorioActual = archivo;
+      // this.loadFilesAndFolders(this.grupoSeleccionado.id, archivo.id);
+      this.archivoSeleccionado = undefined;
+      this.selectedFile = undefined; // Probando
+      // this.selectedUser = undefined;
+      this.currentPath += `${archivo.nombre}/`;
+      this.preview = "";
+    } else {
+      this.selectedUser = undefined;
+      this.tipoArchivo = "compartido";
+      this.groupDocumentService
+        .getGroupDocument(
+          this.grupoSeleccionado.id,
+          archivo.documentId,
+          this.selectedUser?.id
+        )
+        .subscribe(
+          (data) => {
+            const document = data["document"];
+            const archivoDocument =
+              this.documentService.documentToArchivo(document);
+            this.actualizarArchivoSeleccionado(archivoDocument);
+          },
+          (error) => console.log(error)
+        );
+    }
+  }
+
+  /**
+   * Actualiza el estado del archivo seleccionado, así como la preview del
+   * contenido de dicho archivo.
+   * @param archivo Archivo a seleccionar
+   */
+  actualizarArchivoSeleccionado(archivo: Archivo) {
     this.archivoSeleccionado = archivo;
-    this.selectedUser = undefined;
-    this.tipoArchivo = "compartido";
+    this.selectedFile = archivo; //Probando
+    this.preview = !!archivo?.contenido ? archivo.contenido : "";
+  }
+
+  /**
+   * Navega hacia el directorio padre en la vista principal de directorios.
+   * @param shouldUpdateSelectedFile Determina si se debe actualizar el archivo seleccionado cuando se navega hacia atrás
+   */
+  navBack(shouldUpdateSelectedFile = true) {
+    const { padreId } = this.directorioActual;
+
+    if (padreId === ID_ROOT_DIR) {
+      return;
+    }
+
+    // Actualiza el current path
+    const lastDirectoryIndex = this.currentPath.lastIndexOf(
+      `${this.directorioActual.nombre}`
+    );
+
+    this.currentPath = this.currentPath.substring(0, lastDirectoryIndex);
+
+    const padre = findInTree(this.archivos, padreId);
+
+    if (shouldUpdateSelectedFile) {
+      // Cuando se selecciona un directorio cuyo id es el root, significa que ese
+      // es el último archivo de la rama de directorios. En otras palabras, el
+      // root se identifica porque su padreId es ID_ROOT_DIR.
+      const archivoSeleccionado =
+        padre.padreId == ID_ROOT_DIR ? undefined : padre;
+      this.actualizarArchivoSeleccionado(archivoSeleccionado);
+    }
+
+    // Actualiza la vista de directorios y archivos
+    this.directorioActual = padre;
   }
 
   seleccionarEntrega(entrega) {
-    this.archivoSeleccionado = entrega;
-    this.selectedUser = undefined;
+    this.groupDocumentService
+      .getGroupDocument(
+        this.grupoSeleccionado.id,
+        entrega.documentId,
+        this.selectedUser.id
+      )
+      .subscribe(
+        (data) => {
+          // this.archivoSeleccionado = entrega;
+          const document = data["document"];
+          const archivoDocument =
+            this.documentService.documentToArchivo(document);
+          this.actualizarArchivoSeleccionado(archivoDocument);
+        },
+        (error) => console.log(error)
+      );
+    // this.selectedUser = undefined;
     this.tipoArchivo = "entrega";
   }
 
@@ -231,14 +494,35 @@ export class GruposComponent {
     );
   }
 
-  openDestroyGroupModal() {
-    if (!this.grupoSeleccionado) {
+  openRequestFeedbackModal() {
+    if (!this.grupoSeleccionado.id) {
       this.showNotification("warning", "i18n.warning.group.noSelected");
       return;
     }
 
-    if (!roleCanDestroyGroup(this.grupoSeleccionado.role)) {
-      this.showNotification("error", "i18n.warning.group.noPermission");
+    if (!this.selectedFile.id) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
+    }
+
+    this.createModalRef = this.modalService.open(RequestFeedbackModal);
+
+    this.createModalRef.componentInstance.groupName =
+      this.grupoSeleccionado.name;
+    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id;
+    this.createModalRef.componentInstance.fileName =
+      this.archivoSeleccionado.nombre;
+    this.createModalRef.componentInstance.fileId =
+      this.archivoSeleccionado.fileId;
+    this.createModalRef.componentInstance.confirmFeedbackRequested =
+      this.confirmFeedbackRequsted;
+  }
+
+  confirmFeedbackRequsted() {}
+
+  openDestroyGroupModal() {
+    if (!this.grupoSeleccionado) {
+      this.showNotification("warning", "i18n.warning.group.noSelected");
       return;
     }
 
@@ -249,6 +533,8 @@ export class GruposComponent {
     this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id;
     this.createModalRef.componentInstance.confirmGroupDestroyed =
       this.confirmGroupDestroyed;
+    this.createModalRef.componentInstance.userRole =
+      this.grupoSeleccionado.role;
 
     // this.translateService
     //   .get("i18n.warning.group.destroy", {
@@ -382,14 +668,131 @@ export class GruposComponent {
     // } else {
     //   return false;
     // }
+    return true;
   }
 
-  cargarArchivoCompartido() {
+  cargarArchivo() {
     if (!this.archivoSeleccionado || this.archivoSeleccionado.directorio) {
       this.showNotification("warning", "i18n.warning.file.noSelected");
       return;
     }
     this.sessionService.setArchivo(this.archivoSeleccionado);
-    this.router.navigate(["/matefun"]);
+    const userId = !!this.selectedUser
+      ? this.selectedUser.id
+      : JSON.parse(localStorage.getItem("currentUser"))["id"];
+
+    this.router.navigate([
+      `/grupos/${this.grupoSeleccionado.id}/users/${userId}/matefun/${this.archivoSeleccionado.id}`,
+    ]);
+  }
+
+  seleccionarDirectorioAMover() {
+    if (!this.archivoSeleccionado) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
+    }
+
+    // Si el archivo es del alumno, se puede mover.
+    // No se controla por creador, dado que los compartidos mantienen este atributo
+    // if (!this.archivos.some((arch) => arch.id == this.archivoSeleccionado.id)) {
+    //   this.showNotification("warning", "i18n.warning.file.noPermissionMove");
+    //   return;
+    // }
+
+    // this.navBack(false);
+    if (this.archivoSeleccionado.directorio) {
+      this.currentDirOfFileToMove = this.directorioActual.parent;
+    } else {
+      this.currentDirOfFileToMove = this.directorioActual;
+    }
+
+    // Mostrar el modal
+    this.modalMoveFile = true;
+    this.modalMoveFileOpened = true;
+  }
+
+  /**
+   * Valida y confirma la creación del archivo.
+   * @param event Evento devuelto por el modal asociado para crear el archivo. En el detalle contiene el `nombre` y `descripcion` del archivo que se desea agregar.
+   */
+  confirmFileCreation(event: CustomEvent) {
+    const { nombre, descripcion } = event.detail;
+
+    // Antes que nada, se chequea que empiece con mayúscula
+    if (!STARTS_WITH_CAPITAL_LETTER_REGEX.test(nombre)) {
+      this.showNotification("warning", "i18n.warning.file.capitalLetter");
+      return;
+    }
+
+    const archivo = new Archivo();
+    // archivo.cedulaCreador = this.directorioActual.cedulaCreador; // cedula
+    archivo.contenido = this.modalTypeIsFile ? "" : descripcion || "";
+    archivo.directorio = !this.modalTypeIsFile;
+    archivo.editable = true;
+    archivo.fechaCreacion = new Date();
+    archivo.nombre = nombre;
+    archivo.padreId = this.directorioActual.id;
+    archivo.archivos = [];
+
+    const that = this;
+    const idDirectorioActual = this.directorioActual.id;
+
+    const file = this.fileService.archivoToFile(archivo);
+    this.groupFileService
+      .createGroupFile(this.grupoSeleccionado.id, file)
+      .subscribe(
+        () => {
+          that.loadFilesAndFolders(idDirectorioActual);
+
+          // Cerrar el modal en caso de éxito
+          that.modalCreateFileOpened = false;
+        },
+        (error) => {
+          that.notifService.error(error.text());
+
+          // Cerrar el modal en caso de error
+          that.modalCreateFileOpened = false;
+        }
+      );
+  }
+
+  /**
+   * Valida y confirma la creación del archivo.
+   * @param event Evento devuelto por el modal asociado para crear el archivo. En el detalle contiene el `nombre` y `descripcion` del archivo que se desea agregar.
+   */
+  confirmDocumentCreation(event: CustomEvent) {
+    const { nombre } = event.detail;
+
+    // Antes que nada, se chequea que empiece con mayúscula
+    if (!STARTS_WITH_CAPITAL_LETTER_REGEX.test(nombre)) {
+      this.showNotification("warning", "i18n.warning.file.capitalLetter");
+      return;
+    }
+
+    const document = new MDocument();
+    document.text_doc = "";
+    document.title = nombre;
+    document.parent_id = this.directorioActual.id;
+
+    const that = this;
+    const idDirectorioActual = this.directorioActual.id;
+
+    this.documentService
+      .createDocument(document, this.grupoSeleccionado.id)
+      .subscribe(
+        () => {
+          that.loadFilesAndFolders(
+            this.grupoSeleccionado.id,
+            idDirectorioActual
+          );
+
+          that.modalCreateFileOpened = false;
+        },
+        (error) => {
+          that.notifService.error(error.text());
+
+          that.modalCreateFileOpened = false;
+        }
+      );
   }
 }
diff --git a/Frontend Angular 4/src/app/layout/layout-routing.module.ts b/Frontend Angular 4/src/app/layout/layout-routing.module.ts
index 0e510bb..9a4cbb8 100755
--- a/Frontend Angular 4/src/app/layout/layout-routing.module.ts	
+++ b/Frontend Angular 4/src/app/layout/layout-routing.module.ts	
@@ -17,6 +17,11 @@ const routes: Routes = [
         loadChildren: () =>
           import("./matefun/matefun.module").then((m) => m.MateFunModule),
       },
+      {
+        path: "grupos/:groupId/users/:groupUserId/matefun/:id",
+        loadChildren: () =>
+          import("./matefun/matefun.module").then((m) => m.MateFunModule),
+      },
       {
         path: "archivos",
         loadChildren: () =>
diff --git a/Frontend Angular 4/src/app/layout/layout.module.ts b/Frontend Angular 4/src/app/layout/layout.module.ts
index 3e4f59a..ca4ed8e 100755
--- a/Frontend Angular 4/src/app/layout/layout.module.ts	
+++ b/Frontend Angular 4/src/app/layout/layout.module.ts	
@@ -10,6 +10,8 @@ import { AuthenticationService } from "../shared/services/authentication.service
 import { HaskellService } from "../shared/services/haskell.service";
 import { DocumentService } from "../shared/services/document.service";
 import { FileService } from "../shared/services/file.service";
+import { GroupDocumentService } from "../shared/services/group-document.service";
+import { GroupFileService } from "../shared/services/group-file.service";
 import { GroupService } from "../shared/services/group.service";
 import { LtCodemirrorModule } from "lt-codemirror";
 import { CodemirrorModule } from "@ctrl/ngx-codemirror";
@@ -35,6 +37,8 @@ import { TitleCaseModule } from "../shared/modules/titlecase.module";
     HaskellService,
     DocumentService,
     FileService,
+    GroupDocumentService,
+    GroupFileService,
     GroupService,
   ],
 })
diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.html b/Frontend Angular 4/src/app/layout/matefun/matefun.component.html
index 15d5a3e..7dbe99c 100755
--- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.html	
+++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.html	
@@ -169,6 +169,32 @@
                   >
                     <i class="fa fa-plus"></i>
                   </button>
+                  <button
+                    *ngIf="canRequestFeedback()"
+                    style="float: right"
+                    (click)="requestFeedback()"
+                    class="btn btn-sm btn-secondary"
+                    ngbPopover="{{
+                      'i18n.action.requestFeedback' | translate | titleCase
+                    }} {{ 'i18n.object.file' | translate | titleCase }}"
+                    triggers="mouseenter:mouseleave"
+                    placement="bottom"
+                  >
+                    <i class="fa fa-exchange"></i>
+                  </button>
+                  <button
+                    *ngIf="canReturnFeedback()"
+                    style="float: right"
+                    (click)="returnFeedback()"
+                    class="btn btn-sm btn-secondary"
+                    ngbPopover="{{
+                      'i18n.action.returnFeedback' | translate | titleCase
+                    }} {{ 'i18n.object.file' | translate | titleCase }}"
+                    triggers="mouseenter:mouseleave"
+                    placement="bottom"
+                  >
+                    <i class="fa fa-exchange"></i>
+                  </button>
                   <ng-template #popoverContent style="width: 15em">
                     <div style="width: 12em">
                       <div class="form-group">
diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts
index 5f65fc1..f8a3199 100755
--- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts	
+++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts	
@@ -14,6 +14,7 @@ import { HaskellService } from "../../shared/services/haskell.service";
 import { DocumentService } from "../../shared/services/document.service";
 import { UserService } from "../../shared/services/user.service";
 import { FileService } from "../../shared/services/file.service";
+import { GroupFileService } from "../../shared/services/group-file.service";
 import { WebsocketService } from "../../shared/services/websocket.service";
 import { ActionCableService } from "app/shared/services/actioncable.service";
 import { UsuarioService } from "../../shared/services/usuario.service";
@@ -41,7 +42,12 @@ import { CodeMirrorBinding } from "y-codemirror";
 import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
 import { CreateFileModal } from "../../shared/components/create-file-modal/create-file-modal.component";
 import { ImportFileModal } from "../../shared/components/import-file-modal/import-file-modal.component";
-import { ShareFileModal } from "app/shared";
+import {
+  ShareFileModal,
+  RequestFeedbackModal,
+  ReturnFeedbackModal,
+} from "app/shared";
+import { GroupDocumentService } from "../../shared/services/group-document.service";
 
 import * as Y from "yjs";
 
@@ -67,7 +73,8 @@ import "./codemirror/addons/functions_definition_EN.js";
 import "./codemirror/addons/functions_definition_ES.js";
 import { Action } from "rxjs/internal/scheduler/Action";
 import { findInTree, roleCanShareFile } from "app/utils";
-import { filter } from "rxjs";
+import { filter, Observable } from "rxjs";
+import { GroupService } from "app/shared/services/group.service";
 
 var codeMirrorRef: any;
 var componentRef: any;
@@ -80,6 +87,8 @@ var focus: any;
   providers: [WebsocketService, NgbPopoverConfig, UsuarioService],
 })
 export class MateFunComponent implements OnInit, OnChanges {
+  currentUserGroupRole: string;
+  documentBelongsToCurrentUser: boolean;
   titlecasePipe: any;
   translateService: any;
   consoleDisable: boolean = false;
@@ -180,6 +189,8 @@ export class MateFunComponent implements OnInit, OnChanges {
   activeTabId = 1;
   ydoc: null | Y.Doc = null;
   documentId: number | null = null;
+  groupId: number | null = null;
+  groupUserId: number | null = null;
   editorContainer: Element;
   editor: CodeMirror.Editor;
   yUndoManager: Y.UndoManager;
@@ -224,6 +235,7 @@ export class MateFunComponent implements OnInit, OnChanges {
     private documentService: DocumentService,
     private userService: UserService,
     private fileService: FileService,
+    private groupFileService: GroupFileService,
     private authService: AuthenticationService,
     private ghciService: GHCIService,
     private notifService: NotificacionService,
@@ -231,6 +243,8 @@ export class MateFunComponent implements OnInit, OnChanges {
     private usuarioService: UsuarioService,
     public translate: TranslateService,
     private actionCableService: ActionCableService,
+    public groupDocumentService: GroupDocumentService,
+    public groupService: GroupService,
     private router: Router,
     private route: ActivatedRoute,
     private modalService: NgbModal,
@@ -399,10 +413,51 @@ export class MateFunComponent implements OnInit, OnChanges {
 
   getCurrentDocument(nextFunction: () => void = null) {
     this.documentId = parseInt(this.route.snapshot.paramMap.get("id"));
-    this.documentService.getDocument(this.documentId).subscribe({
+    this.groupId = parseInt(this.route.snapshot.paramMap.get("groupId"));
+    this.groupUserId = parseInt(
+      this.route.snapshot.paramMap.get("groupUserId")
+    );
+    let documentSubscription: Observable<{
+      document: MDocument;
+    }> = null;
+    let fileSubscriptionService: {
+      getFile: (id: number) => Observable<{
+        file: MFile;
+      }>;
+    } = null;
+    // TODO: Do something similar to this but for files,
+    // also the redirect to here when choosing a file in the groups page
+    // is setting the wrong user id
+    if (!!this.groupId && !!this.groupUserId) {
+      documentSubscription = this.groupDocumentService.getGroupDocument(
+        this.groupId,
+        this.documentId,
+        this.groupUserId
+      );
+
+      fileSubscriptionService =
+        this.groupFileService.getSpecificUserFileWithSetGroup(
+          this.groupId,
+          this.groupUserId
+        );
+    } else {
+      documentSubscription = this.documentService.getDocument(this.documentId);
+      fileSubscriptionService = this.fileService;
+    }
+    documentSubscription.subscribe({
       next: (data) => {
         this.archivo = this.documentService.documentToArchivo(data.document);
-        if (!!nextFunction) {
+        if (this.router.url.includes("grupos")) {
+          fileSubscriptionService
+            .getFile(this.archivo.fileId)
+            .subscribe((file) => {
+              this.archivo.feedbackRequested = file["file"].feedback_requested;
+
+              if (!!nextFunction) {
+                nextFunction();
+              }
+            });
+        } else if (!!nextFunction) {
           nextFunction();
         }
       },
@@ -412,6 +467,82 @@ export class MateFunComponent implements OnInit, OnChanges {
     });
   }
 
+  canRequestFeedback() {
+    return (
+      !!this.groupId &&
+      !!this.archivo &&
+      this.archivo.feedbackRequested !== undefined &&
+      !this.archivo.feedbackRequested &&
+      this.documentBelongsToCurrentUser
+    );
+  }
+
+  canReturnFeedback() {
+    return (
+      !!this.groupId &&
+      !!this.archivo &&
+      this.archivo.feedbackRequested !== undefined &&
+      this.archivo.feedbackRequested &&
+      this.groupService.userCanReturnFeedback(this.currentUserGroupRole)
+    );
+    // TODO: Do this and the other function
+  }
+
+  requestFeedback() {
+    this.openRequestFeedbackModal();
+  }
+
+  returnFeedback() {
+    this.openReturnFeedbackModal();
+  }
+
+  openRequestFeedbackModal() {
+    if (!this.groupId) {
+      this.showNotification("warning", "i18n.warning.group.noSelected");
+      return;
+    }
+
+    if (!this.archivo.fileId) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
+    }
+
+    this.createModalRef = this.modalService.open(RequestFeedbackModal);
+
+    this.createModalRef.componentInstance.groupName = null;
+    this.createModalRef.componentInstance.groupId = this.groupId;
+    this.createModalRef.componentInstance.fileName = this.archivo.nombre;
+    this.createModalRef.componentInstance.fileId = this.archivo.fileId;
+    this.createModalRef.componentInstance.confirmFeedbackRequested =
+      this.confirmFeedbackRequsted;
+  }
+
+  confirmFeedbackRequsted() {}
+
+  openReturnFeedbackModal() {
+    if (!this.groupId) {
+      this.showNotification("warning", "i18n.warning.group.noSelected");
+      return;
+    }
+
+    if (!this.archivo.fileId) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
+    }
+
+    this.createModalRef = this.modalService.open(ReturnFeedbackModal);
+
+    this.createModalRef.componentInstance.groupName = null;
+    this.createModalRef.componentInstance.groupId = this.groupId;
+    this.createModalRef.componentInstance.fileName = this.archivo.nombre;
+    this.createModalRef.componentInstance.fileId = this.archivo.fileId;
+    this.createModalRef.componentInstance.userId = this.groupUserId;
+    this.createModalRef.componentInstance.confirmFeedbackReturned =
+      this.confirmFeedbackReturned;
+  }
+
+  confirmFeedbackReturned() {}
+
   ngOnChanges() {
     // const docId = this.route.snapshot.paramMap.get("id");
     // this.confirmFileCreation = this.confirmFileCreation.bind(this);
@@ -458,10 +589,34 @@ export class MateFunComponent implements OnInit, OnChanges {
     this.configCodeMirrorDefinicion["readOnly"] = true;
 
     // This was always on ngOnInit
+
+    this.groupId = !!this.route.snapshot.paramMap.get("groupId")
+      ? parseInt(this.route.snapshot.paramMap.get("groupId"))
+      : null;
+    this.groupUserId = !!this.route.snapshot.paramMap.get("groupUserId")
+      ? parseInt(this.route.snapshot.paramMap.get("groupUserId"))
+      : null;
+
     if (!!this.route.snapshot.paramMap.get("id")) {
       this.getCurrentDocument(() => {
         this.copiaContenidoArchivo = this.archivo.contenido;
         this.copiaNombreArchivo = this.archivo.nombre;
+
+        if (
+          this.router.url.includes("grupos") &&
+          !!this.groupId &&
+          !!this.groupUserId
+        ) {
+          this.groupFileService
+            .getSpecificUserGroupFiles(this.groupId, this.groupUserId)
+            .subscribe((data) => {
+              this.importFilesData(data);
+            });
+        } else {
+          this.fileService.getFiles().subscribe((data) => {
+            this.importFilesData(data);
+          });
+        }
       });
     } else {
       this.newFile();
@@ -588,38 +743,42 @@ export class MateFunComponent implements OnInit, OnChanges {
       this.configCodeMirror.mode.name = "matefun-ES";
     }
 
-    this.fileService.getFiles().subscribe((files) => {
-      let matefun_files = files["files"];
-      matefun_files = this.fileService.completeFiles(matefun_files);
-      this.sessionService.setUserFiles(matefun_files);
-      let fileArchivos = this.fileService.fileToArchivo(matefun_files);
-
-      this.archivosTree = fileArchivos;
-      if (!!this.editor) {
-        (this.editor as any).options.files = this.archivosTree;
-      }
-      this.sessionService.setArchivosTree(this.archivosTree);
-    });
+    // this.fileService.getFiles().subscribe((files) => {
+    //   let matefun_files = files["files"];
+    //   matefun_files = this.fileService.completeFiles(matefun_files);
+    //   this.sessionService.setUserFiles(matefun_files);
+    //   let fileArchivos = this.fileService.fileToArchivo(matefun_files);
+
+    //   this.archivosTree = fileArchivos;
+    //   if (!!this.editor) {
+    //     (this.editor as any).options.files = this.archivosTree;
+    //   }
+    //   this.sessionService.setArchivosTree(this.archivosTree);
+    // });
+  }
 
-    this.fileService.getFiles().subscribe((data) => {
-      let matefun_files = data["files"];
-      matefun_files = this.fileService.completeFiles(matefun_files);
-      const tree = this.fileService.fileToArchivo(matefun_files);
-      this.archivosTree = tree;
-      this.sessionService.setArchivosTree(tree);
-      const file = findInTree([tree], this.archivo.fileId);
-      const directorioActual = findInTree([tree], file.padreId);
-      this.sessionService.setDirectorioActual(directorioActual);
-      this.documentService
-        .getDocuments(
-          directorioActual.archivos.map((archivo) => {
-            return archivo.documentId;
-          })
-        )
-        .subscribe((data) => {
-          this.sessionService.setCurrentDirectoryDocuments(data["documents"]);
-        });
-    });
+  importFilesData(data) {
+    let matefun_files = data["files"];
+    matefun_files = this.fileService.completeFiles(matefun_files);
+    this.sessionService.setUserFiles(matefun_files);
+    const tree = this.fileService.fileToArchivo(matefun_files);
+    this.archivosTree = tree;
+    if (!!this.editor) {
+      (this.editor as any).options.files = this.archivosTree;
+    }
+    this.sessionService.setArchivosTree(tree);
+    const file = findInTree([tree], this.archivo.fileId);
+    const directorioActual = findInTree([tree], file.padreId);
+    this.sessionService.setDirectorioActual(directorioActual);
+    this.documentService
+      .getDocuments(
+        directorioActual.archivos.map((archivo) => {
+          return archivo.documentId;
+        })
+      )
+      .subscribe((data) => {
+        this.sessionService.setCurrentDirectoryDocuments(data["documents"]);
+      });
   }
 
   ngAfterViewInit() {
@@ -767,15 +926,35 @@ export class MateFunComponent implements OnInit, OnChanges {
     if (!!this.archivo.users) {
       if (this.archivo.users.length > 0) {
         const id = JSON.parse(localStorage.getItem("currentUser"))["id"];
-        readOnly =
-          this.archivo.users.find((user) => {
-            return user.id === id;
-          }).role === "viewer";
+        const user = this.archivo.users.find((user) => {
+          return user.id === id;
+        });
+        this.documentBelongsToCurrentUser = !!user;
 
-        this.editor.setOption("readOnly", readOnly);
+        if (!!user) {
+          readOnly = user.role === "viewer";
+        }
+        if ((!user || readOnly == true) && !!this.groupId) {
+          const id = JSON.parse(localStorage.getItem("currentUser"))["id"];
+          this.groupService.getUserRole(this.groupId, id).subscribe((data) => {
+            this.currentUserGroupRole = data.role.role;
+
+            this.editor.setOption(
+              "readOnly",
+              this.groupService.userCanEditAnyGroupFile(
+                this.currentUserGroupRole
+              )
+            );
+          });
+        } else {
+          this.editor.setOption("readOnly", readOnly);
+        }
       } else {
+        this.documentBelongsToCurrentUser = true;
         this.editor.setOption("readOnly", false);
       }
+    } else {
+      this.documentBelongsToCurrentUser = true;
     }
 
     if (!!this.binding) {
@@ -802,11 +981,22 @@ export class MateFunComponent implements OnInit, OnChanges {
 
     this.ydoc = yDocument;
 
+    let params: Record<string, string> = {
+      doc_id: this.documentId?.toString(),
+    };
+    if (!!this.groupId && !!this.groupUserId) {
+      params = {
+        ...params,
+        user_id: this.groupUserId.toString(),
+        group_id: this.groupId.toString(),
+      };
+    }
+
     this.provider = new WebsocketProvider(
       this.ydoc,
       this.actionCableService.consumer,
       "ApplicationCable::DocumentChannel",
-      { doc_id: this.documentId?.toString() }
+      params
     );
 
     let awareness = undefined;
diff --git a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts
index 62e1962..f4b4582 100644
--- a/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts	
+++ b/Frontend Angular 4/src/app/shared/components/destroy-group-modal/destroy-group-modal.component.ts	
@@ -17,11 +17,12 @@ import { GroupService } from "../../services/group.service";
 export class DestroyGroupModal implements OnInit {
   @Input() groupName: string;
   @Input() groupId: number;
+  @Input() userRole: string;
   /**
    * Se dispara cuando se quiere administrar los usuarios del grupo
    */
   @Input() confirmGroupDestroyed: () => void;
-  title = "Destruir Grupo";
+  title: string;
   message: string;
 
   constructor(
@@ -32,7 +33,13 @@ export class DestroyGroupModal implements OnInit {
   ngOnInit(): void {
     this.groupName = `${this.groupName}`;
 
-    this.message = `¿Estás seguro de que deseas destruir el grupo ${this.groupName}?`;
+    if (this.userRole === "owner") {
+      this.title = "Destruir Grupo";
+      this.message = `¿Estás seguro de que deseas destruir el grupo ${this.groupName}?`;
+    } else {
+      this.title = "Abandonar Grupo";
+      this.message = `¿Estás seguro de que desea abandonar el grupo ${this.groupName}?`;
+    }
     this.destroyGroup = this.destroyGroup.bind(this);
     this.cancel = this.cancel.bind(this);
   }
diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts
index d7aadd0..ebf3490 100755
--- a/Frontend Angular 4/src/app/shared/components/index.ts	
+++ b/Frontend Angular 4/src/app/shared/components/index.ts	
@@ -12,3 +12,5 @@ export * from "./create-group-modal/create-group-modal.component";
 export * from "./manage-group-users-modal/manage-group-users-modal.component";
 export * from "./action-confirmation-modal/action-confirmation-modal.component";
 export * from "./destroy-group-modal/destroy-group-modal.component";
+export * from "./request-feedback-modal/request-feedback-modal.component";
+export * from "./return-feedback-modal/return-feedback-modal.component";
diff --git a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html
new file mode 100644
index 0000000..626e7c6
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.html	
@@ -0,0 +1,6 @@
+<app-action-confirmation-modal
+  [title]="title"
+  [message]="message"
+  [confirmAction]="requestFeedback"
+  [cancelAction]="cancel"
+></app-action-confirmation-modal>
diff --git a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts
new file mode 100644
index 0000000..ee43f09
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts	
@@ -0,0 +1,65 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { GroupFileService } from "../../services/group-file.service";
+
+@Component({
+  selector: "app-request-feedback-modal",
+  templateUrl: "./request-feedback-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class RequestFeedbackModal implements OnInit {
+  // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal,
+  //       y luego terminar de hacer la llamada y todo en el GroupComponent
+  @Input() fileName: string;
+  @Input() groupName: string | null;
+  @Input() groupId: number;
+  @Input() fileId: number;
+
+  /**
+   * Se dispara cuando se quiere administrar los usuarios del grupo
+   */
+  @Input() confirmFeedbackRequested: () => void;
+  title = "Pedir retroalimentación";
+  message: string;
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupFileService: GroupFileService
+  ) {}
+
+  ngOnInit(): void {
+    this.fileName = `${this.fileName}`;
+
+    if (!!this.groupName) {
+      this.groupName = `${this.groupName}`;
+      this.message = `¿Desea pedir retroalimentación del archivo ${this.fileName} en el grupo ${this.groupName}?`;
+    } else {
+      this.message = `¿Desea pedir retroalimentación del archivo ${this.fileName}?`;
+    }
+
+    this.requestFeedback = this.requestFeedback.bind(this);
+    this.cancel = this.cancel.bind(this);
+  }
+
+  requestFeedback(): void {
+    this.groupFileService.requestFeedback(this.groupId, this.fileId).subscribe({
+      next: (_) => {
+        this.confirmFeedbackRequested();
+
+        this.modal.close();
+      },
+      error: (error) => console.log(error),
+    });
+  }
+
+  cancel(): void {
+    this.modal.dismiss();
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html
new file mode 100644
index 0000000..0451b0c
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.html	
@@ -0,0 +1,6 @@
+<app-action-confirmation-modal
+  [title]="title"
+  [message]="message"
+  [confirmAction]="destroyGroup"
+  [cancelAction]="cancel"
+></app-action-confirmation-modal>
diff --git a/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts
new file mode 100644
index 0000000..1f81dd5
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/request-group-file-feedback-modal/request-group-file-feedback-modal.component.ts	
@@ -0,0 +1,54 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { GroupService } from "../../services/group.service";
+
+@Component({
+  selector: "app-destroy-group-modal",
+  templateUrl: "./destroy-group-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class RequestGroupFileModal implements OnInit {
+  @Input() groupName: string;
+  @Input() fileName: string;
+  @Input() groupId: number;
+  @Input() fileId: number;
+  /**
+   * Se dispara cuando se quiere administrar los usuarios del grupo
+   */
+  title: string;
+  message: string;
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupService: GroupService
+  ) {}
+
+  ngOnInit(): void {
+    this.groupName = `${this.groupName}`;
+
+    this.title = "Pedir Retroalimentación";
+    this.message = `¿Estás seguro de que desea pedir retroalimentación para el archivo ${this.fileName}? \n Esta decisión no se puede deshacer.`;
+    this.requestFeedback = this.requestFeedback.bind(this);
+    this.cancel = this.cancel.bind(this);
+  }
+
+  requestFeedback(): void {
+    this.groupService.requestFeedback(this.groupId, this.fileId).subscribe({
+      next: (_) => {
+        this.modal.close();
+      },
+      error: (error) => console.log(error),
+    });
+  }
+
+  cancel(): void {
+    this.modal.dismiss();
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html
new file mode 100644
index 0000000..8f8bd9d
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.html	
@@ -0,0 +1,6 @@
+<app-action-confirmation-modal
+  [title]="title"
+  [message]="message"
+  [confirmAction]="returnFeedback"
+  [cancelAction]="cancel"
+></app-action-confirmation-modal>
diff --git a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts
new file mode 100644
index 0000000..550dbe3
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts	
@@ -0,0 +1,68 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { GroupFileService } from "../../services/group-file.service";
+
+@Component({
+  selector: "app-return-feedback-modal",
+  templateUrl: "./return-feedback-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ReturnFeedbackModal implements OnInit {
+  // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal,
+  //       y luego terminar de hacer la llamada y todo en el GroupComponent
+  @Input() fileName: string;
+  @Input() groupName: string | null;
+  @Input() groupId: number;
+  @Input() fileId: number;
+  @Input() userId: number;
+
+  /**
+   * Se dispara cuando se quiere administrar los usuarios del grupo
+   */
+  @Input() confirmFeedbackReturned: () => void;
+  title = "Dar retroalimentación";
+  message: string;
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupFileService: GroupFileService
+  ) {}
+
+  ngOnInit(): void {
+    this.fileName = `${this.fileName}`;
+
+    if (!!this.groupName) {
+      this.groupName = `${this.groupName}`;
+      this.message = `¿Desea dar retroalimentación del archivo ${this.fileName} en el grupo ${this.groupName}?`;
+    } else {
+      this.message = `¿Desea dar retroalimentación del archivo ${this.fileName}?`;
+    }
+
+    this.returnFeedback = this.returnFeedback.bind(this);
+    this.cancel = this.cancel.bind(this);
+  }
+
+  returnFeedback(): void {
+    this.groupFileService
+      .returnFeedback(this.groupId, this.fileId, this.userId)
+      .subscribe({
+        next: (_) => {
+          this.confirmFeedbackReturned();
+
+          this.modal.close();
+        },
+        error: (error) => console.log(error),
+      });
+  }
+
+  cancel(): void {
+    this.modal.dismiss();
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts
index 1a9848d..953ce88 100755
--- a/Frontend Angular 4/src/app/shared/config.ts	
+++ b/Frontend Angular 4/src/app/shared/config.ts	
@@ -56,3 +56,19 @@ export const GET_GROUPS = SERVER + "/api/v2/groups";
 export const CREATE_GROUP = SERVER + "/api/v2/groups";
 export const UPDATE_GROUP = SERVER + "/api/v2/groups";
 export const DELETE_GROUP = SERVER + "/api/v2/groups";
+
+export const GET_GROUP_DOCUMENT =
+  SERVER + "/api/v2/groups/:group_id/documents/:id";
+export const GET_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files/:id";
+export const GET_GROUP_FILES = SERVER + "/api/v2/groups/:group_id/files";
+export const GET_GROUP_FILES_LISTS =
+  SERVER + "/api/v2/groups/:group_id/files/lists";
+export const CREATE_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files";
+export const UPDATE_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files";
+export const DELETE_GROUP_FILE = SERVER + "/api/v2/groups/:group_id/files";
+export const GET_GROUP_FEEDBACK_REQUESTED_FILES =
+  SERVER + "/api/v2/groups/:group_id/feedback_requested_files";
+export const CREATE_GROUP_FILE_FEEDBACK_REQUEST =
+  SERVER + "/api/v2/groups/:group_id/feedback_requests";
+export const GET_GROUP_USER_ROLE =
+  SERVER + "/api/v2/groups/:group_id/users/:user_id/role";
diff --git a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts
index 15e62bc..111b577 100755
--- a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts	
+++ b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts	
@@ -22,9 +22,11 @@ export class Archivo {
   eliminado: boolean;
   evaluacion: Evaluacion;
   documentId: number;
+  groupId?: number;
   fileId?: number;
   users?: User[];
   role?: string;
+  feedbackRequested?: boolean;
 
   constructor() {}
 }
@@ -39,8 +41,10 @@ export class MFile {
   parent_id?: number;
   directory: boolean;
   children?: MFile[];
+  group_id?: number;
   users?: User[];
   role?: string;
+  feedback_requested?: boolean;
 
   constructor() {}
 }
diff --git a/Frontend Angular 4/src/app/shared/services/document.service.ts b/Frontend Angular 4/src/app/shared/services/document.service.ts
index 9883de7..4978249 100644
--- a/Frontend Angular 4/src/app/shared/services/document.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/document.service.ts	
@@ -114,10 +114,17 @@ export class DocumentService {
     };
   }
 
-  createDocument(document: MDocument): Observable<HttpResponse<MDocument>> {
+  createDocument(
+    document: MDocument,
+    groupId?: number
+  ): Observable<HttpResponse<MDocument>> {
+    const params = { ...document };
+    if (groupId) {
+      params["group_id"] = groupId;
+    }
     return this.http.post<MDocument>(
       CREATE_DOCUMENT,
-      { document },
+      { document: params },
       {
         observe: "response",
         headers: this.postHeaders(),
diff --git a/Frontend Angular 4/src/app/shared/services/file.service.ts b/Frontend Angular 4/src/app/shared/services/file.service.ts
index f1d56c3..e67468b 100644
--- a/Frontend Angular 4/src/app/shared/services/file.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/file.service.ts	
@@ -107,6 +107,8 @@ export class FileService {
     archivo.evaluacion = null;
     archivo.users = file.users;
     archivo.role = file.role;
+    archivo.groupId = file.group_id;
+    archivo.feedbackRequested = file.feedback_requested;
 
     if (!!file.children) {
       archivo.archivos = file.children.map<Archivo>((child) => {
@@ -145,6 +147,8 @@ export class FileService {
       children: [],
       directory: archivo.directorio,
       role: archivo.role,
+      group_id: archivo.groupId,
+      feedback_requested: archivo.feedbackRequested,
     };
 
     file.children = archivo.archivos.map<MFile>((archivoHijo) => {
diff --git a/Frontend Angular 4/src/app/shared/services/group-document.service.ts b/Frontend Angular 4/src/app/shared/services/group-document.service.ts
new file mode 100644
index 0000000..b1df8dd
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/services/group-document.service.ts	
@@ -0,0 +1,311 @@
+import { throwError as observableThrowError, Observable } from "rxjs";
+import { Injectable } from "@angular/core";
+import { Router } from "@angular/router";
+import {
+  HttpClient,
+  HttpHeaders,
+  HttpParams,
+  HttpResponse,
+} from "@angular/common/http";
+import {
+  Archivo,
+  Evaluacion,
+  MFile,
+  MDocument,
+} from "../objects/archivo-types";
+import { Group } from "../objects/grupo";
+
+import { SERVER } from "../config";
+import { TranslateService } from "@ngx-translate/core";
+import { AuthenticationService } from "./authentication.service";
+import { catchError } from "rxjs/operators";
+import { GET_GROUP_DOCUMENT } from "../config";
+
+@Injectable()
+export class GroupDocumentService {
+  translateService: any;
+
+  /**
+   * Creates a new DocumentService with the injected HttpClient.
+   * @param {HttpClient} http - The injected HttpClient.
+   * @constructor
+   */
+  constructor(
+    private http: HttpClient,
+    private router: Router,
+    private authService: AuthenticationService,
+    public translate: TranslateService
+  ) {
+    this.translateService = translate;
+  }
+
+  private getHeaders() {
+    return new HttpHeaders({
+      "Content-Type": "application/json",
+      Authorization: "Bearer " + this.authService.getToken(),
+    });
+  }
+
+  private postHeaders() {
+    return new HttpHeaders({
+      "Content-Type": "application/json",
+      Accept: "application/json",
+      "Access-Control-Allow-Headers": "Content-Type",
+    });
+  }
+
+  // getDocuments(ids: number[]): Observable<{ documents: MDocument[] }> {
+  //   const params = new HttpParams({
+  //     fromObject: { "document_ids[]": ids },
+  //   });
+  //   return this.http
+  //     .get<{ documents: MDocument[] }>(GET_DOCUMENTS, {
+  //       headers: this.getHeaders(),
+  //       params: params,
+  //     })
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  getGroupDocument(
+    groupId: number,
+    id: number,
+    userId: number
+  ): Observable<{ document: MDocument }> {
+    let params = {};
+    if (!!userId) {
+      params = new HttpParams().set("user_id", userId);
+    }
+    return this.http
+      .get<{ document: MDocument }>(
+        GET_GROUP_DOCUMENT.replace(/:group_id/g, String(groupId)).replace(
+          /:id/g,
+          String(id)
+        ),
+        {
+          headers: this.getHeaders(),
+          params: params,
+        }
+      )
+      .pipe(catchError(this.handleError));
+  }
+
+  // documentToArchivo(document: MDocument): Archivo {
+  //   return {
+  //     id: document.id,
+  //     documentId: document.id,
+  //     nombre: document.title,
+  //     contenido: document.text_doc,
+  //     fechaCreacion: document.created_at,
+  //     cedulaCreador: document.users[0]?.email,
+  //     editable: true,
+  //     padreId: document.parent_id ? document.parent_id : -1,
+  //     fileId: document.file_id,
+  //     archivoOrigenId: null,
+  //     archivos: [],
+  //     directorio: false,
+  //     estado: "activo",
+  //     eliminado: false,
+  //     evaluacion: null,
+  //     users: document.users,
+  //     role: document.role,
+  //   };
+  // }
+
+  // archivoToDocument(archivo: Archivo): MDocument {
+  //   return {
+  //     id: archivo.id,
+  //     title: archivo.nombre,
+  //     text_doc: archivo.contenido,
+  //     created_at: archivo.fechaCreacion,
+  //     parent_id: archivo.padreId,
+  //     role: archivo.role,
+  //     users: [],
+  //   };
+  // }
+
+  // createDocument(
+  //   document: MDocument,
+  //   groupId?: number
+  // ): Observable<HttpResponse<MDocument>> {
+  //   const params = { ...document };
+  //   if (groupId) {
+  //     params["group_id"] = groupId;
+  //   }
+  //   return this.http.post<MDocument>(
+  //     CREATE_DOCUMENT,
+  //     { document: params },
+  //     {
+  //       observe: "response",
+  //       headers: this.postHeaders(),
+  //     }
+  //   );
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // getArchivos(cedula: string): Observable<Archivo[]> {
+  //   let headers = this.getHeaders();
+  //   let params: HttpParams = new HttpParams();
+  //   params = params.set("cedula", cedula);
+  //   let httpOptions = { headers: headers, params: params };
+
+  //   return this.http
+  //     .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions)
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // getArchivosCompartidosAlumno(cedula: string): Observable<Archivo[]> {
+  //   let headers = this.getHeaders();
+  //   let params: HttpParams = new HttpParams();
+  //   params = params.set("cedula", cedula);
+  //   params = params.set("compartidos", "true");
+  //   let httpOptions = { headers: headers, params: params };
+
+  //   return this.http
+  //     .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions)
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  // createFile(
+  //   file_title,
+  //   file_binary_doc_text
+  // ): Observable<HttpResponse<Object>> {
+  //   return this.http.post<Object>(
+  //     CREATE_DOCUMENT,
+  //     {
+  //       document: { title: file_title, text_doc: file_binary_doc_text },
+  //     },
+  //     {
+  //       observe: "response",
+  //       headers: this.postHeaders(),
+  //     }
+  //   );
+  // }
+
+  // updateDocument(
+  //   file_id,
+  //   file_title,
+  //   save_document_content = false
+  // ): Observable<HttpResponse<Object>> {
+  //   return this.http.patch<Object>(
+  //     `${UPDATE_DOCUMENT}/${file_id}`,
+  //     {
+  //       document: {
+  //         title: file_title,
+  //         save_document_content: save_document_content,
+  //       },
+  //     },
+  //     {
+  //       observe: "response",
+  //       headers: this.postHeaders(),
+  //     }
+  //   );
+  // }
+
+  // destroyAuxDocument(): Observable<HttpResponse<Object>> {
+  //   return this.http.delete<Object>(DESTROY_AUX_DOCUMENT, {
+  //     observe: "response",
+  //     headers: this.postHeaders(),
+  //   });
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // eliminarArchivo(archivoId): Observable<Response> {
+  //   let headers = this.getHeaders();
+  //   let httpOptions = { headers: headers };
+
+  //   return this.http
+  //     .delete<Response>(SERVER + "/servicios/archivo/" + archivoId, httpOptions)
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // getCopiaArchivoCompartidoGrupo(cedula, archivoId): Observable<Archivo> {
+  //   let headers = this.getHeaders();
+  //   let params: HttpParams = new HttpParams();
+  //   params = params.set("cedula", cedula);
+  //   let httpOptions = { headers: headers, params: params };
+
+  //   return this.http
+  //     .get<Archivo>(
+  //       SERVER + "/servicios/archivo/compartido/" + archivoId,
+  //       httpOptions
+  //     )
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // compartirArchivoGrupo(grupo, archivoId): Observable<Archivo> {
+  //   let headers = this.getHeaders();
+  //   let httpOptions = { headers: headers };
+  //   var archId = {
+  //     id: archivoId,
+  //   };
+  //   return this.http
+  //     .post<Archivo>(
+  //       SERVER +
+  //         "/servicios/grupo/" +
+  //         grupo.liceoId +
+  //         "/" +
+  //         grupo.anio +
+  //         "/" +
+  //         grupo.grado +
+  //         "/" +
+  //         grupo.grupo +
+  //         "/archivo",
+  //       archId,
+  //       httpOptions
+  //     )
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // calificarArchivo(archivoId, estado, evaluacion): Observable<Evaluacion> {
+  //   let headers = this.getHeaders();
+  //   let httpOptions = { headers: headers };
+  //   return this.http
+  //     .post<Evaluacion>(
+  //       SERVER +
+  //         "/servicios/archivo/" +
+  //         archivoId +
+  //         "/" +
+  //         estado +
+  //         "/evaluacion",
+  //       evaluacion,
+  //       httpOptions
+  //     )
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  // // Legacy method, need to check if it's still used
+  // getGrupos(cedula: string): Observable<Group[]> {
+  //   let headers = this.getHeaders();
+  //   let params: HttpParams = new HttpParams();
+  //   params = params.set("cedula", cedula);
+  //   let httpOptions = { headers: headers, params: params };
+
+  //   return this.http
+  //     .get<Group[]>(SERVER + "/servicios/grupo", httpOptions)
+  //     .pipe(catchError(this.handleError));
+  // }
+
+  /**
+   * Handle HTTP error
+   */
+  private handleError(error: any) {
+    if (error.status == 401) {
+      this.translateService
+        .get("i18n.code")
+        .subscribe((res) => this.router.navigate(["/" + res + "/login"]));
+    }
+    // In a real world app, we might use a remote logging infrastructure
+    // We'd also dig deeper into the error to get a better message
+    let errMsg = error.message
+      ? error.message
+      : error.status
+      ? `${error.status} - ${error.statusText}`
+      : "Server error";
+    console.error(errMsg); // log to console instead
+    return observableThrowError(errMsg);
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/services/group-file.service.ts b/Frontend Angular 4/src/app/shared/services/group-file.service.ts
new file mode 100644
index 0000000..210aef5
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/services/group-file.service.ts	
@@ -0,0 +1,264 @@
+import { throwError as observableThrowError, Observable } from "rxjs";
+import { Injectable } from "@angular/core";
+import { Router } from "@angular/router";
+import {
+  HttpClient,
+  HttpHeaders,
+  HttpParams,
+  HttpResponse,
+} from "@angular/common/http";
+import { Archivo, Evaluacion, MFile } from "../objects/archivo-types";
+
+import { TranslateService } from "@ngx-translate/core";
+import { AuthenticationService } from "./authentication.service";
+import { catchError } from "rxjs/operators";
+import {
+  CREATE_GROUP_FILE,
+  UPDATE_GROUP_FILE,
+  DELETE_GROUP_FILE,
+  GET_GROUP_FILE,
+  GET_GROUP_FILES,
+  GET_GROUP_FILES_LISTS,
+  GET_GROUP_FEEDBACK_REQUESTED_FILES,
+} from "../config";
+
+@Injectable()
+export class GroupFileService {
+  translateService: any;
+
+  /**
+   * Creates a new FileService with the injected HttpClient.
+   * @param {HttpClient} http - The injected HttpClient.
+   * @constructor
+   */
+  constructor(
+    private http: HttpClient,
+    private router: Router,
+    private authService: AuthenticationService,
+    public translate: TranslateService
+  ) {
+    this.translateService = translate;
+  }
+
+  private getHeaders() {
+    return new HttpHeaders({
+      "Content-Type": "application/json",
+      Authorization: "Bearer " + this.authService.getToken(),
+    });
+  }
+
+  private postHeaders() {
+    return new HttpHeaders({
+      "Content-Type": "application/json",
+      Accept: "application/json",
+      "Access-Control-Allow-Headers": "Content-Type",
+    });
+  }
+
+  getGroupFile(groupId: number, id: number): Observable<{ file: MFile }> {
+    return this.http
+      .get<{ file: MFile }>(
+        GET_GROUP_FILE.replace(/:group_id/g, String(groupId)).replace(
+          /:id/g,
+          String(id)
+        ),
+        {
+          headers: this.getHeaders(),
+        }
+      )
+      .pipe(catchError(this.handleError));
+  }
+
+  getSpecificUserFileWithSetGroup(groupId: number, userId: number) {
+    return {
+      getFile: (id: number) => {
+        return this.getSpecificUserGroupFile(groupId, userId, id);
+      },
+    };
+  }
+
+  getGroupFiles(groupId: number): Observable<MFile[]> {
+    return this.http
+      .get<MFile[]>(GET_GROUP_FILES.replace(/:group_id/g, String(groupId)), {
+        headers: this.getHeaders(),
+      })
+      .pipe(catchError(this.handleError));
+  }
+
+  getSpecificUserGroupFiles(
+    groupId: number,
+    userId: number
+  ): Observable<MFile[]> {
+    return this.http
+      .get<MFile[]>(GET_GROUP_FILES.replace(/:group_id/g, String(groupId)), {
+        headers: this.getHeaders(),
+        params: new HttpParams().set("user_id", userId),
+      })
+      .pipe(catchError(this.handleError));
+  }
+
+  getSpecificUserGroupFile(
+    groupId: number,
+    userId: number,
+    fileId: number
+  ): Observable<{ file: MFile }> {
+    return this.http
+      .get<{ file: MFile }>(
+        GET_GROUP_FILE.replace(/:group_id/g, String(groupId)).replace(
+          /:id/g,
+          String(fileId)
+        ),
+        {
+          headers: this.getHeaders(),
+          params: new HttpParams().set("user_id", userId),
+        }
+      )
+      .pipe(catchError(this.handleError));
+  }
+
+  getGroupFilesList(
+    groupId: number,
+    options = {}
+  ): Observable<{ files: MFile[] }> {
+    const siblingsOfId = options["siblingsOfId"];
+    return this.http
+      .get<{ files: MFile[] }>(
+        GET_GROUP_FILES_LISTS.replace(/:group_id/g, String(groupId)),
+        {
+          headers: this.getHeaders(),
+          params: siblingsOfId
+            ? new HttpParams().set("siblings_of_id", siblingsOfId)
+            : null,
+        }
+      )
+      .pipe(catchError(this.handleError));
+  }
+
+  getFeedbackRequestedGroupFiles(
+    groupId: number,
+    userId?: number
+  ): Observable<MFile[]> {
+    let params = {};
+    if (userId) {
+      params["user_id"] = userId;
+    }
+    return this.http
+      .get<MFile[]>(
+        GET_GROUP_FEEDBACK_REQUESTED_FILES.replace(
+          /:group_id/g,
+          String(groupId)
+        ),
+        {
+          headers: this.getHeaders(),
+          params: params,
+        }
+      )
+      .pipe(catchError(this.handleError));
+  }
+
+  createGroupFile(
+    groupId: number,
+    file: MFile
+  ): Observable<HttpResponse<Object>> {
+    return this.http.post<Object>(
+      CREATE_GROUP_FILE.replace(/:group_id/g, String(groupId)),
+      {
+        file: {
+          title: file.title,
+          parent_id: file.parent_id,
+          directory: file.directory,
+        },
+      },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  updateGroupFile(
+    groupId: number,
+    file_id,
+    file_parent_id,
+    users
+  ): Observable<HttpResponse<Object>> {
+    const update_params = {};
+    if (file_parent_id) {
+      update_params["parent_id"] = file_parent_id;
+    }
+    if (users) {
+      users = users.map((user) => {
+        return { ...user, role: user.role.toLowerCase() };
+      });
+      update_params["users"] = users;
+    }
+
+    return this.http.patch<Object>(
+      `${UPDATE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${file_id}`,
+      { file: update_params },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  requestFeedback(
+    groupId: number,
+    fileId: number
+  ): Observable<HttpResponse<Object>> {
+    return this.http.patch<Object>(
+      `${UPDATE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${fileId}`,
+      { file: { feedback_requested: true } },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  returnFeedback(
+    groupId: number,
+    fileId: number,
+    userId: number
+  ): Observable<HttpResponse<Object>> {
+    return this.http.patch<Object>(
+      `${UPDATE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${fileId}`,
+      { file: { feedback_requested: false }, user_id: userId },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  deleteGroupFile(groupId: number, file_id): Observable<HttpResponse<Object>> {
+    return this.http.delete<Object>(
+      `${DELETE_GROUP_FILE.replace(/:group_id/g, String(groupId))}/${file_id}`,
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  /**
+   * Handle HTTP error
+   */
+  private handleError(error: any) {
+    if (error.status == 401) {
+      this.translateService
+        .get("i18n.code")
+        .subscribe((res) => this.router.navigate(["/" + res + "/login"]));
+    }
+    // In a real world app, we might use a remote logging infrastructure
+    // We'd also dig deeper into the error to get a better message
+    let errMsg = error.message
+      ? error.message
+      : error.status
+      ? `${error.status} - ${error.statusText}`
+      : "Server error";
+    console.error(errMsg); // log to console instead
+    return observableThrowError(errMsg);
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts
index f1335f8..bf3e8d8 100644
--- a/Frontend Angular 4/src/app/shared/services/group.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/group.service.ts	
@@ -19,6 +19,8 @@ import {
   DELETE_GROUP,
   GET_GROUP,
   GET_GROUPS,
+  CREATE_GROUP_FILE_FEEDBACK_REQUEST,
+  GET_GROUP_USER_ROLE,
 } from "../config";
 
 @Injectable()
@@ -210,6 +212,48 @@ export class GroupService {
   //   });
   // }
 
+  requestFeedback(
+    groupId: number,
+    fileId: number
+  ): Observable<HttpResponse<Object>> {
+    return this.http.patch<Object>(
+      `${CREATE_GROUP_FILE_FEEDBACK_REQUEST.replace(
+        /:group_id/g,
+        String(groupId)
+      )}`,
+      { feedback_request: { file_id: fileId } },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
+  getUserRole(
+    groupId: number,
+    userId: number
+  ): Observable<{ role: { role: string } }> {
+    return this.http
+      .get<{ role: { role: string } }>(
+        GET_GROUP_USER_ROLE.replace(/:group_id/g, String(groupId)).replace(
+          /:user_id/g,
+          String(userId)
+        ),
+        {
+          headers: this.getHeaders(),
+        }
+      )
+      .pipe(catchError(this.handleError));
+  }
+
+  userCanEditAnyGroupFile(role: string): boolean {
+    return role === "owner" || role === "moderator" || role === "manager";
+  }
+
+  userCanReturnFeedback(role: string): boolean {
+    return this.userCanEditAnyGroupFile(role);
+  }
+
   /**
    * Handle HTTP error
    */
diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts
index 06214de..8012c63 100644
--- a/Frontend Angular 4/src/app/shared/shared.module.ts	
+++ b/Frontend Angular 4/src/app/shared/shared.module.ts	
@@ -3,22 +3,26 @@ import { MainButtonComponent } from "./components/main-button/main-button.compon
 import { TitleCaseModule } from "../shared/modules/titlecase.module";
 import { I18nModule } from "../shared/modules/translate/i18n.module";
 import { CommonModule } from "@angular/common";
-import { CreateFileModal } from "./components";
-import { CreateGroupModal } from "./components";
-import { ImportFileModal } from "./components";
-import { ShareFileModal } from "./components";
-import { ManageGroupUsersModal } from "./components";
 import { FormsModule } from "@angular/forms";
-import { FolderInput } from "./components";
-import { SelectComponent } from "./components";
-import { ChipInputComponent } from "./components";
 import { MatFormFieldModule } from "@angular/material/form-field";
 import { MatChipsModule } from "@angular/material/chips";
 import { MatIconModule } from "@angular/material/icon";
 import { NgbDropdownModule } from "@ng-bootstrap/ng-bootstrap";
-import { AddUsersModal } from "./components";
-import { ActionConfirmationModal } from "./components";
-import { DestroyGroupModal } from "./components";
+import {
+  ActionConfirmationModal,
+  AddUsersModal,
+  ChipInputComponent,
+  CreateFileModal,
+  CreateGroupModal,
+  DestroyGroupModal,
+  FolderInput,
+  ImportFileModal,
+  ManageGroupUsersModal,
+  RequestFeedbackModal,
+  ReturnFeedbackModal,
+  SelectComponent,
+  ShareFileModal,
+} from "./components";
 @NgModule({
   imports: [
     TitleCaseModule,
@@ -43,6 +47,8 @@ import { DestroyGroupModal } from "./components";
     AddUsersModal,
     ActionConfirmationModal,
     DestroyGroupModal,
+    RequestFeedbackModal,
+    ReturnFeedbackModal,
   ],
   exports: [MainButtonComponent, CreateFileModal],
 })
diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json
index 246f220..a33b35e 100755
--- a/Frontend Angular 4/src/assets/i18n/en.json	
+++ b/Frontend Angular 4/src/assets/i18n/en.json	
@@ -33,7 +33,9 @@
       "close": "close",
       "qualify": "qualify",
       "confirm": "confirm",
-      "signup": "sign up"
+      "signup": "sign up",
+      "requestFeedback": "Request feedback",
+      "returnFeedback": "Return feedback"
     },
     "links": {
       "register": "Create account",
diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json
index 44ac256..e3737b6 100755
--- a/Frontend Angular 4/src/assets/i18n/es.json	
+++ b/Frontend Angular 4/src/assets/i18n/es.json	
@@ -33,7 +33,9 @@
       "close": "cerrar",
       "qualify": "calificar",
       "confirm": "confirmar",
-      "signup": "registrarse"
+      "signup": "registrarse",
+      "requestFeedback": "Solicitar retroalimentación",
+      "returnFeedback": "Devolver retroalimentación"
     },
     "links": {
       "register": "Registrarse",
diff --git a/Frontend Angular 4/src/styles/console.css b/Frontend Angular 4/src/styles/console.css
index 4a44020..6441af3 100755
--- a/Frontend Angular 4/src/styles/console.css	
+++ b/Frontend Angular 4/src/styles/console.css	
@@ -70,7 +70,7 @@
 
 .nomArchivoInp {
   /*width: 55% !important;*/
-  width: calc(100% - 315px);
+  width: calc(100% - 345px);
   float: left;
 }
 
-- 
GitLab


From 3d091ccdaa52f75d50d092f501fb4cb203c3f3b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Thu, 6 Feb 2025 18:08:48 -0300
Subject: [PATCH 4/9] wip: User can assign files

---
 .../app/layout/grupos/grupos.component.html   |  31 ++++
 .../src/app/layout/grupos/grupos.component.ts | 133 +++++++++++++++++-
 .../assign-file-modal.component.html          |  33 +++++
 .../assign-file-modal.component.ts            |  79 +++++++++++
 .../checkbox-select.component.html            |  33 +++++
 .../checkbox-select.component.ts              |  64 +++++++++
 .../src/app/shared/components/index.ts        |   2 +
 Frontend Angular 4/src/app/shared/config.ts   |   2 +
 .../src/app/shared/objects/archivo-types.ts   |   4 +
 .../app/shared/services/group-file.service.ts |  20 +++
 .../src/app/shared/services/group.service.ts  |   4 +
 .../src/app/shared/shared.module.ts           |   6 +
 12 files changed, 404 insertions(+), 7 deletions(-)
 create mode 100644 Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts
 create mode 100644 Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html
 create mode 100644 Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html
index 74c0e81..8c5625d 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
@@ -323,6 +323,18 @@
           >
             <i aria-hidden="true" class="fa fa-exchange"></i>
           </button>
+          <button
+            *ngIf="canAssignFile()"
+            ngbPopover="{{
+              'i18n.action.assignFile' | translate | titleCase
+            }} {{ 'i18n.object.file' | translate | titleCase }}"
+            placement="bottom"
+            triggers="mouseenter:mouseleave"
+            class="btn btn-sm btn-secondary pull-left mr-2"
+            (click)="openAssignFileModal()"
+          >
+            <i aria-hidden="true" class="fa fa-share"></i>
+          </button>
           <button
             *ngIf="canReturnFeedback()"
             ngbPopover="{{
@@ -411,3 +423,22 @@
   *ngIf="modalMoveFile"
 >
 </matefun-modal-seleccionar-directorio>
+
+<matefun-modal-borrar-archivo
+  bodyDescription="{{ 'i18n.msg.file.delete' | translate : fileNameToRemove }}"
+  cancelLabel="{{ 'i18n.action.cancel' | translate | titleCase }}"
+  confirmLabel="{{ 'i18n.action.delete' | translate | titleCase }}"
+  header="{{ 'i18n.action.delete' | translate | titleCase }}
+  {{
+    (modalTypeIsFile ? 'i18n.object.file' : 'i18n.object.folder')
+      | translate
+      | titleCase
+  }}"
+  [opened]="modalRemoveFileOpened"
+  [typeOfFile]="modalTypeIsFile ? 'file' : 'directory'"
+  (close)="modalRemoveFile = false"
+  (cancelAction)="modalRemoveFileOpened = false"
+  (removeFile)="confirmFileDeletion()"
+  *ngIf="modalRemoveFile"
+>
+</matefun-modal-borrar-archivo>
diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts
index 02c939e..25adddd 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
@@ -31,6 +31,7 @@ import {
   DestroyGroupModal,
   RequestFeedbackModal,
   ReturnFeedbackModal,
+  AssignFileModal,
 } from "app/shared";
 
 const STARTS_WITH_CAPITAL_LETTER_REGEX = /^[A-Z]/;
@@ -139,6 +140,10 @@ export class GruposComponent {
    */
   modalMoveFileOpened = true;
 
+  fileNameToRemove: { fileName: string } = { fileName: "" };
+  modalRemoveFile = false;
+  modalRemoveFileOpened = true;
+
   constructor(
     private router: Router,
     private authService: AuthenticationService,
@@ -180,6 +185,8 @@ export class GruposComponent {
     this.confirmGroupCreation = this.confirmGroupCreation.bind(this);
     this.confirmGroupUpdated = this.confirmGroupUpdated.bind(this);
     this.confirmGroupDestroyed = this.confirmGroupDestroyed.bind(this);
+    this.confirmFeedbackRequsted = this.confirmFeedbackRequsted.bind(this);
+    this.confirmFeedbackReturned = this.confirmFeedbackReturned.bind(this);
 
     this.loading = true;
     // this.haskellService.getGrupos(cedula).subscribe(
@@ -373,16 +380,43 @@ export class GruposComponent {
 
     this.createModalRef = this.modalService.open(ReturnFeedbackModal);
 
+    this.createModalRef.componentInstance.confirmFeedbackReturned =
+      this.confirmFeedbackReturned;
     this.createModalRef.componentInstance.groupName = null;
-    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado;
+    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id;
     this.createModalRef.componentInstance.fileName = this.selectedFile.nombre;
     this.createModalRef.componentInstance.fileId = this.selectedFile.fileId;
-    this.createModalRef.componentInstance.userId = this.selectedUser.id;
-    this.createModalRef.componentInstance.confirmFeedbackReturned =
-      this.confirmFeedbackReturned;
+    this.createModalRef.componentInstance.userId =
+      this.selectedUser?.id ||
+      JSON.parse(localStorage.getItem("currentUser"))["id"];
   }
 
-  confirmFeedbackReturned() {}
+  confirmFeedbackReturned() {
+    if (!!this.selectedUser) {
+      this.selectedFile = null;
+      this.archivoSeleccionado = null;
+      this.seleccionarUser(this.selectedUser);
+    } else {
+      this.loadFilesAndFolders(
+        this.grupoSeleccionado.id,
+        this.directorioActual.id
+      );
+    }
+    // let selectedFile = this.selectedUserDirectorioActual?.archivos?.find(
+    //   (archivo) => archivo.documentId === this.selectedFile.id
+    // );
+    // if (!!selectedFile) {
+    //   this.seleccionarUser(this.selectedUser);
+    //   selectedFile.feedbackRequested = false;
+    // } else {
+    //   selectedFile = this.directorioActual.archivos.find(
+    //     (archivo) => archivo.documentId === this.selectedFile.id
+    //   );
+    //   if (!!selectedFile) {
+    //     selectedFile.feedbackRequested = false;
+    //   }
+    // }
+  }
 
   seleccionarArchivo(archivo) {
     this.selectedFile = archivo;
@@ -421,8 +455,11 @@ export class GruposComponent {
    * @param archivo Archivo a seleccionar
    */
   actualizarArchivoSeleccionado(archivo: Archivo) {
+    if (!!this.selectedFile) {
+      archivo.feedbackRequested = this.selectedFile.feedbackRequested;
+    }
     this.archivoSeleccionado = archivo;
-    this.selectedFile = archivo; //Probando
+    this.selectedFile = archivo; //Aquí el problema, se necesita para los que pidieron feedback, pero jode para los archivos propios
     this.preview = !!archivo?.contenido ? archivo.contenido : "";
   }
 
@@ -472,6 +509,7 @@ export class GruposComponent {
           const document = data["document"];
           const archivoDocument =
             this.documentService.documentToArchivo(document);
+          archivoDocument.feedbackRequested = true;
           this.actualizarArchivoSeleccionado(archivoDocument);
         },
         (error) => console.log(error)
@@ -518,7 +556,54 @@ export class GruposComponent {
       this.confirmFeedbackRequsted;
   }
 
-  confirmFeedbackRequsted() {}
+  confirmFeedbackRequsted() {
+    this.archivoSeleccionado.feedbackRequested = true;
+    this.selectedFile.feedbackRequested = true;
+    const fileInDirectory = this.directorioActual.archivos.find(
+      (archivo) => archivo.documentId === this.archivoSeleccionado.id
+    );
+    if (!!fileInDirectory) {
+      fileInDirectory.feedbackRequested = true;
+    }
+    this.changeDetectorRef.detectChanges();
+  }
+
+  mostrarEliminarDialogo() {
+    // Chequear si se seleccionó un archivo
+    if (!this.archivoSeleccionado) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
+    }
+
+    this.fileNameToRemove = {
+      fileName: this.archivoSeleccionado?.nombre || this.selectedFile?.nombre,
+    };
+
+    // Determina el tipo de modal a renderizar
+    this.modalTypeIsFile = !this.archivoSeleccionado.directorio;
+
+    // Mostrar el modal
+    this.modalRemoveFile = true;
+    this.modalRemoveFileOpened = true;
+  }
+
+  confirmFileDeletion() {
+    this.groupFileService
+      .deleteGroupFile(
+        this.grupoSeleccionado.id,
+        this.archivoSeleccionado?.fileId || this.selectedFile?.fileId
+      )
+      .subscribe(
+        (_) => {
+          this.modalRemoveFileOpened = false;
+          this.loadFilesAndFolders(
+            this.grupoSeleccionado.id,
+            this.directorioActual.id
+          );
+        },
+        (error) => console.log(error)
+      );
+  }
 
   openDestroyGroupModal() {
     if (!this.grupoSeleccionado) {
@@ -553,6 +638,40 @@ export class GruposComponent {
     //       );
   }
 
+  openAssignFileModal() {
+    if (!this.grupoSeleccionado) {
+      this.showNotification("warning", "i18n.warning.group.noSelected");
+      return;
+    }
+
+    if (!this.selectedFile.fileId) {
+      this.showNotification("warning", "i18n.warning.file.noSelected");
+      return;
+    }
+
+    this.createModalRef = this.modalService.open(AssignFileModal);
+
+    this.createModalRef.componentInstance.groupName = null;
+    this.createModalRef.componentInstance.groupId = this.grupoSeleccionado.id;
+    this.createModalRef.componentInstance.fileName = this.selectedFile.nombre;
+    this.createModalRef.componentInstance.fileId = this.selectedFile.fileId;
+    this.createModalRef.componentInstance.users = this.grupoSeleccionado.users;
+    this.createModalRef.componentInstance.userFilter = "role";
+    this.createModalRef.componentInstance.confirmFileAssigned =
+      this.confirmFileAssigned;
+  }
+
+  confirmFileAssigned() {}
+
+  canAssignFile() {
+    return (
+      !!this.grupoSeleccionado &&
+      !!this.selectedFile &&
+      !this.selectedUser &&
+      this.groupService.userCanAssignFiles(this.grupoSeleccionado.role)
+    );
+  }
+
   confirmGroupDestroyed() {
     this.desseleccionarGrupo();
     this.loadGroups();
diff --git a/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html
new file mode 100644
index 0000000..a8a4e23
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.html	
@@ -0,0 +1,33 @@
+<div class="modal-header">
+  <span class="modal-title font-bold" id="modal-title">{{ title }}</span>
+  <button
+    aria-label="Cerrar diálogo"
+    (click)="modal.dismiss('Close assign file modal')"
+    part="close-button"
+    class="close-button fa fa-close"
+    type="button"
+  ></button>
+</div>
+<div class="modal-body">
+  <div class="flex flex-col justify-between w-full">
+    <label for="new-users">Users</label>
+    <div class="flex justify-between w-full items-center">
+      <app-checkbox-select
+        [(entities)]="users"
+        entitiesName="user"
+        filterName="role"
+        class="w-full"
+      ></app-checkbox-select>
+    </div>
+  </div>
+  <div class="flex justify-end mt-2">
+    <button
+      (click)="assignFile()"
+      [disabled]="selectedUsers().length === 0"
+      class="btn btn-primary"
+      slot="primary"
+    >
+      Asignar
+    </button>
+  </div>
+</div>
diff --git a/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts
new file mode 100644
index 0000000..d7136f2
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/assign-file-modal/assign-file-modal.component.ts	
@@ -0,0 +1,79 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  OnInit,
+  ChangeDetectorRef,
+} from "@angular/core";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+import { GroupFileService } from "../../services/group-file.service";
+
+import { selectableUser } from "../../objects/archivo-types";
+
+@Component({
+  selector: "app-assign-file-modal",
+  templateUrl: "./assign-file-modal.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AssignFileModal implements OnInit {
+  @Input() fileName: string;
+  @Input() groupName: string | null;
+  @Input() groupId: number;
+  @Input() fileId: number;
+
+  @Input() users: selectableUser[] = [];
+  @Input() userFilter: string = null;
+
+  /**
+   * Se dispara cuando se quiere administrar los usuarios del grupo
+   */
+  @Input() confirmFileAssigned: () => void;
+  title = "Asignar archivo";
+
+  constructor(
+    public modal: NgbActiveModal,
+    private groupFileService: GroupFileService,
+    private changeDetectorRef: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {
+    this.fileName = `${this.fileName}`;
+
+    if (!!this.groupName) {
+      this.groupName = `${this.groupName}`;
+      this.title = `¿Asignar archivo ${this.fileName} en el grupo ${this.groupName}?`;
+    } else {
+      this.title = `¿Asignar archivo ${this.fileName}?`;
+    }
+
+    this.assignFile = this.assignFile.bind(this);
+    this.cancel = this.cancel.bind(this);
+  }
+
+  selectedUsers() {
+    return this.users.filter((user) => user.selected);
+  }
+
+  assignFile(): void {
+    this.groupFileService
+      .assignFile(
+        this.groupId,
+        this.fileId,
+        this.users.filter((user) => user.selected).map((user) => user.id),
+        [] // subgroups.map((subgroup) => subgroup.id)
+      )
+      .subscribe({
+        next: (_) => {
+          this.confirmFileAssigned();
+          this.modal.close();
+        },
+        error: (error) => console.log(error),
+      });
+  }
+
+  cancel(): void {
+    this.modal.dismiss();
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html
new file mode 100644
index 0000000..7b0dd18
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html	
@@ -0,0 +1,33 @@
+<div class="flex flex-col justify-between w-full">
+  <label for="new-users">Something</label>
+</div>
+<table class="table-fixed w-96 mx-auto">
+  <thead>
+    <tr>
+      <th>
+        <mat-checkbox
+          [checked]="allEntitiesSelected()"
+          (change)="onCheckboxChange($event.checked)"
+        >
+        </mat-checkbox>
+      </th>
+      <th>{{ entitiesName }}</th>
+      <th *ngIf="{filterName}">{{ filterName }}</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr *ngFor="let entity of entities" class="hover:bg-gray-200">
+      <th>
+        <mat-checkbox
+          [checked]="entity.selected"
+          (change)="selectDeselectEntity(entity)"
+        >
+        </mat-checkbox>
+      </th>
+      <td>{{ entity.username }}</td>
+      <ng-container *ngIf="{filterName}">
+        <td>{{ entity[filterName] }}</td>
+      </ng-container>
+    </tr>
+  </tbody>
+</table>
diff --git a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts
new file mode 100644
index 0000000..4aff606
--- /dev/null
+++ b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.ts	
@@ -0,0 +1,64 @@
+import {
+  Component,
+  ChangeDetectionStrategy,
+  Input,
+  Output,
+  EventEmitter,
+  OnInit,
+  ChangeDetectorRef,
+  OnChanges,
+  SimpleChanges,
+} from "@angular/core";
+
+import { selectableUser } from "../../objects/archivo-types";
+
+import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
+
+@Component({
+  selector: "app-checkbox-select",
+  templateUrl: "./checkbox-select.component.html",
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class CheckboxSelect implements OnInit, OnChanges {
+  @Input() entitiesName: string;
+  @Input() entities: selectableUser[] = [];
+  @Input() filterName: string;
+  @Output() entitiesChanged = new EventEmitter<selectableUser[]>();
+
+  constructor(
+    public modal: NgbActiveModal,
+    private changeDetectorRef: ChangeDetectorRef
+  ) {}
+
+  ngOnInit(): void {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    // if (!!changes.users) {
+    //   this.users.forEach((user) => {
+    //     this.usersObject[user.id] = user.role;
+    //   });
+    // }
+  }
+
+  selectDeselectEntity(entity: selectableUser): void {
+    entity.selected = !entity.selected;
+    this.entitiesChange(this.entities);
+  }
+
+  entitiesChange(entities: selectableUser[]): void {
+    this.entities = entities;
+    this.entitiesChanged.emit(this.entities);
+    this.changeDetectorRef.detectChanges();
+  }
+
+  allEntitiesSelected() {
+    return this.entities.every((entity) => entity.selected);
+  }
+
+  onCheckboxChange(checked: boolean): void {
+    this.entities.forEach((entity) => {
+      entity.selected = checked;
+    });
+    this.entitiesChange(this.entities);
+  }
+}
diff --git a/Frontend Angular 4/src/app/shared/components/index.ts b/Frontend Angular 4/src/app/shared/components/index.ts
index ebf3490..8822948 100755
--- a/Frontend Angular 4/src/app/shared/components/index.ts	
+++ b/Frontend Angular 4/src/app/shared/components/index.ts	
@@ -14,3 +14,5 @@ export * from "./action-confirmation-modal/action-confirmation-modal.component";
 export * from "./destroy-group-modal/destroy-group-modal.component";
 export * from "./request-feedback-modal/request-feedback-modal.component";
 export * from "./return-feedback-modal/return-feedback-modal.component";
+export * from "./assign-file-modal/assign-file-modal.component";
+export * from "./checkbox-select/checkbox-select.component";
diff --git a/Frontend Angular 4/src/app/shared/config.ts b/Frontend Angular 4/src/app/shared/config.ts
index 953ce88..01e774f 100755
--- a/Frontend Angular 4/src/app/shared/config.ts	
+++ b/Frontend Angular 4/src/app/shared/config.ts	
@@ -72,3 +72,5 @@ export const CREATE_GROUP_FILE_FEEDBACK_REQUEST =
   SERVER + "/api/v2/groups/:group_id/feedback_requests";
 export const GET_GROUP_USER_ROLE =
   SERVER + "/api/v2/groups/:group_id/users/:user_id/role";
+export const ASSIGN_FILE =
+  SERVER + "/api/v2/groups/:group_id/files/:file_id/assignment";
diff --git a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts
index 111b577..e2dfc84 100755
--- a/Frontend Angular 4/src/app/shared/objects/archivo-types.ts	
+++ b/Frontend Angular 4/src/app/shared/objects/archivo-types.ts	
@@ -75,6 +75,10 @@ export class User {
   role?: string;
 }
 
+export class selectableUser extends User {
+  selected: boolean;
+}
+
 export class Grupo {
   anio: number;
   grado: number;
diff --git a/Frontend Angular 4/src/app/shared/services/group-file.service.ts b/Frontend Angular 4/src/app/shared/services/group-file.service.ts
index 210aef5..e08db79 100644
--- a/Frontend Angular 4/src/app/shared/services/group-file.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/group-file.service.ts	
@@ -20,6 +20,7 @@ import {
   GET_GROUP_FILES,
   GET_GROUP_FILES_LISTS,
   GET_GROUP_FEEDBACK_REQUESTED_FILES,
+  ASSIGN_FILE,
 } from "../config";
 
 @Injectable()
@@ -242,6 +243,25 @@ export class GroupFileService {
     );
   }
 
+  assignFile(
+    groupId: number,
+    fileId: number,
+    usersIds,
+    subgroupsIds
+  ): Observable<HttpResponse<Object>> {
+    return this.http.post<Object>(
+      `${ASSIGN_FILE.replace(/:group_id/g, String(groupId)).replace(
+        /:file_id/g,
+        String(fileId)
+      )}`,
+      { assignment: { user_ids: usersIds } },
+      {
+        observe: "response",
+        headers: this.postHeaders(),
+      }
+    );
+  }
+
   /**
    * Handle HTTP error
    */
diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts
index bf3e8d8..8d42f40 100644
--- a/Frontend Angular 4/src/app/shared/services/group.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/group.service.ts	
@@ -254,6 +254,10 @@ export class GroupService {
     return this.userCanEditAnyGroupFile(role);
   }
 
+  userCanAssignFiles(role: string): boolean {
+    return this.userCanEditAnyGroupFile(role);
+  }
+
   /**
    * Handle HTTP error
    */
diff --git a/Frontend Angular 4/src/app/shared/shared.module.ts b/Frontend Angular 4/src/app/shared/shared.module.ts
index 8012c63..bf77a6d 100644
--- a/Frontend Angular 4/src/app/shared/shared.module.ts	
+++ b/Frontend Angular 4/src/app/shared/shared.module.ts	
@@ -6,6 +6,7 @@ import { CommonModule } from "@angular/common";
 import { FormsModule } from "@angular/forms";
 import { MatFormFieldModule } from "@angular/material/form-field";
 import { MatChipsModule } from "@angular/material/chips";
+import { MatCheckboxModule } from "@angular/material/checkbox";
 import { MatIconModule } from "@angular/material/icon";
 import { NgbDropdownModule } from "@ng-bootstrap/ng-bootstrap";
 import {
@@ -22,6 +23,8 @@ import {
   ReturnFeedbackModal,
   SelectComponent,
   ShareFileModal,
+  AssignFileModal,
+  CheckboxSelect,
 } from "./components";
 @NgModule({
   imports: [
@@ -31,6 +34,7 @@ import {
     FormsModule,
     MatFormFieldModule,
     MatChipsModule,
+    MatCheckboxModule,
     MatIconModule,
     NgbDropdownModule,
   ],
@@ -49,6 +53,8 @@ import {
     DestroyGroupModal,
     RequestFeedbackModal,
     ReturnFeedbackModal,
+    AssignFileModal,
+    CheckboxSelect,
   ],
   exports: [MainButtonComponent, CreateFileModal],
 })
-- 
GitLab


From f4b1b2ed9f80c1d5393a9343042782b6129bdb38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Fri, 7 Feb 2025 10:25:36 -0300
Subject: [PATCH 5/9] Add translation to assign files

---
 Frontend Angular 4/src/app/layout/grupos/grupos.component.html | 2 +-
 Frontend Angular 4/src/assets/i18n/en.json                     | 3 ++-
 Frontend Angular 4/src/assets/i18n/es.json                     | 3 ++-
 3 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html
index 8c5625d..eb4a69e 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.html	
@@ -327,7 +327,7 @@
             *ngIf="canAssignFile()"
             ngbPopover="{{
               'i18n.action.assignFile' | translate | titleCase
-            }} {{ 'i18n.object.file' | translate | titleCase }}"
+            }} {{ archivoSeleccionado.nombre }}"
             placement="bottom"
             triggers="mouseenter:mouseleave"
             class="btn btn-sm btn-secondary pull-left mr-2"
diff --git a/Frontend Angular 4/src/assets/i18n/en.json b/Frontend Angular 4/src/assets/i18n/en.json
index a33b35e..4f83be6 100755
--- a/Frontend Angular 4/src/assets/i18n/en.json	
+++ b/Frontend Angular 4/src/assets/i18n/en.json	
@@ -35,7 +35,8 @@
       "confirm": "confirm",
       "signup": "sign up",
       "requestFeedback": "Request feedback",
-      "returnFeedback": "Return feedback"
+      "returnFeedback": "Return feedback",
+      "assignFile": "Assign file"
     },
     "links": {
       "register": "Create account",
diff --git a/Frontend Angular 4/src/assets/i18n/es.json b/Frontend Angular 4/src/assets/i18n/es.json
index e3737b6..e7f1086 100755
--- a/Frontend Angular 4/src/assets/i18n/es.json	
+++ b/Frontend Angular 4/src/assets/i18n/es.json	
@@ -35,7 +35,8 @@
       "confirm": "confirmar",
       "signup": "registrarse",
       "requestFeedback": "Solicitar retroalimentación",
-      "returnFeedback": "Devolver retroalimentación"
+      "returnFeedback": "Devolver retroalimentación",
+      "assignFile": "Asignar archivo"
     },
     "links": {
       "register": "Registrarse",
-- 
GitLab


From 95becc1aca360781b51bda75bf257dcf0ce33275 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Fri, 7 Feb 2025 10:40:02 -0300
Subject: [PATCH 6/9] Check checkbox when clicking on row

---
 .../checkbox-select/checkbox-select.component.html         | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html
index 7b0dd18..80297d1 100644
--- a/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html	
+++ b/Frontend Angular 4/src/app/shared/components/checkbox-select/checkbox-select.component.html	
@@ -16,9 +16,14 @@
     </tr>
   </thead>
   <tbody>
-    <tr *ngFor="let entity of entities" class="hover:bg-gray-200">
+    <tr
+      *ngFor="let entity of entities"
+      (mouseup)="selectDeselectEntity(entity)"
+      class="hover:bg-gray-200"
+    >
       <th>
         <mat-checkbox
+          #inputCheckbox
           [checked]="entity.selected"
           (change)="selectDeselectEntity(entity)"
         >
-- 
GitLab


From d315164488caeaef607a0ecca7452e06a0df662b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Fri, 7 Feb 2025 11:08:04 -0300
Subject: [PATCH 7/9] Remove directories from groups

---
 .../src/app/layout/grupos/grupos.component.ts | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts
index 25adddd..0b0fe03 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
@@ -425,6 +425,7 @@ export class GruposComponent {
       // this.loadFilesAndFolders(this.grupoSeleccionado.id, archivo.id);
       this.archivoSeleccionado = undefined;
       this.selectedFile = undefined; // Probando
+      this.archivoSeleccionado = archivo;
       // this.selectedUser = undefined;
       this.currentPath += `${archivo.nombre}/`;
       this.preview = "";
@@ -588,14 +589,30 @@ export class GruposComponent {
   }
 
   confirmFileDeletion() {
+    let fileIdToDelete = null;
+    let navBack = false;
+    if (this.archivoSeleccionado?.fileId) {
+      fileIdToDelete = this.archivoSeleccionado.fileId;
+    } else if (this.selectedFile?.fileId) {
+      fileIdToDelete = this.selectedFile.fileId;
+    } else if (this.archivoSeleccionado?.id) {
+      fileIdToDelete = this.archivoSeleccionado.id;
+      navBack = true;
+    }
+
     this.groupFileService
       .deleteGroupFile(
         this.grupoSeleccionado.id,
-        this.archivoSeleccionado?.fileId || this.selectedFile?.fileId
+        this.archivoSeleccionado?.fileId ||
+          this.selectedFile?.fileId ||
+          this.archivoSeleccionado?.id
       )
       .subscribe(
         (_) => {
           this.modalRemoveFileOpened = false;
+          if (navBack) {
+            this.navBack();
+          }
           this.loadFilesAndFolders(
             this.grupoSeleccionado.id,
             this.directorioActual.id
-- 
GitLab


From 3f76250ef4180f3d546715d0a73126ea64af1228 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Tue, 11 Feb 2025 14:36:30 -0300
Subject: [PATCH 8/9] Fix condition so group moderators can edit all group
 files

---
 .../src/app/layout/matefun/matefun.component.ts               | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts
index f8a3199..dde9109 100755
--- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts	
+++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts	
@@ -923,6 +923,7 @@ export class MateFunComponent implements OnInit, OnChanges {
 
   initializeEditor() {
     let readOnly = false;
+
     if (!!this.archivo.users) {
       if (this.archivo.users.length > 0) {
         const id = JSON.parse(localStorage.getItem("currentUser"))["id"];
@@ -934,6 +935,7 @@ export class MateFunComponent implements OnInit, OnChanges {
         if (!!user) {
           readOnly = user.role === "viewer";
         }
+
         if ((!user || readOnly == true) && !!this.groupId) {
           const id = JSON.parse(localStorage.getItem("currentUser"))["id"];
           this.groupService.getUserRole(this.groupId, id).subscribe((data) => {
@@ -941,7 +943,7 @@ export class MateFunComponent implements OnInit, OnChanges {
 
             this.editor.setOption(
               "readOnly",
-              this.groupService.userCanEditAnyGroupFile(
+              !this.groupService.userCanEditAnyGroupFile(
                 this.currentUserGroupRole
               )
             );
-- 
GitLab


From 6e939451395eb9d38bb17728388c34b7af9889bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Diego=20Rodr=C3=ADguez?= <diego.rodriguez@rootstrap.com>
Date: Tue, 11 Feb 2025 14:45:27 -0300
Subject: [PATCH 9/9] Removed many comments

---
 .../src/app/layout/grupos/grupos.component.ts |  18 +-
 .../app/layout/matefun/matefun.component.ts   |  13 --
 .../manage-group-users-modal.component.html   |  38 ----
 .../manage-group-users-modal.component.ts     |   5 -
 .../request-feedback-modal.component.ts       |   2 -
 .../return-feedback-modal.component.ts        |   2 -
 .../shared/services/group-document.service.ts | 212 ------------------
 .../src/app/shared/services/group.service.ts  |  12 -
 8 files changed, 1 insertion(+), 301 deletions(-)

diff --git a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts
index 0b0fe03..45e72b6 100755
--- a/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
+++ b/Frontend Angular 4/src/app/layout/grupos/grupos.component.ts	
@@ -402,31 +402,15 @@ export class GruposComponent {
         this.directorioActual.id
       );
     }
-    // let selectedFile = this.selectedUserDirectorioActual?.archivos?.find(
-    //   (archivo) => archivo.documentId === this.selectedFile.id
-    // );
-    // if (!!selectedFile) {
-    //   this.seleccionarUser(this.selectedUser);
-    //   selectedFile.feedbackRequested = false;
-    // } else {
-    //   selectedFile = this.directorioActual.archivos.find(
-    //     (archivo) => archivo.documentId === this.selectedFile.id
-    //   );
-    //   if (!!selectedFile) {
-    //     selectedFile.feedbackRequested = false;
-    //   }
-    // }
   }
 
   seleccionarArchivo(archivo) {
     this.selectedFile = archivo;
     if (archivo.directorio) {
       this.directorioActual = archivo;
-      // this.loadFilesAndFolders(this.grupoSeleccionado.id, archivo.id);
       this.archivoSeleccionado = undefined;
       this.selectedFile = undefined; // Probando
       this.archivoSeleccionado = archivo;
-      // this.selectedUser = undefined;
       this.currentPath += `${archivo.nombre}/`;
       this.preview = "";
     } else {
@@ -460,7 +444,7 @@ export class GruposComponent {
       archivo.feedbackRequested = this.selectedFile.feedbackRequested;
     }
     this.archivoSeleccionado = archivo;
-    this.selectedFile = archivo; //Aquí el problema, se necesita para los que pidieron feedback, pero jode para los archivos propios
+    this.selectedFile = archivo;
     this.preview = !!archivo?.contenido ? archivo.contenido : "";
   }
 
diff --git a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts
index dde9109..1906dd1 100755
--- a/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts	
+++ b/Frontend Angular 4/src/app/layout/matefun/matefun.component.ts	
@@ -742,19 +742,6 @@ export class MateFunComponent implements OnInit, OnChanges {
     } else {
       this.configCodeMirror.mode.name = "matefun-ES";
     }
-
-    // this.fileService.getFiles().subscribe((files) => {
-    //   let matefun_files = files["files"];
-    //   matefun_files = this.fileService.completeFiles(matefun_files);
-    //   this.sessionService.setUserFiles(matefun_files);
-    //   let fileArchivos = this.fileService.fileToArchivo(matefun_files);
-
-    //   this.archivosTree = fileArchivos;
-    //   if (!!this.editor) {
-    //     (this.editor as any).options.files = this.archivosTree;
-    //   }
-    //   this.sessionService.setArchivosTree(this.archivosTree);
-    // });
   }
 
   importFilesData(data) {
diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html
index 7608fa9..891497b 100644
--- a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html	
+++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.html	
@@ -9,41 +9,3 @@
   [requiredRole]="'Owner'"
   [save]="save"
 ></app-add-users-modal>
-
-<!-- <div class="modal-header">
-  <span class="modal-title font-bold" id="modal-title">{{ modalTitle }}</span>
-  <button
-    aria-label="Cerrar diálogo"
-    (click)="modal.dismiss('Close share file modal')"
-    part="close-button"
-    class="close-button fa fa-close"
-    type="button"
-  ></button>
-</div>
-<div class="modal-body">
-  <div class="flex flex-col justify-between w-full">
-    <label for="new-users">New Users</label>
-    <div class="flex justify-between w-full items-center">
-      <app-chip-input
-        [(chips)]="chips"
-        [placeholder]="'Add a new user'"
-        [id]="'new-users'"
-        class="w-full"
-      ></app-chip-input>
-      <app-select
-        [options]="newUsersPermissionsEnum"
-        [(selectedOption)]="newUsersPermission"
-      ></app-select>
-    </div>
-  </div>
-  <div class="flex justify-end">
-    <button
-      (click)="confirmFileCreation(documentTitle, currentFolder.id)"
-      [disabled]="!documentTitle || !currentFolder"
-      class="btn btn-primary"
-      slot="primary"
-    >
-      Guardar
-    </button>
-  </div>
-</div> -->
diff --git a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts
index 22303ff..a8dfef4 100644
--- a/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts	
+++ b/Frontend Angular 4/src/app/shared/components/manage-group-users-modal/manage-group-users-modal.component.ts	
@@ -37,11 +37,6 @@ export class ManageGroupUsersModal implements OnInit {
    */
   @Input() confirmGroupUpdated: () => void;
 
-  // Hacer get del grupo al inicializar el modal
-  // Capaz mostrar un loader mientras ocurre
-  // Con eso mostrar los usuarios que tienen acceso al grupo y sus permisos
-  // Y usar el group_id para hacer el share
-
   chips: string[] = [];
 
   constructor(
diff --git a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts
index ee43f09..968d5e1 100644
--- a/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts	
+++ b/Frontend Angular 4/src/app/shared/components/request-feedback-modal/request-feedback-modal.component.ts	
@@ -15,8 +15,6 @@ import { GroupFileService } from "../../services/group-file.service";
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class RequestFeedbackModal implements OnInit {
-  // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal,
-  //       y luego terminar de hacer la llamada y todo en el GroupComponent
   @Input() fileName: string;
   @Input() groupName: string | null;
   @Input() groupId: number;
diff --git a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts
index 550dbe3..422ebad 100644
--- a/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts	
+++ b/Frontend Angular 4/src/app/shared/components/return-feedback-modal/return-feedback-modal.component.ts	
@@ -15,8 +15,6 @@ import { GroupFileService } from "../../services/group-file.service";
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class ReturnFeedbackModal implements OnInit {
-  // TODO: Hacer todo este modal basándome en lo que ya está que viene del DestroyGroupModal,
-  //       y luego terminar de hacer la llamada y todo en el GroupComponent
   @Input() fileName: string;
   @Input() groupName: string | null;
   @Input() groupId: number;
diff --git a/Frontend Angular 4/src/app/shared/services/group-document.service.ts b/Frontend Angular 4/src/app/shared/services/group-document.service.ts
index b1df8dd..a76638c 100644
--- a/Frontend Angular 4/src/app/shared/services/group-document.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/group-document.service.ts	
@@ -54,18 +54,6 @@ export class GroupDocumentService {
     });
   }
 
-  // getDocuments(ids: number[]): Observable<{ documents: MDocument[] }> {
-  //   const params = new HttpParams({
-  //     fromObject: { "document_ids[]": ids },
-  //   });
-  //   return this.http
-  //     .get<{ documents: MDocument[] }>(GET_DOCUMENTS, {
-  //       headers: this.getHeaders(),
-  //       params: params,
-  //     })
-  //     .pipe(catchError(this.handleError));
-  // }
-
   getGroupDocument(
     groupId: number,
     id: number,
@@ -89,206 +77,6 @@ export class GroupDocumentService {
       .pipe(catchError(this.handleError));
   }
 
-  // documentToArchivo(document: MDocument): Archivo {
-  //   return {
-  //     id: document.id,
-  //     documentId: document.id,
-  //     nombre: document.title,
-  //     contenido: document.text_doc,
-  //     fechaCreacion: document.created_at,
-  //     cedulaCreador: document.users[0]?.email,
-  //     editable: true,
-  //     padreId: document.parent_id ? document.parent_id : -1,
-  //     fileId: document.file_id,
-  //     archivoOrigenId: null,
-  //     archivos: [],
-  //     directorio: false,
-  //     estado: "activo",
-  //     eliminado: false,
-  //     evaluacion: null,
-  //     users: document.users,
-  //     role: document.role,
-  //   };
-  // }
-
-  // archivoToDocument(archivo: Archivo): MDocument {
-  //   return {
-  //     id: archivo.id,
-  //     title: archivo.nombre,
-  //     text_doc: archivo.contenido,
-  //     created_at: archivo.fechaCreacion,
-  //     parent_id: archivo.padreId,
-  //     role: archivo.role,
-  //     users: [],
-  //   };
-  // }
-
-  // createDocument(
-  //   document: MDocument,
-  //   groupId?: number
-  // ): Observable<HttpResponse<MDocument>> {
-  //   const params = { ...document };
-  //   if (groupId) {
-  //     params["group_id"] = groupId;
-  //   }
-  //   return this.http.post<MDocument>(
-  //     CREATE_DOCUMENT,
-  //     { document: params },
-  //     {
-  //       observe: "response",
-  //       headers: this.postHeaders(),
-  //     }
-  //   );
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // getArchivos(cedula: string): Observable<Archivo[]> {
-  //   let headers = this.getHeaders();
-  //   let params: HttpParams = new HttpParams();
-  //   params = params.set("cedula", cedula);
-  //   let httpOptions = { headers: headers, params: params };
-
-  //   return this.http
-  //     .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions)
-  //     .pipe(catchError(this.handleError));
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // getArchivosCompartidosAlumno(cedula: string): Observable<Archivo[]> {
-  //   let headers = this.getHeaders();
-  //   let params: HttpParams = new HttpParams();
-  //   params = params.set("cedula", cedula);
-  //   params = params.set("compartidos", "true");
-  //   let httpOptions = { headers: headers, params: params };
-
-  //   return this.http
-  //     .get<Archivo[]>(SERVER + "/servicios/archivo", httpOptions)
-  //     .pipe(catchError(this.handleError));
-  // }
-
-  // createFile(
-  //   file_title,
-  //   file_binary_doc_text
-  // ): Observable<HttpResponse<Object>> {
-  //   return this.http.post<Object>(
-  //     CREATE_DOCUMENT,
-  //     {
-  //       document: { title: file_title, text_doc: file_binary_doc_text },
-  //     },
-  //     {
-  //       observe: "response",
-  //       headers: this.postHeaders(),
-  //     }
-  //   );
-  // }
-
-  // updateDocument(
-  //   file_id,
-  //   file_title,
-  //   save_document_content = false
-  // ): Observable<HttpResponse<Object>> {
-  //   return this.http.patch<Object>(
-  //     `${UPDATE_DOCUMENT}/${file_id}`,
-  //     {
-  //       document: {
-  //         title: file_title,
-  //         save_document_content: save_document_content,
-  //       },
-  //     },
-  //     {
-  //       observe: "response",
-  //       headers: this.postHeaders(),
-  //     }
-  //   );
-  // }
-
-  // destroyAuxDocument(): Observable<HttpResponse<Object>> {
-  //   return this.http.delete<Object>(DESTROY_AUX_DOCUMENT, {
-  //     observe: "response",
-  //     headers: this.postHeaders(),
-  //   });
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // eliminarArchivo(archivoId): Observable<Response> {
-  //   let headers = this.getHeaders();
-  //   let httpOptions = { headers: headers };
-
-  //   return this.http
-  //     .delete<Response>(SERVER + "/servicios/archivo/" + archivoId, httpOptions)
-  //     .pipe(catchError(this.handleError));
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // getCopiaArchivoCompartidoGrupo(cedula, archivoId): Observable<Archivo> {
-  //   let headers = this.getHeaders();
-  //   let params: HttpParams = new HttpParams();
-  //   params = params.set("cedula", cedula);
-  //   let httpOptions = { headers: headers, params: params };
-
-  //   return this.http
-  //     .get<Archivo>(
-  //       SERVER + "/servicios/archivo/compartido/" + archivoId,
-  //       httpOptions
-  //     )
-  //     .pipe(catchError(this.handleError));
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // compartirArchivoGrupo(grupo, archivoId): Observable<Archivo> {
-  //   let headers = this.getHeaders();
-  //   let httpOptions = { headers: headers };
-  //   var archId = {
-  //     id: archivoId,
-  //   };
-  //   return this.http
-  //     .post<Archivo>(
-  //       SERVER +
-  //         "/servicios/grupo/" +
-  //         grupo.liceoId +
-  //         "/" +
-  //         grupo.anio +
-  //         "/" +
-  //         grupo.grado +
-  //         "/" +
-  //         grupo.grupo +
-  //         "/archivo",
-  //       archId,
-  //       httpOptions
-  //     )
-  //     .pipe(catchError(this.handleError));
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // calificarArchivo(archivoId, estado, evaluacion): Observable<Evaluacion> {
-  //   let headers = this.getHeaders();
-  //   let httpOptions = { headers: headers };
-  //   return this.http
-  //     .post<Evaluacion>(
-  //       SERVER +
-  //         "/servicios/archivo/" +
-  //         archivoId +
-  //         "/" +
-  //         estado +
-  //         "/evaluacion",
-  //       evaluacion,
-  //       httpOptions
-  //     )
-  //     .pipe(catchError(this.handleError));
-  // }
-
-  // // Legacy method, need to check if it's still used
-  // getGrupos(cedula: string): Observable<Group[]> {
-  //   let headers = this.getHeaders();
-  //   let params: HttpParams = new HttpParams();
-  //   params = params.set("cedula", cedula);
-  //   let httpOptions = { headers: headers, params: params };
-
-  //   return this.http
-  //     .get<Group[]>(SERVER + "/servicios/grupo", httpOptions)
-  //     .pipe(catchError(this.handleError));
-  // }
-
   /**
    * Handle HTTP error
    */
diff --git a/Frontend Angular 4/src/app/shared/services/group.service.ts b/Frontend Angular 4/src/app/shared/services/group.service.ts
index 8d42f40..94080d5 100644
--- a/Frontend Angular 4/src/app/shared/services/group.service.ts	
+++ b/Frontend Angular 4/src/app/shared/services/group.service.ts	
@@ -72,18 +72,6 @@ export class GroupService {
       .pipe(catchError(this.handleError));
   }
 
-  // getFilesList(options = {}): Observable<{ files: MFile[] }> {
-  //   const siblingsOfId = options["siblingsOfId"];
-  //   return this.http
-  //     .get<{ files: MFile[] }>(GET_FILES_LISTS, {
-  //       headers: this.getHeaders(),
-  //       params: siblingsOfId
-  //         ? new HttpParams().set("siblings_of_id", siblingsOfId)
-  //         : null,
-  //     })
-  //     .pipe(catchError(this.handleError));
-  // }
-
   fileToArchivo(file: MFile, parent: Archivo = null): Archivo {
     let archivo = new Archivo();
     archivo;
-- 
GitLab