Browse Source

subo las validaciones para los submódulos de carga de documentos

SalemRoman 3 months ago
parent
commit
a5761c01cd
19 changed files with 2157 additions and 926 deletions
  1. 28 2
      src/app/components/corrective-maintenance/security-management/security-management.component.html
  2. 1 1
      src/app/components/gdel/gdel.component.html
  3. 31 9
      src/app/components/initial-data-upload/corrective-documents-upload/corrective-documents-upload.component.html
  4. 32 23
      src/app/components/initial-data-upload/equipment-lru-upload/equipment-lru-upload.component.html
  5. 4 15
      src/app/components/initial-data-upload/equipment-lru-upload/equipment-lru-upload.component.ts
  6. 1 1
      src/app/components/initial-data-upload/individual-corrective-upload/individual-corrective-upload.component.ts
  7. 415 63
      src/app/components/initial-data-upload/individual-equipment-upload/individual-upload.component.ts
  8. 1 2
      src/app/components/initial-data-upload/individual-preventive-upload/individual-preventive-upload.component.ts
  9. 429 86
      src/app/components/initial-data-upload/multiple-equipment-upload/multiple-upload.component.ts
  10. 39 33
      src/app/components/initial-data-upload/preventive-documents-upload/preventive-documents-upload.component.html
  11. 61 1
      src/app/components/initial-data-upload/preview-Equipmentexcel-document/preview-excel-document.component.css
  12. 27 39
      src/app/components/initial-data-upload/preview-Equipmentexcel-document/preview-excel-document.component.html
  13. 152 20
      src/app/components/initial-data-upload/preview-Equipmentexcel-document/preview-excel-document.component.ts
  14. 82 14
      src/app/components/initial-data-upload/related-documents-upload/related-documents-upload.component.html
  15. 484 146
      src/app/components/initial-data-upload/related-documents-upload/related-documents-upload.component.ts
  16. 43 106
      src/app/components/initial-data-upload/zipfile-upload/zipfile-upload.component.html
  17. 313 354
      src/app/components/initial-data-upload/zipfile-upload/zipfile-upload.component.ts
  18. 13 10
      src/app/components/preventive-maintenance/unprogrammed-visits/unprogrammed-visits.component.html
  19. 1 1
      src/styles.css

+ 28 - 2
src/app/components/corrective-maintenance/security-management/security-management.component.html

@@ -73,18 +73,44 @@
               <th mat-header-cell *matHeaderCellDef>Acciones</th>
               <td mat-cell *matCellDef="let row">
                 <button mat-mini-fab color="primary" matTooltip="Editar gerencia" class="override_no_shadow mr-4 white_font orange_primary_background" 
+                *ngIf="!btnSmall"
                 (click)="openSecurityManagementForm('Editar', row.ID_GERENCIA_SEGURIDAD)" [disabled]="row.ESTADO == 'Eliminado'">
                   <mat-icon>edit</mat-icon>
                 </button>
                 <button mat-mini-fab [ngClass]="{ pink_primary_background: row.ESTADO != 'Eliminado', white_font: row.ESTADO != 'Eliminado' }" 
-                class="override_no_shadow mr-4" matTooltip="Ver detalles" (click)="openSecurityManagementDetails(row.ID_GERENCIA_SEGURIDAD)" 
+                class="override_no_shadow mr-4" matTooltip="Ver detalles" 
+                *ngIf="!btnSmall"
+                (click)="openSecurityManagementDetails(row.ID_GERENCIA_SEGURIDAD)" 
                 [disabled]="row.ESTADO == 'Eliminado'">
                   <mat-icon>visibility</mat-icon>
                 </button>
+                <button mat-mini-fab color="primary" class="override_no_shadow animated fadeIn gray_dark_font transparent_background" *ngIf="btnSmall" [matMenuTriggerFor]="menu">
+                <mat-icon>more_vert</mat-icon>
+              </button>
                 <button mat-mini-fab color="warn" matTooltip="Eliminar gerencia" class="override_no_shadow red_primary_background white_font" 
+                *ngIf="!btnSmall"                
                 (click)="openDeleteConfirm(row.ID_GERENCIA_SEGURIDAD)" [disabled]="row.ESTADO == 'Eliminado'">
                   <mat-icon>delete</mat-icon>
                 </button>
+
+                <mat-menu #menu>
+                  <button mat-menu-item (click)="openSecurityManagementForm('Editar', row.ID_GERENCIA_SEGURIDAD)">
+                    <mat-icon>edit</mat-icon>
+                    <span>Editar gerencia</span>
+                  </button>
+
+                  <button mat-menu-item (click)="openSecurityManagementDetails(row.ID_GERENCIA_SEGURIDAD)">
+                    <mat-icon>visibility</mat-icon>
+                    <span>Ver detalles</span>
+                  </button>
+
+                  <button mat-menu-item (click)="openDeleteConfirm(row.ID_GERENCIA_SEGURIDAD)">
+                    <mat-icon>delete</mat-icon>
+                    <span>Eliminar gerencia</span>
+                  </button>          
+
+                </mat-menu>
+
               </td>
             </ng-container>
 
@@ -122,7 +148,7 @@
           <button mat-flat-button color="primary" class="override_no_shadow mr-10 animated fadeIn" (click)="openBlockDialog()">
             <mat-icon>add</mat-icon> Añadir bloqueo
           </button>
-          <button mat-mini-fab class="cyan_dark_background white_font override_no_shadow mr-10" matTooltip="Actualizar datos" (click)="getBlockRegisters()">
+          <button mat-mini-fab class="orange_primary_background white_font override_no_shadow mr-10" matTooltip="Actualizar datos" (click)="getBlockRegisters()">
             <mat-icon>refresh</mat-icon>
           </button>
         </div>

+ 1 - 1
src/app/components/gdel/gdel.component.html

@@ -56,7 +56,7 @@
           <mat-form-field appearance="outline" style="margin-bottom: -22px !important; width: 20%;">
             <mat-label>Tipo de archivo</mat-label>
             <mat-select [formControl]="fileTypeControl">
-              <mat-option>--Nungúno--</mat-option>
+              <mat-option>--Ningúno--</mat-option>
               <mat-optgroup *ngFor="let group of supportedFilesArr" [label]="group.category">
                 <mat-option *ngFor="let item of group.files" [value]="item.ext">
                   <span *ngIf="item.name != ''" class="mr-4">{{ item.name }}</span>({{ item.ext }})

+ 31 - 9
src/app/components/initial-data-upload/corrective-documents-upload/corrective-documents-upload.component.html

@@ -5,7 +5,7 @@
   </button>
 </div>
 
-<main class="main-container animated fadeIn">
+<main class="main-container animated fadeIn" (window:resize)="onResize()">
   <mat-card class="override-card override-elevation-z8">
     <mat-card-header class="card_header">
       <mat-card-title class="align-center">Carga de Mantenimientos Correctivos</mat-card-title>
@@ -129,7 +129,7 @@
           </ng-container> -->
           
           <!-- Columna Acciones -->
-          <ng-container matColumnDef="ACCIONES">
+<ng-container matColumnDef="ACCIONES">
             <th mat-header-cell *matHeaderCellDef>Acciones</th>
             <td mat-cell *matCellDef="let element">
               <div class="actions-container">
@@ -138,29 +138,51 @@
                   matTooltip="Ver documento"
                   class="override_no_shadow white_font pink_primary_background mr-5"
                   [disabled]="isLoading || !documentsViewerEnabled"
-                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)"
+                  *ngIf="!btnSmall">
                   <mat-icon>visibility</mat-icon>
                 </button>
-
-                <!-- Botón Descargar -->
+              
                 <button
                   mat-mini-fab
                   matTooltip="Descargar documento"
                   class="override_no_shadow white_font green_primary_background mr-5"
                   [disabled]="isLoading"
-                  (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)">
+                   (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)"
+                   *ngIf="!btnSmall">
                   <mat-icon>download</mat-icon>
                 </button>
 
-                <!-- Botón Eliminar -->
-                <button
+                <button mat-mini-fab color="primary" class="override_no_shadow animated fadeIn gray_dark_font transparent_background" *ngIf="btnSmall" [matMenuTriggerFor]="menu">
+                  <mat-icon>more_vert</mat-icon>
+                </button>
+            
+               <button
                   mat-mini-fab
                   matTooltip="Eliminar documento"
                   class="override_no_shadow white_font red_primary_background"
                   [disabled]="isLoading || !documentsManagementEnabled"
-                  (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)">
+                 (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)"
+                 *ngIf="!btnSmall">
                   <mat-icon>delete</mat-icon>
                 </button>
+
+                <mat-menu #menu>
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>visibility</mat-icon>
+                    <span>Ver documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>download</mat-icon>
+                    <span>Descargar documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>delete</mat-icon>
+                    <span>Eliminar documento</span>
+                  </button>
+                </mat-menu>
               </div>
             </td>
           </ng-container>

+ 32 - 23
src/app/components/initial-data-upload/equipment-lru-upload/equipment-lru-upload.component.html

@@ -1,11 +1,11 @@
 <div class="breadcrumb-container">
-  <button mat-button [routerLink]="['/sam/initial-data-upload']" class="raised-button-background white_font mb-20 mt-20" style="margin-left: 260px;">
+  <button mat-button [routerLink]="['/sam/initial-data-upload']" class="raised-button-background white_font mb-20 mt-20" style="margin-left: 180px;">
     <mat-icon>arrow_back</mat-icon>
       Interfaz de carga de datos
   </button>
 </div>
 
-<main class="main-container animated fadeIn">
+<main class="main-container animated fadeIn" (window:resize)="onResize()">
   <mat-card class="override-card override-elevation-z8">
     <mat-card-header class="card_header">
       <mat-card-title class="align-center">Carga de Equipamientos</mat-card-title>
@@ -100,33 +100,18 @@
               </div>
             </td>
           </ng-container>
-          
-          <!-- Columna Tamaño -->
+
           <ng-container matColumnDef="TAMANIO">
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Tamaño</th>
             <td mat-cell *matCellDef="let element">{{ element.TAMANIO }}</td>
           </ng-container>
           
-          <!-- Columna Propietario -->
           <ng-container matColumnDef="USRREG">
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Propietario</th>
             <td mat-cell *matCellDef="let element">{{ element.USRREG }}</td>
           </ng-container>
 
-          <!-- <ng-container matColumnDef="ACCESO">
-            <th mat-header-cell *matHeaderCellDef mat-sort-header>Acceso</th>
-            <td mat-cell *matCellDef="let element">
-              <div class="access-avatars">
-                <div *ngFor="let user of element.ACCESO" 
-                     class="user-avatar" 
-                     [style.background-color]="user.userColor"
-                     [matTooltip]="user.userName"
-                     (click)="openAccessDialog(element.CODIGO, element.VERSION, element.NOMBRE, user.isTrigger, false)">
-                  {{ user.userLabel }}
-                </div>
-              </div>
-            </td>
-          </ng-container> -->
+
           
 
           <ng-container matColumnDef="ACCIONES">
@@ -138,27 +123,51 @@
                   matTooltip="Ver documento"
                   class="override_no_shadow white_font pink_primary_background mr-5"
                   [disabled]="isLoading || !documentsViewerEnabled"
-                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)"
+                  *ngIf="!btnSmall">
                   <mat-icon>visibility</mat-icon>
                 </button>
-
+              
                 <button
                   mat-mini-fab
                   matTooltip="Descargar documento"
                   class="override_no_shadow white_font green_primary_background mr-5"
                   [disabled]="isLoading"
-                  (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)">
+                   (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)"
+                   *ngIf="!btnSmall">
                   <mat-icon>download</mat-icon>
                 </button>
 
+                <button mat-mini-fab color="primary" class="override_no_shadow animated fadeIn gray_dark_font transparent_background" *ngIf="btnSmall" [matMenuTriggerFor]="menu">
+                  <mat-icon>more_vert</mat-icon>
+                </button>
+            
                <button
                   mat-mini-fab
                   matTooltip="Eliminar documento"
                   class="override_no_shadow white_font red_primary_background"
                   [disabled]="isLoading || !documentsManagementEnabled"
-                  (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)">
+                 (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)"
+                 *ngIf="!btnSmall">
                   <mat-icon>delete</mat-icon>
                 </button>
+
+                <mat-menu #menu>
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>visibility</mat-icon>
+                    <span>Ver documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>download</mat-icon>
+                    <span>Descargar documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>delete</mat-icon>
+                    <span>Eliminar documento</span>
+                  </button>
+                </mat-menu>
               </div>
             </td>
           </ng-container>

+ 4 - 15
src/app/components/initial-data-upload/equipment-lru-upload/equipment-lru-upload.component.ts

@@ -109,7 +109,7 @@ export class EquipmentLruUploadComponent implements OnInit {
     private _functionsService: FunctionsService,
   ) {
     this._functionsService.consultModuleStatus(ID_MODULE);
-    this.btnSmall = false;
+    this.btnSmall = window.innerWidth <= 1530;
     this.isLoading = true;
     this.hasError = true;
     this.errorStr = '';
@@ -213,8 +213,7 @@ export class EquipmentLruUploadComponent implements OnInit {
   }
 
   private updateModulePermissions(modPerm: any) {
-    // Aquí puedes adaptar según la estructura de permisos específica de equipamiento
-    // Por ahora mantengo la estructura similar a GDEL
+  
     let funPerm = modPerm.children?.filter((item: any) => item.id == 'S002V01F01ADDO');
     if (funPerm && funPerm.length > 0) {
       this.documentsManagementEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P01GEDO')[0]?.access > 0 || true;
@@ -381,7 +380,7 @@ async getFiles(module: string | undefined, clas: string | undefined, startDate:
   }
 
   public onResize(): void {
-    this.btnSmall = window.innerWidth <= 1573;
+    this.btnSmall = window.innerWidth <= 1400;
   }
 
   // Métodos para diálogos de carga - adaptados para equipamiento
@@ -613,17 +612,7 @@ async getFiles(module: string | undefined, clas: string | undefined, startDate:
     }
   }
 
-  openAssociationDialog(code: string, version: string, name: string) {
-    let idFile = `${code}=${version}=${name}`;
-    this._dialog.open(OrderAssociatonComponent, {
-      width: '840px',
-      maxWidth: '840px',
-      disableClose: true,
-      data: {
-        idFile: idFile,
-      }
-    });
-  }
+
 
   confirmDelete(code: string, version: string, name: string) {
     let idFile = `${code}=${version}=${name}`;

+ 1 - 1
src/app/components/initial-data-upload/individual-corrective-upload/individual-corrective-upload.component.ts

@@ -22,7 +22,7 @@ export interface Document {
   fecha_carga?: string
 }
 
-// Interfaz para la estructura de headers esperados
+
 interface SheetHeaderStructure {
   [column: string]: string;
 }

+ 415 - 63
src/app/components/initial-data-upload/individual-equipment-upload/individual-upload.component.ts

@@ -22,6 +22,15 @@ export interface Document {
   fecha_carga?: string
 }
 
+
+interface SheetHeaderStructure {
+  [column: string]: string;
+}
+
+interface TemplateStructure {
+  [sheetName: string]: SheetHeaderStructure;
+}
+
 @Component({
   selector: 'app-individual-upload',
   standalone: false,
@@ -35,24 +44,216 @@ export class IndividualUploadComponent implements OnInit {
   public hasError: boolean = false;
   public errorMessage: string = '';
   public isUploading: boolean;
+
   uploadedFile: TempFileInfo | null;
   @ViewChild('file') private uploader?: ElementRef;
 
- moduleControl = new FormControl('', Validators.required);
+  moduleControl = new FormControl('', Validators.required);
 
   public dataSource: MatTableDataSource<Document>;
   public displayedColumns: string[] = ['nombre_del_archivo', 'tamano_del_archivo', 'acciones'];
   
-  // Variables para manejar archivos Excel (solo uno permitido)
+
   public excelFile: Document | null = null;
   public excelData: any = null;
 
-  // Variables para drag and drop
+
   public isDragOver: boolean = false;
 
-  // Configuración específica para archivos Excel
+
   private readonly EXCEL_EXTENSIONS = ['xlsx', 'xls'];
   private readonly MAX_FILE_SIZE_MB = 10;
+  private readonly HEADER_ROW = 7; 
+
+
+  private readonly TEMPLATE_STRUCTURE: TemplateStructure = {
+    'CARGA DE EQUIPOS': {
+      'B': 'TIPO / DESCRIPCIÓN',
+      'C': 'ACRÓNIMO DEL EQUIPO',
+      'D': 'MODELO COMPLETO',
+      'E': 'ACRÓNIMO DEL MODELO',
+      'F': 'ID',
+      'G': 'NO.SERIE',
+      'H': 'NO. CÓDIGO DE BARRAS',
+      'I': 'CARÁCTER',
+      'J': 'PROVEEDOR DEL EQUIPAMIENTO',
+      'K': 'FECHA INICIO DE LA GARANTÍA',
+      'L': 'FECHA FIN DE LA GARANTÍA',
+      'M': 'FECHA DE ADQUSICIÓN DEL ARTÍCULO',
+      'N': 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
+      'O': 'ETIQUETA FINAL DEL EQUIPO',
+    },
+    'LRU': {
+      'B': 'TIPO / DESCRIPCIÓN',
+      'C': 'ACRÓNIMO DEL EQUIPO',
+      'D': 'MODELO COMPLETO',
+      'E': 'ACRÓNIMO DEL MODELO',
+      'F': 'ID',
+      'G': 'NO.SERIE',
+      'H': 'NO. CÓDIGO DE BARRAS',
+      'I': 'CARÁCTER',
+      'J': 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
+      'K': 'ETIQUETA FINAL DEL EQUIPO'
+    },
+    'CASO 1': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ELEMENTO',
+      'L': 'COORDENADAS PLANO GENERAL',
+      'M': 'COORDENADAS DETALLE',
+      'N': 'COORDENADAS DE POSICIÓN',
+      'P': 'FAMILIA',
+      'R': 'SUBFAMILIA',
+      'T': 'ESTADO',
+      'V': 'TIPO',
+      'X': 'MODELO',
+      'Z': 'ID',
+      'AB': 'TIPO',
+      'AD': 'MODELO',
+      'AF': 'ID',
+      'AH': 'CÓDIGO COMPLETO SAM',
+      'AI': 'CÓDIGO EQUIVALENTE'
+    },
+    'CASO 2': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ELEMENTO',
+      'L': 'COORDENADAS PLANO GENERAL',
+      'M': 'COORDENADAS DE POSICIÓN',
+      'N': 'POSICIÓN EN RACK',
+      'P': 'FAMILIA',
+      'R': 'SUBFAMILIA',
+      'T': 'ESTADO',
+      'V': 'TIPO',
+      'X': 'MODELO',
+      'Z': 'ID',
+      'AB': 'TIPO',
+      'AD': 'MODELO',
+      'AF': 'ID',
+      'AH': 'CÓDIGO COMPLETO',
+      'AI': 'CÓDIGO EQUIVALENTE' 
+    },
+    'CASO 3': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN ORIGEN',
+      'F': 'NIVEL ORIGEN',
+      'H': 'OCUPACIÓN ORIGEN',
+      'J': 'ELEMENTO ORIGEN',
+      'L': 'PK ORIGEN',
+      'N': 'UBICACIÓN DESTINO',
+      'P': 'NIVEL DESTINO',
+      'R': 'OCUPACIÓN DESTINO',
+      'T': 'ELEMENTO DESTINO',
+      'V': 'PK DESTINO',
+      'X': 'FAMILIA',
+      'Z': 'SUBFAMILIA',
+      'AB': 'ESTADO',
+      'AD': 'TIPO',
+      'AF': 'MODELO',
+      'AH': 'ID',
+      'AJ': 'TIPO',
+      'AL': 'MODELO',
+      'AN': 'ID',
+      'AP': 'CÓDIGO COMPLETO',
+      'AQ': 'CÓDIGO EQUIVALENTE'
+    },
+    'CASO 4': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN ORIGEN',
+      'F': 'NIVEL ORIGEN',
+      'H': 'OCUPACIÓN ORIGEN',
+      'J': 'ELEMENTO ORIGEN',
+      'L': 'SECUENCIAL ORIGEN',
+      'N': 'COORDENADAS PLANO',
+      'O': 'COORDENADAS DETALLE',
+      'P': 'COORDENADAS DE POSICIÓN',
+      'R': 'UBICACIÓN DESTINO',
+      'T': 'NIVEL DESTINO',
+      'V': 'OCUPACIÓN DESTINO',
+      'X': 'ELEMENTO DESTINO',
+      'Z': 'SECUENCIAL DESTINO',
+      'AB': 'COORDENADAS PLANO',
+      'AC': 'COORDENADAS DETALLE',
+      'AD': 'COORDENADAS DE POSICIÓN',
+      'AF': 'FAMILIA',
+      'AH': 'SUBFAMILIA',
+      'AJ': 'ESTADO',
+      'AL': 'TIPO',
+      'AN': 'MODELO',
+      'AP': 'ID',
+      'AR': 'TIPO',
+      'AT': 'MODELO',
+      'AV': 'ID',
+      'AX': 'CÓDIGO COMPLETO',
+      'AY': 'CÓDIGO EQUIVALENTE',
+    },
+    'CASO 5': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ÁREA',
+      'L': 'ELEMENTO',
+      'N': 'FAMILIA',
+      'P': 'SUBFAMILIA',
+      'R': 'ESTADO',
+      'T': 'TIPO',
+      'V': 'MODELO',
+      'X': 'ID',
+      'Z': 'TIPO',
+      'AB': 'MODELO',
+      'AD': 'ID',
+      'AF': 'CÓDIGO COMPLETO',
+      'AG': 'CÓDIGO EQUIVALENTE', 
+    },
+    'CASO 6': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ELEMENTO',
+      'L': 'POSICIÓN',
+      'N': 'FAMILIA',
+      'P': 'SUBFAMILIA',
+      'R': 'ESTADO',
+      'T': 'TIPO',
+      'V': 'MODELO',
+      'X': 'ID',
+      'Z': 'TIPO',
+      'AB': 'MODELO',
+      'AD': 'ID',
+      'AF': 'CÓDIGO COMPLETO',
+      'AG': 'CÓDIGO EQUIVALENTE',    
+    }
+
+  };
+
+  private readonly CATÁLOGOS_STRUCTURE = {
+    row5: {
+      'B': 'FAMILIA',
+      'C': 'ACRÓNIMO',
+      'E': 'SUBFAMILIA',
+      'F': 'ACRÓNIMO',
+      'G': 'SUBFAMILY',
+      'I': 'UBICACIONES (FRENTES)',
+      'J': 'CÓDIGO',
+      'L': 'RMS',
+      'M': 'ELEMENTO',
+      'N': 'CÓDIGO',
+      'P': 'OCUPACIÓN',
+      'Q': 'CÓDIGO',
+      'S': 'ESTADO',
+      'T': 'ACRÓNIMO'
+    },
+
+  };
+
+
+  private readonly REQUIRED_SHEETS = ['CARGA DE EQUIPOS', 'LRU', 'CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6', 'CATÁLOGOS'];
 
   constructor (
     private dialogRef: MatDialogRef<EquipmentLruUploadComponent>,
@@ -79,7 +280,7 @@ export class IndividualUploadComponent implements OnInit {
     this.uploader?.nativeElement.click();
   }
 
-  // Métodos para drag and drop
+
   onDragOver(event: DragEvent): void {
     event.preventDefault();
     event.stopPropagation();
@@ -110,7 +311,7 @@ export class IndividualUploadComponent implements OnInit {
   }
 
   private processFile(file: File): void {
-    // Validación específica para archivos Excel
+
     if (!this.isValidExcelFile(file)) {
       return;
     }
@@ -136,7 +337,7 @@ export class IndividualUploadComponent implements OnInit {
   }
 
   private isValidFileSize(file: File): boolean {
-    const maxFileSize = this.MAX_FILE_SIZE_MB * 1024 * 1024; // Convertir MB a bytes
+    const maxFileSize = this.MAX_FILE_SIZE_MB * 1024 * 1024;
     
     if (file.size > maxFileSize) {
       this._resourcesService.openSnackBar(`El tamaño del archivo supera el límite de ${this.MAX_FILE_SIZE_MB} MB`);
@@ -146,8 +347,146 @@ export class IndividualUploadComponent implements OnInit {
     return true;
   }
 
+
+private validateExcelTemplate(workbook: XLSX.WorkBook): { isValid: boolean, errors: string[] } {
+  const errors: string[] = [];
+  
+
+  const existingSheets = workbook.SheetNames;
+  const missingSheets = this.REQUIRED_SHEETS.filter(sheet => !existingSheets.includes(sheet));
+  
+  if (missingSheets.length > 0) {
+    errors.push(`Tu documento no cumple con las hojas requeridas de la plantilla`);
+  }
+
+  for (const sheetName of this.REQUIRED_SHEETS) {
+    if (existingSheets.includes(sheetName)) {
+      const sheetErrors = this.validateSheetHeaders(workbook.Sheets[sheetName], sheetName);
+      errors.push(...sheetErrors);
+      
+
+      if (sheetName === 'CARGA DE EQUIPOS') {
+        const dataErrors = this.validateSheetHasData(workbook.Sheets[sheetName], sheetName);
+        errors.push(...dataErrors);
+      }
+
+    }
+  }
+
+  return {
+    isValid: errors.length === 0,
+    errors: errors
+  };
+}
+
+
+private validateSheetHasData(worksheet: XLSX.WorkSheet, sheetName: string): string[] {
+  const errors: string[] = [];
+  
+  try {
+
+    const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
+    
+    
+    const dataStartRow = this.HEADER_ROW + 2; 
+    
+
+    let hasData = false;
+    
+
+    if (range.e.r >= dataStartRow) {
+
+      const mainColumns = ['B', 'D', 'F', 'H']; 
+      
+      for (let row = dataStartRow; row <= range.e.r; row++) {
+        for (const col of mainColumns) {
+          const cellAddress = `${col}${row}`;
+          const cell = worksheet[cellAddress];
+          
+          
+          if (cell && cell.v !== null && cell.v !== undefined && String(cell.v).trim() !== '') {
+            hasData = true;
+            break;
+          }
+        }
+        if (hasData) break;
+      }
+    }
+    
+    if (!hasData) {
+      errors.push(`La hoja "${sheetName}" no contiene datos. Se requiere al menos un registro con información.`);
+    }
+    
+  } catch (error) {
+    errors.push(`Error al validar datos en la hoja "${sheetName}": No se pudo verificar el contenido.`);
+  }
+  
+  return errors;
+}
+
+  private validateSheetHeaders(worksheet: XLSX.WorkSheet, sheetName: string): string[] {
+    const errors: string[] = [];
+    
+
+    if (sheetName === 'CATÁLOGOS') {
+      return this.validateCatalogosSheet(worksheet);
+    }
+    
+    const expectedHeaders = this.TEMPLATE_STRUCTURE[sheetName];
+    
+    if (!expectedHeaders) {
+      errors.push(`No se encontró la estructura esperada para la hoja: ${sheetName}`);
+      return errors;
+    }
+
+
+    const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
+    
+    for (const [column, expectedHeader] of Object.entries(expectedHeaders)) {
+      const cellAddress = `${column}${this.HEADER_ROW}`;
+      const cell = worksheet[cellAddress];
+      const actualHeader = cell ? String(cell.v).trim() : '';
+      
+      if (actualHeader !== expectedHeader) {
+        errors.push(`Tu documento tiene error en los Headers, revísalos e intentalo de nuevo`);
+      }
+    }
+
+    return errors;
+  }
+
+
+  private validateCatalogosSheet(worksheet: XLSX.WorkSheet): string[] {
+    const errors: string[] = [];
+
+   
+    for (const [column, expectedHeader] of Object.entries(this.CATÁLOGOS_STRUCTURE.row5)) {
+      const cellAddress = `${column}5`;
+      const cell = worksheet[cellAddress];
+      const actualHeader = cell ? String(cell.v).trim() : '';
+      
+      if (actualHeader !== expectedHeader) {
+        errors.push(`Hoja "CATÁLOGOS", Fila 5, Columna ${column}: Se esperaba "${expectedHeader}" pero se encontró "${actualHeader}"`);
+      }
+    }
+
+
+
+
+    return errors;
+  }
+
   async uploadExcelFile(fileData: File): Promise<void> {
     try {
+
+      const validationResult = await this.validateFileStructure(fileData);
+      
+      if (!validationResult.isValid) {
+        this._resourcesService.openSnackBar(`Plantilla inválida: ${validationResult.errors.join('. ')}`);
+        this.isUploading = false;
+        return;
+      }
+
       const idUser = localStorage.getItem('idusuario');
       const formData = new FormData();
 
@@ -156,20 +495,19 @@ export class IndividualUploadComponent implements OnInit {
       formData.append('linea', "1");
       formData.append('tipo_archivo', 'excel');
 
-
       const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
 
       if (response.error) {
         this._resourcesService.openSnackBar(response.msg);
       } else {
-        // Actualizar uploadedFile para compatibilidad
+
         this.uploadedFile = {
           id: response.response.idArchivo,
           name: fileData.name,
           size: this.formatBytes(fileData.size)
         };
 
-        // Leer el archivo Excel para obtener información adicional
+
         await this.readExcelFile(fileData, response.response.idArchivo);
         
         const newDocument: Document = {
@@ -180,10 +518,10 @@ export class IndividualUploadComponent implements OnInit {
           fecha_carga: new Date().toLocaleString()
         };
 
-        // Solo mantener un archivo
+
         this.excelFile = newDocument;
         this.updateTableData();
-        this._resourcesService.openSnackBar('Archivo Excel cargado exitosamente');
+        this._resourcesService.openSnackBar('Plantilla Excel válida y cargada exitosamente');
       }
 
       this.isUploading = false;
@@ -193,6 +531,26 @@ export class IndividualUploadComponent implements OnInit {
     }
   }
 
+
+  private async validateFileStructure(file: File): Promise<{ isValid: boolean, errors: string[] }> {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = (e: any) => {
+        try {
+          const data = new Uint8Array(e.target.result);
+          const workbook = XLSX.read(data, { type: 'array' });
+          
+          const validation = this.validateExcelTemplate(workbook);
+          resolve(validation);
+        } catch (error) {
+          reject(error);
+        }
+      };
+      reader.onerror = reject;
+      reader.readAsArrayBuffer(file);
+    });
+  }
+
   private async readExcelFile(file: File, fileId: any): Promise<void> {
     return new Promise((resolve, reject) => {
       const reader = new FileReader();
@@ -201,7 +559,7 @@ export class IndividualUploadComponent implements OnInit {
           const data = new Uint8Array(e.target.result);
           const workbook = XLSX.read(data, { type: 'array' });
           
-          // Obtener información de las hojas
+
           const sheetNames = workbook.SheetNames;
           const sheetsData: any = {};
           
@@ -210,7 +568,7 @@ export class IndividualUploadComponent implements OnInit {
             sheetsData[sheetName] = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
           });
 
-          // Guardar datos del archivo para previsualización
+
           this.excelData = {
             fileName: file.name,
             sheets: sheetsData,
@@ -228,7 +586,6 @@ export class IndividualUploadComponent implements OnInit {
     });
   }
 
-  // Método para eliminar archivo temporal
   async deleteTempFile(): Promise<void> {
     if (!this.uploadedFile) return;
 
@@ -269,7 +626,7 @@ export class IndividualUploadComponent implements OnInit {
   }
 
   private loadExcelFile(): void {
-    // Inicializar con archivo vacío
+
     this.excelFile = null;
     this.updateTableData();
   }
@@ -284,7 +641,6 @@ export class IndividualUploadComponent implements OnInit {
     }
   }
 
-  // Método para abrir previsualización del Excel
   previewExcelFile(): void {
     if (!this.excelData) {
       this._resourcesService.openSnackBar('No hay datos del archivo Excel para mostrar');
@@ -300,74 +656,70 @@ export class IndividualUploadComponent implements OnInit {
     });
 
     dialogRef.afterClosed().subscribe(result => {
-      // Manejar el resultado si es necesario
+
       if (result) {
         console.log('Preview cerrado:', result);
       }
     });
   }
 
-  // Método para eliminar archivo
   removeExcelFile(): void {
     this.deleteTempFile();
   }
 
-  // Getter para verificar si ya hay un archivo cargado
+
   get hasExcelFile(): boolean {
     return this.excelFile !== null;
   }
 
-  // Getter para obtener el array de archivos para la tabla
   get excelFiles(): Document[] {
     return this.excelFile ? [this.excelFile] : [];
   }
 
-  // Método para guardar archivo final (si es necesario)
-// Agrega estos logs en saveFinalFile() de IndividualUploadComponent
 
-async saveFinalFile(): Promise<void> {
-  if (!this.uploadedFile) {
-    this._resourcesService.openSnackBar('No hay archivo cargado para guardar');
-    return;
-  }
+  async saveFinalFile(): Promise<void> {
+    if (!this.uploadedFile) {
+      this._resourcesService.openSnackBar('No hay archivo cargado para guardar');
+      return;
+    }
 
-  try {
-    this.isLoading = true;
-    let idUser = localStorage.getItem('idusuario');
-    let formData = new FormData();
-
-    // Parámetros obligatorios
-    formData.append('id_user', idUser!);
-    formData.append('id_file', this.uploadedFile.id);
-    formData.append('linea', '1');
-    
-    // Parámetros específicos para equipamiento
-    formData.append('module', 'ADSI'); // Módulo fijo para equipamiento
-    formData.append('clasification', 'LA'); // Clasificación fija para LRU/Equipamiento
-    formData.append('has_order', 'N'); // Sin orden asociada
-
-    console.log('Datos enviados al servidor:', {
-      id_user: idUser,
-      id_file: this.uploadedFile.id,
-      module: 'ADSI',
-      clasification: 'LA'
-    });
+    try {
+      this.isLoading = true;
+      let idUser = localStorage.getItem('idusuario');
+      let formData = new FormData();
 
-    const response = await lastValueFrom(this._gdelService.saveFinalFile(formData));
-    
-    console.log('Respuesta del servidor:', response);
 
-    if (response.error) {
-      throw new Error(response.msg || 'Error al guardar el archivo');
-    }
+      formData.append('id_user', idUser!);
+      formData.append('id_file', this.uploadedFile.id);
+      formData.append('linea', '1');
+      
 
-    this._resourcesService.openSnackBar('Archivo guardado correctamente');
-    this.dialogRef.close(true); // Cierra el diálogo y notifica al padre
-  } catch (error: any) {
-    console.error('Error al guardar archivo:', error);
-    this._resourcesService.openSnackBar(error.message || 'Error al guardar el archivo');
-  } finally {
-    this.isLoading = false;
+      formData.append('module', 'ADSI'); 
+      formData.append('clasification', 'LA'); 
+      formData.append('has_order', 'N'); 
+
+      console.log('Datos enviados al servidor:', {
+        id_user: idUser,
+        id_file: this.uploadedFile.id,
+        module: 'ADSI',
+        clasification: 'LA'
+      });
+
+      const response = await lastValueFrom(this._gdelService.saveFinalFile(formData));
+      
+      console.log('Respuesta del servidor:', response);
+
+      if (response.error) {
+        throw new Error(response.msg || 'Error al guardar el archivo');
+      }
+
+      this._resourcesService.openSnackBar('Archivo guardado correctamente');
+      this.dialogRef.close(true); 
+    } catch (error: any) {
+      console.error('Error al guardar archivo:', error);
+      this._resourcesService.openSnackBar(error.message || 'Error al guardar el archivo');
+    } finally {
+      this.isLoading = false;
+    }
   }
-}
 }

+ 1 - 2
src/app/components/initial-data-upload/individual-preventive-upload/individual-preventive-upload.component.ts

@@ -397,11 +397,10 @@ private validateSheetHasData(worksheet: XLSX.WorkSheet, sheetName: string): stri
     return errors;
   }
 
-  // Método específico para validar la hoja CATÁLOGOS
   private validateCatalogosSheet(worksheet: XLSX.WorkSheet): string[] {
     const errors: string[] = [];
 
-    // Validar headers de la fila 3
+
     for (const [column, expectedHeader] of Object.entries(this.CATALOGOS_STRUCTURE.row3)) {
       const cellAddress = `${column}3`;
       const cell = worksheet[cellAddress];

+ 429 - 86
src/app/components/initial-data-upload/multiple-equipment-upload/multiple-upload.component.ts

@@ -22,6 +22,14 @@ export interface Document {
   fecha_carga?: string
 }
 
+interface SheetHeaderStructure {
+  [column: string]: string;
+}
+
+interface TemplateStructure {
+  [sheetName: string]: SheetHeaderStructure;
+}
+
 @Component({
   selector: 'app-multiple-upload',
   standalone: false,
@@ -53,6 +61,196 @@ export class MultipleUploadComponent implements OnInit {
 
   public readonly EXCEL_EXTENSIONS = ['xlsx', 'xls'];
   public readonly MAX_FILE_SIZE_MB = 10;
+    private readonly HEADER_ROW = 7; 
+
+private readonly TEMPLATE_STRUCTURE: TemplateStructure = {
+    'CARGA DE EQUIPOS': {
+      'B': 'TIPO / DESCRIPCIÓN',
+      'C': 'ACRÓNIMO DEL EQUIPO',
+      'D': 'MODELO COMPLETO',
+      'E': 'ACRÓNIMO DEL MODELO',
+      'F': 'ID',
+      'G': 'NO.SERIE',
+      'H': 'NO. CÓDIGO DE BARRAS',
+      'I': 'CARÁCTER',
+      'J': 'PROVEEDOR DEL EQUIPAMIENTO',
+      'K': 'FECHA INICIO DE LA GARANTÍA',
+      'L': 'FECHA FIN DE LA GARANTÍA',
+      'M': 'FECHA DE ADQUSICIÓN DEL ARTÍCULO',
+      'N': 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
+      'O': 'ETIQUETA FINAL DEL EQUIPO',
+    },
+    'LRU': {
+      'B': 'TIPO / DESCRIPCIÓN',
+      'C': 'ACRÓNIMO DEL EQUIPO',
+      'D': 'MODELO COMPLETO',
+      'E': 'ACRÓNIMO DEL MODELO',
+      'F': 'ID',
+      'G': 'NO.SERIE',
+      'H': 'NO. CÓDIGO DE BARRAS',
+      'I': 'CARÁCTER',
+      'J': 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
+      'K': 'ETIQUETA FINAL DEL EQUIPO'
+    },
+    'CASO 1': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ELEMENTO',
+      'L': 'COORDENADAS PLANO GENERAL',
+      'M': 'COORDENADAS DETALLE',
+      'N': 'COORDENADAS DE POSICIÓN',
+      'P': 'FAMILIA',
+      'R': 'SUBFAMILIA',
+      'T': 'ESTADO',
+      'V': 'TIPO',
+      'X': 'MODELO',
+      'Z': 'ID',
+      'AB': 'TIPO',
+      'AD': 'MODELO',
+      'AF': 'ID',
+      'AH': 'CÓDIGO COMPLETO SAM',
+      'AI': 'CÓDIGO EQUIVALENTE'
+    },
+    'CASO 2': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ELEMENTO',
+      'L': 'COORDENADAS PLANO GENERAL',
+      'M': 'COORDENADAS DE POSICIÓN',
+      'N': 'POSICIÓN EN RACK',
+      'P': 'FAMILIA',
+      'R': 'SUBFAMILIA',
+      'T': 'ESTADO',
+      'V': 'TIPO',
+      'X': 'MODELO',
+      'Z': 'ID',
+      'AB': 'TIPO',
+      'AD': 'MODELO',
+      'AF': 'ID',
+      'AH': 'CÓDIGO COMPLETO',
+      'AI': 'CÓDIGO EQUIVALENTE' 
+    },
+    'CASO 3': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN ORIGEN',
+      'F': 'NIVEL ORIGEN',
+      'H': 'OCUPACIÓN ORIGEN',
+      'J': 'ELEMENTO ORIGEN',
+      'L': 'PK ORIGEN',
+      'N': 'UBICACIÓN DESTINO',
+      'P': 'NIVEL DESTINO',
+      'R': 'OCUPACIÓN DESTINO',
+      'T': 'ELEMENTO DESTINO',
+      'V': 'PK DESTINO',
+      'X': 'FAMILIA',
+      'Z': 'SUBFAMILIA',
+      'AB': 'ESTADO',
+      'AD': 'TIPO',
+      'AF': 'MODELO',
+      'AH': 'ID',
+      'AJ': 'TIPO',
+      'AL': 'MODELO',
+      'AN': 'ID',
+      'AP': 'CÓDIGO COMPLETO',
+      'AQ': 'CÓDIGO EQUIVALENTE'
+    },
+    'CASO 4': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN ORIGEN',
+      'F': 'NIVEL ORIGEN',
+      'H': 'OCUPACIÓN ORIGEN',
+      'J': 'ELEMENTO ORIGEN',
+      'L': 'SECUENCIAL ORIGEN',
+      'N': 'COORDENADAS PLANO',
+      'O': 'COORDENADAS DETALLE',
+      'P': 'COORDENADAS DE POSICIÓN',
+      'R': 'UBICACIÓN DESTINO',
+      'T': 'NIVEL DESTINO',
+      'V': 'OCUPACIÓN DESTINO',
+      'X': 'ELEMENTO DESTINO',
+      'Z': 'SECUENCIAL DESTINO',
+      'AB': 'COORDENADAS PLANO',
+      'AC': 'COORDENADAS DETALLE',
+      'AD': 'COORDENADAS DE POSICIÓN',
+      'AF': 'FAMILIA',
+      'AH': 'SUBFAMILIA',
+      'AJ': 'ESTADO',
+      'AL': 'TIPO',
+      'AN': 'MODELO',
+      'AP': 'ID',
+      'AR': 'TIPO',
+      'AT': 'MODELO',
+      'AV': 'ID',
+      'AX': 'CÓDIGO COMPLETO',
+      'AY': 'CÓDIGO EQUIVALENTE',
+    },
+    'CASO 5': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ÁREA',
+      'L': 'ELEMENTO',
+      'N': 'FAMILIA',
+      'P': 'SUBFAMILIA',
+      'R': 'ESTADO',
+      'T': 'TIPO',
+      'V': 'MODELO',
+      'X': 'ID',
+      'Z': 'TIPO',
+      'AB': 'MODELO',
+      'AD': 'ID',
+      'AF': 'CÓDIGO COMPLETO',
+      'AG': 'CÓDIGO EQUIVALENTE', 
+    },
+    'CASO 6': {
+      'B': 'LÍNEA',
+      'D': 'UBICACIÓN',
+      'F': 'NIVEL',
+      'H': 'OCUPACIÓN',
+      'J': 'ELEMENTO',
+      'L': 'POSICIÓN',
+      'N': 'FAMILIA',
+      'P': 'SUBFAMILIA',
+      'R': 'ESTADO',
+      'T': 'TIPO',
+      'V': 'MODELO',
+      'X': 'ID',
+      'Z': 'TIPO',
+      'AB': 'MODELO',
+      'AD': 'ID',
+      'AF': 'CÓDIGO COMPLETO',
+      'AG': 'CÓDIGO EQUIVALENTE',    
+    }
+
+  };
+
+  private readonly CATÁLOGOS_STRUCTURE = {
+    row5: {
+      'B': 'FAMILIA',
+      'C': 'ACRÓNIMO',
+      'E': 'SUBFAMILIA',
+      'F': 'ACRÓNIMO',
+      'G': 'SUBFAMILY',
+      'I': 'UBICACIONES (FRENTES)',
+      'J': 'CÓDIGO',
+      'L': 'RMS',
+      'M': 'ELEMENTO',
+      'N': 'CÓDIGO',
+      'P': 'OCUPACIÓN',
+      'Q': 'CÓDIGO',
+      'S': 'ESTADO',
+      'T': 'ACRÓNIMO'
+    },
+
+  };
+
+
+  private readonly REQUIRED_SHEETS = ['CARGA DE EQUIPOS', 'LRU', 'CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6', 'CATÁLOGOS'];
 
   constructor (
     private dialogRef: MatDialogRef<EquipmentLruUploadComponent>,
@@ -111,37 +309,18 @@ export class MultipleUploadComponent implements OnInit {
     event.target.value = '';
   }
 
-  private processMultipleFiles(files: File[]): void {
-
-
-
-
-    const validFiles = files.filter(file => this.isValidExcelFile(file) && this.isValidFileSize(file));
-    
-    if (validFiles.length === 0) {
-      return;
-    }
-
-
-    const duplicateFiles = validFiles.filter(file => 
-      this.excelFiles.some(existingFile => existingFile.nombre_del_archivo === file.name)
-    );
-
-    if (duplicateFiles.length > 0) {
-      const duplicateNames = duplicateFiles.map(f => f.name).join(', ');
-      this._resourcesService.openSnackBar(`Los siguientes archivos ya están cargados: ${duplicateNames}`);
-    }
+private processMultipleFiles(files: File[]): void {
 
+  const validFiles = files.filter(file => this.isValidExcelFile(file) && this.isValidFileSize(file));
+  
+  if (validFiles.length === 0) {
+    return;
+  }
 
-    const uniqueFiles = validFiles.filter(file => 
-      !this.excelFiles.some(existingFile => existingFile.nombre_del_archivo === file.name)
-    );
 
-    if (uniqueFiles.length > 0) {
-      this.isUploading = true;
-      this.uploadMultipleExcelFiles(uniqueFiles);
-    }
-  }
+  this.isUploading = true;
+  this.uploadMultipleExcelFiles(validFiles);
+}
 
   private isValidExcelFile(file: File): boolean {
     const fileName = file.name.toLowerCase();
@@ -166,75 +345,239 @@ export class MultipleUploadComponent implements OnInit {
     return true;
   }
 
-  async uploadMultipleExcelFiles(files: File[]): Promise<void> {
-    try {
-      const idUser = localStorage.getItem('idusuario');
-      const uploadPromises: Promise<any>[] = [];
+private validateExcelTemplate(workbook: XLSX.WorkBook): { isValid: boolean, errors: string[] } {
+  const errors: string[] = [];
+  
 
-      for (const file of files) {
-        const formData = new FormData();
-        formData.append('file', file);
-        formData.append('id_user', idUser!);
-        formData.append('linea', "1");
-        formData.append('tipo_archivo', 'excel');
+  const existingSheets = workbook.SheetNames;
+  const missingSheets = this.REQUIRED_SHEETS.filter(sheet => !existingSheets.includes(sheet));
+  
+  if (missingSheets.length > 0) {
+    errors.push(`Tu documento no cumple con las hojas requeridas de la plantilla`);
+  }
 
-        uploadPromises.push(
-          lastValueFrom(this._gdelService.uploadTempFile(formData)).then(async (response) => {
-            if (response.error) {
-              throw new Error(`${file.name}: ${response.msg}`);
-            } else {
-
-              const tempFileInfo: TempFileInfo = {
-                id: response.response.idArchivo,
-                name: file.name,
-                size: this.formatBytes(file.size)
-              };
-
-              this.uploadedFiles.push(tempFileInfo);
-
-
-              await this.readExcelFile(file, response.response.idArchivo);
-              
-              const newDocument: Document = {
-                nombre_del_archivo: file.name,
-                tamano_del_archivo: this.formatBytes(file.size),
-                archivo_id: response.response.idArchivo,
-                tipo_archivo: 'excel',
-                fecha_carga: new Date().toLocaleString()
-              };
-
-              this.excelFiles.push(newDocument);
-              return { success: true, fileName: file.name };
-            }
-          })
-        );
+  for (const sheetName of this.REQUIRED_SHEETS) {
+    if (existingSheets.includes(sheetName)) {
+      const sheetErrors = this.validateSheetHeaders(workbook.Sheets[sheetName], sheetName);
+      errors.push(...sheetErrors);
+      
+
+      if (sheetName === 'CARGA DE EQUIPOS') {
+        const dataErrors = this.validateSheetHasData(workbook.Sheets[sheetName], sheetName);
+        errors.push(...dataErrors);
       }
 
-      const results = await Promise.allSettled(uploadPromises);
-      
+    }
+  }
 
-      const successful = results.filter(result => result.status === 'fulfilled').length;
-      const failed = results.filter(result => result.status === 'rejected');
+  return {
+    isValid: errors.length === 0,
+    errors: errors
+  };
+}
 
-      if (successful > 0) {
-        this.updateTableData();
-        this._resourcesService.openSnackBar(
-          `${successful} archivo${successful > 1 ? 's' : ''} cargado${successful > 1 ? 's' : ''} exitosamente`
-        );
+private validateSheetHasData(worksheet: XLSX.WorkSheet, sheetName: string): string[] {
+  const errors: string[] = [];
+  
+  try {
+
+    const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
+    
+    
+    const dataStartRow = this.HEADER_ROW + 2; 
+    
+
+    let hasData = false;
+    
+
+    if (range.e.r >= dataStartRow) {
+
+      const mainColumns = ['B', 'D', 'F', 'H']; 
+      
+      for (let row = dataStartRow; row <= range.e.r; row++) {
+        for (const col of mainColumns) {
+          const cellAddress = `${col}${row}`;
+          const cell = worksheet[cellAddress];
+          
+          
+          if (cell && cell.v !== null && cell.v !== undefined && String(cell.v).trim() !== '') {
+            hasData = true;
+            break;
+          }
+        }
+        if (hasData) break;
       }
+    }
+    
+    if (!hasData) {
+      errors.push(`La hoja "${sheetName}" no contiene datos. Se requiere al menos un registro con información.`);
+    }
+    
+  } catch (error) {
+    errors.push(`Error al validar datos en la hoja "${sheetName}": No se pudo verificar el contenido.`);
+  }
+  
+  return errors;
+}
 
-      if (failed.length > 0) {
-        const errorMessages = failed.map(result => 
-          result.status === 'rejected' ? result.reason.message : 'Error desconocido'
-        );
-        this._resourcesService.openSnackBar(`Errores: ${errorMessages.join('; ')}`);
+  private validateSheetHeaders(worksheet: XLSX.WorkSheet, sheetName: string): string[] {
+    const errors: string[] = [];
+    
+
+    if (sheetName === 'CATÁLOGOS') {
+      return this.validateCatalogosSheet(worksheet);
+    }
+    
+    const expectedHeaders = this.TEMPLATE_STRUCTURE[sheetName];
+    
+    if (!expectedHeaders) {
+      errors.push(`No se encontró la estructura esperada para la hoja: ${sheetName}`);
+      return errors;
+    }
+
+
+    const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
+    
+    for (const [column, expectedHeader] of Object.entries(expectedHeaders)) {
+      const cellAddress = `${column}${this.HEADER_ROW}`;
+      const cell = worksheet[cellAddress];
+      const actualHeader = cell ? String(cell.v).trim() : '';
+      
+      if (actualHeader !== expectedHeader) {
+        errors.push(`Hoja "${sheetName}", Columna ${column}: Se esperaba "${expectedHeader}" pero se encontró "${actualHeader}"`);
       }
+    }
 
-      this.isUploading = false;
-    } catch (error: any) {
-      this.handleUploadError(error);
-      this.isUploading = false;
+    return errors;
+  }
+
+
+  private validateCatalogosSheet(worksheet: XLSX.WorkSheet): string[] {
+    const errors: string[] = [];
+
+   
+    for (const [column, expectedHeader] of Object.entries(this.CATÁLOGOS_STRUCTURE.row5)) {
+      const cellAddress = `${column}5`;
+      const cell = worksheet[cellAddress];
+      const actualHeader = cell ? String(cell.v).trim() : '';
+      
+      if (actualHeader !== expectedHeader) {
+        errors.push(`Hoja "CATÁLOGOS", Fila 5, Columna ${column}: Se esperaba "${expectedHeader}" pero se encontró "${actualHeader}"`);
+      }
     }
+
+
+
+
+    return errors;
+  }
+
+ async uploadMultipleExcelFiles(files: File[]): Promise<void> {
+   try {
+     const idUser = localStorage.getItem('idusuario');
+     const uploadPromises: Promise<any>[] = [];
+ 
+     for (const file of files) {
+ 
+       const validationResult = await this.validateFileStructure(file);
+       
+       if (!validationResult.isValid) {
+ 
+         this._resourcesService.openSnackBar(`Archivo "${file.name}" - Plantilla inválida: ${validationResult.errors.join('. ')}`);
+         continue; 
+       }
+ 
+ 
+       const formData = new FormData();
+       formData.append('file', file);
+       formData.append('id_user', idUser!);
+       formData.append('linea', "1");
+       formData.append('tipo_archivo', 'excel');
+ 
+       uploadPromises.push(
+         lastValueFrom(this._gdelService.uploadTempFile(formData)).then(async (response) => {
+           if (response.error) {
+             throw new Error(`${file.name}: ${response.msg}`);
+           } else {
+ 
+             const tempFileInfo: TempFileInfo = {
+               id: response.response.idArchivo,
+               name: file.name,
+               size: this.formatBytes(file.size)
+             };
+ 
+             this.uploadedFiles.push(tempFileInfo);
+ 
+ 
+             await this.readExcelFile(file, response.response.idArchivo);
+             
+             const newDocument: Document = {
+               nombre_del_archivo: file.name,
+               tamano_del_archivo: this.formatBytes(file.size),
+               archivo_id: response.response.idArchivo,
+               tipo_archivo: 'excel',
+               fecha_carga: new Date().toLocaleString()
+             };
+ 
+             this.excelFiles.push(newDocument);
+             return { success: true, fileName: file.name };
+           }
+         })
+       );
+     }
+ 
+ 
+     if (uploadPromises.length === 0) {
+       this._resourcesService.openSnackBar('El archivo debe de contener por lo menos un registro y cumplir con la estructura requerida ');
+       this.isUploading = false;
+       return;
+     }
+ 
+  
+     const results = await Promise.allSettled(uploadPromises);
+     
+ 
+     const successful = results.filter(result => result.status === 'fulfilled').length;
+     const failed = results.filter(result => result.status === 'rejected');
+ 
+     if (successful > 0) {
+       this.updateTableData();
+       this._resourcesService.openSnackBar(
+         `${successful} archivo${successful > 1 ? 's' : ''} cargado${successful > 1 ? 's' : ''} exitosamente`
+       );
+     }
+ 
+     if (failed.length > 0) {
+       const errorMessages = failed.map(result => 
+         result.status === 'rejected' ? result.reason.message : 'Error desconocido'
+       );
+       this._resourcesService.openSnackBar(`Errores: ${errorMessages.join('; ')}`);
+     }
+ 
+     this.isUploading = false;
+   } catch (error: any) {
+     this.handleUploadError(error);
+     this.isUploading = false;
+   }
+ }
+
+  private async validateFileStructure(file: File): Promise<{ isValid: boolean, errors: string[] }> {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = (e: any) => {
+        try {
+          const data = new Uint8Array(e.target.result);
+          const workbook = XLSX.read(data, { type: 'array' });
+          
+          const validation = this.validateExcelTemplate(workbook);
+          resolve(validation);
+        } catch (error) {
+          reject(error);
+        }
+      };
+      reader.onerror = reject;
+      reader.readAsArrayBuffer(file);
+    });
   }
 
   private async readExcelFile(file: File, fileId: any): Promise<void> {

+ 39 - 33
src/app/components/initial-data-upload/preventive-documents-upload/preventive-documents-upload.component.html

@@ -5,7 +5,7 @@
   </button>
 </div>
 
-<main class="main-container animated fadeIn">
+<main class="main-container animated fadeIn" (window:resize)="onResize()">
   <mat-card class="override-card override-elevation-z8">
     <mat-card-header class="card_header">
       <mat-card-title class="align-center">Carga de Mantenimientos Preventivos</mat-card-title>
@@ -19,7 +19,7 @@
           </mat-form-field>
 
 
-          <!-- Campo de rango de fechas -->
+
           <mat-form-field appearance="outline" class="search-field ml-10">
             <mat-label>Rango de fechas</mat-label>
             <input matInput readonly placeholder="Seleccionar fechas..." [formControl]="datesRangeControl" #dateRange (click)="openDateRangePicker()">
@@ -78,19 +78,19 @@
       <div class="content_table">
         <table mat-table matSort [dataSource]="dataSource" class="animated fadeIn">
           
-          <!-- Columna Código -->
+
           <ng-container matColumnDef="CODIGO" >
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Código</th>
             <td mat-cell *matCellDef="let element">{{ element.CODIGO }}</td>
           </ng-container>
           
-          <!-- Columna Versión -->
+
           <ng-container matColumnDef="VERSION">
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Versión</th>
             <td mat-cell *matCellDef="let element">{{ element.VERSION }}</td>
           </ng-container>
           
-          <!-- Columna Nombre -->
+
           <ng-container matColumnDef="NOMBRE">
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Nombre</th>
             <td mat-cell *matCellDef="let element">
@@ -101,34 +101,19 @@
             </td>
           </ng-container>
           
-          <!-- Columna Tamaño -->
+
           <ng-container matColumnDef="TAMANIO">
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Tamaño</th>
             <td mat-cell *matCellDef="let element">{{ element.TAMANIO }}</td>
           </ng-container>
           
-          <!-- Columna Propietario -->
+
           <ng-container matColumnDef="USRREG">
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Propietario</th>
             <td mat-cell *matCellDef="let element">{{ element.USRREG }}</td>
           </ng-container>
 
-          <!-- <ng-container matColumnDef="ACCESO">
-            <th mat-header-cell *matHeaderCellDef mat-sort-header>Acceso</th>
-            <td mat-cell *matCellDef="let element">
-              <div class="access-avatars">
-                <div *ngFor="let user of element.ACCESO" 
-                     class="user-avatar" 
-                     [style.background-color]="user.userColor"
-                     [matTooltip]="user.userName"
-                     (click)="openAccessDialog(element.CODIGO, element.VERSION, element.NOMBRE, user.isTrigger, false)">
-                  {{ user.userLabel }}
-                </div>
-              </div>
-            </td>
-          </ng-container> -->
-          
-          <!-- Columna Acciones -->
+
           <ng-container matColumnDef="ACCIONES">
             <th mat-header-cell *matHeaderCellDef>Acciones</th>
             <td mat-cell *matCellDef="let element">
@@ -138,38 +123,60 @@
                   matTooltip="Ver documento"
                   class="override_no_shadow white_font pink_primary_background mr-5"
                   [disabled]="isLoading || !documentsViewerEnabled"
-                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)"
+                  *ngIf="!btnSmall">
                   <mat-icon>visibility</mat-icon>
                 </button>
-
-                <!-- Botón Descargar -->
+              
                 <button
                   mat-mini-fab
                   matTooltip="Descargar documento"
                   class="override_no_shadow white_font green_primary_background mr-5"
                   [disabled]="isLoading"
-                  (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)">
+                   (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)"
+                   *ngIf="!btnSmall">
                   <mat-icon>download</mat-icon>
                 </button>
 
-                <!-- Botón Eliminar -->
-                <button
+                <button mat-mini-fab color="primary" class="override_no_shadow animated fadeIn gray_dark_font transparent_background" *ngIf="btnSmall" [matMenuTriggerFor]="menu">
+                  <mat-icon>more_vert</mat-icon>
+                </button>
+            
+               <button
                   mat-mini-fab
                   matTooltip="Eliminar documento"
                   class="override_no_shadow white_font red_primary_background"
                   [disabled]="isLoading || !documentsManagementEnabled"
-                  (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)">
+                 (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)"
+                 *ngIf="!btnSmall">
                   <mat-icon>delete</mat-icon>
                 </button>
+
+                <mat-menu #menu>
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>visibility</mat-icon>
+                    <span>Ver documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>download</mat-icon>
+                    <span>Descargar documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>delete</mat-icon>
+                    <span>Eliminar documento</span>
+                  </button>
+                </mat-menu>
               </div>
             </td>
           </ng-container>
           
-          <!-- Filas de la tabla -->
+
           <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
           <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
           
-          <!-- Fila cuando no hay datos -->
+
           <tr class="mat-row" *matNoDataRow style="height: 100%;">
             <td class="mat-cell p-20 align-center" [attr.colspan]="displayedColumns.length">
               <object data="assets/img/empty_data.svg" width="150"></object>
@@ -185,5 +192,4 @@
   </mat-card>
 </main>
 
-<!-- Elemento oculto para descargas -->
 <a id="download" style="display: none;"></a>

+ 61 - 1
src/app/components/initial-data-upload/preview-Equipmentexcel-document/preview-excel-document.component.css

@@ -215,4 +215,64 @@ button:focus {
 
 @keyframes spin {
   to { transform: rotate(360deg); }
-}
+}
+
+
+
+/* Estilos adicionales para mejorar la experiencia de edición */
+.cell-input:focus {
+  border-color: #007bff !important;
+  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
+}
+
+.cell-input {
+  transition: all 0.2s ease;
+}
+
+.data-cell {
+  position: relative;
+}
+
+.data-cell:hover {
+  background-color: rgba(0, 123, 255, 0.05);
+}
+
+.row-number:hover .mat-icon-button {
+  opacity: 1;
+}
+
+.column-header:hover .mat-icon-button {
+  opacity: 1;
+}
+
+/* Animaciones suaves */
+.preview-excel-container {
+  animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+  .header-controls {
+    flex-direction: column;
+    gap: 12px;
+  }
+  
+  .edit-controls {
+    flex-wrap: wrap;
+  }
+  
+  .table-container {
+    overflow-x: scroll;
+  }
+}

+ 27 - 39
src/app/components/initial-data-upload/preview-Equipmentexcel-document/preview-excel-document.component.html

@@ -3,7 +3,6 @@
   <div class="preview-header" style="padding: 16px; border-bottom: 1px solid #e0e0e0; position: sticky; top: 0; background: white; z-index: 100;">
     <div class="header-top" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; width: 50%;">
 
-
       <div class="changes-indicator" *ngIf="hasUnsavedChanges" 
            style="display: flex; align-items: center; gap: 6px; padding: 6px 12px; background: #fff3cd; border-radius: 20px; font-size: 12px; color: #856404; border: 1px solid #ffeaa7;">
         <mat-icon style="font-size: 14px;">edit</mat-icon>
@@ -11,15 +10,14 @@
       </div>
     </div>
 
-    <div class="header-controls" style="display: flex;  gap: 16px; width: 100%; justify-content: end; display: flex; align-items:center;">
+    <div class="header-controls" style="display: flex; gap: 16px; width: 100%; justify-content: end; align-items: center;">
       <div class="docu">
-      <h2 mat-dialog-title style="margin: 0; font-size: 18px; display: flex; align-items: center; gap: 8px; color: #424242; margin-bottom: 13px;" >
-        <mat-icon style="color: #1976d2;">table_chart</mat-icon>
-        {{ fileName }}
-      </h2>
+        <h2 mat-dialog-title style="margin: 0; font-size: 18px; display: flex; align-items: center; gap: 8px; color: #424242; margin-bottom: 13px;" >
+          <mat-icon style="color: #1976d2;">table_chart</mat-icon>
+          {{ fileName }}
+        </h2>
       </div>
 
-
       <div class="sheet-selector" *ngIf="sheetNames.length > 1">
         <mat-form-field appearance="outline" style="width: 200px;">
           <mat-label>Hoja</mat-label>
@@ -31,14 +29,13 @@
         </mat-form-field>
       </div>
       
-
       <div class="edit-controls" style="display: flex; align-items: center; gap: 8px;">
         <button mat-icon-button 
                 (click)="toggleEditMode()" 
                 [color]="editMode ? 'warn' : 'primary'"
                 [matTooltip]="editMode ? 'Salir del modo edición' : 'Activar modo edición'"
                 style="border-radius: 10px; margin-bottom: 20px;"
-                  [ngClass]="editMode ? 'pink_primary_background white_font' : 'orange_primary_background white_font'">
+                [ngClass]="editMode ? 'pink_primary_background white_font' : 'orange_primary_background white_font'">
           <mat-icon>{{ editMode ? 'visibility' : 'edit' }}</mat-icon>
         </button>
         
@@ -48,7 +45,7 @@
                   matTooltip="Agregar fila"
                   color="primary"
                   style="border-radius: 8px;">
-            <mat-icon style="size: 30px;">table_rows/></mat-icon>
+            <mat-icon>table_rows</mat-icon>
           </button>
           
           <button mat-icon-button 
@@ -81,25 +78,20 @@
     </div>
   </div>
 
-
   <div class="table-container" style="overflow: auto; background: #fafafa; position: relative;">
     
-
     <table class="excel-table" 
            *ngIf="currentSheetData.length > 0" 
            style="width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; background: white; margin: 16px; border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
       
-
       <thead>
         <tr style="background: linear-gradient(to bottom, #f8f9fa, #e9ecef); border-radius: 8px 8px 0 0;">
-
           <th class="row-header" 
               style="width: 60px; padding: 12px 8px; border-right: 1px solid #dee2e6; text-align: center; font-weight: 600; color: #495057; font-size: 12px; border-radius: 8px 0 0 0;">
             #
           </th>
           
-
-          <th *ngFor="let header of getTableHeaders(); let colIndex = index; let isLast = last" 
+          <th *ngFor="let header of getTableHeaders(); let colIndex = index; let isLast = last; trackBy: trackByIndex" 
               class="column-header" 
               [style.border-radius]="isLast ? '0 8px 0 0' : '0'"
               style="min-width: 140px; padding: 12px 16px; border-right: 1px solid #dee2e6; text-align: left; font-weight: 600; color: #495057; font-size: 12px; position: relative;">
@@ -117,15 +109,12 @@
         </tr>
       </thead>
       
-
       <tbody>
-        <tr *ngFor="let row of currentSheetData; let rowIndex = index; let isLast = last"
+        <tr *ngFor="let row of currentSheetData; let rowIndex = index; let isLast = last; trackBy: trackByIndex"
             [style.background]="rowIndex % 2 === 0 ? '#ffffff' : '#f8f9fa'"
             [style.border-radius]="isLast ? '0 0 8px 8px' : '0'"
-            style="transition: all 0.2s ease; border-bottom: 1px solid #e9ecef;"
-            >
+            style="transition: all 0.2s ease; border-bottom: 1px solid #e9ecef;">
           
-
           <td class="row-number" 
               [style.border-radius]="isLast ? '0 0 0 8px' : '0'"
               style="width: 60px; padding: 12px 8px; border-right: 1px solid #dee2e6; text-align: center; font-weight: 500; color: #6c757d; font-size: 12px; background: #f8f9fa !important;">
@@ -142,7 +131,7 @@
           </td>
           
 
-          <td *ngFor="let cell of row; let colIndex = index; let isLastCol = last" 
+          <td *ngFor="let cell of row; let colIndex = index; let isLastCol = last; trackBy: trackByIndex" 
               class="data-cell" 
               [style.border-radius]="isLast && isLastCol ? '0 0 8px 0' : '0'"
               style="min-width: 140px; max-width: 250px; padding: 12px 16px; border-right: 1px solid #dee2e6; vertical-align: top; position: relative;">
@@ -154,19 +143,23 @@
               {{ getCellValue(row, colIndex) }}
             </div>
 
-            <textarea *ngIf="editMode"
-                      class="cell-input"
-                      [value]="getCellValue(row, colIndex)"
-                      (input)="onCellChange(rowIndex, colIndex, $event)"
-                      style="width: 100%; min-height: 32px; margin-right: 3px; border: 1px solid #ced4da; border-radius: 4px; font-size: 13px; font-family: 'Roboto', sans-serif; resize: vertical; background: white; transition: border-color 0.2s ease;"
-                      >
-            </textarea>
+
+            <input *ngIf="editMode"
+                   type="text"
+                   class="cell-input"
+                   [value]="getCellValue(row, colIndex)"
+                   (input)="onCellInput(rowIndex, colIndex, $event)"
+                   (blur)="onCellBlur(rowIndex, colIndex, $event)"
+                   [attr.data-row]="rowIndex"
+                   [attr.data-col]="colIndex"
+                   style="width: 100%; border: 1px solid #ced4da; border-radius: 4px; padding: 8px; font-size: 13px; font-family: 'Roboto', sans-serif; background: white; transition: border-color 0.2s ease; outline: none;"
+                   onfocus="this.style.borderColor='#007bff'; this.style.boxShadow='0 0 0 2px rgba(0, 123, 255, 0.25)'"
+                   onblur="this.style.borderColor='#ced4da'; this.style.boxShadow='none'">
           </td>
         </tr>
       </tbody>
     </table>
     
-
     <div *ngIf="currentSheetData.length === 0" 
          class="no-data-message"
          style="padding: 60px 20px; text-align: center; color: #6c757d; background: white; margin: 16px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
@@ -183,11 +176,10 @@
     </div>
   </div>
 
-
   <div class="table-summary">
     <div class="summary-info" style="display: flex; gap: 24px;">
       <div style="display: flex; align-items: center; gap: 6px;">
-        <mat-hint style="color: #1976d2; ;">INFORMACIÓN:</mat-hint>
+        <mat-hint style="color: #1976d2;">INFORMACIÓN:</mat-hint>
         <mat-icon style="font-size: 16px;">layers</mat-icon>
         <span>{{ sheetNames.length }} {{ sheetNames.length === 1 ? 'hoja' : 'hojas' }}</span>
       </div>
@@ -222,23 +214,19 @@
       <span>{{ selectedSheet }} • {{ editMode ? 'Modo edición' : 'Solo lectura' }}</span>
     </div>
     
-
     <div class="action-buttons" style="display: flex; gap: 12px;">
-     
       <button mat-button
-              (click)="closeDialog()"
-              >
+              (click)="closeDialog()">
         <mat-icon>close</mat-icon>
         Cancelar
       </button>
      
       <button mat-stroked-button
               (click)="ValidateExcel()"
-              class="white_font blue_send_background" >
+              class="white_font blue_send_background">
         <mat-icon>check</mat-icon>
         Validar documento
       </button>
-
     </div>
   </mat-dialog-actions>
-</div>
+</div>

+ 152 - 20
src/app/components/initial-data-upload/preview-Equipmentexcel-document/preview-excel-document.component.ts

@@ -30,10 +30,13 @@ export class PreviewExcelDocumentComponent implements OnInit {
       this.sheetNames = this.data.sheetNames;
       this.selectedSheet = this.sheetNames[0] || '';
       
-
       this.originalData = JSON.parse(JSON.stringify(this.data.sheets));
       
+      console.log('Datos originales:', this.data.sheets[this.selectedSheet]);
+      
       this.loadSheetData();
+      
+      console.log('Datos procesados:', this.currentSheetData);
     }
   }
 
@@ -41,15 +44,119 @@ export class PreviewExcelDocumentComponent implements OnInit {
     if (this.data?.sheets && this.selectedSheet) {
       this.currentSheetData = this.data.sheets[this.selectedSheet] || [];
       
-
+      this.processAndFormatData();
       this.normalizeTableData();
     }
   }
 
+
+  private processAndFormatData(): void {
+    this.currentSheetData = this.currentSheetData.map((row, rowIndex) => 
+      row.map((cell, colIndex) => {
+        if (rowIndex < 7) {
+          return cell;
+        }
+        
+        const isDateColumn = this.isDateColumn(colIndex);
+        
+        if (isDateColumn && typeof cell === 'number' && cell > 1000) {
+          return this.excelDateToJSDate(cell);
+        }
+        
+        return this.formatCellValue(cell);
+      })
+    );
+  }
+
+
+  private isDateColumn(colIndex: number): boolean {
+
+    if (this.currentSheetData.length <= 7) {
+      return false;
+    }
+    
+    const headerRow = this.currentSheetData[6]; 
+    if (!headerRow || !headerRow[colIndex]) {
+      return false;
+    }
+    
+    const header = String(headerRow[colIndex]).toLowerCase();
+    const dateKeywords = [
+      'fecha', 'date', 'garantía', 'garantia', 'adquisición', 'adquisicion', 
+      'vencimiento', 'inicio', 'fin', 'vigencia', 'caducidad'
+    ];
+    
+    return dateKeywords.some(keyword => header.includes(keyword));
+  }
+
+  private formatCellValue(value: any): any {
+    if (value === null || value === undefined) {
+      return '';
+    }
+
+    if (typeof value === 'number' && this.isExcelDate(value)) {
+      return this.excelDateToJSDate(value);
+    }
+
+    if (typeof value === 'string' && /^\d+$/.test(value.trim())) {
+      const numValue = parseInt(value, 10);
+      if (this.isExcelDate(numValue)) {
+        return this.excelDateToJSDate(numValue);
+      }
+    }
+
+    return value;
+  }
+
+  private isExcelDate(value: number): boolean {
+
+    if (value < 1 || value > 80000) {
+      return false;
+    }
+    
+    if (value >= 40000 && value <= 60000) {
+      return true;
+    }
+    
+    if (Number.isInteger(value) && value > 25000 && value < 80000) {
+      return true;
+    }
+    
+
+    if (!Number.isInteger(value) && value > 25000 && value < 80000) {
+      return true;
+    }
+    
+    return false;
+  }
+
+
+  private excelDateToJSDate(excelDate: number): string {
+    try {
+
+      const excelEpoch = new Date(1899, 11, 30); 
+      const jsDate = new Date(excelEpoch.getTime() + (excelDate * 24 * 60 * 60 * 1000));
+      
+     
+      if (isNaN(jsDate.getTime())) {
+        return excelDate.toString(); 
+      }
+      
+
+      const day = jsDate.getDate().toString().padStart(2, '0');
+      const month = (jsDate.getMonth() + 1).toString().padStart(2, '0');
+      const year = jsDate.getFullYear();
+      
+      return `${day}/${month}/${year}`;
+    } catch (error) {
+
+      return excelDate.toString();
+    }
+  }
+
   private normalizeTableData(): void {
     if (this.currentSheetData.length === 0) return;
     
-
     const maxColumns = Math.max(...this.currentSheetData.map(row => row.length));
     
     this.currentSheetData = this.currentSheetData.map(row => {
@@ -66,7 +173,6 @@ export class PreviewExcelDocumentComponent implements OnInit {
   }
 
   onSheetChange(sheetName: string): void {
-
     if (this.hasUnsavedChanges) {
       if (!confirm('Tienes cambios sin guardar. ¿Deseas continuar sin guardar?')) {
         return;
@@ -79,52 +185,80 @@ export class PreviewExcelDocumentComponent implements OnInit {
     this.cdr.detectChanges();
   }
 
+
   onCellChange(rowIndex: number, colIndex: number, event: any): void {
     const newValue = event.target.value;
     
-
     if (!this.currentSheetData[rowIndex]) {
       this.currentSheetData[rowIndex] = [];
     }
     
     this.currentSheetData[rowIndex][colIndex] = newValue;
     
-
+ 
     if (!this.data.sheets[this.selectedSheet]) {
       this.data.sheets[this.selectedSheet] = [];
     }
     
+
+    if (!this.data.sheets[this.selectedSheet][rowIndex]) {
+      this.data.sheets[this.selectedSheet][rowIndex] = [];
+    }
+    
     this.data.sheets[this.selectedSheet][rowIndex][colIndex] = newValue;
     
+    this.hasUnsavedChanges = true;
+  }
+
+
+  onCellInput(rowIndex: number, colIndex: number, event: any): void {
+    const newValue = event.target.value;
+    
 
+    if (!this.currentSheetData[rowIndex]) {
+      this.currentSheetData[rowIndex] = [];
+    }
+    
+    this.currentSheetData[rowIndex][colIndex] = newValue;
     this.hasUnsavedChanges = true;
   }
 
+
+  onCellBlur(rowIndex: number, colIndex: number, event: any): void {
+    const newValue = event.target.value;
+    
+
+    if (!this.data.sheets[this.selectedSheet]) {
+      this.data.sheets[this.selectedSheet] = [];
+    }
+    
+    if (!this.data.sheets[this.selectedSheet][rowIndex]) {
+      this.data.sheets[this.selectedSheet][rowIndex] = [];
+    }
+    
+    this.data.sheets[this.selectedSheet][rowIndex][colIndex] = newValue;
+  }
+
   toggleEditMode(): void {
     this.editMode = !this.editMode;
     
     if (!this.editMode && this.hasUnsavedChanges) {
-
       console.log('Saliendo del modo edición. Datos modificados:', this.data.sheets[this.selectedSheet]);
     }
   }
 
   saveChanges(): void {
-
     console.log('Guardando cambios para la hoja:', this.selectedSheet);
     console.log('Datos modificados:', this.data.sheets[this.selectedSheet]);
     
-
     this.originalData = JSON.parse(JSON.stringify(this.data.sheets));
     this.hasUnsavedChanges = false;
     
-
     alert('Cambios guardados correctamente');
   }
 
   discardChanges(): void {
     if (confirm('¿Estás seguro de que deseas descartar todos los cambios?')) {
-
       this.data.sheets = JSON.parse(JSON.stringify(this.originalData));
       this.loadSheetData();
       this.hasUnsavedChanges = false;
@@ -137,7 +271,6 @@ export class PreviewExcelDocumentComponent implements OnInit {
     const newRow = new Array(maxColumns).fill('');
     this.currentSheetData.push(newRow);
     
-
     if (!this.data.sheets[this.selectedSheet]) {
       this.data.sheets[this.selectedSheet] = [];
     }
@@ -147,12 +280,10 @@ export class PreviewExcelDocumentComponent implements OnInit {
   }
 
   addColumn(): void {
-
     this.currentSheetData.forEach(row => {
       row.push('');
     });
     
-
     if (this.data.sheets[this.selectedSheet]) {
       this.data.sheets[this.selectedSheet].forEach((row: any[]) => {
         row.push('');
@@ -166,7 +297,6 @@ export class PreviewExcelDocumentComponent implements OnInit {
     if (confirm('¿Estás seguro de que deseas eliminar esta fila?')) {
       this.currentSheetData.splice(rowIndex, 1);
       
-
       if (this.data.sheets[this.selectedSheet]) {
         this.data.sheets[this.selectedSheet].splice(rowIndex, 1);
       }
@@ -177,12 +307,10 @@ export class PreviewExcelDocumentComponent implements OnInit {
 
   deleteColumn(colIndex: number): void {
     if (confirm('¿Estás seguro de que deseas eliminar esta columna?')) {
-
       this.currentSheetData.forEach(row => {
         row.splice(colIndex, 1);
       });
       
-
       if (this.data.sheets[this.selectedSheet]) {
         this.data.sheets[this.selectedSheet].forEach((row: any[]) => {
           row.splice(colIndex, 1);
@@ -220,7 +348,6 @@ export class PreviewExcelDocumentComponent implements OnInit {
 
   ValidateExcel(): void {
     console.log('Exportando Excel con datos:', this.data.sheets);
-
   }
 
   closeDialog(): void {
@@ -236,12 +363,17 @@ export class PreviewExcelDocumentComponent implements OnInit {
     });
   }
 
-
   getCellValue(row: any[], colIndex: number): string {
-    return row[colIndex] !== null && row[colIndex] !== undefined ? row[colIndex] : '';
+    const value = row[colIndex];
+    return value !== null && value !== undefined ? String(value) : '';
   }
 
   getRowNumber(index: number): number {
     return index + 1;
   }
+
+ 
+  trackByIndex(index: number, item: any): number {
+    return index;
+  }
 }

+ 82 - 14
src/app/components/initial-data-upload/related-documents-upload/related-documents-upload.component.html

@@ -1,11 +1,11 @@
 <div class="breadcrumb-container">
-  <button mat-button routerLink="/sam/initial-data-upload" class="raised-button-background white_font mb-20 mt-20" style="margin-left: 260px;">
+  <button mat-button [routerLink]="['/sam/initial-data-upload']" class="raised-button-background white_font mb-20 mt-20" style="margin-left: 260px;">
     <mat-icon>arrow_back</mat-icon>
-    Interfaz de carga de datos
+      Interfaz de carga de datos
   </button>
 </div>
 
-<main class="main-container animated fadeIn">
+<main class="main-container animated fadeIn" (window:resize)="onResize()">
   <mat-card class="override-card override-elevation-z8">
     <mat-card-header class="card_header">
       <mat-card-title class="align-center">Carga de documentos relacionados</mat-card-title>
@@ -13,21 +13,44 @@
       <div class="card_actions">
         <div class="card_action_filters ml-20">
           <mat-form-field appearance="outline" class="search-field">
-            <mat-label>Buscar documento
-            </mat-label>
-            <input matInput placeholder="Buscar..." #searchInput>
+            <mat-label>Buscar documentos</mat-label>
+            <input matInput placeholder="Buscar..." [formControl]="fileNameControl" #searchInput>
             <mat-icon matSuffix>search</mat-icon>
           </mat-form-field>
+
+          <mat-form-field appearance="outline" class="search-field ml-10">
+            <mat-label>Rango de fechas</mat-label>
+            <input matInput readonly placeholder="Seleccionar fechas..." [formControl]="datesRangeControl" #dateRange (click)="openDateRangePicker()">
+            <mat-icon matSuffix>date_range</mat-icon>
+            <button mat-icon-button matSuffix *ngIf="startDate || endDate" (click)="clearDatesRange(); $event.stopPropagation()">
+              <mat-icon>clear</mat-icon>
+            </button>
+          </mat-form-field>
+
+          <mat-form-field appearance="outline" style=" width: 30%;" class="ml-10">
+            <mat-label>Tipo de archivo</mat-label>
+            <mat-select [formControl]="fileTypeControl">
+              <mat-option>    -----------Ningúno----------</mat-option>
+              <mat-optgroup *ngFor="let group of supportedFilesArr" [label]="group.category">
+                <mat-option *ngFor="let item of group.files" [value]="item.ext">
+                  <span *ngIf="item.name != ''" class="mr-4">{{ item.name }}</span>({{ item.ext }})
+                </mat-option>
+              </mat-optgroup>
+            </mat-select>
+          </mat-form-field>          
+      
         </div>
         
         <div class="card_action_items">
+
           <button
             mat-button
             (click)="openIndividualUpload()"
+            [disabled]="!registerDocumentsEnabled"
             class="raised-button-background white_font mr-10">
             Carga individual
           </button>
-          
+
           <button
             mat-mini-fab
             matTooltip="Actualizar datos"
@@ -59,7 +82,7 @@
         <table mat-table matSort [dataSource]="dataSource" class="animated fadeIn">
           
 
-          <ng-container matColumnDef="CODIGO">
+          <ng-container matColumnDef="CODIGO" >
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Código</th>
             <td mat-cell *matCellDef="let element">{{ element.CODIGO }}</td>
           </ng-container>
@@ -92,20 +115,62 @@
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Propietario</th>
             <td mat-cell *matCellDef="let element">{{ element.USRREG }}</td>
           </ng-container>
-          
 
-          <ng-container matColumnDef="acciones">
+
+          <ng-container matColumnDef="ACCIONES">
             <th mat-header-cell *matHeaderCellDef>Acciones</th>
             <td mat-cell *matCellDef="let element">
               <div class="actions-container">
                 <button
+                  mat-mini-fab
+                  matTooltip="Ver documento"
+                  class="override_no_shadow white_font pink_primary_background mr-5"
+                  [disabled]="isLoading || !documentsViewerEnabled"
+                  (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)"
+                  *ngIf="!btnSmall">
+                  <mat-icon>visibility</mat-icon>
+                </button>
+              
+                <button
+                  mat-mini-fab
+                  matTooltip="Descargar documento"
+                  class="override_no_shadow white_font green_primary_background mr-5"
+                  [disabled]="isLoading"
+                   (click)="downloadFile(element.CODIGO, element.VERSION, element.NOMBRE)"
+                   *ngIf="!btnSmall">
+                  <mat-icon>download</mat-icon>
+                </button>
+
+                <button mat-mini-fab color="primary" class="override_no_shadow animated fadeIn gray_dark_font transparent_background" *ngIf="btnSmall" [matMenuTriggerFor]="menu">
+                  <mat-icon>more_vert</mat-icon>
+                </button>
+            
+               <button
                   mat-mini-fab
                   matTooltip="Eliminar documento"
                   class="override_no_shadow white_font red_primary_background"
-                  [disabled]="isLoading"
-                  (click)="confirmDelete(element)">
+                  [disabled]="isLoading || !documentsManagementEnabled"
+                 (click)="confirmDelete(element.CODIGO, element.VERSION, element.NOMBRE)"
+                 *ngIf="!btnSmall">
                   <mat-icon>delete</mat-icon>
                 </button>
+
+                <mat-menu #menu>
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>visibility</mat-icon>
+                    <span>Ver documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>download</mat-icon>
+                    <span>Descargar documento</span>
+                  </button>
+                  
+                  <button mat-menu-item (click)="openViewer(element.CODIGO, element.VERSION, element.NOMBRE)">
+                    <mat-icon>delete</mat-icon>
+                    <span>Eliminar documento</span>
+                  </button>
+                </mat-menu>
               </div>
             </td>
           </ng-container>
@@ -114,7 +179,8 @@
           <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
           <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
           
-          <tr class="mat-row" *matNoDataRow>
+
+          <tr class="mat-row" *matNoDataRow style="height: 100%;">
             <td class="mat-cell p-20 align-center" [attr.colspan]="displayedColumns.length">
               <object data="assets/img/empty_data.svg" width="150"></object>
               <h2>Sin datos</h2>
@@ -127,4 +193,6 @@
 
     <mat-paginator [pageSizeOptions]="[10, 20, 50]" class="override-paginator" showFirstLastButtons></mat-paginator>
   </mat-card>
-</main>
+</main>
+
+<a id="download" style="display: none;"></a>

+ 484 - 146
src/app/components/initial-data-upload/related-documents-upload/related-documents-upload.component.ts

@@ -1,29 +1,36 @@
-import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
-import { MatDialog } from '@angular/material/dialog';
-import { MatTableDataSource } from '@angular/material/table';
+import { OrderAssociatonComponent } from './../../gdel/order-associaton/order-associaton.component';
+import { AccessDialogComponent } from './../../gdel/access-dialog/access-dialog.component';
+import { DateRangePickerComponent } from './../../gdel/date-range-picker/date-range-picker.component';
+import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
+import { apiTemp, docClassification, modules, supportedFiles } from '../../../../environments/environment.prod';
 import { MatPaginator } from '@angular/material/paginator';
 import { MatSort } from '@angular/material/sort';
-import { FunctionsService } from '../../../services/functions.service';
-import { TempFileInfo } from '../../../interfaces/temp-file-info.interface';
+import { FormControl } from '@angular/forms';
 import { FindedFile, FindedFilesResponse } from '../../../interfaces/finded-files.interface';
-import { availableFiles } from '../../../../environments/environment.prod';
-import { ResourcesService } from '../../../services/resources.service';
+import { MatTableDataSource } from '@angular/material/table';
+import { DOCUMENT } from '@angular/common';
 import { EncService } from '../../../services/enc.service';
-import { lastValueFrom } from 'rxjs';
+import { MatDialog } from '@angular/material/dialog';
 import { GdelService } from '../../../services/gdel.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { ResourcesService } from '../../../services/resources.service';
+import { SocketService } from '../../../services/socket.service';
+import { UsersProfilesService } from '../../../services/users-profiles.service';
+import { FunctionsService } from '../../../services/functions.service';
+import { UserConsultResponse } from '../../../interfaces/user.interface';
+import { lastValueFrom } from 'rxjs';
+import { ProfileInterface } from '../../../interfaces/profile.interface';
+import { Permissions } from '../../../interfaces/permissions.interface';
 import { IndividualUploadComponent } from '../individual-equipment-upload/individual-upload.component';
 import { MultipleUploadComponent } from '../multiple-equipment-upload/multiple-upload.component';
 import { AlertComponent } from '../../resources/dialogs/alert/alert.component';
+import { WebviewerComponent } from '../../gdel/webviewer/webviewer.component';
+import { ShareFileComponent } from '../../gdel/share-file/share-file.component';
+import { IndividualPreventiveUploadComponent } from '../individual-preventive-upload/individual-preventive-upload.component';
+import { MultiplePreventiveUploadComponent } from '../multiple-preventive-upload/multiple-preventive-upload.component';
 import { ZipfileUploadComponent } from '../zipfile-upload/zipfile-upload.component';
 
-
-export interface Document {
-  nombre_del_archivo: string,
-  tamano_del_archivo: string,
-  archivo_id?: string,
-  tipo_archivo?: string,
-  fecha_carga?: string
-}
+const ID_MODULE = "S002V01M15IDUP"; // Cambio el ID del módulo para equipamiento
 
 export interface DocumentoEquipamiento {
   CODIGO: string;
@@ -31,8 +38,14 @@ export interface DocumentoEquipamiento {
   NOMBRE: string;
   TAMANIO: string;
   USRREG: string;
-  estado: 'Activo' | 'Inactivo' | 'Procesando' | 'Error';
-  id?: number;
+  ACCESO: UsersAccess[];
+}
+
+export interface UsersAccess {
+  userLabel: string;
+  userName: string;
+  userColor: string;
+  isTrigger: boolean;
 }
 
 @Component({
@@ -42,116 +55,277 @@ export interface DocumentoEquipamiento {
   styleUrl: './related-documents-upload.component.css'
 })
 export class RelatedDocumentsUploadComponent {
-
+  public btnSmall: boolean;
 
   isLoading: boolean;
   hasError: boolean;
   errorStr: string;
-  public displayedColumns: string[];
-  uploadedFile: TempFileInfo | null;
-  files: FindedFile[];
-  
-  @ViewChild('file') private uploader?: ElementRef;
+  category: string;
+
+  modulesArr = modules;
+  clasificationsArr = docClassification;
+  supportedFilesArr = supportedFiles;
+
+  startDate: Date | null;
+  endDate: Date | null;
+
+  @ViewChild('dateRange') dateRange?: ElementRef;
   @ViewChild(MatPaginator) paginator!: MatPaginator;
   @ViewChild(MatSort) sort!: MatSort;
 
+  moduleControl: FormControl;
+  clasificationControl: FormControl;
+  fileNameControl: FormControl;
+  fileTypeControl: FormControl;
+  datesRangeControl: FormControl;
+
+  files: FindedFile[];
   dataSource: MatTableDataSource<DocumentoEquipamiento>;
+  displayedColumns = ['CODIGO', 'VERSION', 'NOMBRE', 'TAMANIO', 'USRREG', 'ACCIONES'];
+
+  avatarColors = {
+    A: 244, B: 67, C:  54, D: 233, E: 30, F:  99, G: 156, H:  39, I: 176, 
+    J: 103, K: 58, L:  58, M: 183, N: 63, Ñ:  81, O: 181, P:  33, Q: 150, 
+    R: 243, S:  3, T: 169, U: 244, V: 76, W: 175, X:  80, Y: 255, Z: 193
+  };
+
+  isFromModule: boolean;
+  module: string;
+
+  documentsManagementEnabled: boolean;
+  documentsViewerEnabled: boolean;
+  documentsEditorEnabled: boolean;
+  shareDocumentsEnabled: boolean;
+  registerDocumentsEnabled: boolean;
+  versionsHistoryEnabled: boolean;
 
   constructor(
+    @Inject(DOCUMENT) private _document: Document,
+    private _encService: EncService,
+    private _dialog: MatDialog,
     private _gdelService: GdelService,
-    private _matDialog: MatDialog,
-    private _functionService: FunctionsService,
+    private _activatedRoute: ActivatedRoute,
     private _resourcesService: ResourcesService,
-    private _encService: EncService,
+    private _socketService: SocketService,
+    private _usersProfilesService: UsersProfilesService,
+    private _router: Router,
+    private _functionsService: FunctionsService,
   ) {
-    this.isLoading = false;
-    this.hasError = false;
+    this.clasificationControl = new FormControl('');
+    this._functionsService.consultModuleStatus(ID_MODULE);
+    this.btnSmall = false;
+    this.isLoading = true;
+    this.hasError = true;
     this.errorStr = '';
-    this.uploadedFile = null;
-    this.files = [];
-    this.displayedColumns = ['CODIGO', 'VERSION', 'NOMBRE', 'TAMANIO', 'USRREG', 'acciones'];
+    this.category = 'my-files';
+    this.startDate = null;
+    this.endDate = null;
+
+    this.moduleControl = new FormControl('IDUP'); 
+    this.clasificationControl = new FormControl('LA');
+    this.fileNameControl = new FormControl('');
+    this.fileTypeControl = new FormControl('');
+    this.datesRangeControl = new FormControl('');
+
     this.dataSource = new MatTableDataSource();
+    this.files = [];
+
+    this.isFromModule = true; 
+    this.module = 'ADSI';
+
+    this.documentsManagementEnabled = true;
+    this.documentsViewerEnabled = true;
+    this.documentsEditorEnabled = true;
+    this.shareDocumentsEnabled = true;
+    this.registerDocumentsEnabled = true;
+    this.versionsHistoryEnabled = true;
   }
 
   ngOnInit(): void {
-    this.loadData();
-  }
+    this.initPermissions();
+    
 
+    this._socketService.refreshPermissions().subscribe(async (profUpdEnc) => {
+      try {
+        let idUser = localStorage.getItem('idusuario')!;
+        let usrInfo: UserConsultResponse = await lastValueFrom(this._usersProfilesService.getUser(idUser, idUser, 1));
+        
+        if (usrInfo.error) {
+          this._resourcesService.openSnackBar('ERR_NPE000: Ocurrió un error inesperado.');
+        } else {
+          let idProfDec = await this._encService.decrypt(usrInfo.response.PERFIL);
+          let currentProfile = await this._encService.decrypt(localStorage.getItem('perfil')!);
+
+          if (idProfDec != currentProfile) {
+            this._router.navigate(['/sam/home']);
+            return;
+          }
+          
+          let profileUpdated = await this._encService.decrypt(`${profUpdEnc}`);
+          if (profileUpdated == currentProfile) {
+            let profile = localStorage.getItem('perfil')!;
+            let profInfo: ProfileInterface = await lastValueFrom(this._usersProfilesService.getProfile(
+              profile,
+              idUser,
+              1
+            ));
+
+            let permArr = await this._functionsService.processPermissions(profInfo.response.PERMISOS.permissions);
+            let modPerm = permArr.filter(item => item.id == ID_MODULE);
+            if (modPerm.length > 0) {
+
+              this.updateModulePermissions(modPerm[0]);
+            }
+          }
+        }
+      } catch (error: any) {
+        this.handlePermissionsError(error);
+      }
+    });
 
+      this.moduleControl.valueChanges.subscribe(mod => {
+      let cla = this.clasificationControl.value;
+      let sda = this.startDate;
+      let eda = this.endDate;
+      let dna = this.fileNameControl.value;
+      let ext = this.fileTypeControl.value;
 
-  loadData(): void {
+      this.getFiles(mod, cla, sda, eda, dna, ext);
+    });
 
-    this.getFiles('-', '-', null, null, '-', '-');
-  }
+    this.clasificationControl.valueChanges.subscribe(cla => {
+      let mod = this.moduleControl.value;
+      let sda = this.startDate;
+      let eda = this.endDate;
+      let dna = this.fileNameControl.value;
+      let ext = this.fileTypeControl.value;
+
+      this.getFiles(mod, cla, sda, eda, dna, ext);
+    });
+
+    this._socketService.refreshModules().subscribe(async () => this._functionsService.consultModuleStatus(ID_MODULE));
 
+    this.fileNameControl.valueChanges.subscribe(dna => {
+      this.getFiles('ADSI', 'LA', this.startDate, this.endDate, dna, this.fileTypeControl.value);
+    });
+
+    this.fileTypeControl.valueChanges.subscribe(ext => {
+      this.getFiles('ADSI', 'LA', this.startDate, this.endDate, this.fileNameControl.value, ext);
+    });
 
-  refreshData(): void {
-    this.loadData();
+    this.getFiles('ADSI', 'LA', this.startDate, this.endDate, this.fileNameControl.value, this.fileTypeControl.value);
   }
 
-  async getFiles(module: string | undefined, clas: string | undefined, startDate: Date | null, endDate: Date | null, name: string | undefined, type: string | undefined){
+  async initPermissions() {
     try {
-      this.isLoading = true;
-      this.hasError = false;
-      this.errorStr = "";
-
-      let mod = module == undefined || module == '' ? '-' : module;
-      let cla = clas == undefined || clas == '' ? '-' : clas;
-      let sda = startDate == null ? '-' : this.formatSearchDate(startDate);
-      let eda = endDate == null ? '-' : this.formatSearchDate(endDate);
-      let dna = name == undefined || name == '' ? '-' : name;
-      let ext = type == undefined || type == '' ? '-' : JSON.stringify(type.split(','));
-      let idUser = localStorage.getItem('idusuario')!;
-      let category = 'my-files'; 
+      let permissionsEnc = localStorage.getItem('permisos');
+      let permissionsDec = await this._encService.decrypt(permissionsEnc!);
+      let permissionsArr: Permissions = JSON.parse(permissionsDec);
 
-      let files: FindedFilesResponse = await lastValueFrom(this._gdelService.getFiles(
-        mod, cla, sda, eda, dna, ext, category, idUser, 1
-      ));
+      let modPerm = permissionsArr.permissions.filter(item => item.id == ID_MODULE);
+      if (modPerm.length > 0) {
+        this.updateModulePermissions(modPerm[0]);
+      }
+    } catch (error: any) {
+      this._resourcesService.openSnackBar('Hubo un error al obtener los permisos del módulo.');
+      this.documentsManagementEnabled = false;
+    }
+  }
 
-      this.hasError = files.error;
-      this.errorStr = files.msg;
+  private updateModulePermissions(modPerm: any) {
 
-      if (!this.hasError) {
-        this.files = files.response;
-        let filesFound: DocumentoEquipamiento[] = [];
-        
-        files.response.forEach(file => {
-          let fileSize = this.formatBytes(file.TAMANIO as number);
-          let fileObj: DocumentoEquipamiento = {
-            CODIGO: file.CODIGO,
-            VERSION: file.VERSION,
-            NOMBRE: file.NOMBRE,
-            TAMANIO: fileSize,
-            USRREG: file.USRREG,
-            estado: 'Activo'
-          };
-
-          filesFound.push(fileObj);
-        });
-        
-        this.dataSource = new MatTableDataSource(filesFound);
-        this.dataSource.paginator = this.paginator;
-        this.dataSource.sort = this.sort;
-      }
+    let funPerm = modPerm.children?.filter((item: any) => item.id == 'S002V01F01ADDO');
+    if (funPerm && funPerm.length > 0) {
+      this.documentsManagementEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P01GEDO')[0]?.access > 0 || true;
+      this.documentsViewerEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P02VIDO')[0]?.access > 0 || true;
+      this.documentsEditorEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P03EDDO')[0]?.access > 0 || true;
+      this.shareDocumentsEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P04CODO')[0]?.access > 0 || true;
+      this.registerDocumentsEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P05REDO')[0]?.access > 0 || true;
+      this.versionsHistoryEnabled = funPerm[0].children?.filter((item: any) => item.id == 'S002V01P06HIVE')[0]?.access > 0 || true;
+    }
+  }
 
-      this.isLoading = false;
-    } catch (error: any) {
-      if (error.error == undefined) {
-        this.errorStr = 'Ocurrió un error inesperado.';
-      } else if (error.error.msg == undefined) {
-        this.errorStr = 'Ocurrió un error inesperado.';
-      } else {
-        this.errorStr = error.error.msg;
-      }
+  private handlePermissionsError(error: any) {
+    if (error.error == undefined) {
+      this._resourcesService.openSnackBar('ERR_PAD001: Ocurrió un error inesperado.');
+    } else if (error.error.msg == undefined) {
+      this._resourcesService.openSnackBar('ERR_PAD002: Ocurrió un error inesperado.');
+    } else {
+      this._resourcesService.openSnackBar(`ERR_PAD003: ${error.error.msg}`);
+    }
+  }
+
+async getFiles(module: string | undefined, clas: string | undefined, startDate: Date | null, endDate: Date | null, name: string | undefined, type: string | undefined) {
+  try {
+    this.isLoading = true;
+    this.hasError = false;
+    this.errorStr = "";
 
-      this.hasError = true;
-      this.isLoading = false;
+
+    let mod = 'ADSI';
+    let cla = 'LA';
+    let sda = startDate == null ? '-' : this.formatSearchDate(startDate);
+    let eda = endDate == null ? '-' : this.formatSearchDate(endDate);
+    let dna = name == undefined || name == '' ? '-' : name;
+    let ext = type == undefined || type == '' ? '-' : JSON.stringify(type.split(','));
+    let idUser = localStorage.getItem('idusuario')!;
+
+    let files: FindedFilesResponse = await lastValueFrom(this._gdelService.getFiles(
+      mod, cla, sda, eda, dna, ext, this.category, idUser, 1
+    ));
+
+    this.hasError = files.error;
+    this.errorStr = files.msg;
+
+    if (!this.hasError) {
+      // Filtrar solo archivos que contengan "EQUIPAMIENTOS" en el nombre 
+      const filteredFiles = files.response.filter(file => 
+        file.NOMBRE.toLowerCase().includes('documentos')
+      );
+      
+      this.files = filteredFiles;
+      let filesFound: DocumentoEquipamiento[] = [];
+      
+      filteredFiles.forEach(file => {
+        let fileSize = this.formatBytes(file.TAMANIO as number);
+        let fileAccess = this.getUsersAccess(file.ACCESO);
+        let fileObj: DocumentoEquipamiento = {
+          CODIGO: file.CODIGO,
+          VERSION: file.VERSION,
+          NOMBRE: file.NOMBRE,
+          TAMANIO: fileSize,
+          USRREG: file.USRREG,
+          ACCESO: fileAccess,
+        };
+
+        filesFound.push(fileObj);
+      });
+      
+      this.dataSource = new MatTableDataSource(filesFound);
+      this.dataSource.paginator = this.paginator;
+      this.dataSource.sort = this.sort;
+    }
+
+    this.isLoading = false;
+  } catch (error: any) {
+    this.handleGetFilesError(error);
+  }
+}
+
+  private handleGetFilesError(error: any) {
+    if (error.error == undefined) {
+      this.errorStr = 'Ocurrió un error inesperado.';
+    } else if (error.error.msg == undefined) {
+      this.errorStr = 'Ocurrió un error inesperado.';
+    } else {
+      this.errorStr = error.error.msg;
     }
+
+    this.hasError = true;
+    this.isLoading = false;
   }
 
   formatSearchDate(date: Date): string {
-    if (!date) return '-';
-    
     let day = date.getDate() < 10 ? `0${date.getDate()}` : `${date.getDate()}`;
     let month = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : `${date.getMonth() + 1}`;
     let fullYear = `${date.getFullYear()}`;
@@ -160,7 +334,6 @@ export class RelatedDocumentsUploadComponent {
     return `${year}${month}${day}`;
   }
 
-
   formatBytes(bytes: number, decimals = 2) {
     if (bytes === 0) return '0 Bytes';
 
@@ -173,41 +346,236 @@ export class RelatedDocumentsUploadComponent {
     return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
   }
 
+  getUsersAccess(accessStr: string): UsersAccess[] {
+    let accessArr = JSON.parse(accessStr);
+    let arrFn: UsersAccess[] = [];
 
-  openIndividualUpload(): void {
-    let dialogRef = this._matDialog.open(ZipfileUploadComponent, {
-      width: '1000px',
-      minWidth: '1000px',
-      maxHeight: '90vh',
-      disableClose: true,
-    });
+    for (const user of accessArr) {
+      let userNameArr = user.split(' ');
+      let userLabel = '';
 
-    dialogRef.afterClosed().subscribe(res => {
-      if (res == true) {
-        this.refreshData();
+      for (let i = 0; i < 2; i++) {
+        userLabel += userNameArr[i][0];
       }
-    });
+
+      let userColor = "rgb(";
+      for (let i = 0; i < 3; i++) {
+        let char: never = user.charAt(i) as never;
+        let code = this.avatarColors[char];
+
+        userColor += `${code}, `;
+      }
+
+      userColor = userColor.substring(0, userColor.length - 2);
+      userColor += ")";
+
+      let userObj: UsersAccess = {
+        userLabel: userLabel,
+        userName: user,
+        userColor: userColor,
+        isTrigger: false,
+      }
+
+      arrFn.push(userObj);
+    }
+
+    let arrFnAux: UsersAccess[] = [];
+    if (arrFn.length > 4) {
+      for (let i = 0; i < 3; i++) {
+        arrFnAux.push(arrFn[i]);
+      }
+
+      let moreUsers: UsersAccess = {
+        userLabel: `${arrFn.length - 3}+`,
+        userName: `Ver ${arrFn.length - 3} más...`,
+        userColor: 'rgba(0,0,0,0.54)',
+        isTrigger: true,
+      };
+
+      arrFnAux.push(moreUsers);
+      return arrFnAux;
+    }
+
+    return arrFn;
+  }
+
+  public onResize(): void {
+    this.btnSmall = window.innerWidth <= 1573;
   }
 
 
-  openMassiveUpload(): void {
-    let dialogRef = this._matDialog.open(MultipleUploadComponent, {
-      width: '1000px',
-      minWidth: '1000px',
-      maxHeight: '90vh',
+  openIndividualUpload() {
+    if (this.registerDocumentsEnabled) {
+      let dialogRef = this._dialog.open(ZipfileUploadComponent, {
+        width: '1000px',
+        minWidth: '1000px',
+        maxHeight: '90vh',
+        disableClose: true,
+      });
+  
+      dialogRef.afterClosed().subscribe(res => {
+        if (res == true) {
+          this.refreshData();
+        }
+      });
+    }
+  }
+
+  openMassiveUpload() {
+    if (this.registerDocumentsEnabled) {
+      let dialogRef = this._dialog.open(MultiplePreventiveUploadComponent, {
+        width: '1000px',
+        minWidth: '1000px',
+        maxHeight: '90vh',
+        disableClose: true,
+      });
+  
+      dialogRef.afterClosed().subscribe(res => {
+        if (res == true) {
+          this.refreshData();
+        }
+      });
+    }
+  }
+
+  refreshData() {
+    this.getFiles('ADSI', 'LA', this.startDate, this.endDate, this.fileNameControl.value, this.fileTypeControl.value);
+  }
+
+  updateCategory(category: string) {
+    this.category = category;
+    this.getFiles('ADSI', 'LA', this.startDate, this.endDate, this.fileNameControl.value, this.fileTypeControl.value);
+  }
+
+  openDateRangePicker() {
+    let dialogRef = this._dialog.open(DateRangePickerComponent, {
       disableClose: true,
+      width: '480px',
+      maxWidth: '480px',
+      data: {
+        startDate: this.startDate,
+        endDate: this.endDate
+      }
     });
 
     dialogRef.afterClosed().subscribe(res => {
-      if (res == true) {
-        this.refreshData();
+      if (res != null && res != undefined && res != '') {
+        let resObj = JSON.parse(res);
+        let dateRange = "";
+
+        if (resObj.startDate != null || resObj.endDate != null) {
+          if (resObj.startDate != null) {
+            let startDateTimeArr = resObj.startDate.split('T');
+            let startDateArr = startDateTimeArr[0].split('-').reverse();
+            let startDateStr = startDateArr.join('/');
+  
+            this.startDate = new Date(resObj.startDate);
+            dateRange += `desde ${startDateStr}, `;
+          }
+          
+          if (resObj.endDate != null) {
+            let endDateTimeArr = resObj.endDate.split('T');
+            let endDateArr = endDateTimeArr[0].split('-').reverse();
+            let endDateStr = endDateArr.join('/');
+  
+            this.endDate = new Date(resObj.endDate);
+            dateRange += `hasta ${endDateStr}, `;
+          }
+  
+          let fl = dateRange[0].toUpperCase();
+          let dateRangeArr = dateRange.split("");
+  
+          dateRangeArr[0] = fl;
+          dateRange = dateRangeArr.join("");
+          dateRange = dateRange.substring(0, dateRange.length - 2);
+
+          this.dateRange!.nativeElement.value = dateRange;
+          this.getFiles('ADSI', 'LA', this.startDate, this.endDate, this.fileNameControl.value, this.fileTypeControl.value);
+        }
       }
     });
   }
 
-  confirmDelete(element: DocumentoEquipamiento): void {
-    let idFile = `${element.CODIGO}=${element.VERSION}=${element.NOMBRE}`;
-    let dialogRef = this._matDialog.open(AlertComponent, {
+  clearDatesRange() {
+    this.startDate = null;
+    this.endDate = null;
+    this.datesRangeControl.setValue('');
+
+    this.getFiles('ADSI', 'LA', this.startDate, this.endDate, this.fileNameControl.value, this.fileTypeControl.value);
+  }
+
+
+
+
+
+  async downloadFile(code: string, version: string, name: string) {
+    try {
+      let idFile = `${code}=${version}=${name}`;
+      this._resourcesService.openSnackBar(`Iniciando la descarga del archivo ${idFile}...`);
+
+      let idFileEnc = await this._encService.encrypt(idFile);
+      let download = this._document.getElementById(`download`) as HTMLAnchorElement;
+  
+      let idUser = localStorage.getItem('idusuario')!;
+      let downloadToken = await lastValueFrom(this._gdelService.getDownloadToken(
+        idFileEnc,
+        idUser,
+        1
+      ));
+  
+      download.href = `${apiTemp}download-file/${downloadToken.response.TOKEN}/${idUser}/1`;
+      download.download = idFile;
+      download.click();
+    } catch (error: any) {
+      if (error.error == undefined) {
+        this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+      } else if (error.error.msg == undefined) {
+        this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+      } else {
+        this._resourcesService.openSnackBar(error.error.msg);
+      }
+    }
+  }
+
+  async openViewer(code: string, version: string, name: string) {
+    try {
+      let idFile = `${code}=${version}=${name}`;
+      this._resourcesService.openSnackBar(`Abriendo el archivo ${idFile}...`);
+      
+      let idFileEnc = await this._encService.encrypt(idFile);
+      let idUser = localStorage.getItem('idusuario')!;
+
+      let publicURL = await lastValueFrom(this._gdelService.getPublicDocumentUrl(
+        idFileEnc,
+        idUser,
+        1
+      ));
+
+      let fileData = {
+        url: publicURL.response.public_uri,
+      };
+
+      this._dialog.open(WebviewerComponent, {
+        data: fileData,
+        width: '1080px',
+        maxWidth: '1080px',
+      });
+    } catch (error: any) {
+      if (error.error == undefined) {
+        this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+      } else if (error.error.msg == undefined) {
+        this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+      } else {
+        this._resourcesService.openSnackBar(error.error.msg);
+      }
+    }
+  }
+
+
+
+  confirmDelete(code: string, version: string, name: string) {
+    let idFile = `${code}=${version}=${name}`;
+    let dialogRef = this._dialog.open(AlertComponent, {
       disableClose: true,
       width: '480px',
       maxWidth: '480px',
@@ -221,12 +589,12 @@ export class RelatedDocumentsUploadComponent {
 
     dialogRef.afterClosed().subscribe(res => {
       if (res == true) {
-        this.deleteDocument(idFile);
+        this.deleteFile(idFile);
       }
     });
   }
 
-  async deleteDocument(idFile: string): Promise<void> {
+  async deleteFile(idFile: string) {
     try {
       let idUser = localStorage.getItem('idusuario');
       let idFileEnc = await this._encService.encrypt(idFile);
@@ -250,34 +618,4 @@ export class RelatedDocumentsUploadComponent {
       }
     }
   }
-
-  getStatusClass(estado: string): string {
-    switch (estado) {
-      case 'Activo':
-        return 'status-active';
-      case 'Inactivo':
-        return 'status-inactive';
-      case 'Procesando':
-        return 'status-processing';
-      case 'Error':
-        return 'status-error';
-      default:
-        return 'status-default';
-    }
-  }
-
-  getStatusIcon(estado: string): string {
-    switch (estado) {
-      case 'Activo':
-        return 'check_circle';
-      case 'Inactivo':
-        return 'cancel';
-      case 'Procesando':
-        return 'hourglass_empty';
-      case 'Error':
-        return 'error';
-      default:
-        return 'help';
-    }
-  }
 }

+ 43 - 106
src/app/components/initial-data-upload/zipfile-upload/zipfile-upload.component.html

@@ -1,10 +1,10 @@
 <h1 mat-dialog-title class="prevent-select" style="text-align: center; margin-top: 15px;">
-  Carga múltiple de documentos
+  Carga de documentos relacionados
 </h1>
 
 <div class="uploader">
   <h2 mat-dialog-title class="prevent-select" style="font-size: 15px; display: flex; justify-content: center;">
-    Seleccione uno o más archivos Excel (.xlsx, .xls) o ZIP
+    Seleccione un archivo Excel (.xlsx, .xls)
   </h2>
   
   <input 
@@ -13,14 +13,13 @@
      id="file"
      #file
      class="hidden"
-     accept=".xlsx,.xls,.zip"
-     multiple
+     accept=".xlsx,.xls"
      (change)="checkFile($event)">
   
   <div 
     class="drag-drop-area"
     [class.drag-over]="isDragOver"
-    [class.has-files]="hasFiles"
+    [class.has-file]="hasExcelFile"
     (dragover)="onDragOver($event)"
     (dragleave)="onDragLeave($event)"
     (drop)="onDrop($event)"
@@ -28,27 +27,26 @@
     *ngIf="!isUploading">
     
     <div class="drag-drop-content">
-      <mat-icon class="upload-icon ">
-        {{ hasFiles ? 'add' : 'cloud_upload' }}
+      <mat-icon class="upload-icon">
+        {{ hasExcelFile ? 'refresh' : 'table_chart' }}
       </mat-icon>
       
-      <p class="drag-drop-text" *ngIf="!hasFiles">
-        <strong>Haga clic aquí o arrastre sus archivos</strong><br>
-        <span>Formatos soportados: .xlsx, .xls, .zip (máximo 50MB cada uno)</span><br>
-        <span>Puede seleccionar múltiples archivos</span>
+      <p class="drag-drop-text" *ngIf="!hasExcelFile">
+        <strong>Haga clic aquí o arrastre su archivo Excel</strong><br>
+        <span>Solo archivos .xlsx y .xls (máximo 10MB)</span>
       </p>
       
-      <p class="drag-drop-text" *ngIf="hasFiles">
-        <strong>Agregar más archivos</strong><br>
-        <span>Haga clic aquí o arrastre archivos adicionales</span><br>
-        <span>{{ filesCount }} archivo(s) ya cargado(s)</span>
+      <p class="drag-drop-text" *ngIf="hasExcelFile">
+        <strong>Haga clic aquí o arrastre un nuevo archivo Excel</strong><br>
+        <span>Para reemplazar el archivo actual</span>
       </p>
     </div>
   </div>
+  
 
   <div *ngIf="isUploading" class="upload-progress drag-drop-area uploading">
     <mat-spinner diameter="30"></mat-spinner>
-    <p style="margin-top: 10px;">{{ uploadingText }}</p>
+    <p style="margin-top: 10px;">Subiendo archivo Excel...</p>
   </div>
 </div>
 
@@ -56,45 +54,22 @@
 
   <div class="is-loading animated fadeIn fast" *ngIf="isLoading" style="text-align: center; padding: 20px;">
     <mat-spinner></mat-spinner>
-    <h3 style="margin-top: 10px;">Cargando datos...</h3>
+    <h3 style="margin-top: 10px;">Procesando archivo Excel...</h3>
   </div>
 
+
   <div class="has-error animated fadeIn fast" *ngIf="hasError" style="text-align: center; padding: 20px;">
     <mat-icon class="red_primary_font" style="font-size: 48px; margin-bottom: 10px;">error</mat-icon>
     <h3 style="font-weight: bold; font-style: italic;">{{ errorMessage }}</h3>
   </div>
 
 
-  <div *ngIf="hasExcelTemplate && codigosEquipos.length > 0" class="codigos-info" style="margin: 20px 0; padding: 15px; background-color: #e8f5e8; border-radius: 5px;">
-    <h4 style="margin: 0 0 10px 0; color: #2e7d32;">
-      <mat-icon style="vertical-align: middle; margin-right: 5px;">check_circle</mat-icon>
-      Códigos de equipos extraídos ({{ codigosEquipos.length }})
-    </h4>
-    <div class="codigos-list" style="max-height: 100px; overflow-y: auto;">
-      <span *ngFor="let codigo of codigosDisponibles; let i = index" 
-            class="codigo-tag" 
-            style="display: inline-block; background: #4caf50; color: white; padding: 4px 8px; margin: 2px; border-radius: 12px; font-size: 12px;">
-        {{ codigo }}
-      </span>
-    </div>
-  </div>
-
-  <div *ngIf="hasFiles && !isLoading" class="files-summary">
-    <div class="summary-header">
-      <h3 style="margin: 20px 0;">Archivos cargados ({{ filesCount }}):</h3>
-      <button 
-        mat-stroked-button 
-        class="white_font red_primary_background"
-        style="border: none;"
-        (click)="removeAllFiles()"
-        [disabled]="isLoading || isUploading">
-        <mat-icon>delete</mat-icon>
-        Eliminar todos
-      </button>
-    </div>
-  </div>
-
-  <div *ngIf="hasFiles && !isLoading" class="files-container">
+     <div *ngIf="hasExcelFile && !isLoading" class="files-container">
+    <h3 style="margin: 20px 0; display: flex; align-items: center;">
+      <mat-icon style="margin-right: 8px; color: #2e7d32;">table_chart</mat-icon>
+      Archivo Excel cargado:
+    </h3>
+        
     <table mat-table matSort class="animated fadeIn" [dataSource]="dataSource">
 
       <ng-container matColumnDef="nombre_del_archivo">
@@ -104,18 +79,13 @@
         </th>
         <td mat-cell *matCellDef="let element">
           <div class="file-name-cell">
-            <mat-icon color="primary">
-              {{ element.tipo_archivo === 'excel' ? 'table_chart' : 'archive' }}
-            </mat-icon>
+            <mat-icon color="primary">table_chart</mat-icon>
             <span>{{ element.nombre_del_archivo }}</span>
-            <span *ngIf="element.es_plantilla" class="template-badge" 
-                  style="margin-left: 10px; background: #2196f3; color: white; padding: 2px 6px; border-radius: 8px; font-size: 10px;">
-              PLANTILLA
-            </span>
           </div>
         </td>
       </ng-container>
 
+
       <ng-container matColumnDef="tamano_del_archivo">
         <th mat-header-cell *matHeaderCellDef mat-sort-header>
           <mat-icon>storage</mat-icon>
@@ -126,38 +96,6 @@
         </td>
       </ng-container>
 
-      <ng-container matColumnDef="tipo_archivo">
-        <th mat-header-cell *matHeaderCellDef mat-sort-header>
-          <mat-icon>category</mat-icon>
-          Tipo
-        </th>
-        <td mat-cell *matCellDef="let element">
-          <span class="tipo-badge" 
-                [style.background-color]="element.tipo_archivo === 'excel' ? '#4caf50' : '#ff9800'"
-                style="color: white; padding: 4px 8px; border-radius: 12px; font-size: 11px; text-transform: uppercase;">
-            {{ element.tipo_archivo }}
-          </span>
-        </td>
-      </ng-container>
-
-      <ng-container matColumnDef="codigo_equipo">
-        <th mat-header-cell *matHeaderCellDef>
-          <mat-icon>settings</mat-icon>
-          Estado/Códigos
-        </th>
-        <td mat-cell *matCellDef="let element">
-          <div *ngIf="element.tipo_archivo === 'excel' && element.es_plantilla">
-            <span style="color: #4caf50; font-size: 12px;">
-              {{ codigosEquipos.length }} códigos extraídos
-            </span>
-          </div>
-          <div *ngIf="element.tipo_archivo === 'zip'">
-            <span style="color: #ff9800; font-size: 12px;">
-              ZIP procesado
-            </span>
-          </div>
-        </td>
-      </ng-container>
 
       <ng-container matColumnDef="acciones">
         <th mat-header-cell *matHeaderCellDef>Acciones</th>
@@ -169,19 +107,20 @@
                color="primary"
                style="border-radius: 10px; margin-right: 5px;"
                class="pink_primary_background white_font"
-               [matTooltip]="element.tipo_archivo === 'excel' ? 'Previsualizar Excel' : 'Ver contenido ZIP'"
-               (click)="previewFile(element)"
+               matTooltip="Previsualizar Excel"
+               (click)="previewExcelFile()"
                [disabled]="isLoading || isUploading">
               <mat-icon>visibility</mat-icon>
             </button>
+            
 
             <button 
                mat-icon-button 
                color="warn"
                style="border-radius: 10px;"
                class="red_primary_background white_font"
-               matTooltip="Eliminar archivo"
-               (click)="removeFile(element)"
+               matTooltip="Eliminar archivo Excel"
+               (click)="removeExcelFile()"
                [disabled]="isLoading || isUploading">
               <mat-icon>delete</mat-icon>
             </button>
@@ -194,19 +133,15 @@
     </table>
   </div>
 
-  <div *ngIf="!hasFiles && !isLoading && !isUploading" class="no-files-message" style="height: 120px; text-align: center; padding: 20px;">
-    <mat-icon style="color: #999; margin-bottom: 10px;">upload_file</mat-icon>
-    <p style="color: #999; font-size: 14px; margin-top: 10px;">Use el área de arriba para cargar sus archivos.</p>
-    <p style="color: #999; font-size: 12px;">Puede seleccionar múltiples archivos Excel y ZIP a la vez</p>
-  </div>
 
-<div *ngIf="hasExcelTemplate && hasZipFiles" class="processing-info" style="margin: 20px 0; padding: 15px; background-color: #fff3e0; border-radius: 5px;">
-    <h4 style="margin: 0 0 10px 0; color: #f57c00;">
-      <mat-icon style="vertical-align: middle; margin-right: 5px;">info</mat-icon>
-      Información de procesamiento
-    </h4>
-    <p style="margin: 5px 0; font-size: 14px;">• Se han extraído {{ codigosEquipos.length }} códigos de la plantilla Excel</p>
-    <p style="margin: 5px 0; font-size: 14px;">• Los archivos ZIP se procesarán con los códigos asignados automáticamente</p>
+  <div *ngIf="!hasExcelFile && !isLoading && !isUploading" class="no-files-message" style="height: 120px; text-align: center; padding: 20px;">
+    <mat-icon style=" color: #9e9e9e; margin-bottom: 10px;">upload_file</mat-icon>
+    <p style="color: #666; font-size: 14px; margin-bottom: 5px;">
+      <strong>No hay archivos Excel cargados</strong>
+    </p>
+    <p style="color: #999; font-size: 12px;">
+      Use el área de arriba para cargar su archivo Excel (.xlsx o .xls)
+    </p>
   </div>
 </div>
 
@@ -219,14 +154,16 @@
     <mat-icon>close</mat-icon>
     Cancelar
   </button>
+  
 
   <button 
      mat-raised-button
      color="primary"
      class="blue_send_background white_font"
-     *ngIf="hasFiles"
-     [disabled]="isLoading || isUploading || (!hasExcelTemplate && hasZipFiles)">
-    <mat-icon>check</mat-icon>
-    Procesar archivos ({{ filesCount }})
+     *ngIf="hasExcelFile"
+     (click)="saveFinalFile()"
+     [disabled]="isLoading || isUploading">
+    <mat-icon>table_chart</mat-icon>
+    Procesar Excel
   </button>
 </mat-dialog-actions>

+ 313 - 354
src/app/components/initial-data-upload/zipfile-upload/zipfile-upload.component.ts

@@ -1,33 +1,34 @@
-import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { MatDialogRef } from '@angular/material/dialog';
 import { EquipmentLruUploadComponent } from '../equipment-lru-upload/equipment-lru-upload.component';
 import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
 import { MatDialog } from '@angular/material/dialog';
 import { MatTableDataSource } from '@angular/material/table';
 import { FunctionsService } from '../../../services/functions.service';
 import { TempFileInfo } from '../../../interfaces/temp-file-info.interface';
-import { availableFiles } from '../../../../environments/environment.prod';
 import { ResourcesService } from '../../../services/resources.service';
 import { lastValueFrom } from 'rxjs';
 import { GdelService } from '../../../services/gdel.service';
 import { PreviewExcelDocumentComponent } from '../preview-Equipmentexcel-document/preview-excel-document.component';
 import * as XLSX from 'xlsx';
-import JSZip from 'jszip';
+import { FormControl, Validators } from '@angular/forms';
+
+const ID_MODULE = "S002V01M15IDUP";
 
 export interface Document {
   nombre_del_archivo: string,
   tamano_del_archivo: string,
   archivo_id?: string,
   tipo_archivo?: string,
-  fecha_carga?: string,
-  codigo_equipo?: string, // Nuevo campo para el código del equipo
-  es_plantilla?: boolean  // Para identificar si es la plantilla Excel
+  fecha_carga?: string
+}
+
+// Interfaz para la estructura de headers esperados
+interface SheetHeaderStructure {
+  [column: string]: string;
 }
 
-// Interface para manejar los códigos extraídos del Excel
-export interface CodigoEquipo {
-  codigo: string;
-  fila: number;
-  columnas: any[];
+interface TemplateStructure {
+  [sheetName: string]: SheetHeaderStructure;
 }
 
 @Component({
@@ -42,21 +43,47 @@ export class ZipfileUploadComponent {
   public hasError: boolean = false;
   public errorMessage: string = '';
   public isUploading: boolean;
+
   uploadedFile: TempFileInfo | null;
   @ViewChild('file') private uploader?: ElementRef;
+
+  moduleControl = new FormControl('', Validators.required);
+
   public dataSource: MatTableDataSource<Document>;
-  public displayedColumns: string[] = ['nombre_del_archivo', 'tamano_del_archivo', 'tipo_archivo', 'codigo_equipo', 'acciones'];
+  public displayedColumns: string[] = ['nombre_del_archivo', 'tamano_del_archivo', 'acciones'];
   
-  public files: Document[] = [];
-  public excelDataMap: Map<string, any> = new Map();
-  public zipContentsMap: Map<string, any[]> = new Map(); 
-  public codigosEquipos: CodigoEquipo[] = []; 
-  public uploadingCount: number = 0;
+  // Variables para manejar archivos Excel (solo uno permitido)
+  public excelFile: Document | null = null;
+  public excelData: any = null;
 
+  // Variables para drag and drop
   public isDragOver: boolean = false;
 
-
-  private readonly COLUMNAS_CODIGOS = ['CÓDIGO DE EQUIPAMIENTO', 'CODIGO DE EQUIPAMIENTO', 'CÓDIGO LRU'];
+  // Configuración específica para archivos Excel
+  private readonly EXCEL_EXTENSIONS = ['xlsx', 'xls'];
+  private readonly MAX_FILE_SIZE_MB = 10;
+  private readonly HEADER_ROW = 7; 
+
+  // Estructura de la plantilla esperada
+  private readonly TEMPLATE_STRUCTURE: TemplateStructure = {
+    'CARGA DE DOCUMENTOS': {
+      'B': 'CÓDIGO EQUIVALENTE',
+      'D': 'CÓDIGO DE EQUIPO',
+      'F': 'CÓDIGO DEL LRU',
+      'H': 'FICHA DE SEGURIDAD DE PRODUCTOS QUÍMICOS',
+      'J': 'FICHA TÉCNICA',
+      'L': 'FOTOGRAFÍAS - DIAGRAMAS',
+      'N': 'DATOS DEL PROVEEDOR',
+      'P': 'MANUALES DE OPERACIÓN',
+      'R': 'MANUALES DE MANTENIMIENTO PREVENTIVO',
+      'T': 'MANUALES DE MANTENIMIENTO CORRECTIVO',
+      'V': 'DOCUMENTOS ADICIONALES'
+     }
+  };
+
+
+  // Hojas requeridas en la plantilla
+  private readonly REQUIRED_SHEETS = ['CARGA DE DOCUMENTOS'];
 
   constructor (
     private dialogRef: MatDialogRef<EquipmentLruUploadComponent>,
@@ -69,11 +96,10 @@ export class ZipfileUploadComponent {
     this.isUploading = false;
     this.uploadedFile = null;
     this.dataSource = new MatTableDataSource();
-    this.uploadingCount = 0;
   }
 
   ngOnInit(): void {
-    this.loadFiles();
+    this.loadExcelFile();
   }
 
   cancelar(): void {
@@ -84,6 +110,7 @@ export class ZipfileUploadComponent {
     this.uploader?.nativeElement.click();
   }
 
+  // Métodos para drag and drop
   onDragOver(event: DragEvent): void {
     event.preventDefault();
     event.stopPropagation();
@@ -103,84 +130,170 @@ export class ZipfileUploadComponent {
 
     const files = event.dataTransfer?.files;
     if (files && files.length > 0) {
-      this.processMultipleFiles(Array.from(files));
+      this.processFile(files[0]);
     }
   }
 
   checkFile(event: any): void {
-    const files = event.target.files;
-    if (!files || files.length === 0) return;
-    
-    this.processMultipleFiles(Array.from(files));
-    event.target.value = '';
+    const file = event.target.files[0];
+    if (!file) return;
+    this.processFile(file);
   }
 
-  private processMultipleFiles(files: File[]): void {
-    const validFiles: File[] = [];
-    const invalidFiles: string[] = [];
+  private processFile(file: File): void {
+    // Validación específica para archivos Excel
+    if (!this.isValidExcelFile(file)) {
+      return;
+    }
 
-    //este formato está mal, porque como tal el zip no se sube
-    files.forEach(file => {
-      const fileName = file.name.toLowerCase();
-      const isValidFile = fileName.endsWith('.xlsx') || 
-                         fileName.endsWith('.xls') || 
-                         fileName.endsWith('.zip');
-      
-      if (!isValidFile) {
-        invalidFiles.push(`${file.name} (formato no válido - solo Excel y ZIP)`);
-        return;
-      }
+    if (!this.isValidFileSize(file)) {
+      return;
+    }
 
-      const maxFileSize = 50 * 1024 * 1024; 
-      if (file.size > maxFileSize) {
-        invalidFiles.push(`${file.name} (supera 50MB)`);
-        return;
-      }
+    this.isUploading = true;
+    this.uploadExcelFile(file);
+  }
 
-      const existingFile = this.files.find(f => f.nombre_del_archivo === file.name);
-      if (existingFile) {
-        invalidFiles.push(`${file.name} (ya existe)`);
-        return;
-      }
+  private isValidExcelFile(file: File): boolean {
+    const fileName = file.name.toLowerCase();
+    const fileExtension = fileName.split('.').pop();
+    
+    if (!fileExtension || !this.EXCEL_EXTENSIONS.includes(fileExtension)) {
+      this._resourcesService.openSnackBar('Solo se permiten archivos Excel (.xlsx, .xls)');
+      return false;
+    }
+    
+    return true;
+  }
 
-      validFiles.push(file);
-    });
+  private isValidFileSize(file: File): boolean {
+    const maxFileSize = this.MAX_FILE_SIZE_MB * 1024 * 1024; // Convertir MB a bytes
+    
+    if (file.size > maxFileSize) {
+      this._resourcesService.openSnackBar(`El tamaño del archivo supera el límite de ${this.MAX_FILE_SIZE_MB} MB`);
+      return false;
+    }
+    
+    return true;
+  }
+
+// Método para validar la estructura de la plantilla Y que tenga datos
+private validateExcelTemplate(workbook: XLSX.WorkBook): { isValid: boolean, errors: string[] } {
+  const errors: string[] = [];
+  
+  // Verificar que existan todas las hojas requeridas
+  const existingSheets = workbook.SheetNames;
+  const missingSheets = this.REQUIRED_SHEETS.filter(sheet => !existingSheets.includes(sheet));
+  
+  if (missingSheets.length > 0) {
+    errors.push(`Tu documento no cumple con las hojas requeridas de la plantilla`);
+  }
+
+  // Validar headers de cada hoja Y verificar que tengan datos
+  for (const sheetName of this.REQUIRED_SHEETS) {
+    if (existingSheets.includes(sheetName)) {
+      const sheetErrors = this.validateSheetHeaders(workbook.Sheets[sheetName], sheetName);
+      errors.push(...sheetErrors);
+      
+      if (sheetName === 'CARGA DE DOCUMENTOS') {
+        const dataErrors = this.validateSheetHasData(workbook.Sheets[sheetName], sheetName);
+        errors.push(...dataErrors);
+      }
 
-    if (invalidFiles.length > 0) {
-      this._resourcesService.openSnackBar(
-        `Archivos no válidos: ${invalidFiles.join(', ')}`
-      );
     }
+  }
+
+  return {
+    isValid: errors.length === 0,
+    errors: errors
+  };
+}
 
-    if (validFiles.length > 0) {
-      this.uploadingCount = validFiles.length;
-      this.isUploading = true;
+// Nuevo método para validar que una hoja tenga datos
+private validateSheetHasData(worksheet: XLSX.WorkSheet, sheetName: string): string[] {
+  const errors: string[] = [];
+  
+  try {
+    // Obtener el rango de la hoja
+    const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
+    
+    // Para MANTENIMIENTO CORRECTIVO, los datos empiezan después de la fila de headers (fila 7 en adelante)
+    const dataStartRow = this.HEADER_ROW + 2; // Fila 7
+    
+    // Verificar si hay al menos una fila con datos después de los headers
+    let hasData = false;
+    
+    // Revisar desde la fila de datos hasta el final del rango
+    if (range.e.r >= dataStartRow) {
+      // Revisar las columnas principales para ver si hay datos
+      const mainColumns = ['B', 'D', 'F', 'H']; // Las primeras columnas importantes
       
-      validFiles.forEach(file => {
-        const fileName = file.name.toLowerCase();
-        if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) {
-          this.uploadExcelFile(file);
-        } else if (fileName.endsWith('.zip')) {
-          this.uploadZipFile(file);
+      for (let row = dataStartRow; row <= range.e.r; row++) {
+        for (const col of mainColumns) {
+          const cellAddress = `${col}${row}`;
+          const cell = worksheet[cellAddress];
+          
+          // Si encontramos una celda con contenido (no vacía, no null, no undefined)
+          if (cell && cell.v !== null && cell.v !== undefined && String(cell.v).trim() !== '') {
+            hasData = true;
+            break;
+          }
         }
-      });
+        if (hasData) break;
+      }
     }
+    
+    if (!hasData) {
+      errors.push(`La hoja "${sheetName}" no contiene datos. Se requiere al menos un registro con información.`);
+    }
+    
+  } catch (error) {
+    errors.push(`Error al validar datos en la hoja "${sheetName}": No se pudo verificar el contenido.`);
   }
+  
+  return errors;
+}
 
-  formatBytes(bytes: number, decimals = 2): string {
-    if (bytes === 0) return '0 Bytes';
 
-    const k = 1024;
-    const dm = decimals < 0 ? 0 : decimals;
-    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+  // Método para validar los headers de una hoja específica
+  private validateSheetHeaders(worksheet: XLSX.WorkSheet, sheetName: string): string[] {
+    const errors: string[] = [];
+     
+    const expectedHeaders = this.TEMPLATE_STRUCTURE[sheetName];
+    
+    if (!expectedHeaders) {
+      errors.push(`No se encontró la estructura esperada para la hoja: ${sheetName}`);
+      return errors;
+    }
 
-    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    // Obtener los headers de la fila especificada (fila 6)
+    const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
+    
+    for (const [column, expectedHeader] of Object.entries(expectedHeaders)) {
+      const cellAddress = `${column}${this.HEADER_ROW}`;
+      const cell = worksheet[cellAddress];
+      const actualHeader = cell ? String(cell.v).trim() : '';
+      
+      if (actualHeader !== expectedHeader) {
+        errors.push(`Hoja "${sheetName}", Columna ${column}: Se esperaba "${expectedHeader}" pero se encontró "${actualHeader}"`);
+      }
+    }
 
-    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+    return errors;
   }
 
+
   async uploadExcelFile(fileData: File): Promise<void> {
     try {
+      // Primero validar la estructura antes de subir
+      const validationResult = await this.validateFileStructure(fileData);
+      
+      if (!validationResult.isValid) {
+        this._resourcesService.openSnackBar(`Debe contener al menos un registro y cumplir con la estructura requerida`);
+        this.isUploading = false;
+        return;
+      }
+
       const idUser = localStorage.getItem('idusuario');
       const formData = new FormData();
 
@@ -192,130 +305,50 @@ export class ZipfileUploadComponent {
       const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
 
       if (response.error) {
-        this._resourcesService.openSnackBar(`Error al subir ${fileData.name}: ${response.msg}`);
+        this._resourcesService.openSnackBar(response.msg);
       } else {
-        await this.readExcelFile(fileData, response.response.idArchivo);
-        
-        const newDocument: Document = {
-          nombre_del_archivo: fileData.name,
-          tamano_del_archivo: this.formatBytes(fileData.size),
-          archivo_id: response.response.idArchivo,
-          tipo_archivo: 'excel',
-          fecha_carga: new Date().toLocaleString(),
-          es_plantilla: true
+        // Actualizar uploadedFile para compatibilidad
+        this.uploadedFile = {
+          id: response.response.idArchivo,
+          name: fileData.name,
+          size: this.formatBytes(fileData.size)
         };
 
-        this.files.push(newDocument);
-        this.updateTableData();
-        
-        // Extraer códigos del Excel
-        this.extractCodigosFromExcel(response.response.idArchivo);
+        // Leer el archivo Excel para obtener información adicional
+        await this.readExcelFile(fileData, response.response.idArchivo);
         
-        this._resourcesService.openSnackBar(`${fileData.name} cargado exitosamente. Códigos de equipos extraídos.`);
-      }
-
-      this.uploadingCount--;
-      if (this.uploadingCount === 0) {
-        this.isUploading = false;
-      }
-    } catch (error: any) {
-      this.handleUploadError(error, fileData.name);
-      this.uploadingCount--;
-      if (this.uploadingCount === 0) {
-        this.isUploading = false;
-      }
-    }
-  }
-
-  async uploadZipFile(fileData: File): Promise<void> {
-    try {
-      const idUser = localStorage.getItem('idusuario');
-      
-      // Primero procesamos el contenido del ZIP
-      await this.processZipFile(fileData);
-      
-      const formData = new FormData();
-      formData.append('file', fileData);
-      formData.append('id_user', idUser!);
-      formData.append('linea', "1");
-      formData.append('tipo_archivo', 'zip');
-
-      const response = await lastValueFrom(this._gdelService.uploadTempFile(formData));
-
-      if (response.error) {
-        this._resourcesService.openSnackBar(`Error al subir ${fileData.name}: ${response.msg}`);
-      } else {
         const newDocument: Document = {
           nombre_del_archivo: fileData.name,
           tamano_del_archivo: this.formatBytes(fileData.size),
           archivo_id: response.response.idArchivo,
-          tipo_archivo: 'zip',
-          fecha_carga: new Date().toLocaleString(),
-          es_plantilla: false
+          tipo_archivo: 'excel',
+          fecha_carga: new Date().toLocaleString()
         };
 
-        this.files.push(newDocument);
+        // Solo mantener un archivo
+        this.excelFile = newDocument;
         this.updateTableData();
-        
-        this._resourcesService.openSnackBar(`${fileData.name} cargado exitosamente`);
+        this._resourcesService.openSnackBar('Plantilla Excel válida y cargada exitosamente');
       }
 
-      this.uploadingCount--;
-      if (this.uploadingCount === 0) {
-        this.isUploading = false;
-      }
+      this.isUploading = false;
     } catch (error: any) {
-      this.handleUploadError(error, fileData.name);
-      this.uploadingCount--;
-      if (this.uploadingCount === 0) {
-        this.isUploading = false;
-      }
+      this.handleUploadError(error);
+      this.isUploading = false;
     }
   }
 
-  private async processZipFile(file: File): Promise<void> {
+  // Método para validar la estructura del archivo antes de subirlo
+  private async validateFileStructure(file: File): Promise<{ isValid: boolean, errors: string[] }> {
     return new Promise((resolve, reject) => {
       const reader = new FileReader();
-      reader.onload = async (e: any) => {
+      reader.onload = (e: any) => {
         try {
-          const arrayBuffer = e.target.result;
-          const zipContents = await JSZip.loadAsync(arrayBuffer);
-          
-          const fileList: any[] = [];
-          
- 
-          const filePromises: Promise<void>[] = [];
-          
-          zipContents.forEach((relativePath, zipEntry) => {
-            if (!zipEntry.dir) { 
-              const filePromise = zipEntry.async('uint8array').then((content) => {
-                fileList.push({
-                  name: relativePath,
-                  originalName: relativePath,
-                  size: content.length, 
-                  codigoAsignado: null 
-                });
-              }).catch(() => {
-
-                fileList.push({
-                  name: relativePath,
-                  originalName: relativePath,
-                  size: 0,
-                  codigoAsignado: null
-                });
-              });
-              
-              filePromises.push(filePromise);
-            }
-          });
-          
-
-          await Promise.all(filePromises);
-          
-
-          this.zipContentsMap.set(file.name, fileList);
+          const data = new Uint8Array(e.target.result);
+          const workbook = XLSX.read(data, { type: 'array' });
           
-          resolve();
+          const validation = this.validateExcelTemplate(workbook);
+          resolve(validation);
         } catch (error) {
           reject(error);
         }
@@ -325,92 +358,6 @@ export class ZipfileUploadComponent {
     });
   }
 
-private extractCodigosFromExcel(fileId: string): void {
-  const excelData = this.excelDataMap.get(fileId);
-  if (!excelData) return;
-
-  this.codigosEquipos = [];
-  
-  
-  const HEADER_ROW_INDEX = 0;  
-  const DATA_START_ROW = 8;   
-  
-
-  Object.keys(excelData.sheets).forEach(sheetName => {
-    const sheetData = excelData.sheets[sheetName];
-    if (!sheetData || sheetData.length === 0) return;
-
-  
-    if (sheetData.length <= DATA_START_ROW) {
-      console.warn(`La hoja ${sheetName} no tiene suficientes filas. Se requieren al menos ${DATA_START_ROW + 1} filas.`);
-      return;
-    }
-
-
-    const headerRow = sheetData[HEADER_ROW_INDEX];
-    
-
-    const columnasConCodigos: number[] = [];
-    headerRow.forEach((header: string, index: number) => {
-      if (header && typeof header === 'string') {
-        const headerUpper = header.toUpperCase().trim();
-        if (this.COLUMNAS_CODIGOS.some(col => headerUpper.includes(col))) {
-          columnasConCodigos.push(index);
-          console.log(`Encontrada columna de códigos: "${header}" en índice ${index}`);
-        }
-      }
-    });
-
-    if (columnasConCodigos.length === 0) {
-      console.warn(`No se encontraron columnas de códigos en la hoja ${sheetName}`);
-      return;
-    }
-
-
-    for (let i = DATA_START_ROW; i < sheetData.length; i++) {
-      const row = sheetData[i];
-      if (!row || row.length === 0) continue;
-
-      columnasConCodigos.forEach(colIndex => {
-        const codigo = row[colIndex];
-        if (codigo && typeof codigo === 'string' && codigo.trim() !== '') {
-          this.codigosEquipos.push({
-            codigo: codigo.trim(),
-            fila: i + 1, 
-            columnas: row
-          });
-        }
-      });
-    }
-  });
-
-  console.log(`Códigos extraídos desde la fila 9:`, this.codigosEquipos);
-  
-
-  this.assignCodigosToZipFiles();
-}
-
-  private assignCodigosToZipFiles(): void {
-
-    
-    this.zipContentsMap.forEach((fileList, zipName) => {
-      fileList.forEach((file, index) => {
-        if (this.codigosEquipos.length > index) {
-          file.codigoAsignado = this.codigosEquipos[index].codigo;
-          file.newName = `${this.codigosEquipos[index].codigo}_${file.originalName}`;
-        }
-      });
-    });
-  }
-
-  public assignCodigoManually(zipFileName: string, fileIndex: number, codigo: string): void {
-    const fileList = this.zipContentsMap.get(zipFileName);
-    if (fileList && fileList[fileIndex]) {
-      fileList[fileIndex].codigoAsignado = codigo;
-      fileList[fileIndex].newName = `${codigo}_${fileList[fileIndex].originalName}`;
-    }
-  }
-
   private async readExcelFile(file: File, fileId: any): Promise<void> {
     return new Promise((resolve, reject) => {
       const reader = new FileReader();
@@ -419,6 +366,7 @@ private extractCodigosFromExcel(fileId: string): void {
           const data = new Uint8Array(e.target.result);
           const workbook = XLSX.read(data, { type: 'array' });
           
+          // Obtener información de las hojas
           const sheetNames = workbook.SheetNames;
           const sheetsData: any = {};
           
@@ -427,12 +375,13 @@ private extractCodigosFromExcel(fileId: string): void {
             sheetsData[sheetName] = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
           });
 
-          this.excelDataMap.set(fileId, {
+          // Guardar datos del archivo para previsualización
+          this.excelData = {
             fileName: file.name,
             sheets: sheetsData,
             sheetNames: sheetNames,
             fileId: fileId
-          });
+          };
 
           resolve();
         } catch (error) {
@@ -444,46 +393,65 @@ private extractCodigosFromExcel(fileId: string): void {
     });
   }
 
-  private updateTableData(): void {
-    this.dataSource.data = [...this.files];
+  // Método para eliminar archivo temporal
+  async deleteTempFile(): Promise<void> {
+    if (!this.uploadedFile) return;
+
+    try {
+      let idUser = localStorage.getItem('idusuario');
+      let formData = new FormData();
+
+      formData.append('id_user', idUser!);
+      formData.append('id_file', this.uploadedFile.id);
+      formData.append('linea', '1');
+
+      await lastValueFrom(this._gdelService.deleteTempFile(formData));
+
+      this.uploadedFile = null;
+      this.excelFile = null;
+      this.excelData = null;
+      this.updateTableData();
+      this._resourcesService.openSnackBar('Archivo eliminado exitosamente');
+    } catch (error: any) {
+      this.handleUploadError(error);
+    }
   }
 
-  private loadFiles(): void {
-    this.files = [];
-    this.excelDataMap.clear();
-    this.zipContentsMap.clear();
-    this.codigosEquipos = [];
-    this.updateTableData();
+  formatBytes(bytes: number, decimals = 2): string {
+    if (bytes === 0) return '0 Bytes';
+
+    const k = 1024;
+    const dm = decimals < 0 ? 0 : decimals;
+    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
   }
 
-  private handleUploadError(error: any, fileName: string): void {
-    let errorMessage = 'Ocurrió un error inesperado.';
-    
-    if (error.error?.msg) {
-      errorMessage = error.error.msg;
-    } else if (error.error) {
-      errorMessage = 'Ocurrió un error inesperado.';
-    }
-    
-    this._resourcesService.openSnackBar(`Error al subir ${fileName}: ${errorMessage}`);
+  private updateTableData(): void {
+    this.dataSource.data = this.excelFile ? [this.excelFile] : [];
   }
 
-  previewFile(element: Document): void {
-    if (!element.archivo_id) {
-      this._resourcesService.openSnackBar('No se encontró el ID del archivo');
-      return;
-    }
+  private loadExcelFile(): void {
+    // Inicializar con archivo vacío
+    this.excelFile = null;
+    this.updateTableData();
+  }
 
-    if (element.tipo_archivo === 'excel') {
-      this.previewExcelFile(element);
-    } else if (element.tipo_archivo === 'zip') {
-      this.previewZipFile(element);
+  private handleUploadError(error: any): void {
+    if (error.error == undefined) {
+      this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+    } else if (error.error.msg == undefined) {
+      this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
+    } else {
+      this._resourcesService.openSnackBar(error.error.msg);
     }
   }
 
-  private previewExcelFile(element: Document): void {
-    const excelData = this.excelDataMap.get(element.archivo_id!);
-    if (!excelData) {
+  // Método para abrir previsualización del Excel
+  previewExcelFile(): void {
+    if (!this.excelData) {
       this._resourcesService.openSnackBar('No hay datos del archivo Excel para mostrar');
       return;
     }
@@ -492,86 +460,77 @@ private extractCodigosFromExcel(fileId: string): void {
       width: '1800px',
       height: '900px',
       maxWidth: '1800px',
-      data: excelData,
+      data: this.excelData,
       disableClose: true
     });
 
     dialogRef.afterClosed().subscribe(result => {
+      // Manejar el resultado si es necesario
       if (result) {
         console.log('Preview cerrado:', result);
       }
     });
   }
 
-  private previewZipFile(element: Document): void {
-    const zipContents = this.zipContentsMap.get(element.nombre_del_archivo);
-    if (!zipContents) {
-      this._resourcesService.openSnackBar('No hay contenido del ZIP para mostrar');
-      return;
-    }
-
-    console.log('Contenido del ZIP:', zipContents);
-    this._resourcesService.openSnackBar(`ZIP contiene ${zipContents.length} archivos`);
+  // Método para eliminar archivo
+  removeExcelFile(): void {
+    this.deleteTempFile();
   }
 
-  removeFile(element: Document): void {
-    const index = this.files.findIndex(f => f.archivo_id === element.archivo_id);
-    if (index > -1) {
-      this.files.splice(index, 1);
-      
-      if (element.archivo_id) {
-        this.excelDataMap.delete(element.archivo_id);
-      }
-      
-      if (element.tipo_archivo === 'zip') {
-        this.zipContentsMap.delete(element.nombre_del_archivo);
-      }
-      
-      if (element.tipo_archivo === 'excel') {
-
-        this.codigosEquipos = [];
-      }
-      
-      this.updateTableData();
-      this._resourcesService.openSnackBar(`${element.nombre_del_archivo} eliminado`);
-    }
+  // Getter para verificar si ya hay un archivo cargado
+  get hasExcelFile(): boolean {
+    return this.excelFile !== null;
   }
 
-  removeAllFiles(): void {
-    this.files = [];
-    this.excelDataMap.clear();
-    this.zipContentsMap.clear();
-    this.codigosEquipos = [];
-    this.updateTableData();
-    this._resourcesService.openSnackBar('Todos los archivos han sido eliminados');
+  // Getter para obtener el array de archivos para la tabla
+  get excelFiles(): Document[] {
+    return this.excelFile ? [this.excelFile] : [];
   }
 
-  get hasFiles(): boolean {
-    return this.files.length > 0;
-  }
+  // Método para guardar archivo final
+  async saveFinalFile(): Promise<void> {
+    if (!this.uploadedFile) {
+      this._resourcesService.openSnackBar('No hay archivo cargado para guardar');
+      return;
+    }
 
-  get filesCount(): number {
-    return this.files.length;
-  }
+    try {
+      this.isLoading = true;
+      let idUser = localStorage.getItem('idusuario');
+      let formData = new FormData();
 
-  get hasExcelTemplate(): boolean {
-    return this.files.some(f => f.tipo_archivo === 'excel' && f.es_plantilla);
-  }
+      // Parámetros obligatorios
+      formData.append('id_user', idUser!);
+      formData.append('id_file', this.uploadedFile.id);
+      formData.append('linea', '1');
+      
+      // Parámetros específicos para equipamiento
+      formData.append('module', 'ADSI'); // Módulo fijo para equipamiento
+      formData.append('clasification', 'LA'); // Clasificación fija para LRU/Equipamiento
+      formData.append('has_order', 'N'); // Sin orden asociada
+
+      console.log('Datos enviados al servidor:', {
+        id_user: idUser,
+        id_file: this.uploadedFile.id,
+        module: 'ADSI',
+        clasification: 'LA'
+      });
 
-  get hasZipFiles(): boolean {
-    return this.files.some(f => f.tipo_archivo === 'zip');
-  }
+      const response = await lastValueFrom(this._gdelService.saveFinalFile(formData));
+      
+      console.log('Respuesta del servidor:', response);
 
-  get codigosDisponibles(): string[] {
-    return this.codigosEquipos.map(c => c.codigo);
-  }
+      if (response.error) {
+        throw new Error(response.msg || 'Error al guardar el archivo');
+      }
 
-  get uploadingText(): string {
-    if (this.uploadingCount === 1) {
-      return 'Subiendo 1 archivo...';
-    } else if (this.uploadingCount > 1) {
-      return `Subiendo ${this.uploadingCount} archivos...`;
+      this._resourcesService.openSnackBar('Archivo guardado correctamente');
+      this.dialogRef.close(true); // Cierra el diálogo y notifica al padre
+    } catch (error: any) {
+      console.error('Error al guardar archivo:', error);
+      this._resourcesService.openSnackBar(error.message || 'Error al guardar el archivo');
+    } finally {
+      this.isLoading = false;
     }
-    return 'Subiendo archivos...';
   }
 }

+ 13 - 10
src/app/components/preventive-maintenance/unprogrammed-visits/unprogrammed-visits.component.html

@@ -67,24 +67,27 @@
             <th mat-header-cell *matHeaderCellDef mat-sort-header>Acciones</th>
             <td mat-cell *matCellDef="let row">
               <button mat-mini-fab color="primary" class="mr-4 override_no_shadow animated fadeIn white_font pink_primary_background" matTooltip="Ver detalles" (click)="openVisitDetails(row.IDVISITA)" 
-              *ngIf="!btnSmall">
+              *ngIf="!btnSmall && row.ESTATUS == 'P'" >
                 <mat-icon>visibility</mat-icon>
               </button>
-              <button mat-mini-fab class="white_font green_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Aprobar visita" 
-              [ngClass]="{ green_primary_background: !isLoading && row.ESTATUS != 'A', gray_disabled: row.ESTATUS == 'F'}" class="no_shadow" 
-              [disabled]="row.ESTATUS =='F'">
+              <button mat-mini-fab #toolTip="matTooltip" matTooltip="Aprobar visita" 
+              [ngClass]="{green_primary_background: !isLoading && row.ESTATUS == 'P', white_font: !isLoading && row.ESTATUS == 'P', hidden : row.ESTATUS == 'A' || row.ESTATUS == 'R'
+              }" class="no_shadow" (click)="openCommentsDialog(row.IDVISITA, 'auth')"
+              [disabled]="row.ESTATUS =='F' || row.ESTATUS == 'C'"
+              *ngIf="!btnSmall">
                 <mat-icon>done</mat-icon>
               </button>
-              <button mat-mini-fab class="ml-4 white_font red_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Rechazar visita" 
-              [ngClass]="{red_primary_font: !isLoading && row.ESTATUS != 'R'}"
-              [disabled]="row.ESTATUS == 'C' || 'F'">
+              <button mat-mini-fab class="ml-4 no_shadow"  matTooltip="Rechazar visita" 
+              [ngClass]="{red_primary_background: row.ESTATUS == 'P', white_font: row.ESTATUS == 'P', hidden : row.ESTATUS == 'A' || row.ESTATUS == 'R'}" (click)="openCommentsDialog(row.IDVISITA, 'decline')"
+              [disabled]="row.ESTATUS == 'C' || row.ESTATUS == 'F'"
+              *ngIf="!btnSmall">
                 <mat-icon>close</mat-icon>
               </button>
-              <button mat-mini-fab class="white_font green_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Finalizar visita" 
+              <button mat-mini-fab class="mt-4 white_font green_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Finalizar visita" 
               *ngIf="row.ESTATUS == 'A' && !btnSmall" (click)="openCommentsDialog(row.IDVISITA, 'end')">
                 <mat-icon>check_circle</mat-icon>
               </button>
-              <button mat-mini-fab class="white_font red_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Cancelar visita" 
+              <button mat-mini-fab class="mt-4 white_font red_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Cancelar visita" 
               *ngIf="row.ESTATUS == 'A' && !btnSmall" (click)="openCommentsDialog(row.IDVISITA, 'cancel')">
                 <mat-icon>cancel</mat-icon>
               </button>
@@ -92,7 +95,7 @@
                 <mat-icon>more_vert</mat-icon>
               </button>
               <button mat-mini-fab class="white_font green_primary_background mr-4 override_no_shadow animated fadeIn" matTooltip="Aprobar visita"
-              *ngIf="row.ESTATUS == 'A' && !btnSmall" (click)="openCommentsDialog(row.IDVISITA, 'cancel')">  
+              [ngClass]="{hidden: row.ESTATUS == 'A' || row.ESTATUS == 'P' || row.ESTATUS == 'C'  || row.ESTATUS == 'R'|| row.ESTATUS == 'F' }" (click)="openCommentsDialog(row.IDVISITA, 'cancel')">  
               </button>
               <mat-menu #menu>
                 <button mat-menu-item (click)="openVisitDetails(row.IDVISITA)">

+ 1 - 1
src/styles.css

@@ -206,7 +206,7 @@ body { margin: 0; font-family: 'SourceCodePro', Roboto, "Helvetica Neue", sans-s
 .gray_light_font {color: #CFCFCF !important;}
 .gray_dark_background {background-color: #707070 !important;}
 .gray_dark_font {color: #686968 !important;}
-.gray_disabled {background-color:#b1b1b1 !important}
+.gray_disabled {background-color:#b1b1b1 !important; color:#9E9E9E !important}
 /* GRIS AZUL */
 .blue_gray_primary_background {background-color: #607D8B !important;}
 .blue_gray_primary_font {color: #607D8B !important;}