1
0

3 Ревизии 3e43997286 ... 02448d0595

Автор SHA1 Съобщение Дата
  EmilianoOrtiz 02448d0595 uso de staff-selection para aprobar visita tecnica que significa seleccionar operarios, se reutiliza assignOperariosToPreventiveVisit преди 4 дни
  EmilianoOrtiz 61195be985 modificacion de status-timeline para usarlo tambien en el lado de visitas tecnicas no programadas. преди 4 дни
  EmilianoOrtiz 505d59ef56 limpieza de logs y mejor manejo de errores преди 4 дни

+ 13 - 63
src/app/components/corrective-maintenance/operations-management/staff-selection/staff-selection.component.ts

@@ -84,9 +84,6 @@ export class StaffSelectionComponent implements OnInit {
       let personalData: string = '';
       let historialEstatus: string = '';
 
-      console.log('🔍 Staff Selection - Order Type:', this.orderType);
-      console.log('🔍 Staff Selection - Order ID:', this._data.idOrder);
-
       // Fetch order details based on orderType
       if (this.orderType === 'Correctivo') {
         let order: CorrectiveWorkOrderDetailsResponse = await lastValueFrom(
@@ -97,20 +94,17 @@ export class StaffSelectionComponent implements OnInit {
           )
         );
 
-        console.log('✅ Corrective Order Response:', order);
-
         this.hasError = order.error;
         this.errorStr = order.msg;
 
         if (this.hasError) {
-          console.error('❌ Error getting corrective order:', this.errorStr);
+          this._resourcesService.openSnackBar(this.errorStr);
           this.isLoading = false;
           return;
         }
 
         personalData = order.response.PERSONAL;
         historialEstatus = order.response.HISTORIAL_ESTATUS;
-        console.log('📋 PERSONAL data (raw):', personalData);
       } else {
         // Preventive maintenance
         let order: PreventiveVisitDetailsResponse = await lastValueFrom(
@@ -121,13 +115,11 @@ export class StaffSelectionComponent implements OnInit {
           )
         );
 
-        console.log('✅ Preventive Order Response:', order);
-
         this.hasError = order.error;
         this.errorStr = order.msg;
 
         if (this.hasError) {
-          console.error('❌ Error getting preventive order:', this.errorStr);
+          this._resourcesService.openSnackBar(this.errorStr);
           this.isLoading = false;
           return;
         }
@@ -136,7 +128,6 @@ export class StaffSelectionComponent implements OnInit {
         // Preventive visits might not have HISTORIAL_ESTATUS in the same format
         // We'll handle this case by using an empty string
         historialEstatus = '';
-        console.log('📋 PERSONAL data (raw):', personalData);
       }
 
       // Process excluded users from status history (only for corrective orders)
@@ -160,14 +151,9 @@ export class StaffSelectionComponent implements OnInit {
       // Process staff requirements (same for both types)
       if (personalData) {
         let staffArr = JSON.parse(personalData);
-        console.log('👥 Staff Array (parsed):', staffArr);
 
         for (const specialty of staffArr) {
           let idDec = await this._encService.decrypt(specialty.ID);
-          console.log(`🔓 Specialty ID decrypted: ${specialty.ID} -> ${idDec}`);
-          console.log(
-            `📛 Specialty Name: ${specialty.NAME}, Cantidad: ${specialty.CANT}`
-          );
 
           this.staffSpecialtiesMap.set(idDec, specialty.NAME);
 
@@ -177,7 +163,6 @@ export class StaffSelectionComponent implements OnInit {
               : this.staffOperariesMap.get(idDec)!;
           for (let i = 0; i < specialty.CANT; i++) {
             let index = `${idDec}${i}Control`;
-            console.log(`➕ Adding form control: ${index}`);
             this.formGroup.addControl(
               index,
               new FormControl('', Validators.required)
@@ -188,24 +173,14 @@ export class StaffSelectionComponent implements OnInit {
 
           this.staffOperariesMap.set(idDec, operaries);
         }
-
-        console.log('🗺️ staffSpecialtiesMap:', this.staffSpecialtiesMap);
-        console.log('🗺️ staffOperariesMap:', this.staffOperariesMap);
-      } else {
-        console.warn('⚠️ No PERSONAL data found in response');
       }
 
       this.getEmployees();
     } 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;
-      }
+      this.errorStr = error?.error?.msg || 'Ocurrió un error inesperado.';
       this.hasError = true;
       this.isLoading = false;
+      this._resourcesService.openSnackBar(this.errorStr);
     }
   }
 
@@ -216,20 +191,15 @@ export class StaffSelectionComponent implements OnInit {
         this._employeeService.getConsultOfEmployees(idUser, 1)
       );
 
-      console.log('👷 Employees Response:', employees);
-
       this.hasError = employees.error;
       this.errorStr = employees.msg;
 
       if (this.hasError) {
-        console.error('❌ Error getting employees:', this.errorStr);
+        this._resourcesService.openSnackBar(this.errorStr);
         this.isLoading = false;
       } else {
         let employeesArr: EmployeesListItem[] = [];
         let subcontratistsEmployees: EmployeesListItem[] = [];
-        console.log(
-          `📊 Total employees received: ${employees.response.length}`
-        );
 
         for (const employee of employees.response) {
           let idEmployee = await this._encService.decrypt(employee.ID_EMPLOYEE);
@@ -270,20 +240,13 @@ export class StaffSelectionComponent implements OnInit {
         }
 
         this.employees = employeesArr;
-        console.log(`✅ Internal employees filtered: ${employeesArr.length}`);
-        console.log(
-          `🏢 Subcontractor employees to process: ${subcontratistsEmployees.length}`
-        );
         this.getSubcontratists(subcontratistsEmployees);
       }
     } 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;
-      }
+      this.errorStr = error?.error?.msg || 'Ocurrió un error inesperado.';
+      this.hasError = true;
+      this.isLoading = false;
+      this._resourcesService.openSnackBar(this.errorStr);
     }
   }
 
@@ -348,26 +311,14 @@ export class StaffSelectionComponent implements OnInit {
         }
 
         this.subcontratists = subcontratistEmployeesArr;
-        console.log(
-          `✅ Final subcontractor employees: ${subcontratistEmployeesArr.length}`
-        );
       }
 
-      console.log('🎯 Final data loaded:');
-      console.log('  - Employees:', this.employees.length);
-      console.log('  - Subcontratists:', this.subcontratists.length);
-      console.log('  - Specialties Map:', this.staffSpecialtiesMap);
-      console.log('  - Operaries Map:', this.staffOperariesMap);
-
       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;
-      }
+      this.errorStr = error?.error?.msg || 'Ocurrió un error inesperado.';
+      this.hasError = true;
+      this.isLoading = false;
+      this._resourcesService.openSnackBar(this.errorStr);
     }
   }
 
@@ -420,7 +371,6 @@ export class StaffSelectionComponent implements OnInit {
     }
 
     let configStr = JSON.stringify(config);
-    console.log('Staff Selection Request:', configStr);
     this._dialogRef.close(configStr);
   }
 }

+ 44 - 21
src/app/components/corrective-maintenance/operations-management/status-timeline/status-timeline.component.ts

@@ -1,22 +1,27 @@
 import { Component, Inject, OnInit } from '@angular/core';
-import { WorkOrderStatusHistoryListItem, WorkOrderStatusHistoryListResponse } from '../../../../interfaces/corrective-maintenance.interface';
+import {
+  WorkOrderStatusHistoryListItem,
+  WorkOrderStatusHistoryListResponse,
+} from '../../../../interfaces/corrective-maintenance.interface';
 import { MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { EncService } from '../../../../services/enc.service';
 import { CorrectiveMaintenanceService } from '../../../../services/corrective-maintenance.service';
+import { PreventiveMaintenanceService } from '../../../../services/preventive-maintenance.service';
 import { FunctionsService } from '../../../../services/functions.service';
 import { lastValueFrom } from 'rxjs';
 
 @Component({
-    selector: 'app-status-timeline',
-    templateUrl: './status-timeline.component.html',
-    styleUrl: './status-timeline.component.css',
-    standalone: false
+  selector: 'app-status-timeline',
+  templateUrl: './status-timeline.component.html',
+  styleUrl: './status-timeline.component.css',
+  standalone: false,
 })
 export class StatusTimelineComponent implements OnInit {
   isLoading: boolean;
   hasError: boolean;
   errorStr: string;
   idOrder: string;
+  orderType: 'Correctivo' | 'Preventivo';
 
   historyArr: WorkOrderStatusHistoryListItem[];
 
@@ -24,38 +29,56 @@ export class StatusTimelineComponent implements OnInit {
     @Inject(MAT_DIALOG_DATA) private _data: any,
     private _encService: EncService,
     private _correctiveMaintenanceService: CorrectiveMaintenanceService,
-    private _functionsService: FunctionsService,
-  ) { 
+    private _preventiveMaintenanceService: PreventiveMaintenanceService,
+    private _functionsService: FunctionsService
+  ) {
     this.isLoading = true;
     this.hasError = false;
     this.errorStr = '';
     this.idOrder = '';
+    this.orderType = 'Correctivo'; // default
 
     this.historyArr = [];
   }
 
   ngOnInit(): void {
     this.idOrder = this._data.idOrder;
+    this.orderType = this._data.orderType || 'Correctivo';
     this.getOrderStatusHistory();
   }
 
-  async getOrderStatusHistory(){
-    try{
-      let idUser =localStorage.getItem('idusuario')!;
+  async getOrderStatusHistory() {
+    try {
+      let idUser = localStorage.getItem('idusuario')!;
       let idOrderEnc = await this._encService.encrypt(this._data.idOrder);
-      let orderStatusHistory: WorkOrderStatusHistoryListResponse = await lastValueFrom(this._correctiveMaintenanceService.getWorkOrderStatusHistory(
-        idOrderEnc,
-        idUser, 
-        1
-      ));
+      let orderStatusHistory: WorkOrderStatusHistoryListResponse;
+
+      // Call appropriate service based on orderType
+      if (this.orderType === 'Correctivo') {
+        orderStatusHistory = await lastValueFrom(
+          this._correctiveMaintenanceService.getWorkOrderStatusHistory(
+            idOrderEnc,
+            idUser,
+            1
+          )
+        );
+      } else {
+        orderStatusHistory = await lastValueFrom(
+          this._preventiveMaintenanceService.getVisitStatusHistory(
+            idOrderEnc,
+            idUser,
+            1
+          )
+        );
+      }
 
       this.hasError = orderStatusHistory.error;
       this.errorStr = orderStatusHistory.msg;
 
-      if(!this.hasError){
+      if (!this.hasError) {
         console.log(orderStatusHistory.response);
         let historyArr: WorkOrderStatusHistoryListItem[] = [];
-        for(const item of orderStatusHistory.response){
+        for (const item of orderStatusHistory.response) {
           item.FECHA = this._functionsService.orderDate(item.FECHA);
           historyArr.push(item);
         }
@@ -64,12 +87,12 @@ export class StatusTimelineComponent implements OnInit {
       }
 
       this.isLoading = false;
-    }catch(error: any){
-      if(error.error == undefined){
+    } catch (error: any) {
+      if (error.error == undefined) {
         this.errorStr = 'Ocurrió un error inesperado.';
-      }else if(error.error.msg == undefined){
+      } else if (error.error.msg == undefined) {
         this.errorStr = 'Ocurrió un error inesperado.';
-      }else{
+      } else {
         this.errorStr = error.error.msg;
       }
 

+ 68 - 7
src/app/components/preventive-maintenance/unprogrammed-visits/unprogrammed-visits.component.html

@@ -94,11 +94,9 @@
           class="animated fadeIn"
           [style.display]="!isLoading && !hasError ? 'revert' : 'none'"
         >
-          <ng-container matColumnDef="numeracion">
-            <th mat-header-cell *matHeaderCellDef>#</th>
-            <td mat-cell *matCellDef="let row; let i = index">
-              {{ getRowIndex(i) }}
-            </td>
+          <ng-container matColumnDef="Número de visita">
+            <th mat-header-cell *matHeaderCellDef>Número de visita</th>
+            <td mat-cell *matCellDef="let row">#{{ row.IDVISITA }}</td>
           </ng-container>
 
           <ng-container matColumnDef="equipamiento">
@@ -177,25 +175,88 @@
                 <mat-icon>more_vert</mat-icon>
               </button>
               <mat-menu #actionsMenu="matMenu">
+                <!-- TODOS: Ver detalles, Seguimiento del estado -->
                 <button mat-menu-item (click)="openVisitDetails(row.IDVISITA)">
                   <mat-icon>visibility</mat-icon>
                   <span>Ver detalles</span>
                 </button>
+                <button
+                  mat-menu-item
+                  (click)="openStatusTimeline(row.IDVISITA)"
+                >
+                  <mat-icon>timeline</mat-icon>
+                  <span>Seguimiento del estado</span>
+                </button>
+
+                <!-- P: Aprobar, Rechazar, Cancelar, Traspasar orden, Eliminar -->
+                @if (row.ESTADO === 'P') {
+                <button
+                  mat-menu-item
+                  (click)="openStaffSelectionDialog(row.IDVISITA)"
+                >
+                  <mat-icon>check</mat-icon>
+                  <span>Aprobar</span>
+                </button>
+                <button mat-menu-item (click)="rejectVisit(row)">
+                  <mat-icon>close</mat-icon>
+                  <span>Rechazar</span>
+                </button>
+                <button mat-menu-item (click)="cancelVisit(row)">
+                  <mat-icon>cancel</mat-icon>
+                  <span>Cancelar</span>
+                </button>
+                <button mat-menu-item (click)="transferVisit(row)">
+                  <mat-icon>swap_horiz</mat-icon>
+                  <span>Traspasar orden</span>
+                </button>
+                <button mat-menu-item (click)="deleteVisit(row)">
+                  <mat-icon>delete</mat-icon>
+                  <span>Eliminar</span>
+                </button>
+                }
+
+                <!-- VA / A: Iniciar ejecución, Cancelar, Eliminar -->
                 @if (row.ESTADO === 'A' || row.ESTADO === 'VA') {
                 <button mat-menu-item (click)="startVisitExecution(row)">
                   <mat-icon>play_arrow</mat-icon>
                   <span>Iniciar ejecución</span>
                 </button>
-                } @if (row.ESTADO === 'EP') {
+                <button mat-menu-item (click)="cancelVisit(row)">
+                  <mat-icon>cancel</mat-icon>
+                  <span>Cancelar</span>
+                </button>
+                <button mat-menu-item (click)="deleteVisit(row)">
+                  <mat-icon>delete</mat-icon>
+                  <span>Eliminar</span>
+                </button>
+                }
+
+                <!-- EP: Cerrar visita, Cancelar, Eliminar -->
+                @if (row.ESTADO === 'EP') {
                 <button mat-menu-item (click)="openCloseVisitDialog(row)">
                   <mat-icon>done</mat-icon>
                   <span>Cerrar visita</span>
                 </button>
-                } @if (row.ESTADO === 'CP') {
+                <button mat-menu-item (click)="cancelVisit(row)">
+                  <mat-icon>cancel</mat-icon>
+                  <span>Cancelar</span>
+                </button>
+                <button mat-menu-item (click)="deleteVisit(row)">
+                  <mat-icon>delete</mat-icon>
+                  <span>Eliminar</span>
+                </button>
+                }
+
+                <!-- CP: Cierre final, Análisis de costos -->
+                @if (row.ESTADO === 'CP') {
                 <button mat-menu-item (click)="openFinalCloseDialog(row)">
                   <mat-icon>check_circle</mat-icon>
                   <span>Cierre final</span>
                 </button>
+                <button mat-menu-item (click)="openCostAnalysis(row)">
+                  <mat-icon>assessment</mat-icon>
+                  <span>Análisis de costos</span>
+                </button>
                 }
               </mat-menu>
             </td>

+ 262 - 1
src/app/components/preventive-maintenance/unprogrammed-visits/unprogrammed-visits.component.ts

@@ -18,6 +18,8 @@ import { PreventiveOrderDetailsComponent } from '../preventive-order-details/pre
 import { ResourcesService } from '../../../services/resources.service';
 import { AlertComponent } from '../../resources/dialogs/alert/alert.component';
 import { CommnetsDialogComponent } from '../../corrective-maintenance/operations-management/commnets-dialog/commnets-dialog.component';
+import { StatusTimelineComponent } from '../../corrective-maintenance/operations-management/status-timeline/status-timeline.component';
+import { StaffSelectionComponent } from '../../corrective-maintenance/operations-management/staff-selection/staff-selection.component';
 
 @Component({
   selector: 'app-unprogrammed-visits',
@@ -31,7 +33,7 @@ export class UnprogrammedVisitsComponent implements OnInit {
 
   dataSource?: MatTableDataSource<UnprogrammedVisit>;
   displayedColumns = [
-    'numeracion',
+    'Número de visita',
     'equipamiento',
     'descripcion',
     'estado',
@@ -64,6 +66,7 @@ export class UnprogrammedVisitsComponent implements OnInit {
     R: 'Rechazado',
     A: 'Aprobado',
     F: 'Finalizado',
+    EL: 'Eliminado',
   };
 
   constructor(
@@ -528,4 +531,262 @@ export class UnprogrammedVisitsComponent implements OnInit {
     const pageSize = this.paginator?.pageSize ?? 10;
     return pageIndex * pageSize + index + 1;
   }
+
+  async openStaffSelectionDialog(idVisit: string) {
+    const idVisitStr = idVisit.replace('Visita #', '').trim();
+    const idVisitEnc = await this._encService.encrypt(idVisitStr);
+    const dialogRef = this._dialog.open(StaffSelectionComponent, {
+      disableClose: true,
+      width: '540px',
+      maxWidth: '540px',
+      data: {
+        idOrder: idVisitEnc,
+        orderType: 'Preventivo',
+      },
+    });
+
+    dialogRef.afterClosed().subscribe((res) => {
+      if (res != null && res != undefined && res != '') {
+        this.approveVisit(idVisitEnc, res);
+      }
+    });
+  }
+  // Métodos de acción implementados
+  private async approveVisit(idVisit: string, config: string) {
+    try {
+      const idUser = localStorage.getItem('idusuario')!;
+      const formData = new FormData();
+      formData.append('id_user', idUser);
+      formData.append('linea', '1');
+      formData.append('id_order', idVisit);
+      formData.append('config', config);
+
+      const response = await lastValueFrom(
+        this._prevMaintService.assignOperariosToPreventiveVisit(formData)
+      );
+      if (response?.error) {
+        this._resourcesService.openSnackBar(
+          response.msg || 'Ocurrió un error al aprobar la visita.'
+        );
+      } else {
+        this._resourcesService.openSnackBar(
+          response?.msg || 'La visita se aprobó correctamente.'
+        );
+      }
+      this.getUnprogrammedVisits();
+    } 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);
+      }
+    }
+  }
+
+  rejectVisit(visit: UnprogrammedVisit) {
+    const visitId = `${visit.IDVISITA ?? ''}`.trim();
+    if (!visitId) {
+      this._resourcesService.openSnackBar('No se pudo identificar la visita.');
+      return;
+    }
+
+    const confirmDialogRef = this._dialog.open(AlertComponent, {
+      width: '420px',
+      maxWidth: '420px',
+      disableClose: true,
+      data: {
+        title: 'Confirmación',
+        icon: 'warning',
+        description: `¿Está seguro de rechazar la visita #${visitId}?`,
+      },
+    });
+
+    confirmDialogRef.afterClosed().subscribe((confirmed) => {
+      if (confirmed === true) {
+        const commentsDialogRef = this._dialog.open(CommnetsDialogComponent, {
+          width: '480px',
+          maxWidth: '480px',
+          disableClose: true,
+          data: {
+            idOrder: visitId,
+            status: 'Rechazar',
+          },
+        });
+
+        commentsDialogRef.afterClosed().subscribe((comments) => {
+          if (comments != undefined && comments != null && comments != '') {
+            if (comments.length < 15) {
+              this._resourcesService.openSnackBar(
+                'Los comentarios deben tener al menos 15 caracteres.'
+              );
+              return;
+            }
+            this.updateVisitStatus(visitId, 'R', comments);
+          }
+        });
+      }
+    });
+  }
+
+  cancelVisit(visit: UnprogrammedVisit) {
+    const visitId = `${visit.IDVISITA ?? ''}`.trim();
+    if (!visitId) {
+      this._resourcesService.openSnackBar('No se pudo identificar la visita.');
+      return;
+    }
+
+    const confirmDialogRef = this._dialog.open(AlertComponent, {
+      width: '420px',
+      maxWidth: '420px',
+      disableClose: true,
+      data: {
+        title: 'Confirmación',
+        icon: 'warning',
+        description: `¿Está seguro de cancelar la visita #${visitId}?`,
+      },
+    });
+
+    confirmDialogRef.afterClosed().subscribe((confirmed) => {
+      if (confirmed === true) {
+        const commentsDialogRef = this._dialog.open(CommnetsDialogComponent, {
+          width: '480px',
+          maxWidth: '480px',
+          disableClose: true,
+          data: {
+            idOrder: visitId,
+            status: 'Cancelar',
+          },
+        });
+
+        commentsDialogRef.afterClosed().subscribe((comments) => {
+          if (comments != undefined && comments != null && comments != '') {
+            if (comments.length < 15) {
+              this._resourcesService.openSnackBar(
+                'Los comentarios deben tener al menos 15 caracteres.'
+              );
+              return;
+            }
+            this.updateVisitStatus(visitId, 'C', comments);
+          }
+        });
+      }
+    });
+  }
+
+  transferVisit(visit: UnprogrammedVisit) {
+    console.log('transferVisit', visit);
+    // TODO: Implementar diálogo de selección de usuario para traspasar
+    this._resourcesService.openSnackBar(
+      'Funcionalidad de traspaso en desarrollo.'
+    );
+  }
+
+  deleteVisit(visit: UnprogrammedVisit) {
+    const visitId = `${visit.IDVISITA ?? ''}`.trim();
+    if (!visitId) {
+      this._resourcesService.openSnackBar('No se pudo identificar la visita.');
+      return;
+    }
+
+    const confirmDialogRef = this._dialog.open(AlertComponent, {
+      width: '420px',
+      maxWidth: '420px',
+      disableClose: true,
+      data: {
+        title: 'Confirmación',
+        icon: 'warning',
+        description: `¿Está seguro de eliminar la visita #${visitId}? Esta acción no se puede deshacer.`,
+      },
+    });
+
+    confirmDialogRef.afterClosed().subscribe((confirmed) => {
+      if (confirmed === true) {
+        // Eliminar no requiere comentarios según el endpoint, solo status EL
+        this.updateVisitStatus(visitId, 'EL', 'Eliminada por el usuario');
+      }
+    });
+  }
+
+  openCostAnalysis(visit: UnprogrammedVisit) {
+    const visitId = `${visit.IDVISITA ?? ''}`.trim();
+    if (!visitId) {
+      this._resourcesService.openSnackBar('No se pudo identificar la visita.');
+      return;
+    }
+
+    // Navegar a la ruta de análisis de costos
+    this._encService.encrypt(visitId).then((idEnc) => {
+      this._router.navigate(['sam/GMPR/ORTR/COTP/analisis'], {
+        queryParams: {
+          data: idEnc,
+        },
+      });
+    });
+  }
+
+  // Método común para actualizar estado de visita
+  private async updateVisitStatus(
+    idVisit: string,
+    newStatus: string,
+    comments: string
+  ) {
+    try {
+      const idUser = localStorage.getItem('idusuario')!;
+      const idVisitEnc = await this._encService.encrypt(idVisit);
+
+      const formData = new FormData();
+      formData.append('id_user', idUser);
+      formData.append('linea', '1');
+      formData.append('id_visit', idVisitEnc);
+      formData.append('comments', comments);
+      formData.append('status', newStatus);
+
+      const response = await lastValueFrom(
+        this._prevMaintService.updateVisitStatus(formData)
+      );
+
+      if (response?.error) {
+        this._resourcesService.openSnackBar(
+          response.msg ||
+            'Ocurrió un error al actualizar el estado de la visita.'
+        );
+      } else {
+        const statusMessages: Record<string, string> = {
+          VA: 'La visita se aprobó correctamente.',
+          R: 'La visita se rechazó correctamente.',
+          C: 'La visita se canceló correctamente.',
+          EL: 'La visita se eliminó correctamente.',
+        };
+        this._resourcesService.openSnackBar(
+          response?.msg ||
+            statusMessages[newStatus] ||
+            'El estado se actualizó correctamente.'
+        );
+      }
+      this.getUnprogrammedVisits();
+    } 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);
+      }
+      this.getUnprogrammedVisits();
+    }
+  }
+
+  // Seguimiento del estado - Implementado
+  openStatusTimeline(idVisit: string) {
+    this._dialog.open(StatusTimelineComponent, {
+      width: '480px',
+      maxWidth: '480px',
+      data: {
+        idOrder: idVisit,
+        orderType: 'Preventivo',
+      },
+    });
+  }
 }