Comentario
Texto libre sin respaldo documental. Sirve para registrar avance, no para cerrar acciones críticas.
“Llamé al cliente y quedamos en hablar mañana.”
Flujo formal para cargar, validar, revisar y aprobar evidencias asociadas a una acción FARO. Sin evidencia válida el cierre es opinión; y una empresa no se dirige con opiniones sueltas.
FARO-UI-004 convierte la evidencia en control real. No se cierra porque alguien dice que hizo. Se cierra porque existe una prueba válida, revisada y trazable. Sin este flujo, FARO sería otro gestor de tareas; con él, empieza a ser un sistema de dirección verificable.
Tesis de producto. FARO debe impedir que el usuario cierre acciones críticas solo con una frase. “Listo, hablado” puede servir como avance, pero no como cierre. La evidencia no es un adjunto simpático; es el respaldo que permite afirmar “esta acción fue ejecutada correctamente”.
El módulo separa tres niveles de respaldo que normalmente se mezclan en herramientas de tareas tradicionales. Cada uno tiene un peso distinto en el cierre operativo y en el FARO Score.
Texto libre sin respaldo documental. Sirve para registrar avance, no para cerrar acciones críticas.
“Llamé al cliente y quedamos en hablar mañana.”
Comprobante, captura, acta, política o registro tipificado por código EVD-NNN con metadata obligatoria.
“Adjunto política de descuentos firmada por dirección, vigente desde 2026-06-01.”
Aprobación formal hecha por un rol con permiso explícito (gerente, director). Cierra acciones críticas de forma trazable.
“Director general valida cambio de política de comisiones para Q3 2026.”
El alcance de FARO-UI-004 cubre toda la secuencia desde que el responsable abre el modal hasta que la acción queda habilitada para cierre. Bloquea cierres ficticios, exige metadata obligatoria por tipo, exige aprobación según trust level y deja trazabilidad completa para auditoría posterior.
Decisión MVP confirmada: el sistema no cambia automáticamente a closed cuando todas las evidencias quedan aprobadas. Habilita el botón “Cerrar acción”. Esto es conservador, trazable y menos propenso a errores de evaluación temprana del motor. El cierre lo dispara siempre una persona con permiso, sobre un estado verificable.
Decisión de status enum: este documento usa el set único de 5 estados coordinado con FARO-WF-001 (submitted, approved, rejected, needs_more_info, archived). El estado draft queda fuera del MVP para no fragmentar el workflow ni introducir un sexto camino en la UI.
El flujo soporta los 12 tipos definidos en catalogo-evidencias-mvp.html. Cada tipo declara qué archivo acepta, qué metadata obligatoria pide, qué trust level tiene y si puede o no cerrar la acción. La UI lee este catálogo y construye el formulario dinámico.
Regla dura. Si un evidence_code no figura en este catálogo, el endpoint responde UNKNOWN_EVIDENCE_CODE y no permite cargar. Si una acción no declara ese código en su evidence_required_codes, el endpoint responde ACTION_DOES_NOT_ACCEPT_EVIDENCE_CODE. Nada de evidencia “por las dudas”.
file_name submitted_by submitted_atsource_system captured_at submitted_byapproved_by approved_at approval_scopecomment_text (mín. 20 caracteres)order_number issued_by issued_atprovider document_id amountpolicy_name approved_by effective_from scopekpi_code kpi_value measured_atjustification approved_by override_reasonmeeting_date attendees decisionscontact_name contact_date channel outcomevalidated_by validated_at validation_scope| Código | Evidencia | Trust | Archivo | Revisión | Puede cerrar |
|---|---|---|---|---|---|
EVD-001 | Documento cargado | Medium | Obligatorio | Sí | Según acción |
EVD-002 | Captura de sistema | Medium | Obligatorio | Sí | Según contexto |
EVD-003 | Registro de aprobación | High | Recomendado | Sí | Sí |
EVD-004 | Comentario validado | Low | No | Sí | No crítica solo |
EVD-005 | Orden emitida | High | Obligatorio | Sí | Sí |
EVD-006 | Comprobante externo | High | Obligatorio | Sí | Sí |
EVD-007 | Cambio de política | Critical | Recomendado | Sí | Sí |
EVD-008 | KPI posterior | Medium | No (auto) | No | Sistema |
EVD-009 | Cierre manual justificado | Low | Opcional | Sí | Excepcional |
EVD-010 | Acta / minuta / reporte | High | Obligatorio | Sí | Sí |
EVD-011 | Cliente / proveedor contactado | Medium | Opcional | Sí | Según acción |
EVD-012 | Validación de dirección | Critical | No | No | Sí · crítica |
Cada evidencia recorre cuatro capas de validación antes de quedar como submitted. Si alguna falla, el endpoint responde con un código de error específico y la evidencia no se persiste. Archivo cargado ≠ evidencia aprobada; evidencia enviada ≠ evidencia válida; evidencia aprobada ≠ acción cerrada automáticamente.
Resuelve evidence_code contra faro.evidence_definitions con status = 'active'. Si no existe, no hay metadata que validar ni archivo que aceptar.
UNKNOWN_EVIDENCE_CODE
Llama faro.action_accepts_evidence_code(action_id, evidence_code) que verifica que el código está en action_definitions.evidence_required_codes. Evita subir evidencia “por las dudas”.
ACTION_DOES_NOT_ACCEPT_EVIDENCE_CODE
Llama faro.validate_evidence_metadata(evidence_code, metadata) que recorre required_metadata_keys y exige que cada clave esté presente y no vacía. Devuelve lista de errores por campo.
INVALID_EVIDENCE_METADATA
RLS chequea app.company_id y app.role_codes contra la matriz de permisos (ver sección 7). Solo responsables, aprobadores, gerentes y directores pueden cargar. Solo aprobador+ puede aprobar/rechazar.
UNAUTHORIZED_ROLE_FOR_EVIDENCE
Validación frontend espejo. El cliente ejecuta una versión simplificada de las capas 1-3 con validateEvidenceClientSide para que el botón “Guardar y enviar a revisión” quede deshabilitado mientras haya errores. Pero el backend revalida siempre: el frontend es UX, no seguridad.
El status enum es único y compartido con el workflow general. draft queda fuera del MVP para no fragmentar el flujo ni introducir un sexto camino visual en la UI. Una evidencia entra al sistema directamente como submitted.
Pasó las 4 capas de validación. Queda visible en la cola de revisión del aprobador. La acción cambia a in_review.
Aprobador firmó con comentario opcional. Cuenta para can_close_action. Si todas las requeridas quedan aprobadas, habilita el botón “Cerrar acción”.
Aprobador rechazó con comentario obligatorio. La acción vuelve a waiting_evidence. La evidencia no se borra: queda como histórico auditable.
Aprobador pide info adicional con comentario obligatorio. El responsable puede actualizar la evidencia sin crear una nueva. La acción vuelve a waiting_evidence.
Solo gerentes y directores pueden archivar. Saca a la evidencia de la cuenta de cierre sin borrarla. Útil para evidencia obsoleta o reemplazada por una versión nueva.
| Evento sobre evidencia | Estado acción resultante | Habilita cierre |
|---|---|---|
Evidencia cargada (submitted) | in_review | No |
| Evidencia rechazada | waiting_evidence | No |
Evidencia needs_more_info | waiting_evidence | No |
Todas las requeridas en approved | Sigue in_review | Sí (botón “Cerrar acción”) |
| Acción cerrada manualmente | closed | — |
| Evidencia cargada en acción vencida | in_review | No (revisar SLA) |
| Evidencia aprobada en acción vencida | Sigue in_review | Sí (cierre manual auditable) |
Secuencia que el módulo garantiza extremo a extremo. Cada paso valida algo concreto, persiste algo concreto y deja un evento en action_events y en audit.audit_log. El responsable, el aprobador y el gerente ven la misma historia.
El responsable abre la acción y elige un código EVD-NNN del panel lateral “Evidencia requerida”. La UI muestra nombre, descripción, trust level y si requiere archivo.
El endpoint llama faro.action_accepts_evidence_code antes de cualquier upload. Si la acción no requiere ese código, responde ACTION_DOES_NOT_ACCEPT_EVIDENCE_CODE.
El modal renderiza el formulario dinámico según required_metadata_keys. El dropzone valida extensión, MIME y tamaño máximo 25 MB. El frontend ya bloquea el botón si falta algo.
El backend persiste en faro.evidence con status = 'submitted', sube archivo a storage privado con path companies/{company_id}/actions/{action_id}/evidence/{evidence_id}/{filename}, calcula SHA-256.
La acción cambia a in_review. El aprobador recibe la evidencia en su cola (faro.v_evidence_review_queue) y opcionalmente recibe alerta (FARO-TPL-001).
POST /api/v1/evidence/:id/approve. Estado pasa a approved. Si todas las requeridas quedan aprobadas, can_close vuelve true.
POST /api/v1/evidence/:id/reject con review_comment obligatorio. Estado pasa a rejected. La acción vuelve a waiting_evidence.
POST /api/v1/evidence/:id/needs-more-info con comentario obligatorio. Estado pasa a needs_more_info. El responsable puede actualizar sin crear evidencia nueva.
Después de cada cambio se llama faro.can_close_action(action_id). Si devuelve true, la UI habilita el botón “Cerrar acción”. El cierre lo dispara siempre una persona, nunca automáticamente.
La UI tiene dos puntos de contacto principales: un modal que se abre desde el panel de evidencia requerida, y el panel lateral que vive dentro del detalle de acción (FARO-UI-003). El modal es la operación, el panel es el inventario.
+----------------------------------------------------------+ | FARO · Evidencia [ Cerrar ] | | Cargar evidencia | | Acción: ACT-COM-001 · Revisar política de descuentos | | Evidencia requerida: EVD-007 · Cambio de política | +----------------------------------------------------------+ | | | Tipo de evidencia | | [ EVD-007 · Cambio de política CRITICAL ] | | | | Título | | [ Política de descuentos comerciales mayo 2026 ] | | | | Descripción | | [ Se adjunta política revisada y aprobada por... ] | | [ ] | | | | Metadata obligatoria | | +---------------------------+--------------------------+ | | | Policy Name | Approved By | | | | [ Política descuentos... ]| [ Director General ] | | | +---------------------------+--------------------------+ | | | Effective From | Scope | | | | [ 2026-06-01 ] | [ Comercial · todas... ] | | | +---------------------------+--------------------------+ | | | | Archivo de respaldo | | +------------------------------------------------------+ | | | Formatos permitidos: pdf, docx, xlsx | | | | [ politica-descuentos-2026.pdf · 318 KB ] | | | +------------------------------------------------------+ | | | +----------------------------------------------------------+ | [ Cancelar ] [ Guardar y enviar a revisión ] | +----------------------------------------------------------+
Evidencia requerida ------------------------------------------------------- EVD-007 · Cambio de política Estado: missing Trust: critical [ Cargar evidencia ] EVD-012 · Validación de dirección Estado: missing Trust: critical [ Solicitar validación ] EVD-001 · Documento cargado Estado: approved Trust: medium Subido por María Fernández · 2026-05-28 14:22 Aprobado por Carlos Méndez · 2026-05-28 16:05 [ Ver archivo ] [ Ver historial ] ------------------------------------------------------- Cierre de acción Estado actual: BLOQUEADO Faltan: EVD-007, EVD-012 [ Cerrar acción ] (deshabilitado)
Diseño visual recomendado. Modal blanco con bordes suaves; jerarquía clara; EVD crítica con badge sobrio oscuro; error de metadata en ámbar; rechazo en rojo tenue; aprobado en verde sobrio; dropzone con borde punteado y fondo arena; botón principal azul navy. Nada de convertir esto en Dropbox con colores: es evidencia ejecutiva, no un álbum familiar.
Cada pieza del flujo es un componente independiente con responsabilidad única. Esto permite testear, reemplazar y reusar sin tocar el resto. Todos viven en components/evidence/.
Modal a pantalla completa en mobile, centrado en desktop. Orquesta selector, formulario dinámico, dropzone y validación.
Lista los EVD pendientes con código, nombre, descripción y trust level. Click cambia el formulario dinámico.
Renderiza inputs según evidence_code seleccionado. Soporta texto, fecha, select y referencia a usuarios.
Acepta drag & drop o click. Valida extensión contra allowed_file_types, MIME real y tamaño máximo 25 MB.
Grid 2 columnas con cada clave declarada en required_metadata_keys. Humaniza el nombre del campo automáticamente.
Bloque ámbar con la lista de errores actuales antes de enviar. Aparece solo si hay algo que corregir.
Lista de evidencias ya subidas a la acción. Muestra estado, trust badge, autor y fecha. Permite abrir detalle.
Panel del aprobador con textarea de comentario y 3 botones: aprobar, pedir más info, rechazar. Rechazo exige comentario.
Pildora con color según el enum (submitted, approved, rejected, needs_more_info, archived).
Indica el trust level (low / medium / high / critical). Useful para que el aprobador sepa rápido qué nivel de revisión exigir.
Línea inferior con “Subido por X el dd/mm · revisado por Y”. Muestra hash SHA-256 truncado para verificación posterior.
Checkbox visual por cada EVD requerido. Verde si aprobado, ámbar si submitted, rojo si missing. Muestra el bloqueo de cierre.
export type EvidenceStatus = | "submitted" | "approved" | "rejected" | "needs_more_info" | "archived"; export type EvidenceTrustLevel = "low" | "medium" | "high" | "critical"; export type EvidenceDefinitionForUpload = { evidence_code: string; name: string; description: string; evidence_type: string; trust_level: EvidenceTrustLevel; allowed_submission_modes: string[]; allowed_file_types: string[]; required_metadata_keys: string[]; requires_review: boolean; can_close_action: boolean; validation_rules: Record<string, unknown>; }; export type EvidenceUploadRequest = { action_id: string; evidence_code: string; title: string; description?: string; metadata: Record<string, unknown>; submit_for_review: boolean; }; export type EvidenceReviewAction = { review_comment: string; };
El endpoint principal recibe un multipart/form-data con campos planos más un blob metadata serializado como JSON. La metadata varía según el evidence_code; el backend valida contra required_metadata_keys del catálogo antes de persistir.
POST /api/v1/actions/23000000-0000-0000-0000-000000000001/evidence Content-Type: multipart/form-data; boundary=----faro ------faro Content-Disposition: form-data; name="evidence_code" EVD-007 ------faro Content-Disposition: form-data; name="title" Política de descuentos comerciales mayo 2026 ------faro Content-Disposition: form-data; name="description" Se adjunta política revisada y aprobada por dirección. ------faro Content-Disposition: form-data; name="metadata" {"policy_name":"Política de descuentos comerciales", "approved_by":"12000000-0000-0000-0000-000000000001", "effective_from":"2026-06-01", "scope":"Comercial · todas las sucursales"} ------faro Content-Disposition: form-data; name="submit_for_review" true ------faro Content-Disposition: form-data; name="file"; filename="politica-descuentos-2026.pdf" Content-Type: application/pdf <binary PDF data> ------faro--
// EVD-007 · Cambio de política { "policy_name": "Política de descuentos comerciales", "approved_by": "12000000-0000-0000-0000-000000000001", "effective_from": "2026-06-01", "scope": "Comercial · todas las sucursales" } // EVD-003 · Registro de aprobación { "approved_by": "12000000-0000-0000-0000-000000000003", "approved_at": "2026-05-30T15:30:00-03:00", "approval_scope": "Comercial · descuentos mayores a 12%" } // EVD-012 · Validación de dirección { "validated_by": "12000000-0000-0000-0000-000000000099", "validated_at": "2026-05-30T16:00:00-03:00", "validation_scope": "Cambio de política comercial Q3 2026" } // EVD-006 · Comprobante externo { "provider": "Distribuidora Cuyo S.A.", "document_id": "FAC-2026-04582", "amount": "482350.00" } // EVD-011 · Cliente / proveedor contactado { "contact_name": "Cliente Demo Norte", "contact_date": "2026-05-29", "channel": "email", "outcome": "Aceptó nueva política · firma pendiente" }
import type { EvidenceReviewAction, EvidenceUploadResponse } from "./evidence.types"; export async function uploadActionEvidence(params: { actionId: string; evidenceCode: string; title: string; description?: string; metadata: Record<string, unknown>; file?: File | null; submitForReview: boolean; }): Promise<EvidenceUploadResponse> { const formData = new FormData(); formData.set("evidence_code", params.evidenceCode); formData.set("title", params.title); formData.set("description", params.description ?? ""); formData.set("metadata", JSON.stringify(params.metadata)); formData.set("submit_for_review", String(params.submitForReview)); if (params.file) { formData.set("file", params.file); } const response = await fetch(`/api/v1/actions/${params.actionId}/evidence`, { method: "POST", body: formData }); if (!response.ok) { const error = await response.json().catch(() => null); throw new Error(error?.message ?? "No se pudo cargar la evidencia"); } return response.json(); }
import type { EvidenceDefinitionForUpload } from "./evidence.types"; export function validateEvidenceClientSide(params: { definition: EvidenceDefinitionForUpload; title: string; metadata: Record<string, unknown>; file: File | null; }): string[] { const errors: string[] = []; if (!params.title.trim()) { errors.push("El título es obligatorio."); } for (const key of params.definition.required_metadata_keys ?? []) { const value = params.metadata[key]; if (value === undefined || value === null || String(value).trim() === "") { errors.push(`Falta completar: ${humanizeKey(key)}.`); } } const requiresFile = evidenceUsuallyRequiresFile(params.definition.evidence_code); if (requiresFile && !params.file) { errors.push("Este tipo de evidencia requiere archivo."); } if (params.file && params.definition.allowed_file_types.length > 0) { const ext = params.file.name.split(".").pop()?.toLowerCase(); if (!ext || !params.definition.allowed_file_types.includes(ext)) { errors.push(`Formato no permitido. Permitidos: ${params.definition.allowed_file_types.join(", ")}.`); } } if (params.file && params.file.size > 25 * 1024 * 1024) { errors.push("El archivo supera el máximo permitido de 25 MB."); } return errors; }
Cada response del endpoint devuelve tres bloques: la evidencia recién creada (o actualizada), el estado nuevo de la acción y un can_close calculado por faro.can_close_action. El frontend usa ese flag para habilitar el botón de cierre sin tener que recargar.
{
"ok": true,
"evidence": {
"evidence_id": "24000000-0000-0000-0000-000000000099",
"evidence_code": "EVD-007",
"title": "Política de descuentos comerciales mayo 2026",
"status": "submitted",
"submitted_at": "2026-05-30T15:30:00-03:00"
},
"action": {
"action_id": "23000000-0000-0000-0000-000000000001",
"status": "in_review",
"can_close": false
}
}
{
"ok": true,
"evidence": {
"evidence_id": "24000000-0000-0000-0000-000000000099",
"status": "approved"
},
"action": {
"action_id": "23000000-0000-0000-0000-000000000001",
"can_close": true,
"blocking_reasons": []
}
}
{
"ok": true,
"evidence": {
"evidence_id": "24000000-0000-0000-0000-000000000099",
"status": "rejected"
},
"action": {
"action_id": "23000000-0000-0000-0000-000000000001",
"status": "waiting_evidence"
}
}
El motor FARO Score (FARO-SCORE, pendiente como módulo dedicado en motor-score-mvp.html) consume el estado de evidencia para ponderar la confianza de cierre. La lógica resumida:
approved con trust critical: confidence 100%, recupera impacto Score completo.approved con trust high: confidence 90%, recupera impacto Score con factor 0.9.submitted sin aprobar: confidence 50%, recupera mitad del impacto Score pendiente.rejected o missing: confidence 0%, no recupera Score; la tensión sigue penalizando.archived: no cuenta para confidence; el Score evalúa como si no existiera.Esto es lo que evita el clásico “cerré la acción, pero el Score no se mueve” o, peor, el inverso: Score subiendo por cierres sin respaldo. La función faro.evidence_confidence_for_action(action_id) devuelve el factor que el motor Score consume al recalcular.
| Operación | Responsable | Aprobador | Gerente | Director |
|---|---|---|---|---|
| Cargar evidencia | Sí | Sí | Sí | Sí |
| Ver evidencia | Sí | Sí | Sí | Sí |
| Aprobar evidencia | No | Sí | Sí | Sí |
| Rechazar evidencia | No | Sí | Sí | Sí |
| Pedir más información | No | Sí | Sí | Sí |
| Archivar evidencia | No | No | Sí | Sí |
| Descargar evidencia | Según permiso | Sí | Sí | Sí |
Pre-visualización HTML/CSS de la composición real. Datos demostrativos de Empresa Demo Cuyo S.A. sobre la acción ACT-COM-001 · Revisar política de descuentos comerciales. No es UI funcional — es el contrato visual que el frontend debe poder reproducir en React.
Acción ACT-COM-001 · Revisar política de descuentos comerciales. La acción solo podrá cerrarse cuando la evidencia requerida esté aprobada por dirección.
Empty states canónicos. “Todavía no hay evidencia cargada. Esta acción no podrá cerrarse hasta que se cargue y apruebe la evidencia requerida.” · “La evidencia fue rechazada. Revisá el motivo, corregí la información y volvé a enviarla.” · “Evidencia aprobada. Ya puede ser considerada para el cierre de la acción.” · “Faltan datos obligatorios para validar esta evidencia. Completalos antes de enviarla.”
FARO-UI-004 no vive solo. Consume catálogos canónicos, alimenta el workflow general y el motor Score, y depende del modelo de seguridad RLS para que las 4 capas de validación cierren correctamente.
Catálogo canónico EVD-001..EVD-012. Define required_metadata_keys, allowed_file_types, trust_level y can_close_action que esta UI lee.
FARO-UI-003 · detalle de acción + workflow. Aloja el panel lateral “Evidencia requerida” desde donde se abre este modal.
Motor FARO Score. Consume el estado de evidencia y el trust level para ponderar la confianza de cierre y recuperación de Score. Pendiente como pieza dedicada.
Modelo de seguridad RLS multiempresa. La capa 4 de validación (permiso del usuario) se apoya en app.company_id, app.user_id y app.role_codes.
FARO-WF-001 · workflow y escalamiento. Define el status enum compartido (submitted, approved, rejected, needs_more_info, archived).
Catálogo canónico de acciones. Cada acción declara evidence_required_codes; la capa 2 verifica que el código cargado figure ahí.
DDL del sistema. Incluye faro.evidence, faro.evidence_definitions, faro.action_events y las funciones validate_evidence_metadata, action_accepts_evidence_code, can_close_action.
Diagnóstico ejecutivo. El indicador “cierre con evidencia” se calcula a partir de los eventos generados por esta UI.
Con FARO-UI-004 queda definida la pieza que convierte la evidencia en control real. Los próximos pasos son construir el timeline de ejecución (FARO-UI-005) y el motor Score con confidence por evidencia. Volvé al hub para ver el resto del pack o seguí con el catálogo de evidencias canónico.
→ Volver al hub modelos NDA