# B2BCall — Banco de Dados PostgreSQL 17, um único cluster/instância com **dois schemas** que nunca se misturam (ver `docs/ARCHITECTURE.md` §3.3): - **`public`** — domínio da aplicação (este documento). Gerenciado 100% por Prisma (`packages/database/prisma/schema.prisma` + `prisma/migrations/`). - **`asterisk`** — objetos de Realtime do Asterisk (`ps_endpoints`, `ps_auths`, `ps_aors`, `ps_contacts`, `ps_endpoint_id_ips`, `ps_registrations`, `cdr`, `cel`). Criado por `infrastructure/postgres/init/002-asterisk-realtime.sql` e escrito exclusivamente por `apps/api/src/telephony/pjsip-realtime.service.ts` (nunca por SQL solto em outro lugar da aplicação). ## Modelos (schema `public`) | Modelo | Propósito | |---|---| | `User`, `Session`, `PasswordResetToken` | Autenticação (Fase 3) | | `Role`, `Permission`, `UserRole`, `RolePermission` | RBAC | | `AuditLog` | Auditoria de todas as ações sensíveis | | `Trunk`, `Extension`, `ExtensionState` | Telefonia (Fase 4) — `Trunk`/`Extension` são o CRUD da aplicação; os objetos PJSIP reais ficam no schema `asterisk` | | `DialplanEntry`, `DialplanVersion` | Dialplan estruturado versionado | | `Queue`, `QueueMember`, `PauseReason` | Call Center (Fase 5) | | `Agent`, `AgentSession`, `AgentStateEvent`, `AgentPauseEvent` | Máquina de estados do agente — sempre exatamente um `AgentStateEvent` aberto (`ended_at IS NULL`) por agente | | `Campaign`, `LeadImport`, `Lead`, `DialAttempt` | Discador preditivo (Fase 6). `DialAttempt` é a "chamada" como state machine (seção 36) — `id` próprio, nunca o `UNIQUEID` do Asterisk como PK de negócio | | `CallDisposition`, `Callback` | Disposição de chamada e agendamento de retorno | | `SuppressionEntry` | Lista de bloqueio (DNC) | | `ComplianceSettings` | Parâmetros de compliance (singleton) | ## Decisões importantes - **Prisma como ORM** (não Drizzle/TypeORM) — ver justificativa em `docs/ARCHITECTURE.md` §3.6.1. - **Índices de alto volume**: `Lead(campaignId, status, nextAttemptAt)`, `DialAttempt` por `campaignId`/`state`/`asteriskUniqueId`, `AuditLog(createdAt)`, `AuditLog(entityType, entityId)`. Consultas de relatório usam paginação server-side sempre (nunca `SELECT *` sem filtro — seção 54). - **Reserva concorrente de leads**: `SELECT ... FOR UPDATE SKIP LOCKED` via `$queryRaw` em `apps/dialer-worker/src/lead-repository.ts` — não modelado via Prisma de alto nível porque a transição atômica READY→RESERVED precisa de controle fino de lock que o ORM não expõe com segurança suficiente sob concorrência real. - **Particionamento**: avaliado, não implementado (volume atual não justifica). `DialAttempt`/`AuditLog` são os candidatos naturais quando o volume crescer — desenhados para permitir particionamento por `started_at`/`created_at` sem migração destrutiva. - **Segredos**: `Trunk.secretEncrypted` e `Extension.sipPasswordEncrypted` são cifrados com AES-256-GCM (`packages/shared/src/secret-crypto.ts`), chave mestra em `SECRETS_MASTER_KEY` (.env, nunca no banco). ## Migrations ```bash cd packages/database pnpm exec prisma migrate dev --name # desenvolvimento pnpm exec prisma migrate deploy # produção (usado por scripts/update.sh) pnpm run seed # permissões, perfis, bootstrap super_admin ``` Nenhuma alteração de schema é feita manualmente em produção — sempre via migration versionada e commitada (seção 85). ## Backup / Restore Ver `docs/BACKUP_RESTORE.md`. Resumo: `pg_dump -Fc` do banco inteiro cobre `public` **e** `asterisk` num único arquivo, já que ambos vivem na mesma instância Postgres.