@danhachuel/pandanews 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Todas as versões publicadas são registradas aqui antes do publish (regra em AGENTS.md). A etiqueta visual usa PATCH de dois dígitos (ex.: 0.1.00); o registry npm normaliza para a versão canônica (ex.: 0.1.0).
4
4
 
5
+ ## 0.3.01 (2026-10-25)
6
+
7
+ - **Páginas legais e de verificação** em `legal/` (padrão AutoPost-Lunga, adaptado ao PandaNews): `index.html` (apresentação do app, funcionalidades e provedores de API), `terms.html` (Termos de Uso em pt-BR) e `privacy.html` (Política de Privacidade em conformidade com a LGPD — Lei 13.709/2018)
8
+ - Backend serve as páginas em `/legal/index.html`, `/legal/terms.html` e `/legal/privacy.html` (whitelist de arquivos, servidos também via UI em `/legal/*` através do proxy)
9
+ - Conteúdo específico do PandaNews: dados de configuração de blogs, feeds RSS, chaves de API locais (NVIDIA NIM, Google Gemini, Supabase), compartilhamento mínimo com provedores (apenas prompts/contexto editorial), direitos do titular exercidos localmente, armazenamento em SQLite (`data/pandanews.db`) e imagens em `data/imagens/`
10
+ - `legal/` passa a fazer parte do tarball npm (`files`)
11
+
5
12
  ## 0.3.00 (2026-10-24)
6
13
 
7
14
  - **Pipeline de notícias completo** (Fase 3): coleta RSS (feedparser) → filtragem por relevância (palavras-chave do tema + bônus de região) → redação via LLM com fallback NVIDIA → Gemini (resposta em JSON: título, resumo, corpo) → imagem fotojornalística via Gemini Image salva em `data/imagens/`
@@ -10,7 +10,7 @@ from contextlib import asynccontextmanager
10
10
  from fastapi import FastAPI
11
11
  from fastapi.middleware.cors import CORSMiddleware
12
12
 
13
- from backend.app.routers import automacao, blogs, configuracoes, dashboard, imagens, llm, logs, noticias
13
+ from backend.app.routers import automacao, blogs, configuracoes, dashboard, imagens, legal, llm, logs, noticias
14
14
  from backend.core.database import SessionLocal, init_db
15
15
  from backend.core.llm_router import get_config
16
16
 
@@ -31,7 +31,7 @@ async def lifespan(_: FastAPI):
31
31
  scheduler.parar()
32
32
 
33
33
 
34
- app = FastAPI(title="PandaNews API", version="0.3.0", lifespan=lifespan)
34
+ app = FastAPI(title="PandaNews API", version="0.3.1", lifespan=lifespan)
35
35
 
36
36
  app.add_middleware(
37
37
  CORSMiddleware,
@@ -48,6 +48,7 @@ app.include_router(noticias.router, prefix="/api/v1")
48
48
  app.include_router(imagens.router, prefix="/api/v1")
49
49
  app.include_router(automacao.router, prefix="/api/v1")
50
50
  app.include_router(logs.router, prefix="/api/v1")
51
+ app.include_router(legal.router) # /legal/* na raiz (URLs de verificação)
51
52
 
52
53
 
53
54
  @app.get("/api/v1/health")
@@ -0,0 +1,35 @@
1
+ """Páginas legais e de verificação (/legal) — estáticas.
2
+
3
+ Servem como URL de verificação do app junto aos provedores de API
4
+ (Google Gemini, NVIDIA NIM, Supabase) e como documentos legais oficiais.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from fastapi import APIRouter, HTTPException
9
+ from fastapi.responses import FileResponse
10
+
11
+ from backend.core.config import LEGAL_DIR
12
+
13
+ router = APIRouter(prefix="/legal", tags=["legal"])
14
+
15
+ ARQUIVOS_PERMITIDOS = {"index.html", "terms.html", "privacy.html"}
16
+
17
+
18
+ def _servir(nome: str) -> FileResponse:
19
+ if nome not in ARQUIVOS_PERMITIDOS:
20
+ raise HTTPException(status_code=404, detail="Página não encontrada")
21
+ caminho = LEGAL_DIR / nome
22
+ if not caminho.exists():
23
+ raise HTTPException(status_code=404, detail="Página não encontrada")
24
+ return FileResponse(caminho, media_type="text/html; charset=utf-8")
25
+
26
+
27
+ @router.get("")
28
+ @router.get("/")
29
+ def legal_index() -> FileResponse:
30
+ return _servir("index.html")
31
+
32
+
33
+ @router.get("/{nome}")
34
+ def legal_pagina(nome: str) -> FileResponse:
35
+ return _servir(nome)
@@ -29,6 +29,10 @@ IMAGES_DIR = DATA_DIR / "imagens"
29
29
  DB_PATH = DATA_DIR / "pandanews.db"
30
30
  DB_URL = os.environ.get("PANDANEWS_DB_URL", f"sqlite:///{DB_PATH}")
31
31
 
32
+ # Raiz do pacote (backend/core/config.py -> parents[2])
33
+ PKG_DIR = Path(__file__).resolve().parents[2]
34
+ LEGAL_DIR = PKG_DIR / "legal"
35
+
32
36
  SUPABASE_URL = os.environ.get("SUPABASE_URL", "")
33
37
  SUPABASE_SERVICE_ROLE_KEY = os.environ.get("SUPABASE_SERVICE_ROLE_KEY", "")
34
38
 
@@ -0,0 +1,218 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>PandaNews - Geração e Automação de Blogs de Notícias com IA</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: linear-gradient(135deg, #0b1220 0%, #14532d 100%);
12
+ min-height: 100vh;
13
+ padding: 40px 20px;
14
+ }
15
+ .container { max-width: 800px; margin: 0 auto; }
16
+ .card {
17
+ background: #ffffff;
18
+ border-radius: 20px;
19
+ padding: 40px;
20
+ margin-bottom: 30px;
21
+ box-shadow: 0 20px 40px rgba(0,0,0,0.25);
22
+ }
23
+ .badge {
24
+ background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
25
+ color: #052e16;
26
+ padding: 8px 16px;
27
+ border-radius: 50px;
28
+ display: inline-block;
29
+ font-size: 0.9em;
30
+ font-weight: 600;
31
+ margin-bottom: 20px;
32
+ }
33
+ h1 { color: #0b1220; font-size: 2.4em; margin-bottom: 10px; }
34
+ h2 { color: #14532d; margin: 25px 0 15px 0; font-size: 1.6em; }
35
+ p { color: #475569; line-height: 1.6; margin: 10px 0; }
36
+ .feature-grid {
37
+ display: grid;
38
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
39
+ gap: 20px;
40
+ margin: 25px 0;
41
+ }
42
+ .feature-item {
43
+ background: #f8faf9;
44
+ padding: 20px;
45
+ border-radius: 15px;
46
+ border-left: 4px solid #22c55e;
47
+ transition: transform 0.2s;
48
+ }
49
+ .feature-item:hover { transform: translateY(-4px); }
50
+ .feature-item h3 { margin-bottom: 10px; font-size: 1.15em; color: #0b1220; }
51
+ .feature-item p { color: #64748b; line-height: 1.5; margin: 6px 0; }
52
+ .api-grid {
53
+ display: grid;
54
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
55
+ gap: 20px;
56
+ margin: 25px 0;
57
+ }
58
+ .api-item {
59
+ background: #f8faf9;
60
+ padding: 20px;
61
+ border-radius: 15px;
62
+ border-left: 4px solid;
63
+ }
64
+ .api-nvidia { border-left-color: #76b900; }
65
+ .api-gemini { border-left-color: #4285f4; }
66
+ .api-supabase { border-left-color: #3ecf8e; }
67
+ .api-item h3 { margin-bottom: 8px; font-size: 1.1em; color: #0b1220; }
68
+ .api-item p { color: #64748b; font-size: 0.92em; line-height: 1.5; margin: 6px 0; }
69
+ code {
70
+ background: #e8f0ea;
71
+ color: #14532d;
72
+ padding: 2px 6px;
73
+ border-radius: 4px;
74
+ font-size: 0.85em;
75
+ }
76
+ .links {
77
+ margin: 30px 0 20px 0;
78
+ padding: 22px;
79
+ background: #ecfdf5;
80
+ border-radius: 15px;
81
+ text-align: center;
82
+ }
83
+ .links a {
84
+ display: inline-block;
85
+ min-height: 44px;
86
+ line-height: 44px;
87
+ padding: 0 14px;
88
+ color: #15803d;
89
+ text-decoration: none;
90
+ margin: 4px 8px;
91
+ font-weight: 600;
92
+ border-radius: 10px;
93
+ }
94
+ .links a:hover { background: #d1fae5; }
95
+ footer { text-align: center; color: rgba(255,255,255,0.85); margin-top: 30px; line-height: 1.6; }
96
+ .contact {
97
+ background: #fff8e6;
98
+ padding: 18px;
99
+ border-radius: 10px;
100
+ margin-top: 20px;
101
+ line-height: 1.7;
102
+ }
103
+ .contact a { color: #15803d; }
104
+ .pipeline {
105
+ background: #0b1220;
106
+ color: #e2e8f0;
107
+ padding: 18px 20px;
108
+ border-radius: 15px;
109
+ font-size: 0.95em;
110
+ line-height: 1.8;
111
+ }
112
+ .pipeline strong { color: #22c55e; }
113
+ @media (max-width: 600px) {
114
+ .card { padding: 25px; }
115
+ h1 { font-size: 1.7em; }
116
+ h2 { font-size: 1.35em; }
117
+ }
118
+ </style>
119
+ </head>
120
+ <body>
121
+ <div class="container">
122
+ <div class="card">
123
+ <div class="badge">🐻 Automação Editorial com IA</div>
124
+ <h1>PandaNews</h1>
125
+ <p style="font-size: 1.15em; color: #334155; margin-bottom: 15px;">
126
+ Geração e automação de blogs de notícias com Inteligência Artificial — SaaS local para criação e operação de múltiplos blogs segmentados por tema e região.
127
+ </p>
128
+ <p>
129
+ O PandaNews é uma ferramenta <strong>local</strong> (executada na máquina do usuário via <code>npx</code>) de automação editorial:
130
+ coleta pautas em feeds RSS configurados pelo usuário, redige notícias com modelos de linguagem de IA (LLMs)
131
+ e produz imagens ilustrativas, organizando tudo em múltiplos blogs com revisão humana e agendamento automático.
132
+ </p>
133
+
134
+ <h2>⚙️ Funcionalidades</h2>
135
+ <div class="feature-grid">
136
+ <div class="feature-item">
137
+ <h3>📰 Múltiplos blogs por tema e região</h3>
138
+ <p>Gestão de blogs segmentados por tema — esporte, política, economia, tecnologia, geral — e por recorte regional (Brasil, Espanha, São Paulo, Rio de Janeiro etc.), cada um com fontes e estilo editorial próprios.</p>
139
+ </div>
140
+ <div class="feature-item">
141
+ <h3>📡 Coleta via feeds RSS</h3>
142
+ <p>Pautas coletadas automaticamente das fontes RSS configuradas pelo usuário, com filtragem por relevância (tema e região) e deduplicação de itens.</p>
143
+ </div>
144
+ <div class="feature-item">
145
+ <h3>🤖 Redação com LLM multi-provedor</h3>
146
+ <p>Geração de notícias (título, resumo e corpo) com fallback por prioridade: <strong>NVIDIA NIM → Google Gemini</strong>. O provedor que responde é registrado em cada notícia.</p>
147
+ </div>
148
+ <div class="feature-item">
149
+ <h3>🎨 Imagens com IA</h3>
150
+ <p>Imagens ilustrativas em estilo fotojornalístico geradas pela API do Google Gemini Image a partir de palavras-chave da notícia, com geração manual por prompt livre.</p>
151
+ </div>
152
+ <div class="feature-item">
153
+ <h3>👁️ Revisão humana</h3>
154
+ <p>Fluxo editorial completo: rascunho → aprovado → publicado. Toda notícia nasce em rascunho para revisão, edição manual ou regeneração antes da aprovação.</p>
155
+ </div>
156
+ <div class="feature-item">
157
+ <h3>⏱️ Agendamento automático</h3>
158
+ <p>Agendador com frequência configurável por blog, toggle global e execução imediata — com logs de execução por etapa (coleta, redação, imagem).</p>
159
+ </div>
160
+ <div class="feature-item">
161
+ <h3>💾 Armazenamento local</h3>
162
+ <p>Por padrão, 100% local em SQLite (<code>data/pandanews.db</code>), com opção de migração para Supabase quando o usuário fornecer credenciais.</p>
163
+ </div>
164
+ <div class="feature-item">
165
+ <h3>📊 Dashboard com métricas</h3>
166
+ <p>Métricas de geração (últimos 7 dias), notícias pendentes de revisão, status do agendador, checklist de onboarding e últimos erros registrados.</p>
167
+ </div>
168
+ </div>
169
+
170
+ <h2>🔌 Como funciona</h2>
171
+ <div class="pipeline">
172
+ <strong>Pipeline de geração:</strong> coleta RSS → filtragem por relevância → redação via LLM
173
+ (NVIDIA NIM → Google Gemini) → imagem via Gemini Image → <strong>rascunho</strong> → revisão humana → aprovado/publicado.
174
+ <br>
175
+ O agendador repete o ciclo para cada blog ativo conforme sua frequência configurada.
176
+ </div>
177
+
178
+ <h2>🧩 Provedores de API utilizados</h2>
179
+ <div class="api-grid">
180
+ <div class="api-item api-nvidia">
181
+ <h3>🟩 NVIDIA NIM</h3>
182
+ <p><strong>API:</strong> inference (endpoint compatível com OpenAI)</p>
183
+ <p><strong>Uso:</strong> geração de texto das notícias (provedor primário do fallback)</p>
184
+ <p>Chave de API fornecida pelo próprio usuário, armazenada localmente.</p>
185
+ </div>
186
+ <div class="api-item api-gemini">
187
+ <h3>🔵 Google Gemini</h3>
188
+ <p><strong>APIs:</strong> modelo de linguagem (fallback de texto) e Gemini Image (imagens ilustrativas)</p>
189
+ <p><strong>Uso:</strong> redação alternativa e geração de imagens fotojornalísticas</p>
190
+ <p>Chave de API fornecida pelo próprio usuário, armazenada localmente.</p>
191
+ </div>
192
+ <div class="api-item api-supabase">
193
+ <h3>🟢 Supabase</h3>
194
+ <p><strong>API:</strong> banco gerenciado (opcional)</p>
195
+ <p><strong>Uso:</strong> backend de dados alternativo, apenas se o usuário optar explicitamente e fornecer suas credenciais</p>
196
+ <p>Por padrão, os dados permanecem em SQLite local.</p>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="links">
201
+ <a href="privacy.html">🔒 Política de Privacidade</a>
202
+ <a href="terms.html">📜 Termos de Uso</a>
203
+ </div>
204
+
205
+ <div class="contact">
206
+ <strong>📧 Desenvolvedor:</strong> Dan Hachuel<br>
207
+ <!-- TODO: confirmar e-mail -->
208
+ <strong>📩 Contato:</strong> <a href="mailto:danchannels96@gmail.com">danchannels96@gmail.com</a><br>
209
+ <strong>📍 Propósito:</strong> aplicação pessoal de automação editorial para blogs próprios do usuário.
210
+ </div>
211
+ </div>
212
+ <footer>
213
+ <p>© 2026 PandaNews — Dan Hachuel</p>
214
+ <p>Este site é usado para verificação da aplicação junto aos provedores de API utilizados: Google Gemini, NVIDIA NIM e Supabase.</p>
215
+ </footer>
216
+ </div>
217
+ </body>
218
+ </html>
@@ -0,0 +1,207 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Política de Privacidade - PandaNews</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: linear-gradient(135deg, #0b1220 0%, #14532d 100%);
12
+ min-height: 100vh;
13
+ padding: 40px 20px;
14
+ }
15
+ .container { max-width: 800px; margin: 0 auto; }
16
+ .card {
17
+ background: #ffffff;
18
+ border-radius: 20px;
19
+ padding: 40px;
20
+ margin-bottom: 30px;
21
+ box-shadow: 0 20px 40px rgba(0,0,0,0.25);
22
+ }
23
+ .badge {
24
+ background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
25
+ color: #052e16;
26
+ padding: 8px 16px;
27
+ border-radius: 50px;
28
+ display: inline-block;
29
+ font-size: 0.9em;
30
+ font-weight: 600;
31
+ margin-bottom: 20px;
32
+ }
33
+ h1 { color: #0b1220; font-size: 2.2em; margin-bottom: 10px; }
34
+ h2 { color: #14532d; margin: 28px 0 12px 0; font-size: 1.45em; }
35
+ h3 { color: #166534; margin: 18px 0 8px 0; font-size: 1.15em; }
36
+ p { color: #475569; line-height: 1.65; margin: 10px 0; }
37
+ ul, ol { margin: 10px 0 10px 28px; color: #475569; line-height: 1.65; }
38
+ li { margin: 6px 0; }
39
+ strong { color: #0b1220; }
40
+ code {
41
+ background: #e8f0ea;
42
+ color: #14532d;
43
+ padding: 2px 6px;
44
+ border-radius: 4px;
45
+ font-size: 0.85em;
46
+ }
47
+ a { color: #15803d; text-decoration: none; }
48
+ a:hover { text-decoration: underline; }
49
+ .destaque {
50
+ background: #ecfdf5;
51
+ border-left: 4px solid #22c55e;
52
+ padding: 14px 18px;
53
+ border-radius: 10px;
54
+ margin: 15px 0;
55
+ color: #14532d;
56
+ }
57
+ .links {
58
+ margin: 30px 0 20px 0;
59
+ padding: 22px;
60
+ background: #ecfdf5;
61
+ border-radius: 15px;
62
+ text-align: center;
63
+ }
64
+ .links a {
65
+ display: inline-block;
66
+ min-height: 44px;
67
+ line-height: 44px;
68
+ padding: 0 14px;
69
+ color: #15803d;
70
+ text-decoration: none;
71
+ margin: 4px 8px;
72
+ font-weight: 600;
73
+ border-radius: 10px;
74
+ }
75
+ .links a:hover { background: #d1fae5; }
76
+ footer { text-align: center; color: rgba(255,255,255,0.85); margin-top: 30px; line-height: 1.6; }
77
+ .contact {
78
+ background: #fff8e6;
79
+ padding: 18px;
80
+ border-radius: 10px;
81
+ margin-top: 20px;
82
+ line-height: 1.7;
83
+ }
84
+ .contact a { color: #15803d; }
85
+ @media (max-width: 600px) {
86
+ .card { padding: 25px; }
87
+ h1 { font-size: 1.6em; }
88
+ h2 { font-size: 1.25em; }
89
+ }
90
+ </style>
91
+ </head>
92
+ <body>
93
+ <div class="container">
94
+ <div class="card">
95
+ <div class="badge">🔒 Documento Legal</div>
96
+ <h1>Política de Privacidade</h1>
97
+ <p><strong>Data de vigência:</strong> 25 de outubro de 2026</p>
98
+ <p><strong>Aplicação:</strong> PandaNews — geração e automação de blogs de notícias com IA</p>
99
+
100
+ <p>
101
+ Esta Política de Privacidade descreve como o PandaNews trata dados pessoais e informações do usuário,
102
+ em conformidade com a <strong>Lei Geral de Proteção de Dados Pessoais (LGPD — Lei nº 13.709/2018)</strong>.
103
+ </p>
104
+ <div class="destaque">
105
+ <strong>Princípio fundamental do PandaNews:</strong> o aplicativo é <strong>100% local</strong>. Ele é executado
106
+ na máquina do próprio usuário via <code>npx</code>, e todos os dados — configurações, conteúdo e credenciais —
107
+ residem no banco SQLite local (<code>data/pandanews.db</code>). <strong>Nenhum dado é enviado a servidores
108
+ do desenvolvedor.</strong>
109
+ </div>
110
+
111
+ <h2>1. Dados coletados</h2>
112
+ <p>O PandaNews trata as seguintes categorias de dados, todas armazenadas localmente na máquina do usuário:</p>
113
+ <ul>
114
+ <li><strong>Dados de configuração de blogs:</strong> nome, tema (esporte, política, economia, tecnologia, geral), região, fontes RSS, estilo editorial, frequência de geração e status.</li>
115
+ <li><strong>Conteúdo coletado de feeds RSS públicos:</strong> títulos, resumos e links de notícias das fontes configuradas pelo usuário.</li>
116
+ <li><strong>Conteúdo gerado por IA:</strong> notícias (título, resumo, corpo), imagens ilustrativas e o provedor de LLM que gerou cada texto.</li>
117
+ <li><strong>Chaves de API fornecidas pelo usuário:</strong> NVIDIA NIM, Google Gemini e Supabase — armazenadas localmente, no banco SQLite do próprio usuário.</li>
118
+ <li><strong>Dados de uso da plataforma:</strong> logs de execução (sucessos e erros por etapa), status do agendador e estatísticas de geração — visíveis apenas para o próprio usuário.</li>
119
+ <li><strong>Tokens e credenciais do Supabase:</strong> apenas se o usuário optar explicitamente por esse backend.</li>
120
+ </ul>
121
+
122
+ <h2>2. Finalidade do tratamento</h2>
123
+ <ul>
124
+ <li>Coleta e agregação de notícias de fontes públicas para fins editoriais.</li>
125
+ <li>Geração de conteúdo textual e visual com Inteligência Artificial.</li>
126
+ <li>Automação de geração conforme a frequência configurada por blog.</li>
127
+ <li>Produção de estatísticas e logs de execução para o próprio usuário acompanhar a operação.</li>
128
+ </ul>
129
+
130
+ <h2>3. Compartilhamento de dados</h2>
131
+ <p>O PandaNews é arquitetado para compartilhar <strong>o mínimo necessário</strong>, apenas com os provedores de API
132
+ cujas chaves o próprio usuário configura:</p>
133
+ <ul>
134
+ <li><strong>NVIDIA NIM (LLM):</strong> apenas o texto dos prompts e o contexto da notícia (título e resumo da fonte) são enviados para a redação. <strong>Nenhum dado pessoal do usuário é compartilhado.</strong></li>
135
+ <li><strong>Google Gemini (LLM de fallback):</strong> mesmo escopo do item acima — apenas prompts e contexto editorial.</li>
136
+ <li><strong>Google Gemini Image API:</strong> apenas prompts visuais derivados do conteúdo da notícia (palavras-chave do título/resumo).</li>
137
+ <li><strong>Supabase:</strong> apenas se o usuário optar explicitamente por esse backend, e apenas os dados que ele próprio decidir sincronizar.</li>
138
+ <li>O PandaNews <strong>não compartilha dados com terceiros</strong> para fins publicitários, de marketing ou de perfilação, e <strong>não vende dados</strong> sob nenhuma circunstância.</li>
139
+ </ul>
140
+
141
+ <h2>4. Armazenamento e localização</h2>
142
+ <ul>
143
+ <li>Por padrão, todos os dados residem no <strong>SQLite local</strong> na máquina do usuário: <code>data/pandanews.db</code>.</li>
144
+ <li>As imagens geradas residem em <code>data/imagens/</code>, também na máquina do usuário.</li>
145
+ <li>O runtime (Python portátil e dependências) fica em <code>~/.pandanews/</code>, separado do pacote npm.</li>
146
+ <li><strong>Nenhum dado é enviado a servidores do desenvolvedor do PandaNews.</strong> O tráfego externo limita-se às chamadas de API descritas na seção 3 e à leitura dos feeds RSS configurados.</li>
147
+ </ul>
148
+
149
+ <h2>5. Direitos do titular (LGPD, art. 18)</h2>
150
+ <p>Nos termos da LGPD, o titular dos dados possui direito à confirmação do tratamento, acesso, correção,
151
+ anonimização, portabilidade e eliminação dos dados. Como o PandaNews é uma aplicação local, <strong>o próprio
152
+ usuário exerce esses direitos diretamente</strong>:</p>
153
+ <ul>
154
+ <li>No banco de dados local (<code>data/pandanews.db</code>), acessível com qualquer ferramenta de SQLite;</li>
155
+ <li>Pela interface do aplicativo (exclusão de blogs e notícias, edição de configurações);</li>
156
+ <li>Para dados eventualmente sincronizados ao Supabase, diretamente no painel do Supabase, que pertence ao próprio usuário.</li>
157
+ </ul>
158
+ <p>Dúvidas sobre tratamento de dados podem ser enviadas ao canal de contato indicado ao final deste documento.</p>
159
+
160
+ <h2>6. Segurança</h2>
161
+ <ul>
162
+ <li>As chaves de API são armazenadas <strong>localmente</strong> no SQLite da própria máquina, sem criptografia adicional — cabe ao usuário proteger o acesso ao seu sistema.</li>
163
+ <li>Recomenda-se <strong>não compartilhar</strong> o arquivo <code>data/pandanews.db</code> nem o arquivo <code>.env</code> do projeto, pois ambos podem conter credenciais.</li>
164
+ <li>O aplicativo escuta apenas em <code>127.0.0.1</code> (interface local), não expondo os serviços à rede externa.</li>
165
+ </ul>
166
+
167
+ <h2>7. Cookies</h2>
168
+ <p>O PandaNews é uma aplicação local servida em <code>localhost</code> e <strong>não utiliza cookies de rastreamento
169
+ de terceiros</strong>, analytics externos ou pixels de publicidade. Cookies de sessão, caso existam na interface
170
+ local, servem apenas ao funcionamento da própria UI.</p>
171
+
172
+ <h2>8. Transferência internacional</h2>
173
+ <p>As chamadas às APIs de LLM e imagem (NVIDIA NIM, Google Gemini) implicam envio dos prompts aos servidores
174
+ desses provedores, que podem estar localizados fora do Brasil, regidos pelos termos de uso e políticas de
175
+ privacidade de cada provedor. O PandaNews não envia a esses provedores nenhum dado pessoal além do conteúdo
176
+ dos prompts editoriais descritos na seção 3.</p>
177
+
178
+ <h2>9. Alterações desta Política</h2>
179
+ <p>Esta Política de Privacidade pode ser atualizada a qualquer momento. A versão vigente será publicada neste
180
+ endereço, com a data de vigência atualizada. O uso contínuo do PandaNews após alterações implica concordância
181
+ com a versão vigente.</p>
182
+
183
+ <h2>10. Contato</h2>
184
+ <p>Para exercer direitos de titular ou esclarecer dúvidas sobre esta Política:</p>
185
+ <ul>
186
+ <!-- TODO: confirmar e-mail -->
187
+ <li>Por e-mail: <a href="mailto:danchannels96@gmail.com">danchannels96@gmail.com</a></li>
188
+ </ul>
189
+
190
+ <div class="links">
191
+ <a href="index.html">🏠 Voltar para Home</a>
192
+ <a href="terms.html">📜 Termos de Uso</a>
193
+ </div>
194
+
195
+ <div class="contact">
196
+ <strong>📧 Desenvolvedor:</strong> Dan Hachuel<br>
197
+ <!-- TODO: confirmar e-mail -->
198
+ <strong>📩 Contato:</strong> <a href="mailto:danchannels96@gmail.com">danchannels96@gmail.com</a>
199
+ </div>
200
+ </div>
201
+ <footer>
202
+ <p>© 2026 PandaNews — Dan Hachuel</p>
203
+ <p>Este site é usado para verificação da aplicação junto aos provedores de API utilizados: Google Gemini, NVIDIA NIM e Supabase.</p>
204
+ </footer>
205
+ </div>
206
+ </body>
207
+ </html>
@@ -0,0 +1,185 @@
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Termos de Uso - PandaNews</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: linear-gradient(135deg, #0b1220 0%, #14532d 100%);
12
+ min-height: 100vh;
13
+ padding: 40px 20px;
14
+ }
15
+ .container { max-width: 800px; margin: 0 auto; }
16
+ .card {
17
+ background: #ffffff;
18
+ border-radius: 20px;
19
+ padding: 40px;
20
+ margin-bottom: 30px;
21
+ box-shadow: 0 20px 40px rgba(0,0,0,0.25);
22
+ }
23
+ .badge {
24
+ background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
25
+ color: #052e16;
26
+ padding: 8px 16px;
27
+ border-radius: 50px;
28
+ display: inline-block;
29
+ font-size: 0.9em;
30
+ font-weight: 600;
31
+ margin-bottom: 20px;
32
+ }
33
+ h1 { color: #0b1220; font-size: 2.2em; margin-bottom: 10px; }
34
+ h2 { color: #14532d; margin: 28px 0 12px 0; font-size: 1.45em; }
35
+ p { color: #475569; line-height: 1.65; margin: 10px 0; }
36
+ ul, ol { margin: 10px 0 10px 28px; color: #475569; line-height: 1.65; }
37
+ li { margin: 6px 0; }
38
+ strong { color: #0b1220; }
39
+ code {
40
+ background: #e8f0ea;
41
+ color: #14532d;
42
+ padding: 2px 6px;
43
+ border-radius: 4px;
44
+ font-size: 0.85em;
45
+ }
46
+ a { color: #15803d; text-decoration: none; }
47
+ a:hover { text-decoration: underline; }
48
+ .alerta {
49
+ background: #fff8e6;
50
+ border-left: 4px solid #f59e0b;
51
+ padding: 14px 18px;
52
+ border-radius: 10px;
53
+ margin: 15px 0;
54
+ color: #78350f;
55
+ }
56
+ .links {
57
+ margin: 30px 0 20px 0;
58
+ padding: 22px;
59
+ background: #ecfdf5;
60
+ border-radius: 15px;
61
+ text-align: center;
62
+ }
63
+ .links a {
64
+ display: inline-block;
65
+ min-height: 44px;
66
+ line-height: 44px;
67
+ padding: 0 14px;
68
+ color: #15803d;
69
+ text-decoration: none;
70
+ margin: 4px 8px;
71
+ font-weight: 600;
72
+ border-radius: 10px;
73
+ }
74
+ .links a:hover { background: #d1fae5; }
75
+ footer { text-align: center; color: rgba(255,255,255,0.85); margin-top: 30px; line-height: 1.6; }
76
+ .contact {
77
+ background: #fff8e6;
78
+ padding: 18px;
79
+ border-radius: 10px;
80
+ margin-top: 20px;
81
+ line-height: 1.7;
82
+ }
83
+ .contact a { color: #15803d; }
84
+ @media (max-width: 600px) {
85
+ .card { padding: 25px; }
86
+ h1 { font-size: 1.6em; }
87
+ h2 { font-size: 1.25em; }
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="container">
93
+ <div class="card">
94
+ <div class="badge">📜 Documento Legal</div>
95
+ <h1>Termos de Uso</h1>
96
+ <p><strong>Data de vigência:</strong> 25 de outubro de 2026</p>
97
+
98
+ <h2>1. Aceitação</h2>
99
+ <p>
100
+ Ao instalar e usar o PandaNews, o usuário concorda integralmente com estes Termos de Uso e com a
101
+ <a href="privacy.html">Política de Privacidade</a>. Caso não concorde com qualquer disposição, o usuário
102
+ deve desinstalar e cessar o uso da ferramenta.
103
+ </p>
104
+
105
+ <h2>2. Descrição do Serviço</h2>
106
+ <p>
107
+ O PandaNews é uma ferramenta <strong>local</strong> (executada na máquina do usuário via <code>npx</code>)
108
+ para geração e automação de blogs de notícias com Inteligência Artificial: coleta de pautas em feeds RSS,
109
+ redação com LLMs multi-provedor (fallback NVIDIA NIM → Google Gemini), geração de imagens ilustrativas,
110
+ revisão humana e agendamento automático por blog.
111
+ </p>
112
+ <div class="alerta">
113
+ O serviço é fornecido <strong>"como está"</strong>, sem garantias de disponibilidade contínua, precisão
114
+ editorial ou adequação a fins específicos. A qualidade do conteúdo gerado depende dos modelos de IA e das
115
+ fontes configuradas pelo usuário.
116
+ </div>
117
+
118
+ <h2>3. Responsabilidades do usuário</h2>
119
+ <ul>
120
+ <li><strong>Conteúdo gerado:</strong> o usuário é o único responsável pelas notícias, imagens e textos produzidos, e pela verificação de sua veracidade antes de qualquer publicação.</li>
121
+ <li><strong>Chaves de API:</strong> o usuário responde pelas chaves que fornecer (NVIDIA NIM, Google Gemini, Supabase) e pelo cumprimento dos termos de uso de cada provedor, inclusive cotas, limites de uso e políticas de conteúdo.</li>
122
+ <li><strong>Curadoria das fontes RSS:</strong> o usuário garante que as fontes configuradas respeitam direitos autorais e não violam termos de terceiros.</li>
123
+ <li><strong>Proibições:</strong> é vedado usar o PandaNews para gerar ou difundir desinformação, difamação, discurso de ódio, spam, assédio ou qualquer conteúdo ilegal.</li>
124
+ <li><strong>Termos de plataformas:</strong> é vedado usar o PandaNews para violar os termos de uso das plataformas cujas APIs sejam integradas.</li>
125
+ </ul>
126
+
127
+ <h2>4. Limitação de responsabilidade</h2>
128
+ <p>O PandaNews e seu desenvolvedor <strong>não se responsabilizam</strong> por:</p>
129
+ <ul>
130
+ <li>Bloqueios, penalidades ou restrições impostas por plataformas externas (Google, NVIDIA, Supabase) em decorrência do uso das APIs;</li>
131
+ <li>Conteúdo gerado por IA que contenha imprecisões, vieses ou erros factuais — a revisão humana é etapa obrigatória e de responsabilidade do usuário;</li>
132
+ <li>Danos indiretos, lucros cessantes ou perda de dados decorrentes do uso da ferramenta;</li>
133
+ <li>Uso indevido das chaves de API do usuário por terceiros que tenham acesso à sua máquina.</li>
134
+ </ul>
135
+
136
+ <h2>5. Propriedade Intelectual</h2>
137
+ <ul>
138
+ <li>O código do PandaNews é de uso pessoal e <strong>não pode ser revendido ou redistribuído comercialmente</strong> sem autorização expressa do desenvolvedor.</li>
139
+ <li>O conteúdo gerado com IA pertence ao usuário, observadas as limitações e políticas de uso dos provedores de LLM e de imagem.</li>
140
+ <li>Os feeds RSS coletados são de propriedade de seus respectivos veículos; o usuário deve respeitar os termos de uso de cada fonte.</li>
141
+ </ul>
142
+
143
+ <h2>6. Proteção de dados</h2>
144
+ <p>
145
+ O tratamento de dados pelo PandaNews obedece à <a href="privacy.html">Política de Privacidade</a>, em
146
+ conformidade com a LGPD (Lei nº 13.709/2018). Por se tratar de aplicação local, os dados permanecem na
147
+ máquina do usuário, que exerce diretamente seus direitos de titular.
148
+ </p>
149
+
150
+ <h2>7. Alterações nos Termos</h2>
151
+ <p>
152
+ O desenvolvedor pode atualizar estes Termos a qualquer momento, publicando a nova versão no repositório.
153
+ O uso contínuo após alterações implica aceitação da versão vigente.
154
+ </p>
155
+
156
+ <h2>8. Foro</h2>
157
+ <p>
158
+ Fica eleito o foro da comarca do desenvolvedor (Brasil) para dirimir quaisquer controvérsias decorrentes
159
+ destes Termos, com renúncia expressa a qualquer outro, por mais privilegiado que seja.
160
+ </p>
161
+
162
+ <h2>9. Contato</h2>
163
+ <ul>
164
+ <!-- TODO: confirmar e-mail -->
165
+ <li>Por e-mail: <a href="mailto:danchannels96@gmail.com">danchannels96@gmail.com</a></li>
166
+ </ul>
167
+
168
+ <div class="links">
169
+ <a href="index.html">🏠 Voltar para Home</a>
170
+ <a href="privacy.html">🔒 Política de Privacidade</a>
171
+ </div>
172
+
173
+ <div class="contact">
174
+ <strong>📧 Desenvolvedor:</strong> Dan Hachuel<br>
175
+ <!-- TODO: confirmar e-mail -->
176
+ <strong>📩 Contato:</strong> <a href="mailto:danchannels96@gmail.com">danchannels96@gmail.com</a>
177
+ </div>
178
+ </div>
179
+ <footer>
180
+ <p>© 2026 PandaNews — Dan Hachuel</p>
181
+ <p>Este site é usado para verificação da aplicação junto aos provedores de API utilizados: Google Gemini, NVIDIA NIM e Supabase.</p>
182
+ </footer>
183
+ </div>
184
+ </body>
185
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/pandanews",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "SaaS local de criação e automação de múltiplos blogs de notícias com IA (FastAPI + React + Tailwind).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,6 +12,7 @@
12
12
  "scripts/",
13
13
  "backend/",
14
14
  "frontend/dist/",
15
+ "legal/",
15
16
  "CHANGELOG.md",
16
17
  "README.md"
17
18
  ],
package/scripts/start.js CHANGED
@@ -104,7 +104,7 @@ let uiServer = null;
104
104
  function createUiServer() {
105
105
  const distDir = join(PKG_ROOT, 'frontend', 'dist');
106
106
  return createServer((req, res) => {
107
- if (req.url.startsWith('/api')) {
107
+ if (req.url.startsWith('/api') || req.url.startsWith('/legal')) {
108
108
  proxyApi(req, res);
109
109
  return;
110
110
  }