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.
Interfaz anotada
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
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
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ámetro | Tipo | Descripción |
|---|---|---|
dueDays | {[string]: number} | Días de vencimiento por tipo |
shiftDays | number | Duración del turno en días |
shiftHours | number | Duració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
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ámetro | Tipo | Descripción |
|---|---|---|
name | string | Nombre de la categoría |
description | string | Descripción de la categoría. Se muestra en un botón de ayuda. |
filter | (SchedulingProcess, WorkOrder, Config) => bool | Filtro de OT para la categoría. Si la función retorna true la OT pertenece a la categoría. |
Ejemplo
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ámetro | Tipo | Descripción |
|---|---|---|
key | string | Identificador de la categoría |
name | string | Nombre de la categoría |
description | string | Descripción de la categoría. Se muestra en un botón de ayuda. |
filter | (WorkOrder) => bool | Filtro de OT para la categoría. Si la función retorna true la OT pertenece a la categoría. |
Ejemplo
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
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:
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
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
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
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
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
{
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
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
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
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
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
automaticSchedulingWorkOrderFilter: (workOrder: WorkOrder) => {
return workOrder.classes.priority !== 'PARADA_PLANTA'
}Traducciones
translations
Mensajes de ayuda para atributos específicos, como estadísticas de ready backlog.