# Plano de Implementação - Chamadas de Áudio e Vídeo com Jitsi Meet

## Opção Escolhida: Docker Local (Desenvolvimento)

---

## 📋 Etapas Fora do Código - Configuração Docker

### Etapa 1: Preparar Ambiente Docker

**Requisitos:**

- Docker Desktop instalado e rodando
- Mínimo 4GB RAM disponível
- Portas livres: 8000, 8443, 10000-20000/udp

**Passos:**

1. **Criar diretório para configuração Docker Jitsi:**

```bash
mkdir -p ~/jitsi-docker
cd ~/jitsi-docker
```

2. **Clonar repositório oficial:**

```bash
git clone https://github.com/jitsi/docker-jitsi-meet.git
cd docker-jitsi-meet
```

3. **Configurar variáveis de ambiente:**

```bash
cp env.example .env
```

4. **Editar arquivo `.env` com as seguintes configurações:**

```env
# Configuração básica para desenvolvimento local
CONFIG=~/.jitsi-meet-cfg
TZ=America/Recife

# Desabilitar Let's Encrypt (não necessário para localhost)
ENABLE_LETSENCRYPT=0

# Portas HTTP/HTTPS
HTTP_PORT=8000
HTTPS_PORT=8443

# Domínio local
PUBLIC_URL=http://localhost:8000
DOMAIN=localhost

# Desabilitar autenticação para facilitar testes
ENABLE_AUTH=0
ENABLE_GUESTS=1

# Desabilitar transcrissão (não necessário para desenvolvimento)
ENABLE_TRANSCRIPTION=0

# Desabilitar gravação no servidor (usaremos gravação local)
ENABLE_RECORDING=0

# Configurações de vídeo (ajustar conforme necessidade)
ENABLE_PREJOIN_PAGE=0
START_AUDIO_MUTED=0
START_VIDEO_MUTED=0

# Configurações de segurança
ENABLE_XMPP_WEBSOCKET=0
ENABLE_P2P=1

# Limites
MAX_NUMBER_OF_PARTICIPANTS=10
RESOLUTION_WIDTH=1280
RESOLUTION_HEIGHT=720
```

5. **Criar diretórios necessários:**

```bash
mkdir -p ~/.jitsi-meet-cfg/{web/letsencrypt,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb}
```

6. **Iniciar containers:**

```bash
docker-compose up -d
```

7. **Verificar status:**

```bash
docker-compose ps
```

8. **Ver logs se necessário:**

```bash
docker-compose logs -f
```

9. **Testar acesso:**

- Acessar: http://localhost:8000
- Criar uma sala de teste e verificar se funciona

**Troubleshooting:**

- Se houver erro de permissão nos diretórios: `sudo chown -R $USER:$USER ~/.jitsi-meet-cfg`
- Se portas estiverem em uso, alterar HTTP_PORT e HTTPS_PORT no .env
- Para parar: `docker-compose down`
- Para reiniciar: `docker-compose restart`

---

## 📦 Etapas no Código - Backend Convex

### Etapa 2: Atualizar Schema

**Arquivo:** `packages/backend/convex/schema.ts`

**Adicionar nova tabela `chamadas`:**

```typescript
chamadas: defineTable({
	conversaId: v.id('conversas'),
	tipo: v.union(v.literal('audio'), v.literal('video')),
	roomName: v.string(), // Nome único da sala Jitsi
	criadoPor: v.id('usuarios'), // Anfitrião/criador
	participantes: v.array(v.id('usuarios')),
	status: v.union(
		v.literal('aguardando'),
		v.literal('em_andamento'),
		v.literal('finalizada'),
		v.literal('cancelada')
	),
	iniciadaEm: v.optional(v.number()),
	finalizadaEm: v.optional(v.number()),
	duracaoSegundos: v.optional(v.number()),
	gravando: v.boolean(),
	gravacaoIniciadaPor: v.optional(v.id('usuarios')),
	gravacaoIniciadaEm: v.optional(v.number()),
	gravacaoFinalizadaEm: v.optional(v.number()),
	configuracoes: v.optional(
		v.object({
			audioHabilitado: v.boolean(),
			videoHabilitado: v.boolean(),
			participantesConfig: v.optional(
				v.array(
					v.object({
						usuarioId: v.id('usuarios'),
						audioHabilitado: v.boolean(),
						videoHabilitado: v.boolean(),
						forcadoPeloAnfitriao: v.optional(v.boolean()) // Se foi forçado pelo anfitrião
					})
				)
			)
		})
	),
	criadoEm: v.number()
})
	.index('by_conversa', ['conversaId', 'status'])
	.index('by_conversa_ativa', ['conversaId', 'status'])
	.index('by_criado_por', ['criadoPor'])
	.index('by_status', ['status'])
	.index('by_room_name', ['roomName']);
```

### Etapa 3: Criar Backend de Chamadas

**Arquivo:** `packages/backend/convex/chamadas.ts`

**Funções a implementar:**

#### Mutations:

1. `criarChamada` - Criar nova chamada
2. `iniciarChamada` - Marcar como em andamento
3. `finalizarChamada` - Finalizar e calcular duração
4. `adicionarParticipante` - Adicionar participante
5. `removerParticipante` - Remover participante
6. `toggleAudioVideo` - Anfitrião controla áudio/vídeo de participante
7. `atualizarConfiguracaoParticipante` - Atualizar configuração individual
8. `iniciarGravacao` - Marcar início de gravação
9. `finalizarGravacao` - Marcar fim de gravação

#### Queries:

1. `obterChamadaAtiva` - Buscar chamada ativa de uma conversa
2. `listarChamadas` - Listar histórico
3. `verificarAnfitriao` - Verificar se usuário é anfitrião
4. `obterParticipantesChamada` - Listar participantes

**Tipos TypeScript (sem usar `any`):**

```typescript
import type { Id } from './_generated/dataModel';
import type { QueryCtx, MutationCtx } from './_generated/server';

type ChamadaTipo = 'audio' | 'video';
type ChamadaStatus = 'aguardando' | 'em_andamento' | 'finalizada' | 'cancelada';

interface ParticipanteConfig {
	usuarioId: Id<'usuarios'>;
	audioHabilitado: boolean;
	videoHabilitado: boolean;
	forcadoPeloAnfitriao?: boolean;
}

interface ConfiguracoesChamada {
	audioHabilitado: boolean;
	videoHabilitado: boolean;
	participantesConfig?: ParticipanteConfig[];
}
```

---

## 🎨 Etapas no Código - Frontend Svelte

### Etapa 4: Instalar Dependências

**Arquivo:** `apps/web/package.json`

```bash
cd apps/web
bun add lib-jitsi-meet
```

**Dependências adicionais necessárias:**

- `lib-jitsi-meet` - Biblioteca oficial Jitsi
- (Possivelmente tipos) `@types/lib-jitsi-meet` se disponível

### Etapa 5: Configurar Variáveis de Ambiente

**Arquivo:** `apps/web/.env`

```env
# Jitsi Meet Configuration (Docker Local)
VITE_JITSI_DOMAIN=localhost:8443
VITE_JITSI_APP_ID=sgse-app
VITE_JITSI_ROOM_PREFIX=sgse
VITE_JITSI_USE_HTTPS=false
```

### Etapa 6: Criar Utilitários Jitsi

**Arquivo:** `apps/web/src/lib/utils/jitsi.ts`

**Funções:**

- `gerarRoomName(conversaId: string, tipo: "audio" | "video"): string` - Gerar nome único da sala
- `obterConfiguracaoJitsi()` - Retornar configuração do Jitsi baseada em .env
- `validarDispositivos()` - Validar disponibilidade de microfone/webcam
- `obterDispositivosDisponiveis()` - Listar dispositivos de mídia

**Tipos (sem `any`):**

```typescript
interface ConfiguracaoJitsi {
	domain: string;
	appId: string;
	roomPrefix: string;
	useHttps: boolean;
}

interface DispositivoMedia {
	deviceId: string;
	label: string;
	kind: 'audioinput' | 'audiooutput' | 'videoinput';
}

interface DispositivosDisponiveis {
	microphones: DispositivoMedia[];
	speakers: DispositivoMedia[];
	cameras: DispositivoMedia[];
}
```

### Etapa 7: Criar Store de Chamadas

**Arquivo:** `apps/web/src/lib/stores/callStore.ts`

**Estado gerenciado:**

- Chamada ativa (se houver)
- Estado de mídia (áudio/vídeo ligado/desligado)
- Dispositivos selecionados
- Status de gravação
- Lista de participantes
- Duração da chamada
- É anfitrião ou não

**Tipos:**

```typescript
interface EstadoChamada {
	chamadaId: Id<'chamadas'> | null;
	conversaId: Id<'conversas'> | null;
	tipo: 'audio' | 'video' | null;
	roomName: string | null;
	estaConectado: boolean;
	audioHabilitado: boolean;
	videoHabilitado: boolean;
	gravando: boolean;
	ehAnfitriao: boolean;
	participantes: Array<{
		usuarioId: Id<'usuarios'>;
		nome: string;
		avatar?: string;
		audioHabilitado: boolean;
		videoHabilitado: boolean;
	}>;
	duracaoSegundos: number;
	dispositivos: {
		microphoneId: string | null;
		cameraId: string | null;
		speakerId: string | null;
	};
}

interface EventosChamada {
	'participant-joined': (participant: ParticipanteJitsi) => void;
	'participant-left': (participantId: string) => void;
	'audio-mute-status-changed': (isMuted: boolean) => void;
	'video-mute-status-changed': (isMuted: boolean) => void;
	'connection-failed': (error: Error) => void;
	'connection-disconnected': () => void;
}
```

**Métodos principais:**

- `iniciarChamada(conversaId, tipo)`
- `finalizarChamada()`
- `toggleAudio()`
- `toggleVideo()`
- `iniciarGravacao()`
- `finalizarGravacao()`
- `atualizarDispositivos()`

### Etapa 8: Criar Utilitários de Gravação

**Arquivo:** `apps/web/src/lib/utils/mediaRecorder.ts`

**Funções:**

- `iniciarGravacaoAudio(stream: MediaStream): MediaRecorder` - Gravar apenas áudio
- `iniciarGravacaoVideo(stream: MediaStream): MediaRecorder` - Gravar áudio + vídeo
- `pararGravacao(recorder: MediaRecorder): Promise<Blob>` - Parar e retornar blob
- `salvarGravacao(blob: Blob, nomeArquivo: string): void` - Salvar localmente
- `obterDuracaoGravacao(recorder: MediaRecorder): number` - Obter duração

**Tipos:**

```typescript
interface OpcoesGravacao {
	audioBitsPerSecond?: number;
	videoBitsPerSecond?: number;
	mimeType?: string;
}

interface ResultadoGravacao {
	blob: Blob;
	duracaoSegundos: number;
	nomeArquivo: string;
}
```

### Etapa 9: Criar Componente CallWindow

**Arquivo:** `apps/web/src/lib/components/call/CallWindow.svelte`

**Características:**

- Janela flutuante redimensionável e arrastável
- Integração com lib-jitsi-meet
- Container para vídeo dos participantes
- Barra de controles
- Indicador de gravação
- Contador de duração

**Props (TypeScript estrito):**

```typescript
interface Props {
	chamadaId: Id<'chamadas'>;
	conversaId: Id<'conversas'>;
	tipo: 'audio' | 'video';
	roomName: string;
	ehAnfitriao: boolean;
	onClose: () => void;
}
```

**Estrutura:**

- `<script lang="ts">` com tipos explícitos
- Uso de `$state`, `$derived`, `$effect` (Svelte 5)
- Integração com `callStore`
- Eventos do Jitsi tratados tipados

**Bibliotecas para janela flutuante:**

- Usar eventos nativos de mouse/touch para drag
- CSS para redimensionamento com handles
- localStorage para persistir posição/tamanho

### Etapa 10: Criar Componente CallControls

**Arquivo:** `apps/web/src/lib/components/call/CallControls.svelte`

**Controles:**

- Botão toggle áudio
- Botão toggle vídeo
- Botão gravação (se anfitrião)
- Botão configurações
- Botão encerrar chamada
- Contador de duração (HH:MM:SS)

**Props:**

```typescript
interface Props {
	audioHabilitado: boolean;
	videoHabilitado: boolean;
	gravando: boolean;
	ehAnfitriao: boolean;
	duracaoSegundos: number;
	onToggleAudio: () => void;
	onToggleVideo: () => void;
	onIniciarGravacao: () => void;
	onPararGravacao: () => void;
	onAbrirConfiguracoes: () => void;
	onEncerrar: () => void;
}
```

### Etapa 11: Criar Componente CallSettings

**Arquivo:** `apps/web/src/lib/components/call/CallSettings.svelte`

**Funcionalidades:**

- Listar microfones disponíveis
- Listar webcams disponíveis
- Listar alto-falantes disponíveis
- Preview de vídeo antes de aplicar
- Teste de áudio
- Botões aplicar/cancelar

**Props:**

```typescript
interface Props {
	open: boolean;
	dispositivoAtual: {
		microphoneId: string | null;
		cameraId: string | null;
		speakerId: string | null;
	};
	onClose: () => void;
	onAplicar: (dispositivos: {
		microphoneId: string | null;
		cameraId: string | null;
		speakerId: string | null;
	}) => void;
}
```

### Etapa 12: Criar Componente HostControls

**Arquivo:** `apps/web/src/lib/components/call/HostControls.svelte`

**Funcionalidades (apenas para anfitrião):**

- Lista de participantes
- Toggle áudio por participante
- Toggle vídeo por participante
- Indicador visual de quem está gravando
- Status de cada participante

**Props:**

```typescript
interface Props {
	participantes: Array<{
		usuarioId: Id<'usuarios'>;
		nome: string;
		avatar?: string;
		audioHabilitado: boolean;
		videoHabilitado: boolean;
		forcadoPeloAnfitriao?: boolean;
	}>;
	onToggleParticipanteAudio: (usuarioId: Id<'usuarios'>) => void;
	onToggleParticipanteVideo: (usuarioId: Id<'usuarios'>) => void;
}
```

### Etapa 13: Criar Componente RecordingIndicator

**Arquivo:** `apps/web/src/lib/components/call/RecordingIndicator.svelte`

**Características:**

- Banner visível no topo da janela
- Ícone animado de gravação
- Mensagem clara de que está gravando
- Informação de quem iniciou (se disponível)

**Props:**

```typescript
interface Props {
	gravando: boolean;
	iniciadoPor?: string; // Nome do usuário que iniciou
}
```

### Etapa 14: Criar Utilitário de Janela Flutuante

**Arquivo:** `apps/web/src/lib/utils/floatingWindow.ts`

**Funções:**

- `criarDragHandler(element: HTMLElement, handle: HTMLElement): () => void` - Criar handler de arrastar
- `criarResizeHandler(element: HTMLElement, handles: HTMLElement[]): () => void` - Criar handler de redimensionar
- `salvarPosicaoJanela(id: string, posicao: { x: number; y: number; width: number; height: number }): void` - Salvar no localStorage
- `restaurarPosicaoJanela(id: string): { x: number; y: number; width: number; height: number } | null` - Restaurar do localStorage

**Tipos:**

```typescript
interface PosicaoJanela {
	x: number;
	y: number;
	width: number;
	height: number;
}

interface LimitesJanela {
	minWidth: number;
	minHeight: number;
	maxWidth?: number;
	maxHeight?: number;
}
```

### Etapa 15: Integrar com ChatWindow

**Arquivo:** `apps/web/src/lib/components/chat/ChatWindow.svelte`

**Modificações:**

- Adicionar botão de chamada de áudio
- Adicionar botão de chamada de vídeo
- Mostrar indicador quando há chamada ativa
- Importar e usar CallWindow quando houver chamada

**Adicionar no topo (junto com outros botões):**

```svelte
<button
	type="button"
	class="btn btn-sm btn-circle"
	onclick={() => iniciarChamada('audio')}
	title="Ligação de áudio"
>
	<Phone class="h-4 w-4" />
</button>

<button
	type="button"
	class="btn btn-sm btn-circle"
	onclick={() => iniciarChamada('video')}
	title="Ligação de vídeo"
>
	<Video class="h-4 w-4" />
</button>
```

---

## 🔄 Ordem de Implementação Recomendada

1. ✅ **Etapa 1:** Configurar Docker Jitsi (fora do código)
2. ✅ **Etapa 2:** Atualizar schema com tabela chamadas
3. ✅ **Etapa 3:** Criar backend chamadas.ts com todas as funções
4. ✅ **Etapa 4:** Instalar dependências frontend
5. ✅ **Etapa 5:** Configurar variáveis de ambiente
6. ✅ **Etapa 6:** Criar utilitários Jitsi (jitsi.ts)
7. ✅ **Etapa 7:** Criar store de chamadas (callStore.ts)
8. ✅ **Etapa 8:** Criar utilitários de gravação (mediaRecorder.ts)
9. ✅ **Etapa 9:** Criar CallWindow básico (apenas estrutura)
10. ✅ **Etapa 10:** Integrar lib-jitsi-meet no CallWindow
11. ✅ **Etapa 11:** Criar CallControls e integrar
12. ✅ **Etapa 12:** Implementar contador de duração
13. ✅ **Etapa 13:** Implementar janela flutuante (drag & resize)
14. ✅ **Etapa 14:** Criar CallSettings e integração de dispositivos
15. ✅ **Etapa 15:** Criar HostControls e lógica de anfitrião
16. ✅ **Etapa 16:** Implementar gravação local
17. ✅ **Etapa 17:** Criar RecordingIndicator
18. ✅ **Etapa 18:** Integrar botões no ChatWindow
19. ✅ **Etapa 19:** Testes completos
20. ✅ **Etapa 20:** Ajustes finais e tratamento de erros

---

## 🛡️ Segurança e Boas Práticas

### TypeScript

- ❌ **NUNCA** usar `any`
- ✅ Usar tipos explícitos em todas as funções
- ✅ Usar tipos inferidos do Convex quando possível
- ✅ Criar interfaces para objetos complexos

### Svelte 5

- ✅ Usar `$props()` para props
- ✅ Usar `$state()` para estado reativo
- ✅ Usar `$derived()` para valores derivados
- ✅ Usar `$effect()` para side effects

### Validação

- ✅ Validar permissões no backend antes de mutações
- ✅ Validar entrada de dados
- ✅ Tratar erros adequadamente
- ✅ Logs de segurança (criação/finalização de chamadas)

### Performance

- ✅ Cleanup adequado de event listeners
- ✅ Desconectar Jitsi ao fechar janela
- ✅ Parar gravação ao finalizar chamada
- ✅ Liberar streams de mídia

---

## 📝 Notas Importantes

1. **Room Names:** Gerar room names únicos usando conversaId + timestamp + hash
2. **Persistência:** Salvar posição/tamanho da janela no localStorage
3. **Notificações:** Notificar participantes quando chamada é criada/finalizada
4. **Limpeza:** Sempre limpar recursos ao finalizar chamada
5. **Erros:** Tratar erros de conexão, permissões de mídia, etc.
6. **Acessibilidade:** Adicionar labels, ARIA attributes, suporte a teclado

---

## 🧪 Testes

### Testes Funcionais

- [ ] Criar chamada de áudio individual
- [ ] Criar chamada de vídeo individual
- [ ] Criar chamada em grupo
- [ ] Toggle áudio/vídeo
- [ ] Anfitrião controlar participantes
- [ ] Iniciar/parar gravação
- [ ] Contador de duração
- [ ] Configuração de dispositivos
- [ ] Janela flutuante drag/resize

### Testes de Segurança

- [ ] Não anfitrião não pode controlar outros
- [ ] Não anfitrião não pode iniciar gravação
- [ ] Validação de participantes
- [ ] Rate limiting de criação de chamadas

### Testes de Erros

- [ ] Conexão perdida
- [ ] Sem permissão de mídia
- [ ] Dispositivos não disponíveis
- [ ] Servidor Jitsi offline

---

## 📚 Referências

- [Jitsi Meet Docker](https://github.com/jitsi/docker-jitsi-meet)
- [lib-jitsi-meet Documentation](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-ljm-api)
- [Svelte 5 Documentation](https://svelte.dev/docs)
- [Convex Documentation](https://docs.convex.dev)
- [WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)
- [MediaRecorder API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder)

---

**Data de Criação:** 2025-01-XX  
**Versão:** 1.0  
**Opção:** Docker Local (Desenvolvimento)
