01 · Resumen ejecutivo

Distinguir comentario, evidencia y validación

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.

Nivel · Bajo

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.”

Nivel · Medio / Alto

Evidencia

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.”

Nivel · Alto / Crítico

Validación

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.

02 · 12 tipos EVD canónicos

EVD-001 a EVD-012 · catálogo desde FARO-SQL-006

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”.

EVD-001
Medium

Documento cargado

Archivo
Obligatorio · PDF, DOCX, XLSX, CSV, PNG, JPG
Metadata
file_name submitted_by submitted_at
Revisión
Puede cerrar: Según acción CONDICIONAL
EVD-002
Medium

Captura de sistema

Archivo
Obligatorio · PNG, JPG, PDF
Metadata
source_system captured_at submitted_by
Revisión
Puede cerrar: Según contexto CONDICIONAL
EVD-003
High

Registro de aprobación

Archivo
Recomendado
Metadata
approved_by approved_at approval_scope
Revisión
Puede cerrar: SÍ CIERRA
EVD-004
Low

Comentario validado

Archivo
No obligatorio
Metadata
comment_text (mín. 20 caracteres)
Revisión
Puede cerrar: No crítica solo NO CIERRA CRÍTICA
EVD-005
High

Orden emitida

Archivo
Obligatorio · PDF, XLSX
Metadata
order_number issued_by issued_at
Revisión
Puede cerrar: SÍ CIERRA
EVD-006
High

Comprobante externo

Archivo
Obligatorio · PDF, PNG, JPG
Metadata
provider document_id amount
Revisión
Puede cerrar: SÍ CIERRA
EVD-007
Critical

Cambio de política

Archivo
Recomendado / obligatorio según acción
Metadata
policy_name approved_by effective_from scope
Revisión
Puede cerrar: SÍ CIERRA
EVD-008
Medium

KPI posterior

Archivo
No (automático)
Metadata
kpi_code kpi_value measured_at
Revisión
No (sistema)
Puede cerrar: Sistema, no manual SISTEMA
EVD-009
Low

Cierre manual justificado

Archivo
Opcional
Metadata
justification approved_by override_reason
Revisión
Sí (excepcional)
Puede cerrar: Excepcional EXCEPCIONAL
EVD-010
High

Acta / minuta / reporte ejecutivo

Archivo
Obligatorio · PDF, DOCX
Metadata
meeting_date attendees decisions
Revisión
Puede cerrar: SÍ CIERRA
EVD-011
Medium

Cliente / proveedor contactado

Archivo
Opcional · email exportado, captura
Metadata
contact_name contact_date channel outcome
Revisión
Puede cerrar: Sí según acción SEGÚN ACCIÓN
EVD-012
Critical

Validación de dirección

Archivo
No (workflow)
Metadata
validated_by validated_at validation_scope
Revisión
No (ya es validación)
Puede cerrar: Sí · crítica SÍ CIERRA

Tabla maestra · qué EVD puede cerrar acción

Código Evidencia Trust Archivo Revisión Puede cerrar
EVD-001Documento cargadoMediumObligatorioSegún acción
EVD-002Captura de sistemaMediumObligatorioSegún contexto
EVD-003Registro de aprobaciónHighRecomendado
EVD-004Comentario validadoLowNoNo crítica solo
EVD-005Orden emitidaHighObligatorio
EVD-006Comprobante externoHighObligatorio
EVD-007Cambio de políticaCriticalRecomendado
EVD-008KPI posteriorMediumNo (auto)NoSistema
EVD-009Cierre manual justificadoLowOpcionalExcepcional
EVD-010Acta / minuta / reporteHighObligatorio
EVD-011Cliente / proveedor contactadoMediumOpcionalSegún acción
EVD-012Validación de direcciónCriticalNoNoSí · crítica
03 · 4 capas de validación

Cuatro chequeos secuenciales antes de aceptar evidencia

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.

01

El código EVD existe en el catálogo

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
02

El tipo corresponde a lo que la acción requiere

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
03

La metadata obligatoria está completa

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
04

El usuario tiene permiso para subirla o aprobarla

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.

04 · Estados de evidencia

5 estados coordinados con FARO-WF-001

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.

Cargada y enviada a revisión

Pasó las 4 capas de validación. Queda visible en la cola de revisión del aprobador. La acción cambia a in_review.

approved

Revisada y aprobada

Aprobador firmó con comentario opcional. Cuenta para can_close_action. Si todas las requeridas quedan aprobadas, habilita el botón “Cerrar acción”.

rejected

Rechazada con motivo

Aprobador rechazó con comentario obligatorio. La acción vuelve a waiting_evidence. La evidencia no se borra: queda como histórico auditable.

needs_more_info

Requiere corrección o ampliación

Aprobador pide info adicional con comentario obligatorio. El responsable puede actualizar la evidencia sin crear una nueva. La acción vuelve a waiting_evidence.

archived

Archivada · no válida para cierre

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.

Reglas de actualización de estado de acción

Evento sobre evidencia Estado acción resultante Habilita cierre
Evidencia cargada (submitted)in_reviewNo
Evidencia rechazadawaiting_evidenceNo
Evidencia needs_more_infowaiting_evidenceNo
Todas las requeridas en approvedSigue in_review (botón “Cerrar acción”)
Acción cerrada manualmenteclosed
Evidencia cargada en acción vencidain_reviewNo (revisar SLA)
Evidencia aprobada en acción vencidaSigue in_reviewSí (cierre manual auditable)
05 · Flujo funcional

9 pasos desde “seleccionar EVD” hasta “habilitar cierre”

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.

01
Seleccionar EVD requerida

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.

02
Validar tipo EVD aceptado por la acción

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.

03
Cargar archivo + metadata obligatoria

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.

04
Guardar evidencia

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.

05
Enviar a revisión

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).

06
Aprobador decide: aprobar

POST /api/v1/evidence/:id/approve. Estado pasa a approved. Si todas las requeridas quedan aprobadas, can_close vuelve true.

07
Aprobador decide: rechazar

POST /api/v1/evidence/:id/reject con review_comment obligatorio. Estado pasa a rejected. La acción vuelve a waiting_evidence.

08
Aprobador decide: pedir más información

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.

09
Actualizar estado de acción y habilitar cierre

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.

06 · Layout UI

Modal de carga + panel lateral en acción

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.

6.1 Modal de carga · estructura conceptual

6.2 Panel lateral dentro de la acción

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.

07 · 12 componentes UI

Inventario de componentes React reutilizables

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/.

EvidenceUploadModal
Contenedor principal

Modal a pantalla completa en mobile, centrado en desktop. Orquesta selector, formulario dinámico, dropzone y validación.

EvidenceRequirementSelector
Selección de tipo

Lista los EVD pendientes con código, nombre, descripción y trust level. Click cambia el formulario dinámico.

EvidenceDynamicForm
Formulario dinámico

Renderiza inputs según evidence_code seleccionado. Soporta texto, fecha, select y referencia a usuarios.

EvidenceFileDropzone
Carga de archivo

Acepta drag & drop o click. Valida extensión contra allowed_file_types, MIME real y tamaño máximo 25 MB.

EvidenceMetadataFields
Metadata obligatoria

Grid 2 columnas con cada clave declarada en required_metadata_keys. Humaniza el nombre del campo automáticamente.

EvidenceValidationSummary
Resumen errores

Bloque ámbar con la lista de errores actuales antes de enviar. Aparece solo si hay algo que corregir.

EvidenceSubmittedList
Inventario cargado

Lista de evidencias ya subidas a la acción. Muestra estado, trust badge, autor y fecha. Permite abrir detalle.

EvidenceReviewPanel
Aprobación / rechazo

Panel del aprobador con textarea de comentario y 3 botones: aprobar, pedir más info, rechazar. Rechazo exige comentario.

EvidenceStatusBadge
Badge estado

Pildora con color según el enum (submitted, approved, rejected, needs_more_info, archived).

EvidenceTrustBadge
Badge confianza

Indica el trust level (low / medium / high / critical). Useful para que el aprobador sepa rápido qué nivel de revisión exigir.

EvidenceAuditMeta
Metadata auditoría

Línea inferior con “Subido por X el dd/mm · revisado por Y”. Muestra hash SHA-256 truncado para verificación posterior.

EvidenceRequiredChecklist
Checklist cierre

Checkbox visual por cada EVD requerido. Verde si aprobado, ámbar si submitted, rojo si missing. Muestra el bloqueo de cierre.

Tipos TypeScript compartidos

▸ lib/faro/evidence.types.ts
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;
};
08 · Data contract

POST /api/v1/actions/:id/evidence · multipart + metadata JSON

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.

▸ HTTP request · multipart/form-data
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--

Metadata por tipo de evidencia · ejemplos

▸ metadata JSON · varía según evidence_code
// 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"
}

API client TypeScript

▸ lib/faro/evidence.api.ts · uploadActionEvidence
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();
}

Validación frontend

▸ lib/faro/evidence-validation.ts · validateEvidenceClientSide
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;
}
09 · Response + can_close + Score

El backend devuelve evidencia + estado acción + flag de cierre

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.

▸ response · POST /api/v1/actions/:id/evidence
{
  "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
  }
}

Response después de aprobar

▸ response · POST /api/v1/evidence/:id/approve
{
  "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": []
  }
}

Response después de rechazar

▸ response · POST /api/v1/evidence/:id/reject
{
  "ok": true,
  "evidence": {
    "evidence_id": "24000000-0000-0000-0000-000000000099",
    "status": "rejected"
  },
  "action": {
    "action_id": "23000000-0000-0000-0000-000000000001",
    "status": "waiting_evidence"
  }
}

Integración con FARO Score · confidence

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:

  • Evidencia approved con trust critical: confidence 100%, recupera impacto Score completo.
  • Evidencia approved con trust high: confidence 90%, recupera impacto Score con factor 0.9.
  • Evidencia submitted sin aprobar: confidence 50%, recupera mitad del impacto Score pendiente.
  • Evidencia rejected o missing: confidence 0%, no recupera Score; la tensión sigue penalizando.
  • Evidencia 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.

Matriz de permisos por rol

Operación Responsable Aprobador Gerente Director
Cargar evidencia
Ver evidencia
Aprobar evidenciaNo
Rechazar evidenciaNo
Pedir más informaciónNo
Archivar evidenciaNoNo
Descargar evidenciaSegún permiso
10 · Mockup visual estático

Modal de carga + panel evidencia · Empresa Demo Cuyo S.A.

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.

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.”

11 · Cross-references

Dónde se cruza este flujo con el resto del pack

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.

Próximos pasos para cerrar el loop

  1. FARO-UI-005 · Timeline de Ejecución. Mostrar cronológicamente evidence_uploaded, evidence_approved, evidence_rejected, action_closed e impacto Score como una historia auditable.
  2. FARO-SCORE · Motor Score con confidence por evidencia. Construir motor-score-mvp.html que documente cómo el trust level y el estado de evidencia ponderan la confianza de cierre y la recuperación de Score.
  3. FARO-TPL-001 · Alertas email. Enviar mail al aprobador cuando se carga evidencia que él tiene que revisar, y al responsable cuando le rechazan o le piden más información.
  4. FARO-AUDIT · Auditoría y trazabilidad. Agregar registros en audit.audit_log para evidence_uploaded, evidence_approved, evidence_rejected, evidence_archived y, en el futuro, file_downloaded.
  5. Fase 4 · Seguridad avanzada de archivos. Signed URLs temporales, virus scan posterior al upload, descarga auditada y versionado de evidencia para reemplazos sin perder histórico.