--- tags: [projeto, intranet, sessao, bling, social-media, monitor-apps, bugfix] date: 2026-04-22 originSessionId: 2b74afa8-ad08-407b-80cf-49ce17097f66 --- # Sessão 22/04/2026 - Bling + Social Media completo + MonitorApps + Calendário Sessão longa (continuação), múltiplas frentes: correção bling fingerprint, refactor social media completo, publicação Instagram ponta-a-ponta, UX cliente e admin, monitor apps. ## 1. Bling - fix fingerprint (falso positivo) + limpeza contaminação **Problema**: Contas com múltiplos canais (Shopee, ML, Amazon) caíam em loop de reautorização porque `getLojaFingerprint()` usava `loja.id` do pedido mais recente como identificador único do tenant. **Fix**: - Novo método `BlingApi::getLojaIdsRecentes(int $paginas=3)` — coleta SET de loja_ids - `Bling::callback()` valida contra pedidos históricos de OUTRAS contas (não só `bling_loja_id` singular) - `Bling::sincronizar()` REMOVIDA checagem "token mudou de loja" (era o falso positivo) - Ao detectar 404 em `getPedido`: marca pedido como Cancelado (pedido excluído no Bling) **Contaminação descoberta**: G Closet (cliente 2) tinha 1000 pedidos da GotechBr (importados errado em 30/03 com OAuth na conta errada). Limpos via DELETE + backup em `bling_pedidos_backup_contaminacao_2026_04_22`. **Resultado**: 3 contas ativas, 0 erros. Dashboard "pendentes envio" agora bate com estado real. Detalhes em [[bling-fingerprint-fix]]. ## 2. Publicação Instagram - pipeline completo corrigido **4 bugs encadeados** que impediam publicação real: ### Bug 1: Cron mentia status `CronSocialMedia::publicar` quando `social_post_redes` vazio: loop não rodava, marcava `status=publicado` sem publicar em nada. Fix: guard explícito → marca erro "Nenhuma rede destino configurada". ### Bug 2: Posts aprovados nasciam sem redes `SocialDemandaService::onDemandaConcluida` copiava mídias mas não vinculava contas em `social_post_redes`. Fix: vincula automaticamente todas as contas `conectada` do cliente ao post. ### Bug 3: URL da mídia bloqueada Publisher usava `base_url('writable/uploads/...')` — `/writable/` bloqueada por segurança no CI4 (404). Fix: criada rota pública `social-media/midia-publica/(:any)` (sem auth) em `Routes.php` apontando para `AreaClienteSocial::servirMidia`. Publisher passou a usar ela. ### Bug 4: Race condition Graph API Código chamava `media_publish` logo após criar container, mas IG processa mídia assincronamente → "Media ID is not available". Fix: polling do status do container até `FINISHED` (até 40s foto, 90s vídeo), via `curl` (não `file_get_contents` que falha com `allow_url_fopen=off`). ### Bug 5: URL do post no banco errada `media_publish` retorna `id` numérico mas URL pública usa `shortcode`. Sistema salvava `/p/{id_numerico}/` que dava 404 ao clicar. Fix: após `media_publish`, fazer `GET /{media_id}?fields=permalink` e salvar o permalink real. **Publisher refatorado** (`SocialMediaPublisher::publicarInstagram`) — suporta 5 tipos: - `imagem`: 1 foto - `carrossel`: 2-10 itens, cria container por item + CAROUSEL + publish - `reels` ou `video`: `media_type=REELS` - `story`: `media_type=STORIES` (sem caption) **Helpers extraídos**: `aguardarContainerIG()`, `publicarContainerIG()`. ## 3. Social Media - UX completo refeito ### Modal criar cronograma - validações por tipo - Limites Instagram documentados em `IG_LIMITES` (accept/multiple/maxArquivos/dica por tipo) - Caption com `maxlength=2200` + contador visual dinâmico (amarelo 90%, vermelho 100%) - Hashtags com `maxlength=500` - Switch de tipo ajusta `accept` e `multiple` do input - Carrossel exige 2-10 arquivos (avisa se menos), outros aceitam 1 - Story mostra aviso "sem legenda nem hashtags" - Tipo "texto" removido do select (IG não aceita) ### Calendário admin - cards compactos - `.ne` em 1 linha só com borda lateral cor do cliente + pontinho cor do status - Pontinho 11px com borda branca + sombra sutil - Card inteiro colorido pelo status (bg + cor do texto), borda lateral = cliente - Tooltip "Status - Nome Cliente" ao passar mouse ### Legenda do calendário - dropdown - Botão "Legenda de status" acima do calendário - Dropdown flutuante 340px com 3 seções (Planejamento / Publicação / Final) - Itens em chips com dot + nome em negrito + desc em cinza - Fecha ao clicar fora ### Bug "Este cronograma já foi enviado há menos de 5 minutos" `SocialMedia::cronogramaEnviarCliente` usava `updated_at` como proxy pra "último envio" — mas `updated_at` muda a cada edição. Fix: nova coluna `cronogramas.ultimo_envio_at` (DATETIME NULL) usada exclusivamente pra timestamp do último envio real. Janela reduzida de 5min → 15s (só anti-double-click). ### Bug 3 Swals em sequência ao salvar post `salvarOuEnviarEdicao` sempre abria Swal "O que deseja fazer?". Se user clicasse "Salvar e enviar", abria "Enviar para aprovação?" (2o Swal), daí pra backend → erro "Exige mídia" (3o Swal). Fix: `salvarOuEnviarEdicao` agora é contextual: - Se post tem `cronograma_id` e SEM arte final → planejamento → "Apenas salvar" / "Salvar e reenviar planejamento" - Se tem arte final → publicação → "Apenas salvar" / "Salvar e enviar publicação" - Senão → salva direto Funções `enviarConteudoPost` e `reenviarCronograma` ganharam parâmetro `pularConfirmacao` pra evitar 2o Swal quando vem do fluxo já confirmado. ## 4. Status "descartado" - ciclo de vida completo **Cenário**: cliente recusa planejamento, admin NÃO vai corrigir (vai usar outros posts). Era preciso "descartar" sem reenviar. **Implementação**: - ALTER TABLE: enum `social_posts.status` agora inclui `descartado` - `SocialMedia::postDescartar($id)` + rota `POST /social-media/post/descartar/(:num)` - `SocialMedia::postReativar($id)` + rota `POST /social-media/post/reativar/(:num)` - Motivo opcional → vai em `erro_mensagem` com prefixo "Descartado: ..." **Botão "Descartar"** nos modais de recusa (MODO 3 cronograma rejeitado + MODO 4 publicação rejeitada) entre "Excluir" e "Salvar". **Novo modal `renderModalDescartado`** pra posts descartados — mostra motivo, detalhes recolhíveis, botões: Excluir definitivamente / Fechar / Reativar. **Visibilidade por contexto**: | Lugar | Descartado aparece? | |-------|---------------------| | Calendário admin | SIM - cinza claro, tachado, opacity 0.7 | | Calendário cliente | SIM - cinza, tachado, com motivo no modal | | Link público aprovação | NÃO (filtrado em `CronogramaModel::getCronogramaComPosts`) | | CRON publicar/auto-aprovar/lembrete | NÃO (queries buscam `rascunho/aceito`, descartado não bate) | **Bug ao incluir**: `SocialPostModel::getCalendario` filtrava `whereIn status` sem `descartado` → post sumia do calendário admin. Adicionado. **No cliente (AreaClienteSocial)**: - `ajaxPostsCalendario`: mantém descartado na query, retorna label "Descartado", cor #a3a3a3, e `motivo_descarte` sem prefixo "Descartado: " - `ajaxPostDetalhe`: inclui `motivo_descarte` - View `area_cliente/social/calendario.php`: nova CSS class `.ec-st.descartado` (bg cinza) e `.ec.descartado-card` (tachado). Modal mostra bloco cinza "Publicação descartada" com motivo. ## 5. /monitor-apps - conexão caindo **Bug 1**: Sharing link do MeshCentral é uso único, sistema cachava URL antiga em `api_heartbeats.mesh_share_url` e reutilizava → conexão caía ao clicar. Fix: `Principal::createMeshShare` sempre cria link novo (removido early-return do cache). **Bug 2**: `mesh_share_api.js` (porta 8002, servidor Baileys) usava `execSync` com string → shell expansion do `$` nos node_ids → meshctrl retornava "Invalid node id" pra nodes com `$`. Fix: trocar `execSync` → `execFileSync` com array de argumentos (sem shell no meio). Users afetados: 5 (Crismária), 7 (Valeria), 8 (Erick), 10 (vendas). Todos voltaram a funcionar após restart do PM2. ## 6. Social Media - Layout padrão dashboard Refeito CSS/HTML de `social_media/clientes.php`, `configuracoes.php`, `cliente_form.php` seguindo padrão de `demandas/index.php`: - Sem `page-title-box` — barra simples com `.dash-btn` - `.content-tabs` container branco border 1px, shadow sutil - `.nav-tabs-custom` pills pretos (#1e293b quando ativo) - `.dash-btn` / `.dash-btn.filled` como padrão - Tabela th caixa alta 0.7rem, td 0.813rem - Form controls 32px altura, 0.78rem, border-radius 6px - Remoção de gradientes, padding denso (14px em vez de 20px), border-radius menor ## 7. Clientes Social Media - indicadores de conexão Lista `/social-media/clientes` agora mostra: - **Badge geral ao lado do nome**: verde/amarelo/vermelho/cinza conforme quantas redes do cliente têm OAuth válido - **Dot verde/vermelho em cada badge de rede**: 10px com borda branca, canto inferior direito Removidos: - Botão WhatsApp (compartilhar link) - Botão copiar link (quando ativo) - só "Gerar" tá disponível ## 8. Tela Editar Cliente - vínculo fixo Vínculo com cliente do sistema é automático via checkbox "Gestão de Redes Sociais" no cadastro principal (`Cliente::sincronizarSocialCliente`). Consequência na view: - Dropdown "Vincular ao Cliente" removido quando tem vínculo - Campos Nome/Empresa/Email/Telefone/Observações ocultos quando vinculado (vêm do cadastro) - Box readonly com os dados preenchidos (pra visualizar) - `cliente_id` e dados via hidden inputs pra não quebrar save - Sem botão "Editar cadastro" (usuário pediu) ## 9. Tela "Selecionar Conta" (erro OAuth) - produção-ready Removido bloco de diagnóstico técnico (API Version, permissões, "App em Development Mode" etc) da tela de erro ao tentar conectar Instagram sem conta Business. Substituído por: - Card gradiente com ícone da rede - Título humano: "Ainda não conseguimos conectar" - Mensagem específica por rede - 3 passos numerados: "O que fazer agora" - Botões: Voltar + Tentar novamente App Facebook já foi aprovado → não precisa mais de info de debug. ## Arquivos modificados (principais) | Arquivo | Mudança | |---------|---------| | `app/Libraries/BlingApi.php` | getLojaIdsRecentes + carrossel/reels support | | `app/Libraries/SocialMediaPublisher.php` | Instagram publisher completo refeito | | `app/Controllers/Bling.php` | callback fingerprint + 404→cancelado | | `app/Controllers/CronSocialMedia.php` | guard sem redes destino | | `app/Controllers/SocialMedia.php` | postDescartar, postReativar, anti-duplicata usando ultimo_envio_at | | `app/Controllers/Principal.php` | createMeshShare sempre novo | | `app/Controllers/AreaClienteSocial.php` | descartado visível com motivo | | `app/Libraries/SocialDemandaService.php` | vincula redes automaticamente | | `app/Models/SocialPostModel.php` | getCalendario inclui descartado | | `app/Models/CronogramaModel.php` | filtra descartado + ultimo_envio_at | | `app/Views/social_media/index.php` | calendário reformulado, legenda dropdown, modal descartado | | `app/Views/social_media/clientes.php` | padrão dashboard + indicadores | | `app/Views/social_media/configuracoes.php` | 3 abas no padrão dashboard | | `app/Views/social_media/cliente_form.php` | vínculo fixo + padrão dashboard | | `app/Views/social_media/selecionar_conta.php` | erro amigável produção | | `app/Views/area_cliente/social/calendario.php` | descartado visível ao cliente | | `/root/mesh_share_api.js` (Baileys) | execFileSync evita shell expansion | ## Estado final - Bling: 3 contas ativas, 0 erros, sync funcionando - Instagram: publicação ponta-a-ponta funcionando (imagem + carrossel + reels) - Cron social-media: a cada 1min, só publica quem tem rede destino - MeshCentral: share links novos a cada clique, 5 users funcionando - Descartado: status isolado, aparece para todos com motivo, saía das automações - Layout: 3 telas no padrão dashboard de demandas ## Relacionados - [[bling-fingerprint-fix]] - [[preferencias-usuario]] - [[social-media-modulo]] - [[whatsapp-chat-modulo]]