Skip to content

Perfiles

Sobre los modelos core existe una capa de personalización que permite definir funcionalidades específicas del negocio, por ejemplo las funciones para transformar una OT cargada desde un archivo a la representación interna de la plataforma.

Arquitectura de la plataforma

Interfaz anotada
ts
interface Profile {
  /*
   * Nombre del perfil. Solo de referencia
   */
  name: string

  /*
   * Configuración de exportación. Permite definir
   * columnas del archivo de salida
   */
  exportConfig: {
    columns: { key: string, text: string, format?: (v: any) => string }[]
    mappers: Record<string, ExportConfigMap>
  }

  /*
   * Configuración global por defecto. Debería tener
   * los valores del cliente para días del turno, meta
   * de utilización, etc.
   */
  getDefaultConfig: () => Config

  /*
   * Categorías de OT para mostrar en estadísticas de
   * programación.
   */
  workOrderClasses: {
    name: string
    description: string
    filter: (
      process: SchedulingProcess,
      workOrder: WorkOrder,
      config: Config
    ) => boolean
  }[]


  /*
   * Categorías de OT para mostrar en informes y estadísticas
   * de cumplimiento de programa
   */
  laborWorkOrderClasses: LaborWorkOrderClass[]

  /*
   * Nombres de las columnas obligatorias para los archivos que tienen OTs.
   */
  requiredWorkOrderFileColumns: string[]

  /*
   * Las 4 funciones siguientes toman una lista de strings correspondientes
   * a una fila de un CSV o XLSX y devuelven una instancia de la entidad
   * correspondiente.
   * En la implementación de estas funciones está la lógica de validación
   * de las OT.
   */
  row2WorkOrder: (header: string[]) => (row: string[]) => Promise<WorkOrder | null>
  row2LaborData: (header: string[]) => (row: string[]) => AssignedResource | null
  row2Asset: (header: string[]) => (row: string[]) => Asset | null
  row2Supervisor: (header: string[]) => (row: string[]) => Supervisor | null

  /*
   * Validaciones globales de la base de datos de OT para hacer un programa
   * nuevo. Por ejemplo para verificar que no hay más de una OT para un mismo
   * equipo.
   */
  validateReadyBacklog: (schedulingProcess: SchedulingProcess, workOrder: WorkOrder[]) => Promise<{ workOrders: WorkOrder[], report: Record<string, { workOrder: WorkOrder, messages: string[] }[]> }>

  /*
   * Lista de estadísticas del ready backlog (OTs que pasaron los filtros y no
   * tenían alertas) a mostrar después de cargar una base de OTs para
   * hacer un programa nuevo.
   */
  readyBacklogStats: (schedulingProcess: SchedulingProcess) => { name: string, value: string, unit?: string }[]

  /*
   * Lista de estadísticas sobre el archivo completo a mostrar después de
   * cargar una base de OTs para hacer un programa nuevo.
   */
  workOrderFileStats: (schedulingProcess: SchedulingProcess) => { name: string, value: string, unit?: string }[]


  /*
   * Reglas de ordenación para la selección automǻtica de OTs. Por ejemplo,
   * pueden considerarse ordenar por prioridad de manera ascendente y luego
   * por antiguedad de manera descendente
   */
  workOrderSelectionRules: (schedulingProcess: SchedulingProcess) => WorkOrderSelectionRule[]

  /*
   * Filtro de las OTs que pueden seleccionarse automáticamente. Se usa
   * más que nada para descartar OTs del proceso (por ejemplo cuando necesitan
   * paradas de planta)
   */
  automaticSchedulingWorkOrderFilter: (workOrder: WorkOrder) => boolean

  /*
   * Mensajes de ayuda para algunos atributos
   */
  translations: {
    ready_backlog_file_stats_help_text: string
    ready_backlog_upload_stats_help_text: string
  }
}

General

name

Nombre del perfil. Solo de referencia.

Ejemplo
ts
const profile: Profile = {
    name: 'La Empresa', 
    // resto del perfil
}

Configuración de Exportación

exportConfig

Permite definir columnas del archivo de salida para la exportación de programa completo o archivo para actualizar estados en otro sistema.

Ejemplo
ts
const profile: Profile = {
    exportConfig: () => {  
      columns: [
        { key: 'workOrder', text: 'OT' },
        { key: 'workClass', text: 'Especialidad' },
        { key: 'supervisor', text: 'Supervisor' },
        { key: 'scheduledDay', text: 'Día' },
        { key: 'description', text: 'Descripción' },
        { key: 'asset', text: 'Activo' },
        { key: 'labor', text: 'HH' },
      ],
      mappers: {
        'work-order-update': ({ workOrder: wo }) => ({ workOrder: wo.code, supervisor: wo.scheduling?.supervisorId }),
        'full-program': ({ workOrder, schedulingProcess }) => ({
          workClass: workOrder.classes?.workClass,
          supervisor: schedulingProcess.supervisors.find(
            s => s.id === workOrder.scheduling?.supervisorId,
          )?.name,
          scheduledDay: !workOrder.scheduling?.date
            ? '?'
            : differenceInCalendarDays(
              workOrder.scheduling?.date,
              schedulingProcess.fromDate,
            ) + 1,
          workOrder,

          description: wo.description,
          asset: workOrder.asset?.id,

          labor: workOrder.requiredLabor,
        }),
      },
    },
    // resto del perfil
}

getDefaultConfig

Define los valores iniciales de configuración para una sesión nueva. El usuario puede modificar estos valores en el primer paso del flujo de programación.

La función tiene que devolver un objeto con los siguientes campos

ParámetroTipoDescripción
dueDays{[string]: number}Días de vencimiento por tipo
shiftDaysnumberDuración del turno en días
shiftHoursnumberDuración de cada día del turno en horas
statGoals{scheduledTime: number}Meta de estadísticas. Por ahora solo existe la meta de horas a programar por día.
statLevels{level: 'error' | 'warning' | 'success', from: number, to: number}Niveles de alerta y error para estadísticas. Por ahora solo existen para la meta de horas a programar por día.
Ejemplo
ts
const profile: Profile = {
    getDefaultConfig: () => ({  
      dueDays: { 1: 1, 2: 2, 3: 15, 4: 60, 5: 180 },
      shiftDays: 7,
      shiftHours: 12,
      statGoals: {
        scheduledTime: 10 / 12,
      },
      statLevels: {
        scheduledTime: [
          { level: 'warning', from: 0, to: 0.7 },
          { level: 'success', from: 0.7, to: 0.83 },
          { level: 'error', from: 0.83, to: 100000 },
        ] as {
          level: 'error' | 'warning' | 'success'
          from: number
          to: number
        }[],
      },
    }),
    // resto del perfil
}

Categorías de OT

workOrderClasses

Categorías de OT para mostrar en estadísticas de programación

ParámetroTipoDescripción
namestringNombre de la categoría
descriptionstringDescripción de la categoría. Se muestra en un botón de ayuda.
filter(SchedulingProcess, WorkOrder, Config) => boolFiltro de OT para la categoría. Si la función retorna true la OT pertenece a la categoría.
Ejemplo
ts
const profile: Profile = {
    workOrderClasses: [ 
      {
        name: 'OT Vigentes',
        description: '',
        filter: (schedulingProcess, workOrder, config) =>
          addDays(
            workOrder.creationDate,
            config.dueDays[workOrder.classes.type],
          ) >= schedulingProcess.fromDate,
      },
      {
        name: 'OT Atrasadas',
        description: '',
        filter: (schedulingProcess, workOrder, config) =>
          addDays(
            workOrder.creationDate,
            config.dueDays[workOrder.classes.type],
          ) < schedulingProcess.fromDate,
      },
      {
        name: 'Otras OT',
        description: '',
        filter: () => true,
      },
    ],
    // resto del perfil
}

laborWorkOrderClasses

Categorías de OT para mostrar en informes y estadísticas de cumplimiento de programa.

ParámetroTipoDescripción
keystringIdentificador de la categoría
namestringNombre de la categoría
descriptionstringDescripción de la categoría. Se muestra en un botón de ayuda.
filter(WorkOrder) => boolFiltro de OT para la categoría. Si la función retorna true la OT pertenece a la categoría.
Ejemplo
ts
const profile: Profile = {
    laborWorkOrderClasses: [ 
      {
        key: 'programada-preventiva',
        name: 'OTs Preventivas programada',
        description: 'OT tipo PREV dentro del programa de mantenimiento',
        filter: wo =>
          scheduled(wo)
          && wo.classes.type === 'PREV',
        color: colors.success,
        hideable: false,
      },
      {
        key: 'programadas-correctiva',
        name: 'OTs Correctivas no programadas',
        description: 'OT tipo CORR fuera del programa de mantenimiento',
        filter: (wo: WorkOrder) =>
          !scheduled(wo)
          && wo.classes.type === 'CORR',
        color: colors.warning,
        hideable: false,
      },
      {
        key: 'otras',
        name: 'Otras OTs',
        description: 'Otras OT ejecutadas',
        filter: () => {
          return true
        },
        color: '#fff',
        hideable: true,
      },
    ],
    // resto del perfil
}

requiredWorkOrderFileColumns

Nombres de las columnas obligatorias para los archivos que tienen OTs.

Ejemplo
ts
const profile: Profile = {
  requiredWorkOrderFileColumns: [ 
    'Id',
    'Estado',
    'Tipo',
    'Prioridad',
    'Especialidad',
    'Sitio',
    'HH Planificados',
  ],
  // resto del perfil
}

Importación

Todas las funciones de importación tienen la siguiente estructura:

ts
function row2X = (header: string[]) => (row: string[][]) => {
    if (isInvalid(row)) {
        return null
    }
    return new X({
        prop: row[header.indexOf('Columna CSV')]
        // ...
    })
}

row2WorkOrder

La importación de un archivo de órdenes de trabajo tiene dos funciones: convertir los atributos del archivo en bruto a los modelos de la plataforma, y generar alertas de la base de OTs.

Además de las validaciones por cada OT de manera individual, el perfil también define la función validateReadyBacklog que puede emitir alertas globales (por ejemplo para detectar OTs duplicadas) y genera el formato del reporte de salida.

Las alertas se crean llamando a la función addWorkOrderAlert del objeto del programa (ver ejemplo de más abajo) y se muestran en el reporte de carga

Ejemplo
ts
const profile: Profile = {
    row2WorkOrder: (headers: sring[]) => row(string[]) => { 
      const wo = new WorkOrder({
        status: WorkOrderStatus.UNKNOWN,
        code: row[headedr.indexOf('OT')],
        description: row[headedr.indexOf('Descripción')],
        creationDate: parseDate(row[headedr.indexOf('Creación')]),
      })

      wo.classes = {
        type: row[header.indexOf('Tipo')],
        priority: row[header.indexOf('Prioridad')],
        workClass: row[header.indexOf('Especialidad')],
      }

      // ... asignar otras propiedades de la OT

      // Alerta para OTs muy antiguas
      if (differenceInCalendarDays(schedulingProcess.value.fromDate, wo.creationDate) > 90) {
        schedulingProcess.value?.addWorkOrderAlert(
            'ot-muy-vieja',
            wo,
            'OT tiene más de 90 días de antigüedad',
        )
      }

      return wo
    }
}

row2LaborData

Función que define cómo convertir filas de CSV o XLSX a cambio de estado y asignación de HH de OTs.

Esta función se llama al cargar una base OTs que incluya información de HHs ejecutadas y genera objetos de tipo AssignedResource

Ejemplo
ts
const profile: Profile = {
    row2LaborData: (headers: sring[]) => row(string[]) => { 
      if (!header.includes('HH'))
        return null

        return {
          workOrderCode: row[header.indexOf('N° Orden')],
          labor: +row[header.indexOf('HH')],
          supervisorId: `${row[header.indexOf('Supervisor')]}`,
          date: parseDate(row[header.indexOf('F Inicio Real')]),
          workClass: row[header.indexOf('Especialidad')],
        }
    }
}

row2Asset

Función que define cómo convertir filas de CSV o XLSX a un activo

Esta función se llama al cargar una base de activos en el módulo de activos.

Ejemplo
ts
const profile: Profile = {
    row2Asset: (headers: string[]) => (row: string[]): Asset | null => { 
      if (!row[headers.indexOf('Ubicación')])
        return null

      return ({
        id: row[headers.indexOf('Activo')],
        code: row[headers.indexOf('Tag')],
        name: row[headers.indexOf('Descripción')],
        criticallity: row[headers.indexOf('Criticidad')],
        location: {
          code: row[headers.indexOf('Ubicación')],
          name: row[headers.indexOf('Rótulo del activo')],
          site: row[headers.indexOf('Planta')],
        },
        stats: {
          mtbf: +row[headers.indexOf('MTBF')]
        },
      })
    },
}

row2Supervisor

Función que define cómo convertir filas de CSV o XLSX a un supervisor

Esta función se llama al cargar una base de activos en el módulo de supervisores.

Ejemplo
ts
const profile: Profile = {
    row2Supervisor: (header: string[]) => (row: string[]): Supervisor | null => { 
      return ({
        id: row[header.indexOf('ID')],
        name: row[header.indexOf('Nombre')],
        classes: {
          workClass: row[header.indexOf('Especialidad')],
          shiftName: row[header.indexOf('Turno')],
          site: row[header.indexOf('Planta')].split(',').map(s => s.trim()),
        },
      })
    },
}
}

Priorización de OT

validateReadyBacklog

Esta función tiene 3 usos: generar alertas globales de la base de OTs, construir la versión final del ready backlog y construir el reporte de carga de archivos.

Las alertas se generan de la misma forma que en la función row2WorkOrder, llamando a schedulingProcess.addWorkOrderAlert.

Para generar el reporte, la función tiene que construir un objeto de este tipo

ts
{
  report: {
    "Alertas": [
      { workOrder: wo1, message: "Alerta 1" },
      { workOrder: wo2, message: "Alerta 2" },
    ],
    "Errores": [
      { workOrder: wo1, message: "Error 1" },
      { workOrder: wo2, message: "Error 2" },
      { workOrder: wo2, message: "Error 1" },
  ],
}
Ejemplo
ts
const profile: Profile = {
    validateReadyBacklog: async (schedulingProcess: SchedulingProcess, workOrders: WorkOrder[]) => { 
      const errors = [
        'sin-hh',
        'sin-prioridad',
      ]

      const alertsForWorkCode = schedulingProcess.alerts
        .reduce(
          (acc, alert) => (
            {
              ...acc,
              [alert.workOrder.code]: (acc[alert.workOrder.code] || new Set()).add(alert.type),
            }),
          {} as Record<string, Set<string>>,
        )

      // No considerar OTs con más de 2 alertas
      const readyBacklog = ...workOrders.filter(
        wo => alertsForWorkCode[wo.code].length > 2 
      )


      return {
        workOrders: readyBacklog,
        // Reporte será un excel con solo una hoja con nombre "Alertas"
        report: {
          'Alertas': 
                workOrders
                .map(workOrder => ({
                    workOrder,
                    message: alertsForWorkCode(workOrder.code).join(", "),
                }))
        },
      }
    },
}

readyBacklogStats

Estadísticas del ready backlog a mostrar después de cargar una base de OTs

Ejemplo
ts
const profile: Profile = {
    readyBacklogStats: (schedulingProcess: SchedulingProcess) => { 
      return [
        {
          name: 'HH total',
          value: (~~schedulingProcess.workOrders.map(wo => wo.requiredLabor).reduce(ssum, 0)).toLocaleString('es'),
        },
        {
          name: '# Especialidades',
          value: (new Set(schedulingProcess.workOrders.map(wo => wo.classes.workClass))).size.toLocaleString('es'),
        },
      ]
    },
}

workOrderFileStats

Estadísticas sobre el archivo completo de órdenes de trabajo.

Ejemplo
ts
const profile: Profile = {
    workOrderFileStats: (schedulingProcess: SchedulingProcess) => { 
      const countUniqueWorkOrderWithType = (type: string, filterOutWaitApproval: boolean = true) =>
        schedulingProcess
          ? (new Set(
              schedulingProcess.alerts
                .filter(a => !filterOutWaitApproval || a.workOrder.status !== WorkOrderStatus.WAIT_APPROVAL)
                .filter(a => a.type === type)
                .map(a => a.workOrder.code),
            )).size
          : 0

      return [
        {
          name: '#OT sin HH',
          value: 
            (new Set(
              schedulingProcess.alerts
                .filter(a => a.type === 'ot-sin-hh')
                .map(a => a.workOrder.code),
             )).size.toLocaleString('es'),
          help: 'Cantidad de OTs sin información de HH. No considera OTs en estado ESPAPROB',
        },
        {
          name: '#OT sin prioridad',
          value: 
            (new Set(
              schedulingProcess.alerts
                .filter(a => a.type === 'ot-sin-prioridad')
                .map(a => a.workOrder.code),
             )).size.toLocaleString('es'),
          help: 'Cantidad de OTs sin prioridad asignada. No considera OTs en estado ESPAPROB',
        },
      ]
    },
}

workOrderSelectionRules

Reglas de ordenación para la selección automática de OTs. Cada elemento de la lista tiene un nombre (para mostrar en la configuración de programación que permite deshabilitarlos) y una función que define una relación de orden sobre las OT.

La lista de OTs disponibles se van a orden aplicando cada función por cada regla habilitada de abajo hacia arriba. De forma que la primera regla de la lista es la más relevante, ya que es la última que se aplica antes de asignar las OTs al programa.

Ejemplo
ts
const profile: Profile = {
  workOrderSelectionRules: (schedulingProcess: SchedulingProcess) => [ 
    // Prioridad de mayor a menor
    {
      name: 'Prioridad de OT',
      sort: (wo1: WorkOrder, wo2: WorkOrder) =>
        +(wo1.classes?.priority) - +(wo2.classes.priority),
    },
    {
      name: 'Criticidad activo',
      sort: (wo1: WorkOrder, wo2: WorkOrder) => {
        const map = {
          'ALTA': 4,
          'MEDIA': 3,
          'BAJA': 2,
          'CERO': 1,
        } as Record<string, number>
        return (map[wo2.asset?.criticallity] || 0) - (map[wo1.asset?.criticallity|| 0)
      },
    },
    {
      name: 'MTBF activo',
      sort: (wo1: WorkOrder, wo2: WorkOrder) =>
        +(wo1.asset?.stats?.mtbf || Infinity) - +(wo2.asset?.stats?.mtbf || Infinity),
    },
    {
      name: 'Vencimiento OT',
      sort: (wo1: WorkOrder, wo2: WorkOrder) => {
        const daysDue = (wo: WorkOrder): number => wo.classes?.type
          ? differenceInCalendarDays(
            addDays(
              wo1.creationDate,
              config.dueDays[wo.classes.type],
            ),
            schedulingProcess.fromDate,
          )
          : Infinity

        return daysDue(wo1) - daysDue(wo2)
      },
    },
  ],
}

automaticSchedulingWorkOrderFilter

Filtro de las OTs que pueden seleccionarse automáticamente.

Ejemplo
ts
automaticSchedulingWorkOrderFilter: (workOrder: WorkOrder) => {
  return workOrder.classes.priority !== 'PARADA_PLANTA'
}

Traducciones

translations

Mensajes de ayuda para atributos específicos, como estadísticas de ready backlog.