Initial project import
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
# Sistema de Contabilidade de Condomínios — Arquitetura v3.0 (FINAL)
|
||||
|
||||
> Substitui v2.0. Escopo drasticamente simplificado: o sistema é um **cadastro de referência** para o escritório — não um ERP de condomínio. Sem reservas, sem moradores, sem financeiro do condomínio, sem ocorrências, sem fornecedores, sem votação. Só: condomínios, contatos (síndico/subsíndico/conselheiro), funcionários vinculados, áreas comuns, assembleias e avisos/alertas com documentos anexados.
|
||||
|
||||
**Stack:** Java puro (JDBC) + `HttpServer` do JDK · PostgreSQL 16 (1 database por empresa) · Frontend HTML/CSS/JS puro no mesmo processo · Docker Compose.
|
||||
|
||||
---
|
||||
|
||||
## 1. Diagrama — `db_master` (sem mudança desde v2.0)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
EMPRESAS ||--o{ FUNCIONARIOS : tem
|
||||
EMPRESAS ||--o{ API_KEYS : possui
|
||||
EMPRESAS ||--o{ EMPRESA_FUNCIONALIDADES : libera
|
||||
FUNCIONALIDADES ||--o{ EMPRESA_FUNCIONALIDADES : referenciada_em
|
||||
EMPRESAS ||--o{ EVOLUTION_INSTANCIAS : cadastra
|
||||
PLANOS ||--o{ PLANO_FUNCIONALIDADES : inclui
|
||||
FUNCIONALIDADES ||--o{ PLANO_FUNCIONALIDADES : referenciada_em
|
||||
PLANOS ||--o{ ASSINATURAS : usado_em
|
||||
EMPRESAS ||--o{ ASSINATURAS : possui
|
||||
EMPRESAS ||--o{ PERIODOS_USO : tem
|
||||
PERIODOS_USO ||--o{ PAGAMENTOS : gera
|
||||
EMPRESAS ||--o{ LOGS_ENVIO_AVISOS : gera
|
||||
EMPRESAS ||--o{ LEMBRETES_COBRANCA : recebe
|
||||
```
|
||||
|
||||
Schema igual ao documento v2.0 (empresas, funcionarios, api_keys, funcionalidades, empresa_funcionalidades, evolution_instancias, configuracoes_plataforma, planos, plano_funcionalidades, assinaturas, periodos_uso, pagamentos, logs_envio_avisos, lembretes_cobranca) — nada disso mudou.
|
||||
|
||||
---
|
||||
|
||||
## 2. Diagrama — `db_empresa_<slug>` (NOVO, bem mais simples)
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CONDOMINIOS ||--o{ CONTATO_CONDOMINIO : vincula
|
||||
CONTATOS ||--o{ CONTATO_CONDOMINIO : vinculado_a
|
||||
CONDOMINIOS ||--o{ FUNCIONARIO_CONDOMINIO : atendido_por
|
||||
CONDOMINIOS ||--o{ ESPACOS_COMUNS : possui
|
||||
CONDOMINIOS ||--o{ ASSEMBLEIAS : realiza
|
||||
CONDOMINIOS ||--o{ AVISOS : publica
|
||||
ASSEMBLEIAS ||--o{ AVISOS : gera
|
||||
AVISOS ||--o{ AVISO_DOCUMENTOS : anexa
|
||||
DOCUMENTOS ||--o{ AVISO_DOCUMENTOS : anexado_em
|
||||
AVISOS ||--o{ AVISO_DESTINATARIOS : enviado_para
|
||||
CONDOMINIOS ||--o{ DOCUMENTOS : arquiva
|
||||
```
|
||||
|
||||
### Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE condominios (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
nome VARCHAR(150) NOT NULL,
|
||||
cnpj VARCHAR(18) UNIQUE,
|
||||
endereco VARCHAR(255),
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- pessoa física (síndico/subsíndico/conselheiro) — cadastrada uma vez,
|
||||
-- pode ser vinculada a mais de um condomínio
|
||||
CREATE TABLE contatos (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
nome VARCHAR(150) NOT NULL,
|
||||
telefone VARCHAR(20), -- WhatsApp
|
||||
email VARCHAR(150),
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- vínculo N:N entre contato e condomínio, com o papel que exerce ali
|
||||
CREATE TABLE contato_condominio (
|
||||
contato_id BIGINT NOT NULL REFERENCES contatos(id),
|
||||
condominio_id BIGINT NOT NULL REFERENCES condominios(id),
|
||||
tipo VARCHAR(20) NOT NULL, -- sindico, subsindico, conselheiro
|
||||
data_inicio_mandato DATE,
|
||||
data_fim_mandato DATE,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ativo',
|
||||
PRIMARY KEY (contato_id, condominio_id, tipo)
|
||||
);
|
||||
-- "síndico atual" de um condomínio = SELECT ... WHERE condominio_id=X AND tipo='sindico' AND status='ativo'
|
||||
|
||||
-- funcionário do escritório (cadastro fica em db_master.funcionarios) vinculado
|
||||
-- a quais condomínios ele cuida — também recebe avisos/alertas desses condomínios
|
||||
CREATE TABLE funcionario_condominio (
|
||||
funcionario_id BIGINT NOT NULL, -- referência a db_master.funcionarios, sem FK cross-db
|
||||
condominio_id BIGINT NOT NULL REFERENCES condominios(id),
|
||||
PRIMARY KEY (funcionario_id, condominio_id)
|
||||
);
|
||||
|
||||
CREATE TABLE espacos_comuns (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
condominio_id BIGINT NOT NULL REFERENCES condominios(id),
|
||||
nome VARCHAR(100) NOT NULL, -- salão de festas, churrasqueira, quadra...
|
||||
descricao TEXT,
|
||||
valor_utilizacao_centavos BIGINT,
|
||||
indice_correcao VARCHAR(30), -- IGPM, IPCA, definido em assembleia...
|
||||
data_ultimo_reajuste DATE,
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE assembleias (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
condominio_id BIGINT NOT NULL REFERENCES condominios(id),
|
||||
tipo VARCHAR(20) NOT NULL, -- ordinaria, extraordinaria
|
||||
data_hora TIMESTAMP NOT NULL,
|
||||
local VARCHAR(150),
|
||||
pauta TEXT,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'agendada', -- agendada, realizada, cancelada
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE documentos (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
condominio_id BIGINT NOT NULL REFERENCES condominios(id),
|
||||
tipo VARCHAR(40), -- ata, convocacao, regimento, convencao, contrato, outro
|
||||
nome_arquivo VARCHAR(255) NOT NULL,
|
||||
caminho_armazenamento VARCHAR(500) NOT NULL,
|
||||
versao INT NOT NULL DEFAULT 1,
|
||||
enviado_por BIGINT, -- id em db_master.funcionarios
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE avisos (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
condominio_id BIGINT NOT NULL REFERENCES condominios(id),
|
||||
assembleia_id BIGINT REFERENCES assembleias(id), -- opcional: aviso sobre uma assembleia específica
|
||||
tipo VARCHAR(20) NOT NULL DEFAULT 'aviso', -- aviso, alerta
|
||||
titulo VARCHAR(150) NOT NULL,
|
||||
mensagem TEXT NOT NULL,
|
||||
data_evento TIMESTAMP, -- data/hora do evento relacionado (se houver)
|
||||
data_publicacao TIMESTAMP NOT NULL DEFAULT now(),
|
||||
data_expiracao TIMESTAMP,
|
||||
prioridade VARCHAR(10) DEFAULT 'normal', -- baixa, normal, alta, urgente
|
||||
enviar_whatsapp BOOLEAN NOT NULL DEFAULT false,
|
||||
enviar_email BOOLEAN NOT NULL DEFAULT false,
|
||||
status_envio VARCHAR(20) NOT NULL DEFAULT 'nao_enviado', -- nao_enviado, enviado, falhou
|
||||
criado_por_funcionario BIGINT, -- id em db_master.funcionarios
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE aviso_documentos (
|
||||
aviso_id BIGINT NOT NULL REFERENCES avisos(id),
|
||||
documento_id BIGINT NOT NULL REFERENCES documentos(id),
|
||||
PRIMARY KEY (aviso_id, documento_id)
|
||||
);
|
||||
|
||||
-- destinatário pode ser um contato (síndico/subsíndico/conselheiro) OU um funcionário do escritório
|
||||
CREATE TABLE aviso_destinatarios (
|
||||
aviso_id BIGINT NOT NULL REFERENCES avisos(id),
|
||||
tipo_destinatario VARCHAR(20) NOT NULL, -- contato, funcionario
|
||||
destinatario_id BIGINT NOT NULL, -- contatos.id ou db_master.funcionarios.id, conforme tipo
|
||||
PRIMARY KEY (aviso_id, tipo_destinatario, destinatario_id)
|
||||
);
|
||||
|
||||
CREATE TABLE log_auditoria (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
funcionario_id BIGINT,
|
||||
acao VARCHAR(20) NOT NULL, -- create, update, delete
|
||||
tabela_afetada VARCHAR(60) NOT NULL,
|
||||
registro_id BIGINT,
|
||||
dados_anteriores JSONB,
|
||||
dados_novos JSONB,
|
||||
criado_em TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
**Removido em relação à v2.0:** `contatos_condominio` (virou `contatos` + `contato_condominio`), `reservas_area_comum`, `regras_cobranca`, `historico_reajustes`, `prestacoes_contas`, `lancamentos_financeiros`, `ocorrencias`, `fornecedores`, `votacoes`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Fluxo do exemplo que você deu
|
||||
|
||||
"Assembleia dia 23/11/2026 às 18:00 → vincular documentos pertinentes":
|
||||
1. Cadastra em `assembleias`: `condominio_id`, `tipo='ordinaria'`, `data_hora='2026-11-23 18:00'`, `pauta`.
|
||||
2. Cria um `avisos` com `assembleia_id` apontando pra ela, `tipo='aviso'`, `data_evento` = mesma data/hora.
|
||||
3. Anexa os documentos (convocação, pauta detalhada) via `aviso_documentos`.
|
||||
4. Escolhe destinatários em `aviso_destinatarios` (síndico, subsíndico, conselheiros daquele condomínio + funcionários vinculados) — ou marca "todos" como padrão sugerido.
|
||||
5. Marca `enviar_whatsapp`/`enviar_email` e dispara.
|
||||
|
||||
---
|
||||
|
||||
## 4. Autenticação, financeiro/AbacatePay, dashboard, catálogo de funcionalidades
|
||||
|
||||
Sem mudanças em relação à v2.0 — seguem valendo:
|
||||
- 3 camadas de auth (master / escritório / API Key)
|
||||
- Módulo financeiro do escritório (assinaturas, períodos de uso, pagamentos PIX via AbacatePay, bloqueio automático)
|
||||
- Menu do dashboard (Master: Clientes/Planos/Financeiro/Configurações · Cliente: Cadastro Condomínio/Financeiro/Configurações)
|
||||
- Catálogo de `funcionalidades` (whatsapp, e-mail, API, exportação, auditoria etc.)
|
||||
|
||||
---
|
||||
|
||||
## 5. Endpoints ajustados
|
||||
|
||||
```
|
||||
CRUD /api/condominios
|
||||
CRUD /api/condominios/{id}/contatos (vincula/desvincula tipo=sindico|subsindico|conselheiro)
|
||||
CRUD /api/condominios/{id}/funcionarios-vinculados
|
||||
CRUD /api/condominios/{id}/espacos-comuns
|
||||
CRUD /api/condominios/{id}/assembleias
|
||||
CRUD /api/condominios/{id}/documentos
|
||||
POST /api/condominios/{id}/avisos (body: assembleia_id?, documentos: [id,...], destinatarios: [{tipo, id}, ...])
|
||||
GET /api/condominios/{id}/avisos/{id}
|
||||
```
|
||||
|
||||
**App externo (API Key)**
|
||||
```
|
||||
GET /api/external/condominios/{id}/resumo
|
||||
→ síndico/subsíndico/conselheiros atuais, próxima assembleia, avisos ativos, espaços comuns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Próximos passos
|
||||
|
||||
Escopo definitivo fechado. Posso começar os prompt files numerados agora (`01-setup-projeto-docker.md` em diante)?
|
||||
@@ -0,0 +1,115 @@
|
||||
# Prompt 01 — Setup do projeto, Docker e HttpServer básico
|
||||
|
||||
## Contexto
|
||||
Início do zero. Projeto: sistema de cadastro de referência para escritórios de contabilidade de condomínio (multi-tenant, 1 database PostgreSQL por empresa + 1 `db_master`). Stack: **Java puro** (sem framework), JDBC puro, `com.sun.net.httpserver.HttpServer` (built-in do JDK) para HTTP, PostgreSQL 16, Docker Compose. Frontend HTML/CSS/JS puro, servido pelo mesmo processo Java.
|
||||
|
||||
Este é o primeiro prompt da sequência. Não existe nenhum código ainda.
|
||||
|
||||
## Objetivo
|
||||
Criar o esqueleto do projeto: estrutura de pastas, Dockerfile, docker-compose.yml, conexão JDBC básica com o `db_master`, e um `HttpServer` que sobe, responde a um healthcheck, e já tem a estrutura de roteamento pronta para os próximos prompts registrarem handlers.
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Estrutura de pastas
|
||||
```
|
||||
condominio-contabilidade/
|
||||
├── src/main/java/com/seconectai/condocontab/
|
||||
│ ├── Main.java
|
||||
│ ├── config/
|
||||
│ │ ├── AppConfig.java (lê variáveis de ambiente)
|
||||
│ │ └── DataSourceProvider.java (cria/gerencia conexões JDBC)
|
||||
│ ├── http/
|
||||
│ │ ├── Router.java (registra rotas -> handlers)
|
||||
│ │ └── StaticFileHandler.java (serve arquivos de webapp/)
|
||||
│ ├── master/ (vazio por enquanto, prompts futuros)
|
||||
│ ├── tenant/ (vazio por enquanto)
|
||||
│ ├── auth/ (vazio por enquanto)
|
||||
│ ├── integracao/ (vazio por enquanto)
|
||||
│ └── db/
|
||||
│ └── MigrationRunner.java (executa .sql de resources/migrations)
|
||||
├── src/main/resources/
|
||||
│ └── migrations/
|
||||
│ ├── master/ (vazio por enquanto — prompt 02)
|
||||
│ └── tenant/ (vazio por enquanto — prompt 03)
|
||||
├── src/main/webapp/
|
||||
│ └── index.html (placeholder: "Sistema no ar")
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── .env.example
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### 2. `AppConfig.java`
|
||||
Lê das variáveis de ambiente (com valores default sensatos para dev local):
|
||||
- `DB_HOST`, `DB_PORT` (5432), `DB_USER`, `DB_PASSWORD`, `DB_MASTER_NAME` (db_master)
|
||||
- `HTTP_PORT` (8080)
|
||||
- `MASTER_PASSWORD_HASH` (usado só no prompt 04, mas já lido aqui)
|
||||
- `JWT_SECRET` (usado só no prompt 04)
|
||||
|
||||
Falhar de forma clara (log + exit) se uma variável obrigatória estiver ausente.
|
||||
|
||||
### 3. `DataSourceProvider.java`
|
||||
- Sem HikariCP nem pool de terceiros: implementar um pool simples próprio (uma `BlockingQueue<Connection>` com tamanho configurável, ex: 10 conexões) OU usar `DriverManager.getConnection()` direto por enquanto (mais simples) com um método `getMasterConnection()`. Documentar no código que o cache de DataSource por tenant vem no prompt 03/06.
|
||||
- Testar a conexão na subida da aplicação (`SELECT 1`) e logar sucesso/erro.
|
||||
|
||||
### 4. `MigrationRunner.java`
|
||||
- Lê todos os arquivos `.sql` de um diretório de resources (ordenados por nome, prefixo numérico tipo `001_xxx.sql`, `002_xxx.sql`), executa os que ainda não rodaram.
|
||||
- Cria uma tabela de controle `schema_migrations (id, nome_arquivo, executado_em)` no banco de destino, se não existir.
|
||||
- Método `runMigrations(Connection conn, String diretorioResources)` reutilizável tanto para `db_master` quanto para cada `db_empresa_<slug>` (usado nos prompts 02 e 03).
|
||||
|
||||
### 5. `Router.java` + `Main.java`
|
||||
- `HttpServer` escutando em `HTTP_PORT`, com um `Router` que mapeia `path -> handler` (pode ser simples: um `Map<String, HttpHandler>` com suporte a prefixo, tipo `/api/**` cai num handler, senão cai no `StaticFileHandler`).
|
||||
- Endpoint `GET /health` retornando `200 OK` com `{"status":"ok"}` em JSON puro (sem lib de JSON externa — escrever um helper simples `Json.write(Map<String,Object>)` que serializa tipos básicos; pode ser reaproveitado nos próximos prompts).
|
||||
- `StaticFileHandler` serve os arquivos de `src/main/webapp/` (ou do diretório equivalente dentro do JAR/imagem) para qualquer rota que não comece com `/api/`.
|
||||
|
||||
### 6. Dockerfile (multi-stage)
|
||||
- Stage 1: build com `maven:3.9-eclipse-temurin-21` (ou Gradle, se preferir — usar Maven por simplicidade), gera o JAR.
|
||||
- Stage 2: `eclipse-temurin:21-jre`, copia o JAR + a pasta `webapp/`, expõe `8080`, `ENTRYPOINT ["java","-jar","app.jar"]`.
|
||||
|
||||
### 7. docker-compose.yml
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_USER: app
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: db_master
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
app:
|
||||
build: .
|
||||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_USER: app
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_MASTER_NAME: db_master
|
||||
HTTP_PORT: 8080
|
||||
MASTER_PASSWORD_HASH: ${MASTER_PASSWORD_HASH}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
depends_on:
|
||||
- postgres
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
pgdata:
|
||||
```
|
||||
|
||||
### 8. `.env.example`
|
||||
Listar todas as variáveis usadas, com comentário de exemplo (sem valores reais sensíveis).
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] `docker compose up` sobe `postgres` e `app` sem erro.
|
||||
- [ ] `GET http://localhost:8080/health` retorna `200` com `{"status":"ok"}`.
|
||||
- [ ] `GET http://localhost:8080/` retorna o `index.html` placeholder.
|
||||
- [ ] Log de startup mostra "Conexão com db_master OK".
|
||||
- [ ] `MigrationRunner` roda sem erro mesmo sem nenhum arquivo `.sql` ainda (diretório vazio = no-op).
|
||||
- [ ] Projeto builda com `mvn clean package` (ou `gradle build`) sem dependências externas além do driver JDBC do PostgreSQL (`org.postgresql:postgresql`).
|
||||
- [ ] README.md com instruções de como rodar localmente (`docker compose up`, variáveis de `.env`).
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md` marcando o Prompt 01 como concluído, com a data e um resumo de 1-2 linhas do que foi feito. Não avançar para o Prompt 02 sem esse tracker atualizado.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Prompt 02 — Migrations do `db_master`
|
||||
|
||||
## Contexto
|
||||
O Prompt 01 já entregou: projeto Java rodando em Docker, `HttpServer` no ar, `MigrationRunner` pronto pra rodar arquivos `.sql`, `DataSourceProvider` com conexão ao `db_master`. **Leia o tracker (`10-tracker-progresso.md`) antes de começar** para confirmar que o Prompt 01 está marcado como concluído.
|
||||
|
||||
Este prompt cria todo o schema do banco de catálogo (`db_master`): empresas, funcionários, planos, funcionalidades, financeiro (assinaturas/pagamentos), logs.
|
||||
|
||||
## Objetivo
|
||||
Criar os arquivos de migration SQL em `src/main/resources/migrations/master/`, rodá-los automaticamente na subida da aplicação (se `db_master` ainda não tiver as tabelas), e criar as classes Java de acesso a dados (DAO) básicas para essas tabelas — sem endpoints HTTP ainda (isso vem no Prompt 05).
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Migrations (uma tabela por arquivo, numeração sequencial)
|
||||
Criar em `src/main/resources/migrations/master/`:
|
||||
- `001_empresas.sql`
|
||||
- `002_funcionarios.sql`
|
||||
- `003_api_keys.sql`
|
||||
- `004_funcionalidades.sql`
|
||||
- `005_empresa_funcionalidades.sql`
|
||||
- `006_evolution_instancias.sql`
|
||||
- `007_configuracoes_plataforma.sql`
|
||||
- `008_planos.sql`
|
||||
- `009_plano_funcionalidades.sql`
|
||||
- `010_assinaturas.sql`
|
||||
- `011_periodos_uso.sql`
|
||||
- `012_pagamentos.sql`
|
||||
- `013_logs_envio_avisos.sql`
|
||||
- `014_lembretes_cobranca.sql`
|
||||
- `015_seed_funcionalidades.sql` (INSERT do catálogo — ver abaixo)
|
||||
|
||||
Usar exatamente o DDL do documento de arquitetura v3.0 (seção 1), tabela por tabela — não inventar colunas novas. Reproduzo aqui as tabelas que essa fase cria (copie fielmente, incluindo tipos e defaults):
|
||||
|
||||
- `empresas(id, nome, cnpj, slug, database_name, status, criado_em)`
|
||||
- `funcionarios(id, empresa_id, nome, email, senha_hash, cargo, status, criado_em)`
|
||||
- `api_keys(id, empresa_id, key_hash, descricao, ativa, criado_em, revogada_em)`
|
||||
- `funcionalidades(id, chave, nome, descricao)`
|
||||
- `empresa_funcionalidades(empresa_id, funcionalidade_id, ativa, liberado_em, liberado_por)` — PK composta
|
||||
- `evolution_instancias(id, empresa_id, instance_url, instance_token_enc, numero_whatsapp, status, solicitado_em, aprovado_em, aprovado_por)`
|
||||
- `configuracoes_plataforma(chave, valor, atualizado_em)` — PK = chave
|
||||
- `planos(id, nome, valor_mensal_centavos, descricao, ativo, criado_em)`
|
||||
- `plano_funcionalidades(plano_id, funcionalidade_id)` — PK composta
|
||||
- `assinaturas(id, empresa_id, plano_id, valor_mensal_centavos, dia_vencimento, status, criado_em)`
|
||||
- `periodos_uso(id, empresa_id, mes_referencia, status, data_liberacao, data_bloqueio)` — UNIQUE(empresa_id, mes_referencia)
|
||||
- `pagamentos(id, empresa_id, periodo_uso_id, valor_centavos, abacatepay_charge_id, qr_code_payload, qr_code_base64, status, criado_em, pago_em)`
|
||||
- `logs_envio_avisos(id, empresa_id, condominio_id, aviso_id, contato_id, contato_nome, canal, status, erro_detalhe, criado_em)`
|
||||
- `lembretes_cobranca(id, empresa_id, periodo_uso_id, canal, enviado_por, criado_em)`
|
||||
|
||||
Seed (`015_seed_funcionalidades.sql`): usar o catálogo completo (MVP + Fase 2) da seção 6 do documento de arquitetura v3.0 — 24 chaves MVP + 6 chaves Fase 2. Copiar os `INSERT INTO funcionalidades` literalmente.
|
||||
|
||||
### 2. Índices recomendados
|
||||
Adicionar (na própria migration de cada tabela, ou em um `016_indices.sql`):
|
||||
```sql
|
||||
CREATE INDEX idx_funcionarios_empresa ON funcionarios(empresa_id);
|
||||
CREATE INDEX idx_api_keys_empresa ON api_keys(empresa_id);
|
||||
CREATE INDEX idx_evolution_empresa ON evolution_instancias(empresa_id);
|
||||
CREATE INDEX idx_assinaturas_empresa ON assinaturas(empresa_id);
|
||||
CREATE INDEX idx_periodos_uso_empresa ON periodos_uso(empresa_id);
|
||||
CREATE INDEX idx_pagamentos_empresa ON pagamentos(empresa_id);
|
||||
CREATE INDEX idx_pagamentos_periodo ON pagamentos(periodo_uso_id);
|
||||
CREATE INDEX idx_logs_envio_empresa ON logs_envio_avisos(empresa_id, status);
|
||||
```
|
||||
|
||||
### 3. Rodar migrations na subida
|
||||
No `Main.java`, após confirmar conexão com `db_master`, chamar `MigrationRunner.runMigrations(masterConnection, "migrations/master")`.
|
||||
|
||||
### 4. DAOs básicos (sem endpoints ainda)
|
||||
Criar em `com.seconectai.condocontab.master.dao`:
|
||||
- `EmpresaDao` (métodos: `inserir`, `buscarPorId`, `listarTodas`, `atualizarStatus`)
|
||||
- `FuncionarioDao` (`inserir`, `buscarPorEmail`, `listarPorEmpresa`)
|
||||
- `FuncionalidadeDao` (`listarTodas`, `buscarPorChave`)
|
||||
|
||||
Usar JDBC puro (`PreparedStatement`), sem ORM. Todo valor monetário como `long` (centavos) no Java.
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] `docker compose up` roda as 15 migrations em ordem, sem erro, na primeira subida.
|
||||
- [ ] Subir o container uma segunda vez NÃO tenta re-executar migrations já aplicadas (tabela `schema_migrations` funcionando).
|
||||
- [ ] `SELECT * FROM funcionalidades` retorna as 30 linhas do seed (24 MVP + 6 Fase 2).
|
||||
- [ ] `EmpresaDao.inserir` + `EmpresaDao.buscarPorId` funcionam via um teste manual (pode ser um `main` temporário ou teste JUnit simples).
|
||||
- [ ] Nenhuma tabela usa `DELETE` como estratégia de remoção — todas têm campo de `status` para inativação lógica onde fizer sentido (já está no DDL).
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 02 concluído, data, resumo. Confirmar que Prompt 01 segue marcado como concluído antes de finalizar.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Prompt 03 — Migrations do `db_empresa_<slug>` (tenant) e criação dinâmica de database
|
||||
|
||||
## Contexto
|
||||
Prompts 01 e 02 concluídos: projeto no ar, `db_master` com schema completo. **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt cria: (a) o schema que roda dentro de cada `db_empresa_<slug>`, (b) o mecanismo que cria um novo database Postgres + roda essas migrations quando uma empresa nova é cadastrada, e (c) o cache de conexões por tenant.
|
||||
|
||||
## Objetivo
|
||||
Implementar `TenantDataSourceProvider` (cria/reaproveita conexão por `empresa_id`) e `TenantProvisioner` (cria o database físico + roda migrations quando uma empresa é criada), e as migrations do schema operacional (condomínios, contatos, avisos etc. da arquitetura v3.0).
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Migrations — `src/main/resources/migrations/tenant/`
|
||||
Criar, na ordem, usando o DDL exato da seção 2 do documento de arquitetura v3.0:
|
||||
- `001_condominios.sql`
|
||||
- `002_contatos.sql`
|
||||
- `003_contato_condominio.sql`
|
||||
- `004_funcionario_condominio.sql`
|
||||
- `005_espacos_comuns.sql`
|
||||
- `006_assembleias.sql`
|
||||
- `007_documentos.sql`
|
||||
- `008_avisos.sql`
|
||||
- `009_aviso_documentos.sql`
|
||||
- `010_aviso_destinatarios.sql`
|
||||
- `011_log_auditoria.sql`
|
||||
- `012_indices.sql`:
|
||||
```sql
|
||||
CREATE INDEX idx_contato_condominio_condominio ON contato_condominio(condominio_id);
|
||||
CREATE INDEX idx_contato_condominio_contato ON contato_condominio(contato_id);
|
||||
CREATE INDEX idx_funcionario_condominio_condominio ON funcionario_condominio(condominio_id);
|
||||
CREATE INDEX idx_espacos_condominio ON espacos_comuns(condominio_id);
|
||||
CREATE INDEX idx_assembleias_condominio ON assembleias(condominio_id, data_hora);
|
||||
CREATE INDEX idx_documentos_condominio ON documentos(condominio_id);
|
||||
CREATE INDEX idx_avisos_condominio ON avisos(condominio_id, data_publicacao);
|
||||
CREATE INDEX idx_avisos_assembleia ON avisos(assembleia_id);
|
||||
CREATE INDEX idx_aviso_destinatarios_aviso ON aviso_destinatarios(aviso_id);
|
||||
```
|
||||
|
||||
**Importante:** confira o DDL contra o documento v3.0 antes de escrever — `avisos.assembleia_id` é opcional (nullable), `aviso_destinatarios.destinatario_id` NÃO tem FK (referencia `contatos.id` OU `db_master.funcionarios.id` dependendo de `tipo_destinatario`), `funcionario_condominio.funcionario_id` também não tem FK (cross-database).
|
||||
|
||||
### 2. `TenantProvisioner`
|
||||
Classe responsável por, dado um `nome` e `slug` de empresa:
|
||||
1. Conectar no Postgres com um usuário administrativo (não o `app` comum) e rodar `CREATE DATABASE db_empresa_<slug>` (sanitizar o slug: só `[a-z0-9_]`, gerar erro claro se inválido).
|
||||
2. Abrir conexão no novo database recém-criado.
|
||||
3. Rodar `MigrationRunner.runMigrations(conn, "migrations/tenant")` nele.
|
||||
4. Retornar sucesso/erro de forma que o chamador (Prompt 05, ao cadastrar empresa) possa tratar — se falhar no meio, o ideal é logar claramente e deixar o registro em `empresas.status` como algo como `provisionamento_pendente` (pode adicionar esse status à coluna livremente, é `VARCHAR`).
|
||||
|
||||
### 3. `TenantDataSourceProvider`
|
||||
- Um `Map<Long empresaId, Connection>` (ou pool simples por tenant) em memória, populado sob demanda: na primeira requisição que precisa do tenant X, abre a conexão em `db_empresa_<database_name>` (buscando `database_name` na tabela `empresas` do master) e guarda no cache.
|
||||
- Método `getConnection(long empresaId)` — se não estiver no cache, busca `database_name` no master, conecta, guarda, retorna.
|
||||
- Não precisa de invalidação de cache sofisticada nesta fase — documentar como ponto de atenção futuro (ex: quando uma empresa é cancelada/bloqueada, talvez fechar a conexão do cache; deixar um método `invalidar(empresaId)` já pronto, mesmo que não seja chamado ainda).
|
||||
|
||||
### 4. DAOs básicos do tenant (sem endpoints ainda — vêm no Prompt 06)
|
||||
Criar em `com.seconectai.condocontab.tenant.dao`, todos recebendo a `Connection` do tenant como parâmetro (não guardam estado):
|
||||
- `CondominioDao` (`inserir`, `buscarPorId`, `listarTodos`)
|
||||
- `ContatoDao` (`inserir`, `buscarPorId`)
|
||||
- `ContatoCondominioDao` (`vincular`, `listarPorCondominio(condominioId)`, `buscarSindicoAtual(condominioId)` — query com `WHERE tipo='sindico' AND status='ativo'`)
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] Rodar `TenantProvisioner.provisionar("Empresa Teste", "empresa-teste")` cria de fato um novo database `db_empresa_empresa_teste` no Postgres, com as 11 tabelas do schema tenant.
|
||||
- [ ] Rodar de novo para outro slug cria outro database, isolado do primeiro (inserir um condomínio em um não aparece no outro).
|
||||
- [ ] `TenantDataSourceProvider.getConnection(empresaId)` retorna conexão válida e reaproveita a mesma conexão em chamadas subsequentes (validar com log ou teste).
|
||||
- [ ] Slug inválido (ex: com espaço ou maiúscula) é rejeitado com mensagem de erro clara, sem tentar criar o database.
|
||||
- [ ] `ContatoCondominioDao.buscarSindicoAtual` retorna `null`/vazio quando não há síndico ativo, sem lançar exceção.
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 03 concluído, data, resumo. Confirmar Prompts 01 e 02 seguem marcados.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Prompt 04 — Autenticação: senha master, login de funcionário (JWT) e API Key
|
||||
|
||||
## Contexto
|
||||
Prompts 01–03 concluídos: app no ar, `db_master` e `db_empresa` com schema completo, DAOs básicos, provisionamento de tenant funcionando. **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt implementa as 3 camadas de autenticação da arquitetura v3.0: master (senha única), funcionário do escritório (JWT), app externo (API Key).
|
||||
|
||||
## Objetivo
|
||||
Implementar geração/validação de JWT (sem lib pesada — implementar HMAC-SHA256 manualmente ou usar uma lib mínima tipo `java-jwt`/`jjwt`, only se realmente necessário; preferir implementação própria já que o projeto é "Java puro"), hashing de senha (BCrypt — aqui vale trazer uma lib pequena e específica, tipo `at.favre.lib:bcrypt`, já que reimplementar bcrypt do zero não compensa), geração/validação de API Key, e os middlewares de autenticação usados pelos handlers HTTP dos próximos prompts.
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Hash de senha
|
||||
- Usar BCrypt (biblioteca `at.favre.lib:bcrypt`, única dependência externa além do driver JDBC e (se precisar) da lib de JWT).
|
||||
- `PasswordHasher.hash(senha)` e `PasswordHasher.verificar(senha, hash)`.
|
||||
|
||||
### 2. JWT
|
||||
- `JwtService.gerar(Map<String,Object> claims, long validadeSegundos)` → string do token.
|
||||
- `JwtService.validar(String token)` → `Optional<Map<String,Object>> claims` (vazio se inválido/expirado).
|
||||
- Claims para o token **master**: `{"escopo": "master"}`.
|
||||
- Claims para o token de **funcionário**: `{"escopo": "empresa", "empresa_id": X, "funcionario_id": Y, "cargo": "..."}`.
|
||||
- Assinatura HMAC-SHA256 usando `JWT_SECRET` (variável de ambiente já lida no Prompt 01).
|
||||
- Validade sugerida: 8 horas para funcionário, 2 horas para master (reautenticação frequente por segurança, já que é a chave mestra do sistema).
|
||||
|
||||
### 3. API Key
|
||||
- Geração: `ApiKeyService.gerar()` → retorna a chave em texto puro (mostrada **uma única vez** ao usuário) + o hash (SHA-256 é suficiente aqui, não precisa de bcrypt) que é salvo em `api_keys.key_hash`.
|
||||
- Validação: `ApiKeyService.validar(String chaveRecebida)` → busca por hash no banco master, confere `ativa=true`, retorna o `empresa_id` vinculado (ou vazio se inválida/revogada).
|
||||
|
||||
### 4. Endpoints
|
||||
```
|
||||
POST /api/master/login body: {"senha": "..."}
|
||||
→ confere hash contra MASTER_PASSWORD_HASH (env var); se ok, retorna {"token": "..."}
|
||||
|
||||
POST /api/auth/login body: {"email": "...", "senha": "..."}
|
||||
→ busca funcionario por email (em qual empresa? funcionarios.email é único globalmente,
|
||||
então a busca não precisa de empresa_id no body), confere senha, gera JWT de empresa
|
||||
→ se funcionarios.status != 'ativo' OU empresas.status != 'ativa': erro 403 com mensagem clara
|
||||
("cadastro desativado" ou "escritório com pendência financeira", respectivamente)
|
||||
```
|
||||
|
||||
### 5. Middlewares (para uso nos próximos prompts)
|
||||
Criar filtros/handlers wrapper reutilizáveis:
|
||||
- `exigirMaster(HttpHandler)` — lê header `Authorization: Bearer <token>`, valida escopo=master, senão retorna 401.
|
||||
- `exigirFuncionario(HttpHandler)` — idem, escopo=empresa; injeta `empresa_id`/`funcionario_id`/`cargo` no contexto da requisição (pode ser via `HttpExchange.setAttribute` ou um `ThreadLocal` simples).
|
||||
- `exigirApiKey(HttpHandler)` — lê header `X-API-Key`, valida, injeta `empresa_id` no contexto.
|
||||
|
||||
### 6. Segurança básica
|
||||
- Nunca logar senha, token completo ou API key em texto puro nos logs (logar no máximo os primeiros 6 caracteres + "...").
|
||||
- Rate limiting simples no `/api/master/login` e `/api/auth/login` (ex: máximo 5 tentativas por IP a cada 5 minutos, em memória — não precisa de Redis nesta fase).
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] `POST /api/master/login` com senha errada retorna 401; com senha certa retorna um JWT válido.
|
||||
- [ ] Token master usado em `exigirFuncionario` é rejeitado (escopo errado) — e vice-versa.
|
||||
- [ ] `POST /api/auth/login` com funcionário de empresa bloqueada (`empresas.status='bloqueada'`) retorna 403 com mensagem específica de pendência financeira.
|
||||
- [ ] API Key gerada, salva com hash, validada corretamente; uma chave revogada (`ativa=false`) é rejeitada.
|
||||
- [ ] 6ª tentativa de login em 5 minutos do mesmo IP é bloqueada temporariamente.
|
||||
- [ ] Nenhum log do sistema imprime senha, token completo ou API key completa.
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 04 concluído, data, resumo. Confirmar Prompts 01–03 seguem marcados.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Prompt 05 — Painel Master: clientes, planos e funcionalidades
|
||||
|
||||
## Contexto
|
||||
Prompts 01–04 concluídos: schema completo (master + tenant), provisionamento de tenant, autenticação (master/funcionário/API Key) funcionando. **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt implementa os endpoints do menu **Master → Clientes** e **Master → Planos**, incluindo o provisionamento de empresa nova (usa o `TenantProvisioner` do Prompt 03) e o toggle de funcionalidades por empresa.
|
||||
|
||||
## Objetivo
|
||||
CRUD de empresas (clientes), CRUD de planos, ativação/desativação de funcionalidades por empresa, listagem de logs de envio com erro e listagem de clientes pagantes/inadimplentes.
|
||||
|
||||
Todos os endpoints deste prompt exigem `exigirMaster` (Prompt 04).
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Clientes (empresas)
|
||||
```
|
||||
POST /api/master/empresas
|
||||
body: {"nome": "...", "cnpj": "...", "slug": "..."}
|
||||
→ valida slug único, cria registro em `empresas` (status inicial: 'provisionando'),
|
||||
chama TenantProvisioner de forma síncrona (aceitável nesta fase — trocar por fila
|
||||
assíncrona é uma melhoria futura, não bloquear o prompt por isso),
|
||||
atualiza status para 'ativa' ao concluir (ou 'provisionamento_pendente' se falhar)
|
||||
→ retorna o registro criado, incluindo se o provisionamento deu certo
|
||||
|
||||
GET /api/master/empresas
|
||||
query params opcionais: ?status=ativa|bloqueada|cancelada
|
||||
→ lista com nome, status, plano vinculado (join com assinaturas+planos), situação do
|
||||
período de uso vigente (pago/pendente/bloqueado)
|
||||
|
||||
GET /api/master/empresas/{id}
|
||||
PUT /api/master/empresas/{id} (editar nome/cnpj — NÃO permite editar slug/database_name)
|
||||
```
|
||||
|
||||
### 2. Funcionários da empresa (cadastro pelo master também, já que "cadastro da empresa é feito pelo master")
|
||||
```
|
||||
POST /api/master/empresas/{id}/funcionarios
|
||||
body: {"nome", "email", "senha" (texto puro, só nesta chamada — vira hash), "cargo"}
|
||||
GET /api/master/empresas/{id}/funcionarios
|
||||
```
|
||||
*(Nota: o próprio escritório pode ter uma tela de gestão de funcionários mais pra frente, mas o cadastro inicial do primeiro funcionário é feito pelo master junto com a empresa, senão ninguém consegue logar.)*
|
||||
|
||||
### 3. Funcionalidades
|
||||
```
|
||||
GET /api/master/empresas/{id}/funcionalidades
|
||||
→ lista todas as `funcionalidades` com um booleano indicando se está ativa pra essa empresa
|
||||
POST /api/master/empresas/{id}/funcionalidades/{chave}/ativar
|
||||
POST /api/master/empresas/{id}/funcionalidades/{chave}/desativar
|
||||
→ grava em empresa_funcionalidades (liberado_em=now(), liberado_por=<funcionario_id do
|
||||
token master, se aplicável, senão null>)
|
||||
```
|
||||
Ao criar uma empresa vinculada a um plano (Prompt seguinte trata a criação da assinatura), as funcionalidades de `plano_funcionalidades` daquele plano devem ser copiadas automaticamente como "ativas" em `empresa_funcionalidades` — implementar esse copy aqui mesmo, no momento da criação da empresa, SE o body de criação já incluir um `plano_id` opcional.
|
||||
|
||||
### 4. Planos
|
||||
```
|
||||
GET /api/master/planos
|
||||
POST /api/master/planos body: {"nome", "valor_mensal_centavos", "descricao", "funcionalidades": ["chave1","chave2"]}
|
||||
PUT /api/master/planos/{id}
|
||||
```
|
||||
Ao criar/editar um plano com a lista de `funcionalidades`, sincronizar `plano_funcionalidades` (remover as que saíram, inserir as novas).
|
||||
|
||||
### 5. Logs de envio e clientes pagantes
|
||||
```
|
||||
GET /api/master/logs-envio?status=falhou&empresa_id=X (empresa_id opcional)
|
||||
→ lista de logs_envio_avisos, mais recentes primeiro, paginado (limit/offset)
|
||||
|
||||
GET /api/master/empresas?situacao=pagante
|
||||
→ reaproveita o GET /api/master/empresas, com filtro adicional: join em periodos_uso
|
||||
do mês corrente, status='pago'
|
||||
|
||||
POST /api/master/empresas/{id}/lembrete-cobranca
|
||||
body: {"canal": "whatsapp"|"email"}
|
||||
→ grava em lembretes_cobranca; o ENVIO de fato (mensagem real) só é implementado no
|
||||
Prompt 08, quando a integração Evolution/e-mail existir — por enquanto, só registra
|
||||
a intenção e retorna sucesso (deixar um TODO claro no código apontando pro Prompt 08)
|
||||
```
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] Criar uma empresa via `POST /api/master/empresas` de fato cria o database `db_empresa_<slug>` (reaproveitando o Prompt 03) e um primeiro funcionário consegue logar em seguida via `POST /api/auth/login`.
|
||||
- [ ] Criar empresa com slug já existente retorna erro 409, sem tentar criar database duplicado.
|
||||
- [ ] Ativar/desativar funcionalidade reflete corretamente em `GET /api/master/empresas/{id}/funcionalidades`.
|
||||
- [ ] Criar um plano com 3 funcionalidades e depois criar uma empresa apontando pra esse plano faz as 3 aparecerem já ativas em `empresa_funcionalidades`.
|
||||
- [ ] `GET /api/master/logs-envio?status=falhou` funciona mesmo sem nenhum log ainda (retorna lista vazia, não erro).
|
||||
- [ ] Todos os endpoints deste prompt retornam 401 se chamados sem token master válido.
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 05 concluído, data, resumo. Confirmar Prompts 01–04 seguem marcados.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Prompt 06 — Painel Cliente (escritório): condomínios, contatos, espaços comuns, assembleias, documentos
|
||||
|
||||
## Contexto
|
||||
Prompts 01–05 concluídos: autenticação completa, painel master funcionando, empresas sendo provisionadas com tenant próprio. **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt implementa o CRUD principal do menu **Cliente → Cadastro Condomínio**, que é o coração do sistema: condomínios, contatos (síndico/subsíndico/conselheiro), vínculo de funcionários, espaços comuns, assembleias e documentos.
|
||||
|
||||
Todos os endpoints deste prompt exigem `exigirFuncionario` (Prompt 04) e operam sobre o `db_empresa_<slug>` do `empresa_id` do token (usar `TenantDataSourceProvider` do Prompt 03).
|
||||
|
||||
## Objetivo
|
||||
CRUD completo de `condominios`, `contatos` + `contato_condominio`, `funcionario_condominio`, `espacos_comuns`, `assembleias`, `documentos` (upload de arquivo).
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Condomínios
|
||||
```
|
||||
POST /api/condominios body: {"nome", "cnpj", "endereco"}
|
||||
GET /api/condominios lista todos do tenant
|
||||
GET /api/condominios/{id}
|
||||
→ resposta agregada: dados do condomínio + síndico atual (via ContatoCondominioDao) +
|
||||
contagem de assembleias futuras + contagem de avisos ativos (isso é o "checklist"
|
||||
que o funcionário usa como referência rápida)
|
||||
PUT /api/condominios/{id}
|
||||
```
|
||||
|
||||
### 2. Contatos (síndico/subsíndico/conselheiro)
|
||||
```
|
||||
POST /api/contatos body: {"nome", "telefone", "email"}
|
||||
→ cria a pessoa (sem vínculo ainda)
|
||||
POST /api/condominios/{id}/contatos/vincular
|
||||
body: {"contato_id" (ou dados de um contato novo pra criar+vincular numa chamada só),
|
||||
"tipo": "sindico"|"subsindico"|"conselheiro",
|
||||
"data_inicio_mandato", "data_fim_mandato"}
|
||||
→ grava em contato_condominio; se marcar tipo=sindico e já existir outro síndico ativo
|
||||
pro mesmo condomínio, perguntar/forçar: por padrão, encerrar automaticamente o mandato
|
||||
anterior (status='inativo', data_fim_mandato=hoje) e ativar o novo — deixar isso
|
||||
documentado no código como decisão de negócio
|
||||
GET /api/condominios/{id}/contatos lista todos os vínculos ativos daquele condomínio
|
||||
DELETE /api/condominios/{id}/contatos/{contatoId}?tipo=... (desvincula — não apaga o contato,
|
||||
só remove o vínculo daquele condomínio/tipo específico; um contato pode continuar
|
||||
vinculado a outros condomínios)
|
||||
```
|
||||
|
||||
### 3. Funcionários vinculados a condomínios
|
||||
```
|
||||
POST /api/condominios/{id}/funcionarios-vinculados
|
||||
body: {"funcionario_id"} (funcionario precisa existir em db_master, mesma empresa)
|
||||
GET /api/condominios/{id}/funcionarios-vinculados
|
||||
DELETE /api/condominios/{id}/funcionarios-vinculados/{funcionarioId}
|
||||
```
|
||||
|
||||
### 4. Espaços comuns
|
||||
```
|
||||
POST /api/condominios/{id}/espacos-comuns
|
||||
body: {"nome", "descricao", "valor_utilizacao_centavos", "indice_correcao"}
|
||||
GET /api/condominios/{id}/espacos-comuns
|
||||
PUT /api/condominios/{id}/espacos-comuns/{espacoId}
|
||||
→ se o valor mudar, atualizar data_ultimo_reajuste=hoje automaticamente
|
||||
```
|
||||
|
||||
### 5. Assembleias
|
||||
```
|
||||
POST /api/condominios/{id}/assembleias
|
||||
body: {"tipo", "data_hora", "local", "pauta"}
|
||||
GET /api/condominios/{id}/assembleias (ordenar por data_hora desc)
|
||||
PUT /api/condominios/{id}/assembleias/{id} (inclui mudar status: agendada/realizada/cancelada)
|
||||
```
|
||||
|
||||
### 6. Documentos (upload)
|
||||
```
|
||||
POST /api/condominios/{id}/documentos multipart/form-data: arquivo + tipo
|
||||
→ salvar o arquivo em um diretório local montado como volume Docker (ex: /app/uploads/
|
||||
<empresa_slug>/<condominio_id>/<uuid>-<nome_original>), gravar caminho_armazenamento
|
||||
com esse path relativo
|
||||
→ validar extensão (permitir: pdf, jpg, jpeg, png, docx) e tamanho máximo (ex: 10MB)
|
||||
GET /api/condominios/{id}/documentos
|
||||
GET /api/documentos/{id}/download → serve o arquivo (Content-Disposition: attachment)
|
||||
```
|
||||
Adicionar o volume de uploads no `docker-compose.yml` do Prompt 01 (`./uploads:/app/uploads`).
|
||||
|
||||
### 7. Auditoria
|
||||
Toda operação de `POST`/`PUT`/`DELETE` deste prompt deve gravar uma linha em `log_auditoria` (tabela_afetada, registro_id, ação, dados_anteriores/dados_novos em JSON, funcionario_id do token).
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] Criar um condomínio, vincular um síndico e um subsíndico, e chamar `GET /api/condominios/{id}` mostra o síndico atual corretamente.
|
||||
- [ ] Vincular um segundo síndico ao mesmo condomínio encerra automaticamente o mandato do primeiro (`status='inativo'`).
|
||||
- [ ] O mesmo `contato` pode ser vinculado como síndico em dois condomínios diferentes simultaneamente.
|
||||
- [ ] Upload de um PDF funciona; upload de um `.exe` é rejeitado com erro claro; upload de 15MB é rejeitado.
|
||||
- [ ] `log_auditoria` recebe uma linha para cada operação de escrita feita neste prompt (testar com pelo menos 3 operações diferentes).
|
||||
- [ ] Todos os endpoints só retornam/alteram dados do `empresa_id` do token — tentar acessar `/api/condominios/{id}` de outra empresa retorna 404 (não 403 — não revelar que o ID existe em outro tenant).
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 06 concluído, data, resumo. Confirmar Prompts 01–05 seguem marcados.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Prompt 07 — Financeiro: assinaturas, período de uso e AbacatePay
|
||||
|
||||
## Contexto
|
||||
Prompts 01–06 concluídos: CRUD completo do escritório funcionando. **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt implementa o menu **Cliente → Financeiro** (pagar mensalidade) e a automação de bloqueio/liberação de acesso conforme pagamento, usando AbacatePay para gerar cobrança PIX.
|
||||
|
||||
## Objetivo
|
||||
Criar/consultar `periodos_uso` do mês corrente, gerar cobrança PIX via AbacatePay, processar o webhook de confirmação, e rodar o job diário que bloqueia empresas inadimplentes.
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Cliente AbacatePay
|
||||
Consultar a documentação oficial da AbacatePay (https://docs.abacatepay.com) para confirmar o formato exato do endpoint de criação de cobrança PIX e do payload do webhook — **não assumir o formato sem checar**, a API pode ter mudado. Implementar `AbacatePayClient`:
|
||||
- `criarCobranca(valorCentavos, descricao, identificadorExterno)` → retorna `{chargeId, qrCodePayload, qrCodeBase64}`
|
||||
- Validação de assinatura do webhook, se a AbacatePay oferecer esse mecanismo (verificar na doc).
|
||||
|
||||
### 2. Endpoints do escritório
|
||||
```
|
||||
GET /api/financeiro/periodo-atual
|
||||
→ busca (ou cria, se não existir) o periodos_uso do mes_referencia = mês corrente;
|
||||
retorna status, valor da assinatura vigente, se já tem pagamento pendente/gerado
|
||||
|
||||
POST /api/financeiro/pagamentos
|
||||
→ se já existe um pagamento 'pendente' pro período atual, retorna ele (não duplica
|
||||
cobrança); senão, chama AbacatePayClient.criarCobranca com o valor da assinatura,
|
||||
grava em `pagamentos`, retorna qr_code_base64 + qr_code_payload pro frontend renderizar
|
||||
|
||||
GET /api/financeiro/pagamentos/{id}
|
||||
→ status atual (útil pro frontend fazer polling enquanto o QR Code está na tela)
|
||||
```
|
||||
|
||||
### 3. Webhook
|
||||
```
|
||||
POST /api/master/webhooks/abacatepay
|
||||
→ SEM autenticação de master/funcionário (é a AbacatePay chamando) — validar por
|
||||
assinatura/segredo do webhook, conforme a documentação da AbacatePay
|
||||
→ identifica o pagamento pelo abacatepay_charge_id, marca status='pago', pago_em=now()
|
||||
→ marca periodos_uso.status='pago', data_liberacao=now()
|
||||
→ marca empresas.status='ativa' (caso estivesse 'bloqueada')
|
||||
→ idempotente: se o webhook chegar duplicado (mesma cobrança já processada), não fazer
|
||||
nada de errado, só retornar 200 de novo
|
||||
```
|
||||
|
||||
### 4. Job de bloqueio diário
|
||||
`BloqueioJob`, rodando uma vez por dia (implementar com um `ScheduledExecutorService` simples dentro da própria aplicação Java — não precisa de cron externo nesta fase):
|
||||
1. Para cada `empresa` com status='ativa': busca (ou cria) o `periodos_uso` do mês corrente.
|
||||
2. Se `hoje > dia_vencimento` da assinatura E `periodos_uso.status != 'pago'`: marca `periodos_uso.status='bloqueado'`, `data_bloqueio=now()`, `empresas.status='bloqueada'`.
|
||||
3. Logar quantas empresas foram bloqueadas na execução.
|
||||
|
||||
### 5. Bloqueio de acesso
|
||||
No middleware `exigirFuncionario` (Prompt 04), adicionar a checagem: se `empresas.status='bloqueada'`, retornar 403 com corpo `{"erro": "pendencia_financeira", "mensagem": "..."}` em vez de deixar passar — mas **sempre permitir** o próprio `GET /api/financeiro/periodo-atual` e `POST /api/financeiro/pagamentos` mesmo bloqueado, senão o escritório não consegue nem pagar pra desbloquear.
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] `POST /api/financeiro/pagamentos` gera de fato uma cobrança na AbacatePay (testar em ambiente sandbox, se a AbacatePay oferecer) e retorna QR Code válido.
|
||||
- [ ] Chamar `POST /api/financeiro/pagamentos` duas vezes seguidas não cria duas cobranças, retorna a mesma pendente.
|
||||
- [ ] Simular o webhook (chamada manual com um payload de teste) marca o período como pago e desbloqueia a empresa.
|
||||
- [ ] Rodar o webhook duas vezes com o mesmo `charge_id` não causa erro nem duplica nada.
|
||||
- [ ] `BloqueioJob` rodado manualmente (chamar o método direto, sem esperar o schedule) bloqueia corretamente uma empresa de teste com vencimento no passado e sem pagamento.
|
||||
- [ ] Empresa bloqueada consegue logar e acessar só `/api/financeiro/**`; qualquer outro endpoint retorna 403 com o erro `pendencia_financeira`.
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 07 concluído, data, resumo. Confirmar Prompts 01–06 seguem marcados.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Prompt 08 — Avisos/alertas: Evolution API (WhatsApp), e-mail e destinatários
|
||||
|
||||
## Contexto
|
||||
Prompts 01–07 concluídos: financeiro funcionando, CRUD de condomínios/contatos completo. **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt implementa: conexão da instância WhatsApp (Evolution API) pelo escritório + aprovação pelo master, configuração de SMTP único da plataforma, e o fluxo completo de criação/envio de avisos com documentos anexados e destinatários.
|
||||
|
||||
## Objetivo
|
||||
CRUD de `avisos` + `aviso_documentos` + `aviso_destinatarios`, conexão/aprovação de instância Evolution, configuração de SMTP, envio real de mensagens (WhatsApp e e-mail) respeitando o catálogo de `funcionalidades` liberado por empresa, e gravação em `logs_envio_avisos`.
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Conectar WhatsApp (escritório)
|
||||
```
|
||||
POST /api/config/evolution-instancia/conectar
|
||||
body: {"instance_url", "numero_whatsapp"}
|
||||
→ cria a instância na Evolution API (consultar a documentação oficial da Evolution API
|
||||
pra confirmar o endpoint exato de criação de instância e pareamento por QR Code —
|
||||
não assumir o formato sem checar), grava em evolution_instancias com status='pendente'
|
||||
(mesmo depois de parear o QR Code com sucesso do lado da Evolution — a ativação pra
|
||||
USO no sistema depende da aprovação do master)
|
||||
→ retorna o QR Code (imagem/base64) pro frontend exibir e o funcionário escanear
|
||||
GET /api/config/evolution-instancia/status
|
||||
→ status atual: pendente, aprovada, rejeitada, inativa
|
||||
```
|
||||
|
||||
### 2. Aprovação (master)
|
||||
```
|
||||
GET /api/master/empresas/{id}/evolution-instancias
|
||||
POST /api/master/empresas/{id}/evolution-instancias/{instId}/aprovar
|
||||
POST /api/master/empresas/{id}/evolution-instancias/{instId}/rejeitar
|
||||
```
|
||||
|
||||
### 3. Configuração de e-mail (master, plataforma única)
|
||||
```
|
||||
GET /api/master/configuracoes
|
||||
PUT /api/master/configuracoes
|
||||
body: {"smtp_host", "smtp_port", "smtp_user", "smtp_password", "smtp_remetente"}
|
||||
→ salva em configuracoes_plataforma (senha deve ser guardada de forma reversível pra
|
||||
uso no envio, mas nunca retornada em texto puro no GET — mascarar tipo "••••1234"
|
||||
se quiser mostrar algo, ou simplesmente omitir o campo senha no GET)
|
||||
```
|
||||
|
||||
### 4. Avisos — CRUD completo
|
||||
```
|
||||
POST /api/condominios/{id}/avisos
|
||||
body: {"assembleia_id" (opcional), "tipo": "aviso"|"alerta", "titulo", "mensagem",
|
||||
"data_evento" (opcional), "data_expiracao" (opcional), "prioridade",
|
||||
"documentos": [documentoId, ...], "destinatarios": [{"tipo":"contato","id":X}, ...],
|
||||
"enviar_whatsapp": bool, "enviar_email": bool}
|
||||
→ valida que enviar_whatsapp só pode ser true se a empresa tiver a funcionalidade
|
||||
'whatsapp_envio' ativa E uma evolution_instancia com status='aprovada'; valida que
|
||||
enviar_email só pode ser true se a empresa tiver 'email_envio' ativa
|
||||
→ grava o aviso + vínculos em aviso_documentos + aviso_destinatarios
|
||||
→ se enviar_whatsapp ou enviar_email = true, dispara o envio (ver seção 5) de forma
|
||||
síncrona nesta fase (fila assíncrona fica como melhoria futura documentada no código)
|
||||
GET /api/condominios/{id}/avisos
|
||||
GET /api/condominios/{id}/avisos/{avisoId}
|
||||
```
|
||||
|
||||
### 5. Envio efetivo
|
||||
`NotificacaoService.enviarAviso(aviso)`:
|
||||
1. Busca os `aviso_destinatarios`; para cada um, resolve nome/telefone/email:
|
||||
- se `tipo_destinatario='contato'`: busca em `contatos` (tenant)
|
||||
- se `tipo_destinatario='funcionario'`: busca em `db_master.funcionarios`
|
||||
2. Para cada destinatário, se `enviar_whatsapp`: chama `EvolutionApiClient.enviarMensagem(instanceUrl, token, numero, mensagem, anexos)` (consultar doc da Evolution API pro formato exato de envio com anexo).
|
||||
3. Se `enviar_email`: chama `EmailClient.enviar(smtpConfig, destinatarioEmail, assunto=titulo, corpo=mensagem, anexos=documentos)` — usar `jakarta.mail` (única dependência nova aqui, é a forma padrão de mandar e-mail em Java puro).
|
||||
4. Para cada tentativa (sucesso ou falha), grava uma linha em `logs_envio_avisos` (no `db_master`, incluindo `contato_nome` como snapshot).
|
||||
5. Atualiza `avisos.status_envio` = 'enviado' se todas as tentativas relevantes deram certo, 'falhou' se pelo menos uma falhou.
|
||||
|
||||
### 6. Reenvio manual
|
||||
```
|
||||
POST /api/condominios/{id}/avisos/{avisoId}/reenviar
|
||||
→ reprocessa só os destinatários cujo último log foi 'falhou'
|
||||
```
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] Escritório sem a funcionalidade `whatsapp_envio` ativa recebe erro claro ao tentar criar aviso com `enviar_whatsapp=true`.
|
||||
- [ ] Conectar uma instância Evolution gera QR Code; aviso criado com `enviar_whatsapp=true` antes da aprovação do master é rejeitado com mensagem clara ("instância pendente de aprovação").
|
||||
- [ ] Depois do master aprovar a instância, o mesmo aviso consegue ser enviado.
|
||||
- [ ] Envio por e-mail funciona de ponta a ponta usando um SMTP de teste (ex: Mailtrap ou Gmail com senha de app) configurado via `PUT /api/master/configuracoes`.
|
||||
- [ ] Um destinatário com telefone/e-mail inválido gera uma linha `falhou` em `logs_envio_avisos` sem quebrar o envio pros demais destinatários.
|
||||
- [ ] `POST .../reenviar` só reprocessa quem falhou, não reenvia pra quem já recebeu com sucesso.
|
||||
- [ ] Documento anexado ao aviso (via `aviso_documentos`) é de fato enviado como anexo no WhatsApp/e-mail.
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 08 concluído, data, resumo. Confirmar Prompts 01–07 seguem marcados.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Prompt 09 — Frontend base: layout, menus, autenticação e telas principais
|
||||
|
||||
## Contexto
|
||||
Prompts 01–08 concluídos: toda a API funcionando (master, escritório, financeiro, avisos). **Leia `10-tracker-progresso.md` antes de começar.**
|
||||
|
||||
Este prompt implementa o frontend estático (HTML/CSS/JS puro, sem build step) descrito na arquitetura v3.0 — servido pelo mesmo `HttpServer` Java via `StaticFileHandler` (Prompt 01).
|
||||
|
||||
## Objetivo
|
||||
Layout base com menu lateral (Master e Cliente), tela de login única (detecta se é master ou funcionário pelo formulário escolhido), e as telas principais consumindo a API via `fetch()`.
|
||||
|
||||
## Especificação
|
||||
|
||||
### 1. Estrutura de arquivos
|
||||
```
|
||||
src/main/webapp/
|
||||
├── index.html → redireciona pra /login.html se não autenticado
|
||||
├── login.html → formulário único com abas "Master" / "Escritório"
|
||||
├── css/
|
||||
│ └── app.css (layout base: sidebar + conteúdo, sem framework CSS)
|
||||
├── js/
|
||||
│ ├── api.js (wrapper de fetch: injeta Authorization: Bearer <token do localStorage>,
|
||||
│ trata 401 redirecionando pro login, trata 403 pendencia_financeira
|
||||
│ mostrando um banner de aviso)
|
||||
│ ├── auth.js (login, logout, leitura do token)
|
||||
│ └── util.js (formatação de data, moeda em centavos → R$, toasts de sucesso/erro)
|
||||
├── master/
|
||||
│ ├── clientes.html + clientes.js
|
||||
│ ├── planos.html + planos.js
|
||||
│ ├── financeiro.html + financeiro.js
|
||||
│ └── configuracoes.html + configuracoes.js
|
||||
└── cliente/
|
||||
├── condominios.html + condominios.js (lista + detalhe agregado do Prompt 06)
|
||||
├── condominio-detalhe.html + condominio-detalhe.js
|
||||
│ (abas: Contatos / Espaços comuns / Assembleias / Documentos / Avisos)
|
||||
├── financeiro.html + financeiro.js (mostra QR Code do PIX, faz polling de status)
|
||||
└── configuracoes.html + configuracoes.js (conectar WhatsApp, mostra QR Code da Evolution)
|
||||
```
|
||||
|
||||
### 2. Layout base (menu lateral)
|
||||
Reproduzir exatamente a estrutura definida no documento de arquitetura (seção "Dashboard"):
|
||||
```
|
||||
MASTER: Clientes | Planos | Financeiro | Configurações
|
||||
CLIENTE: Cadastro Condomínio | Financeiro | Configurações
|
||||
```
|
||||
- Sidebar fixa à esquerda, com o nome da empresa/usuário logado no topo e botão de logout.
|
||||
- Indicar visualmente o item de menu ativo.
|
||||
- Responsivo o suficiente pra não quebrar em uma tela de notebook comum (não precisa ser mobile-first).
|
||||
|
||||
### 3. Autenticação no frontend
|
||||
- `login.html`: duas abas. Aba "Master" só pede senha, chama `POST /api/master/login`. Aba "Escritório" pede e-mail+senha, chama `POST /api/auth/login`.
|
||||
- Token salvo em `localStorage` (`master_token` ou `funcionario_token`, com o escopo junto pra saber pra qual sidebar redirecionar).
|
||||
- Todas as páginas verificam token válido no carregamento; se ausente/expirado, redireciona pro login.
|
||||
|
||||
### 4. Telas — nível de detalhe esperado
|
||||
Cada tela deve, no mínimo:
|
||||
- Listar os dados via `GET`, em tabela simples.
|
||||
- Ter um formulário (modal ou seção inline) para criar/editar, chamando `POST`/`PUT`.
|
||||
- Mostrar mensagens de erro da API de forma legível (não só "erro 500").
|
||||
- `condominio-detalhe.html`: a tela mais rica — mostra os dados do condomínio, e nas abas permite cadastrar contato (com seletor de tipo sindico/subsindico/conselheiro), listar/cadastrar espaços comuns, listar/cadastrar assembleias, fazer upload de documento (`<input type="file">`), e criar um aviso selecionando destinatários (checkboxes dos contatos + funcionários vinculados) e documentos já cadastrados pra anexar.
|
||||
- `cliente/financeiro.html`: mostra status do período atual; se pendente, botão "Gerar cobrança PIX" que chama a API e renderiza o QR Code (`qr_code_base64` como `<img src="data:image/png;base64,...">`) + o código copia-e-cola em um campo com botão "Copiar"; faz polling a cada 5s em `GET /api/financeiro/pagamentos/{id}` até status mudar pra 'pago'.
|
||||
- `cliente/configuracoes.html`: botão "Conectar WhatsApp" que chama a API e mostra o QR Code da Evolution pra escanear; mostra o status atual (pendente/aprovada/rejeitada).
|
||||
|
||||
### 5. Erro de pendência financeira
|
||||
Se qualquer chamada de API (fora `/api/financeiro/**`) retornar 403 com `erro: "pendencia_financeira"`, `api.js` deve interceptar e mostrar um banner fixo no topo: "Seu acesso está bloqueado por pendência de pagamento — regularize em Financeiro." com link direto pra `cliente/financeiro.html`, sem deixar a tela quebrada.
|
||||
|
||||
## Critérios de aceite
|
||||
- [ ] Login como master e como funcionário funcionam, cada um caindo na sidebar correta.
|
||||
- [ ] Fluxo completo pela UI: criar condomínio → cadastrar síndico → cadastrar espaço comum → criar assembleia → subir documento → criar aviso vinculando o documento e selecionando o síndico como destinatário.
|
||||
- [ ] Tela de financeiro do escritório gera e exibe QR Code real (integrado com o Prompt 07).
|
||||
- [ ] Tela de configurações do escritório exibe QR Code da Evolution (integrado com o Prompt 08).
|
||||
- [ ] Banner de pendência financeira aparece corretamente quando a empresa está bloqueada, sem quebrar a navegação.
|
||||
- [ ] Nenhuma tela depende de build step (webpack/vite/npm) — abre direto servido pelo `HttpServer` Java.
|
||||
|
||||
## Ao concluir
|
||||
Atualizar `10-tracker-progresso.md`: Prompt 09 concluído, data, resumo. Confirmar Prompts 01–08 seguem marcados.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Tracker de Progresso — Sistema de Contabilidade de Condomínios
|
||||
|
||||
> **Regra obrigatória para a IA executora:** antes de iniciar qualquer prompt, leia este arquivo inteiro e confirme que o prompt anterior está marcado como ✅ Concluído. Nunca avance para o próximo prompt com uma fase anterior incompleta ou com critérios de aceite não verificados. Ao terminar um prompt, atualize a linha correspondente aqui (status, data, resumo) antes de considerar a fase encerrada.
|
||||
|
||||
| # | Prompt | Status | Data | Resumo |
|
||||
|---|---|---|---|---|
|
||||
| 01 | Setup projeto, Docker, HttpServer básico | ✅ Concluído | 2026-09-28 | Projeto Java/Maven criado com Docker, PostgreSQL, HttpServer, healthcheck, arquivos estáticos e MigrationRunner no-op validados. |
|
||||
| 02 | Migrations `db_master` | ✅ Concluído | 2026-09-28 | Criadas 15 migrations master com seed de 30 funcionalidades e DAOs JDBC básicos validados. |
|
||||
| 03 | Migrations `db_empresa` (tenant) + provisionamento | ✅ Concluído | 2026-09-28 | Criadas migrations tenant, TenantProvisioner, cache por empresa e DAOs básicos com smoke test validado. |
|
||||
| 04 | Autenticação: master, JWT, API Key | ✅ Concluído | 2026-09-28 | Implementados BCrypt, JWT HMAC-SHA256, API Key, logins e middlewares com testes de aceite. |
|
||||
| 05 | Painel Master: clientes e planos | ✅ Concluído | 2026-09-28 | Implementados endpoints master de empresas, funcionários, planos, funcionalidades, logs e lembretes com validação HTTP. |
|
||||
| 06 | Painel Cliente: condomínios, contatos, espaços, assembleias, documentos | ✅ Concluído | 2026-09-28 | Implementados endpoints cliente para condomínios, contatos, funcionários vinculados, espaços, assembleias, documentos/upload e auditoria. |
|
||||
| 07 | Financeiro: assinaturas, período de uso, AbacatePay | ✅ Concluído | 2026-09-28 | Implementados período atual, cobrança PIX AbacatePay/mock, webhook idempotente, job de bloqueio e acesso financeiro para inadimplentes. |
|
||||
| 08 | Avisos/alertas: Evolution API, e-mail, destinatários | ✅ Concluído | 2026-09-28 | Implementados conexão/aprovação Evolution, SMTP master, CRUD/envio de avisos, logs e reenvio de falhas. |
|
||||
| 09 | Frontend base: layout, menus, telas principais | 🔶 Em andamento | 2026-09-28 | Frontend estático base implementado com login, layout, telas master/cliente, financeiro PIX, WhatsApp e detalhe de condomínio; build Maven validado, pendente teste E2E HTTP. |
|
||||
|
||||
Status possíveis: `⬜ Pendente` · `🔶 Em andamento` · `✅ Concluído` · `❌ Bloqueado` (se bloqueado, escrever o motivo no resumo).
|
||||
|
||||
## Decisões de arquitetura já fechadas (não reabrir sem necessidade real)
|
||||
- Java puro + JDBC, sem framework; `com.sun.net.httpserver.HttpServer` para HTTP.
|
||||
- PostgreSQL 16, 1 servidor, 1 database por empresa (`db_empresa_<slug>`).
|
||||
- Frontend HTML/CSS/JS puro, servido pelo mesmo processo (sem build step).
|
||||
- Autenticação em 3 camadas: senha master → JWT master; funcionário → JWT empresa; app externo → API Key.
|
||||
- Empresas cadastradas só pelo master (sem autoatendimento).
|
||||
- Liberação de uso mensal automática via webhook AbacatePay; bloqueio automático por job diário se vencido sem pagamento (dados nunca apagados).
|
||||
- Evolution API: cada escritório conecta sua própria instância; ativação depende de aprovação do master.
|
||||
- E-mail: uma única conta SMTP da plataforma, configurada pelo master.
|
||||
- **Sem** moradores/unidades, reservas de área comum, prestação de contas/financeiro do condomínio, ocorrências, fornecedores ou votações — sistema é um cadastro de referência, não um ERP de condomínio.
|
||||
- Contatos (síndico/subsíndico/conselheiro) são entidades desacopladas dos condomínios (N:N via `contato_condominio`) — a mesma pessoa pode atender mais de um condomínio.
|
||||
- Funcionários do escritório podem ser vinculados a condomínios específicos e também recebem avisos/alertas.
|
||||
- Assembleias são uma entidade própria; avisos podem opcionalmente referenciar uma assembleia e ter documentos anexados.
|
||||
|
||||
## Notas / pendências conhecidas (não bloqueiam o MVP, mas ficam registradas)
|
||||
- Provisionamento de tenant (Prompt 05) roda de forma síncrona na criação da empresa — migrar para fila assíncrona é uma melhoria futura.
|
||||
- Envio de aviso (Prompt 08) roda de forma síncrona — idem, fila assíncrona é melhoria futura.
|
||||
- Cache de conexão por tenant (Prompt 03) não tem invalidação automática quando uma empresa é bloqueada/cancelada — só um método pronto, não chamado ainda.
|
||||
- Funcionalidades "Fase 2" do catálogo (templates, avisos recorrentes, webhooks, busca avançada) estão seedadas no banco mas sem handler implementado.
|
||||
Reference in New Issue
Block a user