Ir al contenido

Complaint management

El flujo de trabajo Complaint Management automatiza el ciclo de vida completo de una reclamación de cliente: desde la recepción, pasando por la clasificación y la asignación manual, la investigación (con un bucle opcional de solicitud de información de vuelta al reclamante), hasta la resolución y la comunicación final. A diferencia de los flujos de trabajo que se inician sobre un documento existente de OpenKM, este se inicia desde un formulario de recepción previo al inicio (run_config) y crea su propio expediente de caso en el repositorio como primer paso.

Download: complaint-management.okmflow

Caminos de ejecución admitidos:

  • Resolución directa: la reclamación se clasifica, se asigna, se investiga y se resuelve sin necesitar más aportaciones del reclamante.
  • Bucle de información adicional: durante la investigación, el asignado solicita más información al reclamante por correo. El caso entra entonces en bucle sobre Register Received Information: o bien vuelve a Investigation para continuar, o bien se cierra sin resolución.
  • Cerrado — sin respuesta: si el reclamante nunca responde a la solicitud de información, el caso se cierra a través de una rama terminal independiente, distinguible en los metadatos (closed_no_response) de una resolución normal.

Antes de desplegar este flujo de trabajo, lo siguiente debe existir en OpenKM.

Se usa una tabla de contador personalizada para generar referencias de caso secuenciales. Es genérica y puede compartirse con otros flujos de trabajo (por ejemplo, Purchase Order, Expense Report Approval) mediante distintos valores de counter_key; cree la tabla una sola vez por entorno:

CREATE TABLE IF NOT EXISTS APP_COUNTER (
counter_key VARCHAR(50) NOT NULL,
counter_value INT NOT NULL DEFAULT 1,
CONSTRAINT pk_app_counter PRIMARY KEY (counter_key)
);
INSERT INTO APP_COUNTER (counter_key, counter_value)
VALUES ('complaint', 1);
Identificador Tipo Usado en Descripción
ROLE_CLAIMS_TRIAGE Rol Classification Pool de agentes de triaje que clasifican cada reclamación entrante. Cualquier miembro puede autoasignarse la tarea. Debe haber al menos un usuario asignado.
ROLE_CLAIMS_COORDINATOR Rol Assignment Pool de coordinadores que despachan manualmente cada caso clasificado a un asignado concreto. Debe haber al menos un usuario asignado.
(elegido en Assignment) Investigation, Register Received Information, Resolution Quien se elija en el campo Assignee de la tarea Assignment (cualquier usuario de OpenKM, mediante el plugin OptionSelectUserList). Todas las tareas posteriores de la cadena de investigación/resolución se reasignan a la misma persona mediante una búsqueda en el historial de tareas, no se vuelven a seleccionar.

El grupo de propiedades okg:complaint debe estar registrado en OpenKM antes de la primera ejecución. El propio flujo de trabajo lo aplica automáticamente al expediente del caso; el usuario nunca lo rellena manualmente (aparte del formulario inicial run_config, que no es un formulario de metadatos).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE property-groups PUBLIC "-//OpenKM//DTD Property Groups 3.12//EN"
"http://www.openkm.com/dtd/property-groups-3.12.dtd">
<property-groups>
<property-group label="Complaint" name="okg:complaint" readonly="true">
<input label="Claimant name" name="okp:complaint.contact_name">
<validator type="req"/>
</input>
<input label="Contact email" name="okp:complaint.contact_email">
<validator type="req"/>
<validator type="email"/>
</input>
<input label="Contact phone" name="okp:complaint.contact_phone" />
<textarea label="Complaint description" name="okp:complaint.claim_description" dbColumnSize="2048">
<validator type="req"/>
</textarea>
<input label="Case number" name="okp:complaint.case_number" />
<select label="Complaint type" name="okp:complaint.claim_type" type="simple">
<option label="Product" value="product"/>
<option label="Service" value="service"/>
<option label="Billing" value="billing"/>
<option label="Customer service" value="customer_service"/>
<option label="Delivery / lead time" value="delivery"/>
<option label="Other" value="other"/>
</select>
<select label="Priority" name="okp:complaint.priority" type="simple">
<option label="Low" value="low"/>
<option label="Medium" value="medium"/>
<option label="High" value="high"/>
</select>
<select label="Department" name="okp:complaint.department" type="simple">
<option label="Customer Care" value="customer_care"/>
<option label="Quality" value="quality"/>
<option label="Operations" value="operations"/>
<option label="Billing" value="billing"/>
<option label="Legal" value="legal"/>
<option label="Other" value="other"/>
</select>
<input label="Assigned to" name="okp:complaint.assigned_to" />
<textarea label="Investigation findings" name="okp:complaint.investigation_findings" dbColumnSize="2048" />
<select label="Resolution type" name="okp:complaint.resolution_type" type="simple">
<option label="Accepted" value="accepted"/>
<option label="Rejected" value="rejected"/>
<option label="Partially accepted" value="partial"/>
</select>
<input label="Closed date" name="okp:complaint.closed_date" type="date" />
<select label="Status" name="okp:complaint.status" type="simple">
<option label="New" value="new"/>
<option label="Assigned" value="assigned"/>
<option label="Under investigation" value="under_investigation"/>
<option label="Pending additional information" value="pending_info"/>
<option label="Pending resolution" value="pending_resolution"/>
<option label="Resolved" value="resolved"/>
<option label="Closed" value="closed"/>
<option label="Closed - no response" value="closed_no_response"/>
</select>
</property-group>
</property-groups>

El grupo es readonly="true"; todas las escrituras se realizan de forma programática desde nodos Action del flujo de trabajo.

La acción Generate Claim Reference and Initial Data (el primer nodo tras Start) construye la siguiente estructura en el repositorio de OpenKM cada vez que se inicia una nueva instancia del flujo de trabajo:

/okm:root/
--- Complaints/
--- {year}/ - Folder (type: folder)
--- CLM-{year}-{NNN}/ - Record (type: record) - the case itself
--- resolution/ - Folder (type: folder) - for the optional resolution document

Formato de la referencia: CLM-{year}-{NNN}, donde NNN es un número secuencial de 3 dígitos con ceros a la izquierda (por ejemplo, CLM-2026-001). El contador se lee de APP_COUNTER.counter_key = 'complaint', el número de caso se construye a partir del valor leído, y el contador se incrementa y se guarda de nuevo inmediatamente después.

La instancia del proceso de flujo de trabajo se vincula a este expediente mediante WorkflowUtils.setProcessInstanceNode(context, recordUuid), de modo que el flujo de trabajo es accesible directamente desde el explorador de documentos.

Se muestra al usuario antes de que se cree la instancia del proceso. No está conectado al resto del diagrama y no tiene transiciones salientes; el motor lo presenta automáticamente al iniciarse el flujo de trabajo.

Campo Tipo Obligatorio Notas
Claimant name Input
Contact email Input Se valida el formato de correo.
Contact phone Input No
Complaint description Textarea

Asignación: assignExpr es la expresión literal "";; el formulario previo al inicio se muestra siempre a quien inicia el proceso; no se enruta a través del mecanismo normal de asignación por rol/usuario.

Nodo Start estándar. Este flujo de trabajo se inicia sin un nodo de OpenKM asociado; no hay ningún expediente detrás del proceso hasta que el siguiente nodo Action crea uno. Transición sin nombre → Generate Claim Reference and Initial Data.

Generate Claim Reference and Initial Data — Action

Sección titulada «Generate Claim Reference and Initial Data — Action»

Se ejecuta inmediatamente después de Start. Realiza toda la inicialización del repositorio para el nuevo caso:

  1. Lee el valor actual del contador de APP_COUNTER (counter_key = 'complaint') con una consulta SQL en bruto (ws.repository.executeSqlQuery), construye el número de caso como CLM-{year}-{NNN} a partir de ese valor, y a continuación actualiza la fila del contador al siguiente valor.
  2. Lee los valores enviados en run_config (contactName, contactEmail, contactPhone, claimDescription) mediante WorkflowUtils.getFormElementValue(context, ...).
  3. Crea la carpeta del año /okm:root/Complaints/{year} si no existe ya (ws.folder.createMissingFolders), y a continuación crea el expediente del caso en su interior (ws.record.create), protegiendo ambos pasos con comprobaciones ws.repository.hasNode.
  4. Vincula la instancia del proceso al expediente mediante WorkflowUtils.setProcessInstanceNode(context, recordUuid).
  5. Escribe el grupo de metadatos okg:complaint con los datos de recepción, el número de caso y status = new, eligiendo addGroup o setProperties según si el grupo ya existe en el expediente (ws.propertyGroup.hasGroup).
  6. Almacena variables de contexto de tipo cadena simple (caseNumber, contactName, contactEmail, contactPhone, claimDescription) para su uso posterior en plantillas Mail y scripts de correo en texto plano, además de objetos tipados Input/TextArea (caseNumberData, contactNameData, contactEmailData, claimDescriptionData) para que los campos de formulario de solo lectura posteriores puedan precargarse mediante data="...".
  7. Crea una subcarpeta resolution dentro del expediente y almacena un descriptor Upload (resolutionUploadParams) que apunta a ella, para su uso posterior en la tarea Resolution.
Campo Valor
Recipients ${contactEmail}
Subject We have received your complaint - Case ${caseNumber}
Body Confirma la recepción e indica el número de caso (${caseNumber}), y va dirigido a ${contactName}.

Asignación: pool de usuarios de ROLE_CLAIMS_TRIAGE.

El formulario muestra el número de caso, el nombre del reclamante, el correo de contacto y la descripción de la reclamación en modo de solo lectura, y a continuación recopila:

Campo Tipo Obligatorio Notas
Complaint type Select Product, Service, Billing, Customer service, Delivery / lead time, Other.
Priority Select Low, Medium (por defecto), High.
Department Select Customer Care, Quality, Operations, Billing, Legal, Other.
Classification comments Área de texto No Notas de texto libre del agente de triaje; se vuelven a mostrar en solo lectura en Assignment. No se persisten en los metadatos.
Botón Transición Descripción
Classify and continue classify Continúa a Assignment.

Asignación: pool de usuarios de ROLE_CLAIMS_COORDINATOR.

El formulario muestra el número de caso, la descripción de la reclamación y los datos de clasificación (tipo, prioridad, departamento, comentarios de clasificación) en modo de solo lectura, y a continuación recopila:

Campo Tipo Obligatorio Notas
Assignee Select Plugin OptionSelectUserList; permite al coordinador elegir cualquier usuario de OpenKM. El Id elegido se convierte en assigneeId y determina todas las asignaciones de tareas posteriores.
Assignment comments Área de texto No Notas de texto libre del coordinador. No se persisten en los metadatos.
Botón Transición Descripción
Assign assign Continúa a Save Classification and Assignment.

Save Classification and Assignment — Action

Sección titulada «Save Classification and Assignment — Action»

Se ejecuta inmediatamente después de Assignment. Lee claimType, priority, department y assignee (todos objetos Select) del contexto, almacena assignee.getValue() como assigneeId en el contexto, y escribe claim_type, priority, department, assigned_to y status = assigned en okg:complaint.

Lógica de asignación (reasignación de pool a directa mediante historial de tareas):

1. Query task history: WorkflowUtils.getTaskInstances(piId, "Investigation")
2. If a previous instance with an assigned actor exists → return that actor (String, direct assignment)
(ensures the same investigator handles re-entry after the information-request loop)
3. If first time → return assigneeId (the person chosen in "Assignment")

El formulario muestra el número de caso, la descripción de la reclamación y cualquier información adicional recibida previamente en modo de solo lectura, y a continuación recopila:

Campo Tipo Obligatorio Notas
Investigation notes / findings Área de texto Se vuelve a mostrar en solo lectura en todas las tareas posteriores de esta rama.
Botón Transición Descripción
Investigation complete - move to resolution resolved Continúa a Save Investigation FindingsResolution.
Missing information from the claimant missing_info Continúa a Request Additional Information.

Se ejecuta cuando Investigation se marca como necesitada de más información. Envía un correo en texto plano (ws.mail.sendMail, no un nodo Mail) a contactEmail, incluyendo los hallazgos del investigador hasta el momento, si los hay, pidiendo al reclamante que responda con los detalles que faltan. La dirección de envío está codificada directamente (noreply@nomail.com, marcada con un comentario TODO para sustituirla por la real). Enruta a Register Received Information.

Asignación: el mismo patrón de búsqueda en el historial de tareas que Investigation (consulta las instancias de tarea de "Investigation"); se reasigna a quien esté investigando el caso, y recae en assigneeId si no se encuentra ningún actor previo.

El formulario muestra el número de caso y las notas de investigación previas en modo de solo lectura, y a continuación recopila:

Campo Tipo Obligatorio Notas
Information received from the claimant Área de texto Registrada por el investigador tras recibir una respuesta por correo, teléfono, etc.
Botón Transición Descripción
Continue investigation continue_investigation Continúa de vuelta a Investigation (bucle).
Close - no response close_no_response Pide confirmación y a continuación continúa a Close Claim - No Response → End “Closed - No Response”. Terminal.

Se ejecuta cuando Investigation se marca como completa (resolved). Persiste investigationFindings en okp:complaint.investigation_findings y establece status = pending_resolution, y a continuación enruta a Resolution.

Asignación: el mismo patrón de búsqueda en el historial de tareas, apuntando a "Investigation"; la persona que investigó el caso lo resuelve directamente; no hay un paso de aprobación independiente.

El formulario muestra el número de caso y las notas de investigación en modo de solo lectura, y a continuación recopila:

Campo Tipo Obligatorio Notas
Resolution type Select Accepted, Rejected, Partially accepted.
Resolution text (will be sent to the claimant) Área de texto Se usa literalmente en el cuerpo del correo final.
Resolution document (optional) Upload No Extensiones permitidas: pdf, doc, docx. Se sube a la subcarpeta resolution creada al iniciar el caso, mediante el descriptor resolutionUploadParams.
Botón Transición Descripción
Finish and notify the claimant communicate Continúa a Communicate Resolution to Claimant.

Communicate Resolution to Claimant — Action

Sección titulada «Communicate Resolution to Claimant — Action»

Lee resolutionType y resolutionText del contexto. Lista el contenido de la subcarpeta resolution (ws.document.getChildren) para comprobar si se subió un documento en la tarea anterior. Si no se subió ninguno, envía un correo en texto plano (ws.mail.sendMail); si se subió uno, envía el mismo correo con el documento adjunto mediante ws.mail.sendMailWithAttachments. La dirección de envío también está codificada directamente aquí (noreply@nomail.com, el mismo TODO que en Request Additional Information). Enruta a Close Claim.

Lee resolutionType del contexto (todavía disponible desde la tarea Resolution; nada lo sobrescribe entre medias) y escribe okp:complaint.resolution_type, status = closed y closed_date (marca de tiempo actual, formato en bruto yyyyMMddHHmmss) en okg:complaint. Enruta a End “Closed”.

Se alcanza únicamente desde la rama close_no_response de Register Received Information. Escribe status = closed_no_response y closed_date en okg:complaint. Enruta a End “Closed - No Response”.

Dos nodos End con nombre que representan los dos resultados finales. Usar nombres distintos permite que los sistemas externos o los informes de auditoría distingan un caso resuelto de uno cerrado por falta de respuesta.

El campo okp:complaint.status se actualiza automáticamente en cada transición de etapa. El ciclo de vida completo es:

new → assigned → pending_resolution → closed (direct path)
assigned → assigned (information-request loop, repeatable)
assigned → closed_no_response (terminal, via the information-request loop)

El estado no cambia mientras se repite el bucle de solicitud de información; solo avanza a pending_resolution una vez que Investigation se marca finalmente como “resolved”.

Patrón Dónde se aplica
Tabla de contador para la numeración de referencias APP_COUNTER con counter_key = 'complaint'. Tabla compartida, reutilizable entre flujos de trabajo.
Formulario de recepción previo al inicio (run_config) que alimenta la creación del expediente run_config recopila los datos del reclamante antes de que se inicie el proceso; Generate Claim Reference and Initial Data los lee directamente mediante WorkflowUtils.getFormElementValue y crea el expediente, sin una tarea intermedia de “confirme su envío”.
El propio assignExpr de run_config es una cadena vacía literal "";; el formulario previo al inicio se muestra a quien inicia el proceso, sin enrutarse a través de la lógica de asignación por rol/usuario.
Variables de contexto duales, cadena simple / objeto tipado, a partir de los mismos datos de origen Generate Claim Reference and Initial Data almacena los datos de recepción dos veces: como Strings simples (contactEmail, caseNumber, …) para su uso directo con ${...} en nodos Mail y scripts de correo en texto plano, y como objetos tipados Input/TextArea con sufijo *Data (contactEmailData, caseNumberData, …) para precargar campos de formulario de tarea de solo lectura mediante data="...".
Reasignación directa desde pool mediante historial de tareas WorkflowUtils.getTaskInstances(piId, "Investigation"): lo usan la propia Investigation (su propio bucle de reentrada), Register Received Information y Resolution, de modo que el mismo investigador gestiona todo el caso tras la asignación inicial.
Metadatos escritos por nodos Action, no por usuarios okg:complaint es readonly="true". Todas las escrituras usan ws.propertyGroup.setProperties(), o addGroup() protegido por una comprobación hasGroup() en la primera escritura.
Flujo de trabajo vinculado a un expediente del repositorio creado a mitad del flujo WorkflowUtils.setProcessInstanceNode(context, recordUuid) en Generate Claim Reference and Initial Data: el proceso no tiene ningún nodo asociado en Start; lo obtiene en cuanto se crea el expediente.
Adjunto en un correo de notificación final El tipo de nodo Mail nativo no admite adjuntos. Communicate Resolution to Claimant es un nodo Action que llama de forma condicional a ws.mail.sendMail o ws.mail.sendMailWithAttachments según si se subió un documento; Request Additional Information usa el mismo enfoque de correo en texto plano (sin adjuntos) por el mismo motivo; solo Acknowledgement of Receipt es un nodo Mail nativo.
Subcarpeta dedicada para una subida opcional La subcarpeta resolution (creada una vez, al iniciar el caso) evita mezclar el documento de resolución opcional con cualquier otro contenido que pueda añadirse más adelante al expediente del caso.
Metadatos mantenidos sincronizados en cada transición importante, no solo al final Save Classification and Assignment y Save Investigation Findings persisten cada uno los datos de su etapa inmediatamente, en lugar de esperar a Close Claim para escribirlo todo de una vez; esto mantiene los metadatos del expediente consultables y actualizados incluso a mitad del proceso.