Pular para conteúdo

Especificação de API e Integração Moodle

O funcionamento do Módulo de Cenários Interativos baseia-se em uma arquitetura híbrida. A estrutura visual (layout, polígonos, estilos) é carregada via JSON estático da CDN, enquanto o estado de permissão (acesso liberado/bloqueado) e os links finais são resolvidos dinamicamente consultando a API interna do Moodle.

1. Definição de Cenário (Scenario Definition / Static JSON)

Este arquivo atua como a Fonte de Verdade Visual, determinando o layout, os hotspots e os comportamentos da interface:

  • Regras Visuais: Instrui o renderizador sobre o posicionamento exato dos elementos.
  • Imutabilidade: É um arquivo JSON estático, servido via CDN e otimizado para cache.
  • Geometria Avançada: Suporta coordenadas simples e formas complexas (polígonos em perspectiva) e comportamentos de modal (Exemplo: Expandir a visualização de um TV que compõe um cenário).
  • Ponto de Integração: A propriedade moodle.activityId atua como chave de ligação com o estado dinâmico do Moodle.

1.1 Schema do Objeto (TypeScript: CenarioConfig)

A estrutura suporta hotspots retangulares simples, formas complexas (polígonos) e aninhamento para modais (TV expandida).

export interface CenarioConfig {
  /** Identificador único do cenário para cache/logs */
  id: string;
  /** URL absoluta da imagem de fundo */
  image: string;
  /** Dimensões originais para cálculo de proporção (Reference Resolution) */
  width: number;
  height: number;
  /** Lista de zonas interativas */
  hotspots: Hotspot[];
}

export interface Hotspot {
  id: string;

  // --- Geometria e Estilo ---

  /** 
   * Se definido, usa clip-path: polygon() para formas irregulares (ex: porta em perspectiva).
   * Matriz de coordenadas: [[x1, y1], [x2, y2], ...] 
   */
  points?: number[][];

  /** Se points não for definido, usa geometria retangular padrão */
  x?: number;
  y?: number;
  width?: number;
  height?: number;

  /** Classe CSS para estilização específica (ex: 'glass-door', 'tv-screen') */
  variant?: string;

  /** Estilo inline para ajustes finos (ex: 'opacity: 0.5; transform: skewY(10deg)') */
  customStyle?: string;

  // --- Comportamento ---

  /** Define o que acontece ao clicar. Default: 'navigate' */
  action?: 'navigate' | 'modal';

  /** Obrigatório se action === 'modal'. Define o conteúdo da TV/Zoom */
  modalData?: CenarioConfig;

  // --- Acessibilidade ---
  title: string;     // Texto do Tooltip (vísivel em mouseover)
  ariaLabel: string; // Leitura de tela

  // --- Integração Dinâmica (Moodle - Resolução de Link)
  moodle?: {
    /**
     * 'activity': Busca status/link de uma atividade específica (Padrão).
     * 'course': Gera link para a raiz do curso atual.
     */
    type?: 'activity' | 'course';

    /**
     * ID do Módulo de Atividade(cmid)
     * O JS usará este ID para buscar a URL real e o status de acesso no Moodle.
     * Obrigatório se type === 'activity'. 
     */
    activityId?: number;
  };

  // --- Fallback (Links Externos) 
  // Usado apenas se 'moodle.activityId' não for definido ou se for um link externo
  link?: {
    url: string;
    target: '_self' | '_blank';
  };
}

1.2 Exemplo de JSON (CDN)

Exemplo cobrindo: Porta em perspectiva (Polígono), TV Expandida (Modal) e Botão Voltar (Home).

{
  "id": "cenario-home-v1",
  "image": "https://cdn.exemplo.com/assets/images/cenario-geral-capa.jpg",
  "width": 1920,
  "height": 1080,
  "hotspots": [
    {
      "id": "btn_forum_avisos",
      "x": 100,
      "y": 200,
      "width": 150,
      "height": 150,
      "title": "Avisos Gerais",
      "ariaLabel": "Acessar Fórum de Avisos",
      "moodle": {
        "type": "activity",
        "activityId": 1058
      }
    },
    {
      "id": "porta_vidro_esquerda",
      "title": "Acessar Triagem",
      "ariaLabel": "Entrar na sala de Triagem",
      "variant": "glass-door",
      "points": [ [100, 200], [300, 150], [300, 800], [100, 900] ],
      "action": "navigate",
      "moodle": {
        "type": "activity",
        "activityId": 1058
      }
    },
    {
      "id": "tv_informativa",
      "title": "Ver Materiais Complementares",
      "ariaLabel": "Expandir Televisão",
      "variant": "tv-screen",
      "x": 800, "y": 400, "width": 320, "height": 180,
      "action": "modal",
      "modalData": {
        "id": "modal-tv",
        "image": "https://cdn.exemplo.com/assets/tv-expandida.jpg",
        "width": 1920, "height": 1080,
        "hotspots": [
           { 
             "id": "btn_video_aula", 
             "x": 200, "y": 500, "width": 300, "height": 100, 
             "title": "Assistir Vídeo",
             "ariaLabel": "Assistir Vídeo",
             "moodle": { "activityId": 2001 } 
           }
        ]
      }
    },
    {
      "id": "btn_sair",
      "title": "Voltar ao Curso",
      "ariaLabel": "Sair do Cenário",
      "variant": "btn-exit",
      "x": 1800, "y": 50, "width": 80, "height": 80,
      "moodle": {
        "type": "course"
      }
    },
    {
      "id": "link_externo_biblioteca",
      "x": 900,
      "y": 800,
      "width": 80,
      "height": 80,
      "title": "Biblioteca Virtual",
      "ariaLabel": "Acessar Biblioteca Externa",
      "link": {
        "url": "https://bce.unb.br",
        "target": "_blank"
      }
    }
  ]
}

2. Integração com Estado do Curso (Moodle AJAX)

O script utiliza a API de serviço do Moodle para obter a árvore de atividades do curso atual e determinar o acesso, permitindo resolver links dinâmicos e verificar permissões de forma segura.

2.1 Detecção de Ambiente

Antes de realizar chamadas, o script valida se está rodando dentro do Moodle e extrai as credenciais da sessão:

const isMoodleEnv = typeof window.M !== 'undefined' && window.M.cfg;
const wwwroot = isMoodleEnv ? window.M.cfg.wwwroot : '';
const sesskey = isMoodleEnv ? window.M.cfg.sesskey : '';
const courseId = isMoodleEnv ? document.querySelector('body')?.classList.value.match(/course-(\d+)/)?.[1] : null;

2.2 Requisição de Serviço (core_courseformat_get_state)

Esta chamada retorna a árvore completa do curso, incluindo visibilidade e URLs geradas pelo backend.

Endpoint: ${wwwroot}/lib/ajax/service.php?sesskey=${sesskey} Método: POST Payload:

[
  {
    "index": 0,
    "methodname": "core_courseformat_get_state",
    "args": {
      "courseid": 33 // ID capturado do body (HTML)
    }
  }
]

2.3 Resposta Real (Moodle)

O Moodle retorna um array onde o campo data contém uma string JSON (double-encoded). O script deve fazer o parse dessa string para acessar a lista cm (Course Modules).

{
  "course": {
    "id": "33",
    "baseurl": "https://licosc.unb.br/course/view.php?id=33"
  },
  "cm": [
    {
      "id": "123",
      "name": "Avisos",
      "modname": "forum",
      "visible": true,
      "uservisible": true,
      "url": "https://licosc.unb.br/mod/forum/view.php?id=123"
    },
    {
      "id": "456",
      "name": "Atividade Prática",
      "modname": "middag",
      "visible": true,
      "uservisible": false,
      "url": "https://licosc.unb.br/mod/middag/view.php?id=456"
    }
  ]
}

Nota: O JSON acima foi simplificado para exibir apenas os campos relevantes.

2.4 Lógica de Processamento

Para cada hotspot definido no JSON visual:

  1. Tipo course:

    • Não requer busca no array cm.
    • Gera URL direta: ${wwwroot}/course/view.php?id=${courseId}.
  2. Tipo activity (com activityId):

    • Busca: Itera sobre o array cm da resposta para encontrar o objeto com id correspondente.
    • Resolução de Link: Atualiza a URL do hotspot com o valor de cm.url. Isso garante integridade mesmo se a URL mudar no servidor (se os IDs forem iguais).
    • Controle de Acesso: Verifica a propriedade uservisible.
      • true: O hotspot é habilitado e clicável.
      • false: O hotspot é bloqueado (classe .middag-app-cn-locked), prevenindo o clique e exibindo tooltip de "Acesso Restrito".

2.5 Tratamento de Erros (Fail Open)

Caso a chamada à API do Moodle falhe (ex: Timeout, Erro 500 ou resposta malformada), o sistema adota uma postura de Fail Open (Liberar Acesso) para não prejudicar a experiência do usuário:

  1. Estado Visual: Assume que o hotspot está Habilitado (remove bloqueios visuais).
  2. Resolução de URL:
    • Tenta usar o objeto link definido explicitamente do JSON (se existir).
    • Se não, tenta construir uma URL genérica: .../mod/{modname}/view.php?id={activityId}.
  3. Comportamento: Permite o clique e o aluno acessa o recurso. Se o aluno realmente não tiver permissão, o próprio Moodle exibirá sua página padrão de "Acesso Negado", se necessário.

3. Mock Data (Desenvolvimento/Homologação)

Para testar fora do Moodle (localhost/Cloudflare Pages), utilizamos um arquivo estático que simula a estrutura de resposta da API service.php.

Arquivo: /public/mocks/moodle-state.json

[
  {
    "error": false,
    "data": "{\"course\":{\"id\":\"33\"},\"section\":[{\"id\":\"334\",\"title\":\"Geral\"}],\"cm\":[{\"id\":\"1058\",\"name\":\"Avisos\",\"uservisible\":true,\"url\":\"https://licosc.unb.br/mod/forum/view.php?id=1058\"},{\"id\":\"1069\",\"name\":\"Diagnóstico inicial\",\"uservisible\":false,\"url\":\"https://licosc.unb.br/mod/assign/view.php?id=1069\"}]}"
  }
]

3.1 Interface TypeScript

Para garantir tipagem forte no tratamento dos dados:

export interface MoodleResponse {
  error: boolean;
  data: string; // JSON stringified interno
}

export interface MoodleStateResponse {
  course: {
    id: string;
  };
  cm: MoodleCourseModule[];
}

export interface MoodleCourseModule {
  id: string | number;    // Moodle retorna string, converter para number na comparação
  name: string;
  modname: string;        // ex: 'page', 'quiz', 'forum'
  visible: boolean;       // Visibilidade global
  uservisible: boolean;   // Define se o aluno pode clicar (Checa restrições)
  url: string;            // URL final resolvida
}

Próximo passo na leitura: Entenda como o componente renderiza visualmente estas regras na Especificação do Componente.