01 · Resumen ejecutivo
FARO debe gobernar la resolución, no solo detectar
Este documento es el constitutional document del workflow MVP. Define los enums maestros, las transiciones permitidas, los SLA, las reglas de escalamiento automático y la auditoría. Si un módulo del MVP usa un estado o una transición que no está acá, ese módulo está fuera de contrato.
FARO no es un tablero que muestra problemas. FARO es un sistema que gobierna la resolución de problemas. La diferencia entre alerta y acción es exactamente este documento: estados, plazos, escalamiento, evidencia y consecuencia.
El catálogo canónico de tensiones (catalogo-tensiones-mvp.html) define qué se detecta. El motor evaluador (motor-evaluador-mvp.html) define cómo se evalúa. Este workflow define qué pasa después: quién actúa, con qué plazo, con qué evidencia, qué pasa si no cumple y a quién se escala.
Sin workflow, FARO se convierte en lo que más debe evitar: un tablero elegante mirando cómo se incendia la cocina. Detectar sin gobernar es periodismo, no consultoría.
Tres decisiones aplicadas en este documento son fuente de verdad para el resto del pack:
- Decisión D3: auditoría se centraliza en una sola tabla
execution_events con 40 event_types tipados. La vista action_events queda como filtro sobre esa tabla, no como tabla separada.
- Decisión D4:
escalated es evento, no estado del enum. Una acción puede estar blocked y al mismo tiempo haber sido escalada. Forzar escalated como estado contamina el enum y rompe la trazabilidad.
- Decisión empresa: el seed demo opera contra Empresa Demo Cuyo S.A.. Todos los
company_id de ejemplo apuntan a esa empresa.
El contrato es duro a propósito: si un módulo del MVP introduce un estado nuevo, una transición no listada, un escalamiento fuera de matriz o un cierre sin evidencia, debe ser rechazado en code review. La disciplina del workflow se sostiene por la disciplina del documento que la define.
02 · Tesis del workflow
El workflow es la diferencia entre alerta y acción
Una empresa no se ordena con diagnósticos. Se ordena con responsables, plazos, evidencia, escalamiento y consecuencias. El workflow es el mecanismo que transforma inteligencia en disciplina.
Principio rector
La tensión no termina cuando se detecta. La acción no termina cuando se marca. El problema no termina cuando se conversa. Termina cuando hay evidencia, validación y resultado.
FARO debe responder con precisión: qué se detectó · quién debe actuar · antes de cuándo · con qué evidencia · quién aprueba · qué pasa si no cumple · a quién se escala · qué impacto tiene en Score · qué queda registrado. Si alguna de estas nueve preguntas queda sin respuesta, el workflow tiene un agujero por donde se escapa la dirección.
FARO debe evitar tres vicios empresariales clásicos. Lo hace con tres mecanismos del workflow:
Vicio 01 · "Lo estamos viendo"
Sin responsable, no es tensión: es queja
Toda tensión exige responsible_user_id. Si no se encuentra, FARO crea la acción igual con responsible_user_id = null, dispara evento responsible_missing y escala automáticamente a Gerencia General. La falta de responsable también es una tensión.
Vicio 02 · "Ya está hablado"
Sin evidencia, no hay cierre
Toda acción exige evidence_required. El cierre se valida contra evidencia approved. Cierre excepcional con EVD-009 queda marcado como auditable y reduce el aporte al Score.
Vicio 03 · "Lo vemos la semana que viene"
Sin escalamiento, los temas se eternizan
Toda acción tiene SLA. Vencida, el job OPS-001 workflow-checker dispara action_expired + escalamiento automático según severidad. No depende de que alguien se acuerde de revisar.
Lo que está fuera del MVP. Este workflow define lo mínimo necesario para que FARO Connect pase de diagnóstico a ejecución gobernada. Quedan fuera del MVP: motor BPMN drag&drop, aprobaciones múltiples complejas, firma digital, WhatsApp productivo, IA autónoma cerrando acciones, reasignación automática avanzada. Cada uno es un proyecto serio en sí mismo; meterlos al MVP es perder el MVP.
03 · Estados oficiales
Enums maestros que UI-001..005 deben respetar
Tres entidades (tensions, actions, evidence) más una transversal (execution_events). Cada entidad tiene un enum de status duro, controlado por CHECK constraint en SQL. Ningún módulo del MVP puede introducir un estado fuera de esta lista.
3.1 · 10 estados oficiales de tensión
Enum tensions.status. La tensión es el objeto raíz: si no entra a este enum, el motor evaluador no la persiste.
new
Detectada
Tensión recién creada por el motor evaluador. Aún no fue tomada por nadie.
Owner: sistema / gerencia
in_review
En revisión
Asignada y bajo análisis por el responsable. Aún sin acciones creadas.
Owner: responsable
in_execution
En ejecución
Tiene una o más acciones activas en progreso. Estado dominante durante el ciclo.
Owner: responsable
blocked
Bloqueada
No puede avanzar por dependencia externa, política o aprobación. Requiere razón documentada.
Owner: responsable / gerente
monitoring
En monitoreo
Acciones ejecutadas; FARO sigue el KPI subyacente para confirmar recuperación sostenida.
Owner: aprobador
closed
Cerrada
Resuelta con evidencia aprobada. Estado terminal positivo. Habilita recuperación de Score.
Owner: gerente / dirección
rejected
Rechazada
Descartada con justificación (falso positivo, fuera de scope, etc.). Estado terminal con auditoría.
Owner: gerente / dirección
expired
Vencida
SLA superado sin cierre. Estado intermedio que dispara escalamiento y penaliza Score.
Owner: sistema
cancelled
Cancelada
Cancelada explícitamente por la dirección. Diferente de rejected: no era inválida, ya no aplica.
Owner: dirección
archived
Archivada
Estado terminal de housekeeping para tensiones antiguas sacadas de vistas operativas.
Owner: sistema
Decisión D4 aplicada. escalated NO está en el enum de tensión. Una tensión puede estar in_execution o blocked y al mismo tiempo tener uno o más eventos tension_escalated en su timeline. El estado dice "dónde está el ticket"; el evento dice "qué le pasó". Mezclarlos rompe la auditoría.
3.2 · 12 estados oficiales de acción
Enum actions.status. La acción es la unidad de trabajo. UI-003 (Detalle de Acción) consume este enum directamente para construir el componente WorkflowStatusBadge.
new
Nueva
Acción creada por el motor o asignada manualmente. Aún no iniciada.
Owner: responsable
in_progress
En progreso
El responsable inició la ejecución. Aún no cargó evidencia.
Owner: responsable
waiting_evidence
Esperando evidencia
Acción ejecutada pero la evidencia requerida aún no fue cargada o está incompleta.
Owner: responsable
in_review
En revisión
Evidencia cargada, pendiente de aprobación por el aprobador asignado.
Owner: aprobador
approved
Aprobada
Evidencia aprobada. Lista para cierre formal por gerente o dirección.
Owner: gerente
closed
Cerrada
Cerrada formalmente con evidencia validada. Estado terminal positivo.
Owner: gerente / dirección
blocked
Bloqueada
Dependencia externa, política o aprobación faltante. Requiere blocked_reason + blocked_category.
Owner: responsable
rejected
Rechazada
Acción rechazada justificadamente (falso positivo, fuera de scope, etc.). Estado terminal con auditoría.
Owner: aprobador
expired
Vencida
SLA superado sin cierre. Dispara escalamiento automático según severidad y penaliza Score.
Owner: sistema
cancelled
Cancelada
Cancelada por decisión explícita de dirección. Cierra sin recuperación de Score.
Owner: dirección
reopened
Reabierta
Acción reabierta tras detectar evidencia débil, recurrencia o KPI sin mejora sostenida.
Owner: gerencia
archived
Archivada
Estado terminal de housekeeping para acciones antiguas sacadas de vistas operativas.
Owner: sistema
3.3 · 5 estados oficiales de evidencia
Enum evidence.status. La evidencia es lo que cierra la cadena. UI-004 (Carga de Evidencia) consume este enum para construir el estado de cada archivo cargado.
submitted
Enviada
Evidencia cargada por el responsable. Pendiente de revisión por el aprobador.
Owner: aprobador
approved
Aprobada
Evidencia aprobada formalmente. Habilita el cierre de la acción asociada.
Owner: aprobador
rejected
Rechazada
Evidencia rechazada con motivo documentado. Dos rechazos disparan escalamiento L3.
Owner: aprobador
needs_more_info
Requiere ampliación
El aprobador pide información adicional o complementaria. Vuelve al responsable sin contar como rechazo.
Owner: responsable
archived
Archivada
Evidencia obsoleta o superada por una nueva carga. No válida para cierre.
Owner: sistema
▸ SQL · CHECK constraints (DDL)
-- tensions.status (10 valores)
CHECK (status IN (
'new', 'in_review', 'in_execution', 'blocked', 'monitoring',
'closed', 'rejected', 'expired', 'cancelled', 'archived'
))
-- actions.status (12 valores)
CHECK (status IN (
'new', 'in_progress', 'waiting_evidence', 'in_review', 'approved',
'closed', 'blocked', 'rejected', 'expired', 'cancelled',
'reopened', 'archived'
))
-- evidence.status (5 valores)
CHECK (status IN (
'submitted', 'approved', 'rejected', 'needs_more_info', 'archived'
))
04 · Matriz de transiciones
Qué transiciones de estado están permitidas
No toda transición es válida. Una acción closed no puede volver directamente a in_progress; debe pasar por reopened. Estas tres matrices son el contrato duro. Cualquier UPDATE status que no figure como válido debe ser rechazado por la función faro.transition_action_status().
4.1 · Transiciones permitidas en tensión
Filas = estado origen. Columnas = estado destino. VAL = transición válida. — = transición prohibida.
Notas clave: archived es absorbente (no se sale). expired es excepcional: puede volver a casi todo porque la idea es destrabar, no penalizar la regularización. cancelled y rejected solo van a archived.
4.2 · Transiciones permitidas en acción
Misma convención. La diferencia clave: tras approved solo se puede ir a closed (cierre formal); tras closed solo se puede ir a reopened (reapertura controlada).
4.3 · Transiciones permitidas en evidencia
Más simple: una evidencia cargada solo puede ser aprobada, rechazada o pedida ampliar. Tras ampliación vuelve a submitted.
▸ SQL · función guardia de transición (action)
CREATE OR REPLACE FUNCTION faro.transition_action_status(
p_company_id uuid,
p_action_id uuid,
p_new_status text,
p_user_id uuid,
p_reason text DEFAULT NULL
)
RETURNS boolean AS $$
DECLARE
v_old_status text;
v_allowed boolean;
BEGIN
SELECT status INTO v_old_status
FROM faro.actions
WHERE company_id = p_company_id AND action_id = p_action_id
FOR UPDATE;
-- consulta tabla canónica faro.action_status_transitions
SELECT EXISTS (
SELECT 1 FROM faro.action_status_transitions
WHERE from_status = v_old_status AND to_status = p_new_status
) INTO v_allowed;
IF NOT v_allowed THEN
RAISE EXCEPTION 'INVALID_TRANSITION: % -> %', v_old_status, p_new_status;
END IF;
UPDATE faro.actions
SET status = p_new_status, updated_at = now()
WHERE company_id = p_company_id AND action_id = p_action_id;
PERFORM faro.log_execution_event(
p_company_id, 'action', p_action_id,
'action_status_changed', 'workflow',
'Transición de estado', p_reason,
p_user_id, 'user', NULL, p_action_id, NULL, NULL,
p_new_status, v_old_status, NULL, NULL, NULL,
'transition_function', NULL, '{}'::jsonb
);
RETURN true;
END;
$$ LANGUAGE plpgsql;
05 · Flujo operativo
18 pasos de detección a impacto en Score
El ciclo de vida feliz: cómo viaja una tensión desde que el motor evaluador la detecta hasta que el reporte semanal muestra el resultado validado. Cada paso está asociado a un actor (sistema, responsable, aprobador, gerente) y dispara uno o más eventos en execution_events.
Sistema · motor
Detección de tensión
El motor evaluador corre la regla YAML, dispara la tensión y persiste en faro.tensions con status = 'new'.
Sistema · motor
Enriquecimiento canónico
FARO consulta tension_definitions y asigna severidad, prioridad e impacto Score base.
Sistema · motor
Recomendación de acciones
FARO instancia las recommended_actions del catálogo canónico como filas en faro.actions.
Sistema · motor
Asignación de responsable y SLA
Se aplica default_owner_role y se calcula due_date según prioridad. Si no hay responsable, evento responsible_missing + escalamiento L3.
Responsable
Recepción de la acción
El responsable recibe notificación in-app + email. La acción aparece en su Dashboard (UI-002).
Responsable
Inicio de ejecución
El responsable marca in_progress desde UI-003. Evento action_started.
Responsable
Trabajo de campo
Se ejecuta la acción según closure_criteria. Puede pasar por blocked si hay dependencia.
Responsable
Carga de evidencia
El responsable carga cada EVD-* requerida desde UI-004. La acción pasa a waiting_evidence y luego a in_review.
Sistema · validador
Validación técnica
FARO valida formato, MIME, tamaño y completitud mínima. Evidencia inválida queda rejected automáticamente.
Aprobador
Revisión cualitativa
El aprobador asignado revisa la evidencia desde UI-004. Decide approved, rejected o needs_more_info.
Aprobador
Aprobación / rechazo
Si aprueba, la acción pasa a approved (lista para cierre). Si rechaza dos veces, dispara escalamiento L3.
Gerente
Cierre formal de la acción
El gerente cierra la acción desde UI-003. Evento action_closed.
Sistema · agregador
Agregación a nivel tensión
Si todas las acciones críticas de la tensión están closed, la tensión pasa a monitoring.
Sistema · score
Recalibración Score
El job OPS-001 score-recompute ajusta score_recovery y score_confidence según los eventos del workflow.
Sistema · KPI watcher
Verificación de KPI subyacente
FARO monitorea el KPI que disparó la tensión. Si mejora sostenidamente, tensión cierra. Si no, vuelve a in_execution o reopened.
Gerente / dirección
Cierre formal de la tensión
Tras el monitoreo, gerente o dirección cierra la tensión. Evento tension_closed.
Sistema · timeline
Registro consolidado
UI-005 (Timeline) consolida los 12-20 eventos generados por el ciclo en una vista auditable.
Sistema · reporte
Reporte semanal ejecutivo
El template FARO-TPL-002 incluye la tensión cerrada en el resumen semanal con su impacto Score recuperado.
Variantes del flujo feliz. Los 18 pasos describen el camino positivo. En la práctica, cada paso tiene ramas: bloqueo (paso 7), rechazo de evidencia (paso 11), escalamiento (cualquier paso si SLA vence). El workflow las gobierna con eventos paralelos sin romper el flujo principal.
06 · SLA por prioridad
Plazos base y por tipo de acción
El SLA se calcula desde action_definitions.default_sla_days y se ajusta por severidad. Estos son los plazos base que el job OPS-001 usa para detectar vencimientos.
6.1 · SLA base por prioridad de acción
Critical
2-3d
Acción crítica
Tensión crítica con impacto Score > 8. Vencida, escalamiento L3 inmediato a Gerencia General.
High
5-7d
Acción alta
Tensión high con impacto Score > 5. Vencida más de 2 días, escalamiento L2 a Gerente de área.
Medium
7-14d
Acción media
Tensión medium con impacto Score 3-5. Vencida más de 5 días, escalamiento L2.
Low
14-30d
Acción baja
Tensión low con impacto Score < 3. Vencida más de 10 días, escalamiento L1/L2.
6.2 · SLA por tipo de acción
Cada categoría de acción tiene su rango natural. ACT-OPS-* de escalamiento se mueve en horas; ACT-DIR-* de governance en una a dos semanas.
▸ SQL · cálculo de due_date
CREATE OR REPLACE FUNCTION faro.calculate_due_date(
p_priority text,
p_action_type text,
p_default_sla_days integer
)
RETURNS date AS $$
DECLARE
v_days integer;
BEGIN
v_days := CASE p_priority
WHEN 'critical' THEN 3
WHEN 'high' THEN 7
WHEN 'medium' THEN 14
WHEN 'low' THEN 30
ELSE COALESCE(p_default_sla_days, 7)
END;
RETURN CURRENT_DATE + (v_days || ' days')::interval;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
07 · Reglas de vencimiento
Cuándo una acción se considera vencida y cómo se mide
Una acción vence cuando su due_date es anterior a hoy y su status no es terminal. El cálculo de días en mora (overdue_days) es lo que alimenta la matriz de escalamiento.
▸ SQL · regla de vencimiento + overdue_days
-- Una acción está vencida si:
SELECT
action_code,
due_date,
status,
CASE
WHEN status IN ('closed', 'cancelled', 'rejected', 'archived') THEN false
WHEN due_date IS NULL THEN false
WHEN due_date < CURRENT_DATE THEN true
ELSE false
END AS is_overdue,
CASE
WHEN due_date < CURRENT_DATE
AND status NOT IN ('closed', 'cancelled', 'rejected', 'archived')
THEN (CURRENT_DATE - due_date)
ELSE 0
END AS overdue_days
FROM faro.actions
WHERE company_id = 'EMPRESA-DEMO-CUYO-SA'::uuid;
7.1 · Consecuencias del vencimiento
Cuando una acción vence, el job OPS-001 workflow-checker ejecuta cinco acciones en una sola transacción:
Consecuencia 01
Cambio de estado
El job ejecuta UPDATE actions SET status = 'expired' si el estado actual es activo (new, in_progress, waiting_evidence, in_review, blocked).
Consecuencia 02
Evento auditable
Se inserta action_expired en execution_events con category = 'deadline' y payload con due_date + overdue_days.
Consecuencia 03
Notificación dual
Notificación in-app + email a responsable y gerente de área. Si es critical, también a Gerencia General.
Consecuencia 04
Escalamiento automático
Según matriz (sección 8): critical → L3 inmediato; high → L2 a 24h; medium → L2 tras 5 días; low → L1/L2 tras 10 días.
Consecuencia 05
Penalización Score
Evento score_recovery_blocked en sección 10: la recuperación de Score queda en 0% hasta que la acción se destrabe y cierre con evidencia.
Excepción
Regularización con evento
Una acción expired puede volver a in_progress con evento action_regularized. La penalización Score se mantiene parcial hasta cierre con evidencia.
08 · Escalamiento automático
Niveles L0 a L5 · triggers · destinatarios · jobs
El escalamiento es lo que distingue a FARO de un tablero pasivo. Cuando un SLA se vence, una evidencia se rechaza dos veces, una acción crítica queda sin responsable, FARO no espera: ejecuta. Esta es la matriz completa que rige al job OPS-001.
Decisión D4 aplicada · crítica para integridad de auditoría. escalated NO es un estado. Es un evento que se registra en execution_events. Una acción puede tener status = blocked y al mismo tiempo dos eventos action_escalated a L2 y L3 en su timeline. El estado dice "dónde está parado el ticket"; los eventos dicen "qué le pasó en el camino". Forzar escalated como estado terminal o intermedio rompe la matriz de transiciones y oculta el estado real del trabajo.
8.1 · Niveles de escalamiento
Seis niveles, ordenados por autoridad. L0 es el sistema mismo; L5 son actores externos (legal, auditor, consultor) que el MVP soporta pero usa rara vez.
L0
Sistema FARO
Detección automática. No tiene destinatario humano: es el origen.
L1
Responsable
Usuario asignado a la acción. Ejecuta. La gran mayoría de acciones cierra en L1.
L2
Gerente de área
Responsable superior del responsable. Destraba bloqueos operativos y aprueba excepciones menores.
L3
Gerente General
Dirección operativa. Decide prioridades entre áreas y resuelve conflictos transversales.
L4
Directorio
Máxima decisión. Riesgo crítico de caja, margen, regulatorio o reputacional.
L5
Comité / externo
Legal, auditor, consultor externo. Casos especiales fuera del flujo operativo regular.
8.2 · Matriz de triggers automáticos
Cada fila es una regla del job OPS-001 workflow-checker. Si la condición se cumple, el job dispara escalamiento al nivel indicado, inserta evento en execution_events y notifica al destinatario.
8.3 · Jobs OPS-001 que ejecutan el escalamiento
El motor de workflow se compone de cinco jobs periódicos. Corren cada 15-60 minutos en MVP. Cada uno cubre una rama de la matriz.
OPS-001.1
checkExpiredActions
Detecta acciones con due_date < CURRENT_DATE y estado activo. Marca expired + escalamiento según severidad.
Cada 30 min
OPS-001.2
checkBlockedActions
Detecta acciones con status = 'blocked' y blocked_at < now() - interval '48 hours'. Escala según severidad.
Cada 60 min
OPS-001.3
checkPendingEvidenceReview
Detecta evidencias submitted > 48h sin revisión. Escala al aprobador superior (L2/L3).
Cada 60 min
OPS-001.4
checkCriticalTensionsWithoutOwner
Detecta tensiones críticas con responsible_user_id IS NULL > 60 min. Escala a L3 + crea acción ACT-OPS-001.
Cada 15 min
OPS-001.5
checkRepeatedEvidenceRejections
Detecta evidencias con >= 2 rechazos sobre la misma acción. Escala a L3 + notifica a Gerencia General.
Cada 60 min
OPS-001.0
runWorkflowChecks (master)
Coordinador que invoca los 5 jobs anteriores en orden. Garantiza atomicidad por company_id y previene duplicación de escalamientos.
Cada 30 min
▸ SQL · escalations table (DDL extracto)
CREATE TABLE IF NOT EXISTS faro.escalations (
escalation_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL,
entity_type text NOT NULL CHECK (entity_type IN ('tension', 'action', 'evidence')),
entity_id uuid NOT NULL,
tension_id uuid NULL,
action_id uuid NULL,
evidence_id uuid NULL,
escalation_level text NOT NULL CHECK (
escalation_level IN ('L1', 'L2', 'L3', 'L4', 'L5')
),
reason_code text NOT NULL, -- 'SLA_EXPIRED', 'RESPONSIBLE_MISSING', etc.
reason_description text NOT NULL,
from_user_id uuid NULL,
to_user_id uuid NULL,
to_role text NULL,
status text NOT NULL DEFAULT 'open' CHECK (
status IN ('open', 'acknowledged', 'resolved', 'dismissed')
),
due_at timestamptz NULL,
acknowledged_at timestamptz NULL,
resolved_at timestamptz NULL,
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Índice anti-duplicación: una sola escalation 'open' por nivel + entidad
CREATE UNIQUE INDEX idx_escalations_no_dup_open
ON faro.escalations(company_id, entity_id, escalation_level)
WHERE status = 'open';
09 · Bloqueo · aprobación · cierre
Reglas duras de las tres operaciones críticas
Bloquear, aprobar y cerrar son las tres operaciones donde la disciplina del workflow se pone a prueba. Cada una tiene criterios duros que el backend debe verificar antes de aceptar el comando.
9.1 · Reglas de bloqueo
Bloquear una acción es legítimo cuando hay dependencia real, no como excusa. Para evitar abuso, el sistema exige campos obligatorios.
Bloqueo · 01
Razón obligatoria
Sin blocked_reason + blocked_category, el endpoint /api/actions/{id}/block devuelve 400 BLOCKED_REASON_REQUIRED.
Bloqueo · 02
Categoría tipada
10 categorías enum: approval_dependency, data_dependency, external_dependency, budget_dependency, role_conflict, system_issue, policy_conflict, capacity_issue, commercial_conflict, financial_risk.
Bloqueo · 03
Sugerencia de escalamiento
Si needs_escalation = true, el sistema crea automáticamente una escalation al rol suggested_escalation_role indicado.
Bloqueo · 04
Plazo máximo
Una acción blocked > 48 horas dispara escalamiento L2 automático. Más de 7 días, escalamiento L3 y notificación a dirección.
9.2 · Reglas de aprobación
Quién puede aprobar depende de la prioridad de la acción y del tipo de evidencia. Aprobar fuera de jerarquía dispara UNAUTHORIZED_APPROVER.
9.3 · Reglas de cierre
El cierre es la operación más importante del workflow. Sin evidencia aprobada, no hay cierre. La función faro.can_close_action() ejecuta siete validaciones antes de permitir el comando.
Cierre · 01
Estado no bloqueante
El status actual no puede ser blocked, cancelled ni rejected. Bloquear y cerrar simultáneamente es contradictorio.
Cierre · 02
Criterio de cierre definido
closure_criteria debe existir y ser texto no vacío. Sin criterio explícito, no se puede medir si se cumplió.
Cierre · 03
Evidencia aprobada (regla dura)
TODAS las evidencias de evidence_required deben estar status = 'approved'. Sin esto, error EVIDENCE_NOT_APPROVED. Sin excepciones, salvo EVD-009.
Cierre · 04
Permiso del usuario
El user_id debe tener rol que cubra la prioridad (ver sección 9.2). RLS + función verifican.
Cierre · 05
Sin escalamientos abiertos
Si existe alguna escalation.status = 'open' sobre esta acción, debe resolverse o desestimarse antes del cierre.
Cierre · 06
Evidencia crítica para acción crítica
Si priority = 'critical', al menos una evidencia debe ser de tipo EVD-006, EVD-007 o EVD-012 (peso alto).
Cierre · 07 · Excepción EVD-009
Cierre manual justificado
Si se usa EVD-009, queda marcada como cierre excepcional: requiere aprobador de nivel director, recupera menos Score (sección 10) y queda en lista de revisión posterior por auditoría.
▸ SQL · función can_close_action()
CREATE OR REPLACE FUNCTION faro.can_close_action(
p_company_id uuid,
p_action_id uuid,
p_user_id uuid
)
RETURNS TABLE(can_close boolean, blocking_reasons text[]) AS $$
DECLARE
v_reasons text[] := ARRAY[]::text[];
v_action RECORD;
v_evidence_pending integer;
v_open_escalations integer;
BEGIN
SELECT * INTO v_action
FROM faro.actions
WHERE company_id = p_company_id AND action_id = p_action_id;
IF v_action.status IN ('blocked', 'cancelled', 'rejected') THEN
v_reasons := array_append(v_reasons, 'La acción está ' || v_action.status);
END IF;
IF v_action.closure_criteria IS NULL OR v_action.closure_criteria = '' THEN
v_reasons := array_append(v_reasons, 'Falta closure_criteria');
END IF;
SELECT COUNT(*) INTO v_evidence_pending
FROM faro.evidence
WHERE action_id = p_action_id AND status <> 'approved';
IF v_evidence_pending > 0 THEN
v_reasons := array_append(v_reasons, 'Hay evidencia no aprobada (EVIDENCE_NOT_APPROVED)');
END IF;
SELECT COUNT(*) INTO v_open_escalations
FROM faro.escalations
WHERE action_id = p_action_id AND status = 'open';
IF v_open_escalations > 0 THEN
v_reasons := array_append(v_reasons, 'Hay escalamientos sin resolver');
END IF;
RETURN QUERY SELECT (cardinality(v_reasons) = 0), v_reasons;
END;
$$ LANGUAGE plpgsql;
10 · Impacto en Score
Cómo cada evento del workflow modifica score_recovery y score_confidence
FARO Score tiene dos dimensiones móviles que el workflow ajusta: score_recovery (cuánto del impacto negativo se recuperó por ejecución) y score_confidence (cuánta confianza tiene FARO en que el resultado va a sostenerse). El principio rector es duro: el Score no se recupera por promesa; se recupera por evidencia aprobada.
Principio rector del Score en el workflow. Detectar una tensión penaliza Score (resta puntos según severidad). Iniciar la acción no recupera nada. Cargar evidencia no recupera nada (mejora confianza marginalmente). Aprobar evidencia habilita recuperación. Cerrar con evidencia aprobada recupera Score. Vencer o bloquear penaliza más. La cadena es disciplina, no relato.
10.1 · Eventos del workflow y su impacto en Score
Evento
Recovery permitido
Confidence
tension_detected · tensión nueva
0% (penaliza)
-impacto base
action_created · acción instanciada
0%
+0
action_started · responsable inicia
0% a 10%
+2 (señal ejecución)
action_blocked · bloqueada justificada
0% (congela)
-3
action_expired · SLA vencido
0% (revierte avance)
-8
evidence_submitted · evidencia cargada
0% (no acumula)
+3 (confianza parcial)
evidence_rejected · evidencia rechazada
0%
-5
evidence_approved · evidencia aprobada
10% a 30%
+10
action_approved · acción aprobada
40% a 70%
+8
action_closed · con evidencia EVD-006/007/012
70% a 100%
+12
action_closed_exception · cierre EVD-009
20% a 50%
-4 (penalización auditada)
tension_closed · tensión cerrada con KPI mejorado
+10% bonus
+15 (full recovery)
tension_reopened · recurrencia detectada
0% (resetea)
-15 (doble penalización)
action_reopened · auditoría detecta evidencia débil
0% (devuelve recovery)
-10
10.2 · Tabla de recuperación por estado de acción
Vista consolidada: cuánto del impacto Score puede recuperarse según el estado actual de la acción. Esta tabla la consume el job OPS-001 score-recompute.
11 · Auditoría completa
Tabla execution_events · 40 event_types tipados
Todo lo que ocurre en el workflow queda registrado en faro.execution_events. UI-005 (Timeline de Ejecución) consume esta tabla directamente. Reportes ejecutivos y auditorías posteriores también.
Decisión D3 aplicada · tabla única. No hay tension_events, action_events ni evidence_events separadas. Todo se centraliza en execution_events con discriminador entity_type. La vista v_action_events queda como filtro sobre la tabla única, no como tabla independiente. Esto evita inconsistencias de schema, simplifica la auditoría cruzada y permite reportes que mezclan eventos de tensión + acción + evidencia en un solo timeline.
▸ SQL · execution_events (DDL maestro)
CREATE TABLE IF NOT EXISTS faro.execution_events (
event_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL,
entity_type text NOT NULL CHECK (
entity_type IN ('tension', 'action', 'evidence')
),
entity_id uuid NOT NULL,
event_type text NOT NULL CHECK (event_type IN (
-- Lifecycle (12)
'tension_detected', 'tension_assigned', 'tension_started',
'tension_closed', 'tension_reopened', 'tension_rejected',
'action_created', 'action_assigned', 'action_started',
'action_closed', 'action_reopened', 'action_cancelled',
-- Workflow (8)
'action_status_changed', 'tension_status_changed',
'action_blocked', 'action_unblocked',
'action_regularized', 'tension_to_monitoring',
'responsible_assigned', 'responsible_missing',
-- Escalation (6)
'tension_escalated', 'action_escalated', 'evidence_escalated',
'escalation_acknowledged', 'escalation_resolved', 'escalation_dismissed',
-- Evidence (5)
'evidence_submitted', 'evidence_approved', 'evidence_rejected',
'evidence_needs_more_info', 'evidence_archived',
-- Score (5)
'score_recovery_enabled', 'score_recovered', 'score_recovery_blocked',
'score_penalized', 'score_confidence_updated',
-- Deadline (4)
'action_expired', 'tension_expired',
'sla_warning', 'sla_critical'
)),
category text NOT NULL CHECK (category IN (
'lifecycle', 'workflow', 'escalation',
'evidence', 'score', 'deadline'
)),
title text NOT NULL,
description text NULL,
actor_user_id uuid NULL,
actor_kind text NOT NULL CHECK (
actor_kind IN ('user', 'system', 'integration')
),
tension_id uuid NULL,
action_id uuid NULL,
evidence_id uuid NULL,
escalation_id uuid NULL,
new_status text NULL,
old_status text NULL,
severity text NULL,
priority text NULL,
impact_score numeric NULL,
source text NOT NULL, -- 'workflow_engine', 'manual_ui', 'api', etc.
source_reason text NULL,
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX idx_events_company_entity
ON faro.execution_events(company_id, entity_type, entity_id, created_at DESC);
CREATE INDEX idx_events_company_action_timeline
ON faro.execution_events(company_id, action_id, created_at DESC)
WHERE action_id IS NOT NULL;
CREATE INDEX idx_events_company_tension_timeline
ON faro.execution_events(company_id, tension_id, created_at DESC)
WHERE tension_id IS NOT NULL;
11.1 · Catálogo de los 40 event_types
Agrupados por category. Cada uno tiene title + description tipados que UI-005 muestra en el timeline visual.
tension_detectedLifecycleMotor evaluador detecta nueva tensión.
tension_assignedLifecycleResponsable asignado a la tensión.
tension_startedLifecycleResponsable inicia análisis (in_review → in_execution).
tension_closedLifecycleTensión cerrada con KPI verificado.
tension_reopenedLifecycleTensión reabierta por recurrencia o evidencia débil.
tension_rejectedLifecycleTensión rechazada (falso positivo justificado).
action_createdLifecycleAcción instanciada desde recommended_actions.
action_assignedLifecycleAcción asignada a responsable concreto.
action_startedLifecycleResponsable marca in_progress.
action_closedLifecycleCierre formal de acción con evidencia aprobada.
action_reopenedLifecycleAcción reabierta tras auditoría posterior.
action_cancelledLifecycleAcción cancelada por decisión de dirección.
action_status_changedWorkflowTransición de status validada por matriz 4.2.
tension_status_changedWorkflowTransición de status de tensión validada por matriz 4.1.
action_blockedWorkflowAcción bloqueada con reason + category tipados.
action_unblockedWorkflowAcción destrabada y vuelta a estado activo.
action_regularizedWorkflowAcción expired regularizada y vuelta a in_progress.
tension_to_monitoringWorkflowTodas acciones críticas cerradas; tensión pasa a monitoring.
responsible_assignedWorkflowAsignación inicial o cambio de responsable.
responsible_missingWorkflowNo se pudo resolver responsable; dispara escalamiento L3.
tension_escalatedEscalationEscalamiento de tensión a nivel superior (L2/L3/L4).
action_escalatedEscalationEscalamiento de acción según matriz 8.2.
evidence_escalatedEscalationEscalamiento por rechazo repetido o revisión vencida.
escalation_acknowledgedEscalationDestinatario acusa recibo de escalamiento.
escalation_resolvedEscalationEscalamiento resuelto (acción destrabada o decisión tomada).
escalation_dismissedEscalationEscalamiento desestimado justificadamente.
evidence_submittedEvidenceEvidencia cargada por responsable.
evidence_approvedEvidenceEvidencia aprobada por aprobador.
evidence_rejectedEvidenceEvidencia rechazada con motivo.
evidence_needs_more_infoEvidenceAprobador pide ampliación.
evidence_archivedEvidenceEvidencia obsoleta archivada por housekeeping.
score_recovery_enabledScoreAcción aprobada; recovery se habilita en próximo recompute.
score_recoveredScoreJob recompute aplica recovery efectivo al Score.
score_recovery_blockedScoreAcción cerrada sin evidencia aprobada; recovery bloqueado.
score_penalizedScoreVencimiento o reincidencia aplica penalización adicional.
score_confidence_updatedScoreCambio en score_confidence por carga o aprobación evidencia.
action_expiredDeadlineSLA de acción vencido; estado pasa a expired.
tension_expiredDeadlineTensión sin cierre dentro de plazo razonable.
sla_warningDeadlineJob detecta >75% SLA consumido sin cierre; notifica al responsable.
sla_criticalDeadline>95% SLA consumido sin evidencia; pre-escalamiento al gerente.
11.2 · Vista v_action_events
La vista filtrada que UI-005 consume cuando renderiza el timeline de una acción puntual. No es tabla independiente: es SELECT sobre execution_events (D3 aplicado).
▸ SQL · v_action_events (vista filtro)
CREATE OR REPLACE VIEW faro.v_action_events AS
SELECT
e.event_id,
e.company_id,
e.action_id,
e.tension_id,
e.event_type,
e.category,
e.title,
e.description,
e.actor_user_id,
u.full_name AS actor_name,
e.actor_kind,
e.new_status,
e.old_status,
e.severity,
e.priority,
e.payload,
e.created_at
FROM faro.execution_events e
LEFT JOIN faro.users u ON u.user_id = e.actor_user_id
WHERE e.entity_type = 'action'
OR e.action_id IS NOT NULL;
12 · Cross-references
Dónde se consume, valida y extiende este workflow
El workflow no vive solo. Es el contrato que rige UI, jobs operativos, motor evaluador, modelo Score, alertas y matriz RACI. Estos son los puntos de cruce.
12.1 · Próximos pasos para cerrar el loop
- FARO-ENG-003 · Motor Evaluador MVP. Construir
motor-evaluador-mvp.html que documente cómo el motor crea tensiones que entran al ciclo de este workflow. Debe consumir tension_definitions canónicas y persistir en tensions con status = 'new'.
- FARO-SCORE · Modelo FARO Score MVP. Construir
motor-score-mvp.html que detalle la fórmula de Score y cómo el job OPS-001 score-recompute consume los eventos de la sección 10 de este workflow.
- FARO-UI-001 · Bandeja de Tensiones. Construir
ui-bandeja-tensiones.html con el enum de 10 estados de tensión y la matriz de transiciones 4.1. Debe usar componente WorkflowStatusBadge.
- FARO-UI-002 · Dashboard Responsable. Construir
ui-dashboard-responsable.html consumiendo v_workflow_inbox. Incluir SLA badge (verde / ámbar / coral según estado) y banner de escalamientos activos.
- FARO-UI-003 · Detalle de Acción + Workflow. Construir
ui-workflow-accion.html con enum de 12 estados, decision bar de transiciones permitidas y panel de bloqueo / aprobación.
- FARO-UI-005 · Timeline de Ejecución. Construir
ui-timeline-auditoria.html consumiendo execution_events con render visual de los 40 event_types agrupados por category.
- FARO-TPL-001 · Alertas y Notificaciones MVP. Construir
alertas-notificaciones-mvp.html con templates por evento. Sin esto, el workflow queda como reglamento pegado en una pared: correcto, pero ignorado.
Cierre constitutional. Este documento es la fuente de verdad de los enums maestros del MVP. Cuando UI, motor, score, alertas o cualquier otro módulo se construya, debe respetar exactamente lo definido acá: 10 estados de tensión, 12 de acción, 5 de evidencia, 40 event_types, niveles L1-L4, decisiones D3 (tabla única) y D4 (escalated como evento). Cualquier desviación se rechaza en code review.
Workflow constitucional aprobado · listo para construir UI y motor Score
Este documento es la base para FARO-UI-001..005, FARO-ENG-003 (motor evaluador), FARO-SCORE (modelo Score) y FARO-TPL-001 (alertas). Pasá al hub para ver el resto del pack o seguí con el catálogo canónico de tensiones para entender qué entra al workflow.
→ Volver al hub modelos NDA