@intelex-ia/sdk 0.2.0 → 0.4.0

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/README.md CHANGED
@@ -1,310 +1,310 @@
1
- # `@intelex-ia/sdk`
2
-
3
- SDK pra consumir a **Content API IntelexIA** em sites headless (Next.js, Astro etc.). Multiplataforma Fase 2b.
4
-
5
- > **O que você ganha:** `getArticles` / `getArticleBySlug` tipados, `mergePosts()` pra unir conteúdo antigo + novo do painel, `<ArticleJsonLd>` pra injetar schema JSON-LD, e `articleToMetadata` pra plugar no `generateMetadata` do Next.js App Router. Zero dependências (usa `fetch` global).
6
-
7
- ## Instalação rápida (recomendado)
8
-
9
- Se você está começando agora, use o CLI — ele detecta a estrutura do seu projeto e configura tudo automaticamente:
10
-
11
- ```bash
12
- npx @intelex-ia/cli init
13
- ```
14
-
15
- Cria os arquivos no lugar certo (App Router ou Pages Router, com ou sem `src/`), configura `.env.local`, gera um `INTELEXIA.md` completo na raiz e instala o SDK pra você.
16
-
17
- ## Instalação manual
18
-
19
- ```bash
20
- npm install @intelex-ia/sdk
21
- # ou pnpm add @intelex-ia/sdk / yarn add @intelex-ia/sdk
22
- ```
23
-
24
- ## Configuração
25
-
26
- 1. No painel da IntelexIA, **cadastre o site como "Headless (código)"** (vai pedir domain + URL e secret de revalidate).
27
- 2. Abra o site na lista → seção **"Tokens de API (Content API)"** → **Gerar token**. Você recebe `itx_…` (mostrado uma única vez).
28
- 3. Coloque em `.env.local`:
29
-
30
- ```bash
31
- INTELEXIA_API_TOKEN=itx_seu_token_aqui
32
- INTELEXIA_API_URL=https://api.innovarticles.intelexia.com.br # opcional, é o default
33
- INTELEXIA_REVALIDATE_SECRET=cole-o-mesmo-segredo-do-painel
34
- ```
35
-
36
- ## Três modos de integração
37
-
38
- Cada cliente tem uma situação diferente. Escolha o modo que faz sentido:
39
-
40
- | Modo | Quando usar | O que muda no `/blog` |
41
- |---|---|---|
42
- | **🔀 Additive (recomendado)** | Cliente já tem posts antigos e quer **adicionar** os do painel sem perder os antigos | `mergePosts()` une os dois feeds, dedupa por slug, ordena por data |
43
- | **🔁 Replace** | Cliente migrou tudo pro painel — só os novos contam | Substitui `getAllPosts()` por `client.getArticles()` |
44
- | **🆕 Isolated** | Cliente quer manter `/blog` antigo intacto e criar rota nova só pra IA | Cria `/blog/ia` separado, blog antigo zero alterado |
45
-
46
- ---
47
-
48
- ### Modo 1: Additive (cliente existente — caso mais comum)
49
-
50
- ```tsx
51
- // app/blog/page.tsx
52
- import { intelexia } from "@/lib/intelexia";
53
- import { mergePosts } from "@intelex-ia/sdk";
54
- import { posts as localPosts } from "@/lib/posts"; // seu array atual
55
- import Link from "next/link";
56
-
57
- export default async function BlogIndex() {
58
- // 1) Busca os artigos do painel
59
- const { items: remote } = await intelexia.getArticles({ limit: 50 });
60
-
61
- // 2) Mistura com os seus posts antigos
62
- const all = mergePosts({
63
- local: localPosts,
64
- remote,
65
- mapLocal: (p) => ({
66
- slug: p.slug,
67
- title: p.title,
68
- excerpt: p.excerpt,
69
- publishedAt: p.date,
70
- featuredImageUrl: null,
71
- category: p.category,
72
- }),
73
- // Opcional — defaults sensatos:
74
- // dedupe: "remote-wins" (painel substitui local se slug colide)
75
- // sortBy: "date-desc" (mais recente primeiro)
76
- });
77
-
78
- return (
79
- <ul>
80
- {all.map((p) => (
81
- <li key={p.slug}>
82
- <Link href={`/blog/${p.slug}`}>{p.title}</Link>
83
- <time>{new Date(p.publishedAt).toLocaleDateString("pt-BR")}</time>
84
- {p.source === "remote" && <span className="badge-ia">✨ IA</span>}
85
- </li>
86
- ))}
87
- </ul>
88
- );
89
- }
90
- ```
91
-
92
- Pro post individual, tenta o painel primeiro, cai pro local se 404:
93
-
94
- ```tsx
95
- // app/blog/[slug]/page.tsx
96
- import { intelexia } from "@/lib/intelexia";
97
- import { IntelexiaApiError } from "@intelex-ia/sdk";
98
- import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
99
- import { getPostBySlug } from "@/lib/posts";
100
- import { notFound } from "next/navigation";
101
-
102
- export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
103
- const { slug } = await params;
104
-
105
- // 1) Tenta painel
106
- try {
107
- const { article } = await intelexia.getArticleBySlug(slug);
108
- return (
109
- <article>
110
- <ArticleJsonLd article={article} />
111
- <h1>{article.title}</h1>
112
- <div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
113
- </article>
114
- );
115
- } catch (err) {
116
- if (!(err instanceof IntelexiaApiError) || err.status !== 404) throw err;
117
- }
118
-
119
- // 2) Fallback: post antigo local
120
- const local = getPostBySlug(slug);
121
- if (!local) notFound();
122
- return (
123
- <article>
124
- <h1>{local.title}</h1>
125
- {/* renderiza o local do jeito que você já fazia */}
126
- </article>
127
- );
128
- }
129
- ```
130
-
131
- ### Modo 2: Replace (migração completa)
132
-
133
- ```tsx
134
- // app/blog/page.tsx
135
- import { intelexia } from "@/lib/intelexia";
136
-
137
- export default async function BlogIndex() {
138
- const { items } = await intelexia.getArticles({ limit: 20 });
139
- return (
140
- <ul>
141
- {items.map((a) => (
142
- <li key={a.id}>
143
- <Link href={`/blog/${a.slug}`}>{a.title}</Link>
144
- </li>
145
- ))}
146
- </ul>
147
- );
148
- }
149
- ```
150
-
151
- Pro post individual, sem fallback:
152
-
153
- ```tsx
154
- import { intelexia } from "@/lib/intelexia";
155
- import { IntelexiaApiError } from "@intelex-ia/sdk";
156
- import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
157
- import { notFound } from "next/navigation";
158
-
159
- async function fetchArticle(slug: string) {
160
- try {
161
- return await intelexia.getArticleBySlug(slug);
162
- } catch (err) {
163
- if (err instanceof IntelexiaApiError && err.status === 404) notFound();
164
- throw err;
165
- }
166
- }
167
-
168
- export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
169
- const { slug } = await params;
170
- const { article } = await fetchArticle(slug);
171
- return articleToMetadata(article);
172
- }
173
-
174
- export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
175
- const { slug } = await params;
176
- const { article } = await fetchArticle(slug);
177
- return (
178
- <article>
179
- <ArticleJsonLd article={article} />
180
- <h1>{article.title}</h1>
181
- <div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
182
- </article>
183
- );
184
- }
185
- ```
186
-
187
- ### Modo 3: Isolated (rota nova, blog antigo intacto)
188
-
189
- Cria `app/blog/ia/page.tsx` e `app/blog/ia/[slug]/page.tsx` com o mesmo código do modo Replace, mas em rota separada (`/blog/ia` em vez de `/blog`). Zero alteração no blog existente.
190
-
191
- ---
192
-
193
- ## Cliente — `lib/intelexia.ts`
194
-
195
- ```ts
196
- import { createClient } from "@intelex-ia/sdk";
197
-
198
- export const intelexia = createClient({
199
- token: process.env.INTELEXIA_API_TOKEN!,
200
- // ISR: revalida a cada 60s; o webhook revalidate invalida sob demanda
201
- init: { next: { revalidate: 60, tags: ["intelexia-articles"] } },
202
- });
203
- ```
204
-
205
- ## Webhook de revalidate — `app/api/revalidate/route.ts`
206
-
207
- Quando um artigo publica/edita no painel, nosso worker faz `POST` no seu endpoint. Implemente assim:
208
-
209
- ```ts
210
- import { NextResponse } from "next/server";
211
- import { revalidatePath, revalidateTag } from "next/cache";
212
-
213
- export async function POST(req: Request) {
214
- const headerSecret = req.headers.get("x-revalidate-secret");
215
- const body = (await req.json().catch(() => ({}))) as {
216
- secret?: string;
217
- slug?: string | null;
218
- domain?: string;
219
- };
220
- const got = headerSecret ?? body.secret ?? "";
221
- if (!process.env.INTELEXIA_REVALIDATE_SECRET || got !== process.env.INTELEXIA_REVALIDATE_SECRET) {
222
- return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 });
223
- }
224
-
225
- if (body.slug) revalidatePath(`/blog/${body.slug}`);
226
- revalidatePath("/blog");
227
- revalidateTag("intelexia-articles");
228
-
229
- return NextResponse.json({ ok: true, revalidated: { slug: body.slug ?? null } });
230
- }
231
- ```
232
-
233
- > O worker envia o secret tanto no header `x-revalidate-secret` quanto no body, tem timeout de 15s e é **fire-and-forget** (se seu endpoint falhar, o artigo continua publicado e a ISR atualiza pelo `revalidate: 60`).
234
-
235
- ## API
236
-
237
- ### `createClient(config)`
238
-
239
- | Opção | Tipo | Default |
240
- | -------- | -------------------- | ------------------------------------------------- |
241
- | `token` | `string` (obrig.) | — |
242
- | `apiUrl` | `string` | `https://api.innovarticles.intelexia.com.br` |
243
- | `fetch` | `typeof fetch` | `globalThis.fetch` |
244
- | `init` | `RequestInit` | `undefined` (passe `{ next: { revalidate: N } }`) |
245
-
246
- ### `client.getArticles({ limit?, offset?, init? })`
247
-
248
- Resposta: `{ ok, mode, lifecycle, count, items: ArticleListItem[] }`.
249
-
250
- ### `client.getArticleBySlug(slug, { init? })`
251
-
252
- Resposta: `{ ok, mode, lifecycle, article: Article }`. **404 → joga `IntelexiaApiError`** (use `try/catch` + `notFound()`).
253
-
254
- ### `mergePosts({ local, remote, mapLocal, dedupe?, sortBy? })` 🆕 0.2.0
255
-
256
- | Opção | Tipo | Default |
257
- | ---------- | ------------------------------------------------- | ---------------- |
258
- | `local` | `readonly TLocal[]` (qualquer shape) | — |
259
- | `remote` | `readonly ArticleListItem[]` | — |
260
- | `mapLocal` | `(post: TLocal) => Omit<UnifiedPost,"source"> \| null` | — |
261
- | `dedupe` | `"remote-wins" \| "local-wins" \| "throw"` | `"remote-wins"` |
262
- | `sortBy` | `"date-desc" \| "date-asc" \| "none"` | `"date-desc"` |
263
-
264
- Retorna `UnifiedPost[]` com `source: "local" | "remote"`. `mapLocal` retornando `null` filtra o post (útil pra esconder rascunhos). Slugs duplicados resolvidos pela estratégia `dedupe`. Veja exemplo no Modo Additive acima.
265
-
266
- ### `mode: "full" | "plain"`
267
-
268
- - **`full`** (sites `active`/`paused`): inclui schema JSON-LD, metaTitle/Description, keyword(s), interlinking interno no `contentHtml`.
269
- - **`plain`** (site `cancelled`): só conteúdo cru — schema/meta/keywords vêm `null`, interlinking removido. O `ArticleJsonLd` e o `articleToMetadata` lidam graciosamente (omitem o que não tem).
270
-
271
- A decisão é **server-side e não-negociável** — o cliente não escolhe o modo.
272
-
273
- ### Helpers `/react`
274
-
275
- - `<ArticleJsonLd article={article} />` — renderiza `<script type="application/ld+json">` com o `schemaJson`. No-op se ausente.
276
- - `articleToMetadata(article)` → objeto compatível com o `Metadata` do Next.js App Router (title, description, keywords, openGraph, twitter, customMetaTags como `other`).
277
-
278
- ## Erros
279
-
280
- ```ts
281
- import { IntelexiaApiError, MergeConflictError } from "@intelex-ia/sdk";
282
-
283
- try {
284
- await intelexia.getArticleBySlug("post-inexistente");
285
- } catch (err) {
286
- if (err instanceof IntelexiaApiError) {
287
- console.error(err.status, err.message); // 404, "Artigo não encontrado."
288
- }
289
- }
290
-
291
- try {
292
- mergePosts({ local, remote, mapLocal, dedupe: "throw" });
293
- } catch (err) {
294
- if (err instanceof MergeConflictError) {
295
- console.error(`Conflito de slug: ${err.slug}`);
296
- }
297
- }
298
- ```
299
-
300
- ## Compatibilidade
301
-
302
- - **Node** 18+ (fetch nativo), Edge Runtime, Browser.
303
- - **React** 18+ (peer dep opcional — só pros helpers `/react`).
304
- - **TypeScript:** tipos publicados no `dist`.
305
- - **Frameworks:** Next.js (App Router e Pages Router), Astro, Remix, SvelteKit, Vite, qualquer JS.
306
-
307
- ## Changelog
308
-
309
- - **0.2.0** — adiciona `mergePosts()` + tipo `UnifiedPost` + `MergeConflictError` (3 modos de integração).
310
- - **0.1.0** — release inicial: `createClient`, `getArticles`, `getArticleBySlug`, `<ArticleJsonLd>`, `articleToMetadata`.
1
+ # `@intelex-ia/sdk`
2
+
3
+ SDK pra consumir a **Content API IntelexIA** em sites headless (Next.js, Astro etc.). Multiplataforma Fase 2b.
4
+
5
+ > **O que você ganha:** `getArticles` / `getArticleBySlug` tipados, `mergePosts()` pra unir conteúdo antigo + novo do painel, `<ArticleJsonLd>` pra injetar schema JSON-LD, e `articleToMetadata` pra plugar no `generateMetadata` do Next.js App Router. Zero dependências (usa `fetch` global).
6
+
7
+ ## Instalação rápida (recomendado)
8
+
9
+ Se você está começando agora, use o CLI — ele detecta a estrutura do seu projeto e configura tudo automaticamente:
10
+
11
+ ```bash
12
+ npx @intelex-ia/cli init
13
+ ```
14
+
15
+ Cria os arquivos no lugar certo (App Router ou Pages Router, com ou sem `src/`), configura `.env.local`, gera um `INTELEXIA.md` completo na raiz e instala o SDK pra você.
16
+
17
+ ## Instalação manual
18
+
19
+ ```bash
20
+ npm install @intelex-ia/sdk
21
+ # ou pnpm add @intelex-ia/sdk / yarn add @intelex-ia/sdk
22
+ ```
23
+
24
+ ## Configuração
25
+
26
+ 1. No painel da IntelexIA, **cadastre o site como "Headless (código)"** (vai pedir domain + URL e secret de revalidate).
27
+ 2. Abra o site na lista → seção **"Tokens de API (Content API)"** → **Gerar token**. Você recebe `itx_…` (mostrado uma única vez).
28
+ 3. Coloque em `.env.local`:
29
+
30
+ ```bash
31
+ INTELEXIA_API_TOKEN=itx_seu_token_aqui
32
+ INTELEXIA_API_URL=https://api.innovarticles.intelexia.com.br # opcional, é o default
33
+ INTELEXIA_REVALIDATE_SECRET=cole-o-mesmo-segredo-do-painel
34
+ ```
35
+
36
+ ## Três modos de integração
37
+
38
+ Cada cliente tem uma situação diferente. Escolha o modo que faz sentido:
39
+
40
+ | Modo | Quando usar | O que muda no `/blog` |
41
+ |---|---|---|
42
+ | **🔀 Additive (recomendado)** | Cliente já tem posts antigos e quer **adicionar** os do painel sem perder os antigos | `mergePosts()` une os dois feeds, dedupa por slug, ordena por data |
43
+ | **🔁 Replace** | Cliente migrou tudo pro painel — só os novos contam | Substitui `getAllPosts()` por `client.getArticles()` |
44
+ | **🆕 Isolated** | Cliente quer manter `/blog` antigo intacto e criar rota nova só pra IA | Cria `/blog/ia` separado, blog antigo zero alterado |
45
+
46
+ ---
47
+
48
+ ### Modo 1: Additive (cliente existente — caso mais comum)
49
+
50
+ ```tsx
51
+ // app/blog/page.tsx
52
+ import { intelexia } from "@/lib/intelexia";
53
+ import { mergePosts } from "@intelex-ia/sdk";
54
+ import { posts as localPosts } from "@/lib/posts"; // seu array atual
55
+ import Link from "next/link";
56
+
57
+ export default async function BlogIndex() {
58
+ // 1) Busca os artigos do painel
59
+ const { items: remote } = await intelexia.getArticles({ limit: 50 });
60
+
61
+ // 2) Mistura com os seus posts antigos
62
+ const all = mergePosts({
63
+ local: localPosts,
64
+ remote,
65
+ mapLocal: (p) => ({
66
+ slug: p.slug,
67
+ title: p.title,
68
+ excerpt: p.excerpt,
69
+ publishedAt: p.date,
70
+ featuredImageUrl: null,
71
+ category: p.category,
72
+ }),
73
+ // Opcional — defaults sensatos:
74
+ // dedupe: "remote-wins" (painel substitui local se slug colide)
75
+ // sortBy: "date-desc" (mais recente primeiro)
76
+ });
77
+
78
+ return (
79
+ <ul>
80
+ {all.map((p) => (
81
+ <li key={p.slug}>
82
+ <Link href={`/blog/${p.slug}`}>{p.title}</Link>
83
+ <time>{new Date(p.publishedAt).toLocaleDateString("pt-BR")}</time>
84
+ {p.source === "remote" && <span className="badge-ia">✨ IA</span>}
85
+ </li>
86
+ ))}
87
+ </ul>
88
+ );
89
+ }
90
+ ```
91
+
92
+ Pro post individual, tenta o painel primeiro, cai pro local se 404:
93
+
94
+ ```tsx
95
+ // app/blog/[slug]/page.tsx
96
+ import { intelexia } from "@/lib/intelexia";
97
+ import { IntelexiaApiError } from "@intelex-ia/sdk";
98
+ import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
99
+ import { getPostBySlug } from "@/lib/posts";
100
+ import { notFound } from "next/navigation";
101
+
102
+ export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
103
+ const { slug } = await params;
104
+
105
+ // 1) Tenta painel
106
+ try {
107
+ const { article } = await intelexia.getArticleBySlug(slug);
108
+ return (
109
+ <article>
110
+ <ArticleJsonLd article={article} />
111
+ <h1>{article.title}</h1>
112
+ <div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
113
+ </article>
114
+ );
115
+ } catch (err) {
116
+ if (!(err instanceof IntelexiaApiError) || err.status !== 404) throw err;
117
+ }
118
+
119
+ // 2) Fallback: post antigo local
120
+ const local = getPostBySlug(slug);
121
+ if (!local) notFound();
122
+ return (
123
+ <article>
124
+ <h1>{local.title}</h1>
125
+ {/* renderiza o local do jeito que você já fazia */}
126
+ </article>
127
+ );
128
+ }
129
+ ```
130
+
131
+ ### Modo 2: Replace (migração completa)
132
+
133
+ ```tsx
134
+ // app/blog/page.tsx
135
+ import { intelexia } from "@/lib/intelexia";
136
+
137
+ export default async function BlogIndex() {
138
+ const { items } = await intelexia.getArticles({ limit: 20 });
139
+ return (
140
+ <ul>
141
+ {items.map((a) => (
142
+ <li key={a.id}>
143
+ <Link href={`/blog/${a.slug}`}>{a.title}</Link>
144
+ </li>
145
+ ))}
146
+ </ul>
147
+ );
148
+ }
149
+ ```
150
+
151
+ Pro post individual, sem fallback:
152
+
153
+ ```tsx
154
+ import { intelexia } from "@/lib/intelexia";
155
+ import { IntelexiaApiError } from "@intelex-ia/sdk";
156
+ import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
157
+ import { notFound } from "next/navigation";
158
+
159
+ async function fetchArticle(slug: string) {
160
+ try {
161
+ return await intelexia.getArticleBySlug(slug);
162
+ } catch (err) {
163
+ if (err instanceof IntelexiaApiError && err.status === 404) notFound();
164
+ throw err;
165
+ }
166
+ }
167
+
168
+ export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
169
+ const { slug } = await params;
170
+ const { article } = await fetchArticle(slug);
171
+ return articleToMetadata(article);
172
+ }
173
+
174
+ export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
175
+ const { slug } = await params;
176
+ const { article } = await fetchArticle(slug);
177
+ return (
178
+ <article>
179
+ <ArticleJsonLd article={article} />
180
+ <h1>{article.title}</h1>
181
+ <div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
182
+ </article>
183
+ );
184
+ }
185
+ ```
186
+
187
+ ### Modo 3: Isolated (rota nova, blog antigo intacto)
188
+
189
+ Cria `app/blog/ia/page.tsx` e `app/blog/ia/[slug]/page.tsx` com o mesmo código do modo Replace, mas em rota separada (`/blog/ia` em vez de `/blog`). Zero alteração no blog existente.
190
+
191
+ ---
192
+
193
+ ## Cliente — `lib/intelexia.ts`
194
+
195
+ ```ts
196
+ import { createClient } from "@intelex-ia/sdk";
197
+
198
+ export const intelexia = createClient({
199
+ token: process.env.INTELEXIA_API_TOKEN!,
200
+ // ISR: revalida a cada 60s; o webhook revalidate invalida sob demanda
201
+ init: { next: { revalidate: 60, tags: ["intelexia-articles"] } },
202
+ });
203
+ ```
204
+
205
+ ## Webhook de revalidate — `app/api/revalidate/route.ts`
206
+
207
+ Quando um artigo publica/edita no painel, nosso worker faz `POST` no seu endpoint. Implemente assim:
208
+
209
+ ```ts
210
+ import { NextResponse } from "next/server";
211
+ import { revalidatePath, revalidateTag } from "next/cache";
212
+
213
+ export async function POST(req: Request) {
214
+ const headerSecret = req.headers.get("x-revalidate-secret");
215
+ const body = (await req.json().catch(() => ({}))) as {
216
+ secret?: string;
217
+ slug?: string | null;
218
+ domain?: string;
219
+ };
220
+ const got = headerSecret ?? body.secret ?? "";
221
+ if (!process.env.INTELEXIA_REVALIDATE_SECRET || got !== process.env.INTELEXIA_REVALIDATE_SECRET) {
222
+ return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 });
223
+ }
224
+
225
+ if (body.slug) revalidatePath(`/blog/${body.slug}`);
226
+ revalidatePath("/blog");
227
+ revalidateTag("intelexia-articles");
228
+
229
+ return NextResponse.json({ ok: true, revalidated: { slug: body.slug ?? null } });
230
+ }
231
+ ```
232
+
233
+ > O worker envia o secret tanto no header `x-revalidate-secret` quanto no body, tem timeout de 15s e é **fire-and-forget** (se seu endpoint falhar, o artigo continua publicado e a ISR atualiza pelo `revalidate: 60`).
234
+
235
+ ## API
236
+
237
+ ### `createClient(config)`
238
+
239
+ | Opção | Tipo | Default |
240
+ | -------- | -------------------- | ------------------------------------------------- |
241
+ | `token` | `string` (obrig.) | — |
242
+ | `apiUrl` | `string` | `https://api.innovarticles.intelexia.com.br` |
243
+ | `fetch` | `typeof fetch` | `globalThis.fetch` |
244
+ | `init` | `RequestInit` | `undefined` (passe `{ next: { revalidate: N } }`) |
245
+
246
+ ### `client.getArticles({ limit?, offset?, init? })`
247
+
248
+ Resposta: `{ ok, mode, lifecycle, count, items: ArticleListItem[] }`.
249
+
250
+ ### `client.getArticleBySlug(slug, { init? })`
251
+
252
+ Resposta: `{ ok, mode, lifecycle, article: Article }`. **404 → joga `IntelexiaApiError`** (use `try/catch` + `notFound()`).
253
+
254
+ ### `mergePosts({ local, remote, mapLocal, dedupe?, sortBy? })` 🆕 0.2.0
255
+
256
+ | Opção | Tipo | Default |
257
+ | ---------- | ------------------------------------------------- | ---------------- |
258
+ | `local` | `readonly TLocal[]` (qualquer shape) | — |
259
+ | `remote` | `readonly ArticleListItem[]` | — |
260
+ | `mapLocal` | `(post: TLocal) => Omit<UnifiedPost,"source"> \| null` | — |
261
+ | `dedupe` | `"remote-wins" \| "local-wins" \| "throw"` | `"remote-wins"` |
262
+ | `sortBy` | `"date-desc" \| "date-asc" \| "none"` | `"date-desc"` |
263
+
264
+ Retorna `UnifiedPost[]` com `source: "local" | "remote"`. `mapLocal` retornando `null` filtra o post (útil pra esconder rascunhos). Slugs duplicados resolvidos pela estratégia `dedupe`. Veja exemplo no Modo Additive acima.
265
+
266
+ ### `mode: "full" | "plain"`
267
+
268
+ - **`full`** (sites `active`/`paused`): inclui schema JSON-LD, metaTitle/Description, keyword(s), interlinking interno no `contentHtml`.
269
+ - **`plain`** (site `cancelled`): só conteúdo cru — schema/meta/keywords vêm `null`, interlinking removido. O `ArticleJsonLd` e o `articleToMetadata` lidam graciosamente (omitem o que não tem).
270
+
271
+ A decisão é **server-side e não-negociável** — o cliente não escolhe o modo.
272
+
273
+ ### Helpers `/react`
274
+
275
+ - `<ArticleJsonLd article={article} />` — renderiza `<script type="application/ld+json">` com o `schemaJson`. No-op se ausente.
276
+ - `articleToMetadata(article)` → objeto compatível com o `Metadata` do Next.js App Router (title, description, keywords, openGraph, twitter, customMetaTags como `other`).
277
+
278
+ ## Erros
279
+
280
+ ```ts
281
+ import { IntelexiaApiError, MergeConflictError } from "@intelex-ia/sdk";
282
+
283
+ try {
284
+ await intelexia.getArticleBySlug("post-inexistente");
285
+ } catch (err) {
286
+ if (err instanceof IntelexiaApiError) {
287
+ console.error(err.status, err.message); // 404, "Artigo não encontrado."
288
+ }
289
+ }
290
+
291
+ try {
292
+ mergePosts({ local, remote, mapLocal, dedupe: "throw" });
293
+ } catch (err) {
294
+ if (err instanceof MergeConflictError) {
295
+ console.error(`Conflito de slug: ${err.slug}`);
296
+ }
297
+ }
298
+ ```
299
+
300
+ ## Compatibilidade
301
+
302
+ - **Node** 18+ (fetch nativo), Edge Runtime, Browser.
303
+ - **React** 18+ (peer dep opcional — só pros helpers `/react`).
304
+ - **TypeScript:** tipos publicados no `dist`.
305
+ - **Frameworks:** Next.js (App Router e Pages Router), Astro, Remix, SvelteKit, Vite, qualquer JS.
306
+
307
+ ## Changelog
308
+
309
+ - **0.2.0** — adiciona `mergePosts()` + tipo `UnifiedPost` + `MergeConflictError` (3 modos de integração).
310
+ - **0.1.0** — release inicial: `createClient`, `getArticles`, `getArticleBySlug`, `<ArticleJsonLd>`, `articleToMetadata`.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=autoria.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoria.test.d.ts","sourceRoot":"","sources":["../src/autoria.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { autoriaDoArtigo, corpoDoArtigo, SELO_IA } from "./react.js";
3
+ /**
4
+ * A autoria do artigo tem duas regras duras, e as duas vêm de fora da
5
+ * engenharia:
6
+ *
7
+ * 1. `author` só vale quando é `Organization`. Texto produzido com apoio de IA
8
+ * e assinado por uma PESSOA é o que os Termos do Google proíbem desde
9
+ * 30/07/2026. O profissional aparece como REVISOR.
10
+ *
11
+ * 2. A autoria NUNCA some. Sem cadastro, assina a equipe da empresa — e o selo
12
+ * de apoio de IA sai do mesmo jeito. Artigo no ar sem dizer quem assina foi
13
+ * exatamente o defeito medido no siteIA em 21/10/2026.
14
+ */
15
+ describe("autoriaDoArtigo", () => {
16
+ const comOrganization = {
17
+ schemaJson: {
18
+ "@graph": [
19
+ {
20
+ "@type": "BlogPosting",
21
+ author: { "@type": "Organization", name: "Equipe Editorial Parceria" },
22
+ contributor: {
23
+ "@type": "Person",
24
+ name: "Mauro Roger Pinto",
25
+ jobTitle: "Contador responsável técnico",
26
+ identifier: "CRC-SC 020357/O-4",
27
+ },
28
+ dateModified: "2026-10-21",
29
+ publisher: { "@type": "Organization", name: "Parceria Assessoria Contábil" },
30
+ },
31
+ ],
32
+ },
33
+ };
34
+ it("usa a Organization como autor e o contributor como revisor", () => {
35
+ const a = autoriaDoArtigo(comOrganization);
36
+ expect(a.autor).toBe("Equipe Editorial Parceria");
37
+ expect(a.revisor).toBe("Mauro Roger Pinto, Contador responsável técnico, CRC-SC 020357/O-4");
38
+ expect(a.revisadoEm).toBe("2026-10-21");
39
+ expect(a.temCadastro).toBe(true);
40
+ });
41
+ it("IGNORA author do tipo Person — e cai para a equipe da empresa", () => {
42
+ const a = autoriaDoArtigo({
43
+ schemaJson: {
44
+ "@type": "BlogPosting",
45
+ author: { "@type": "Person", name: "Fulano de Tal" },
46
+ publisher: { "@type": "Organization", name: "Contabilidade Acme" },
47
+ },
48
+ });
49
+ expect(a.autor).not.toContain("Fulano");
50
+ expect(a.autor).toBe("Equipe Editorial Contabilidade Acme");
51
+ expect(a.temCadastro).toBe(false);
52
+ });
53
+ it("sem autoria no schema, assina a equipe tirada do publisher", () => {
54
+ const a = autoriaDoArtigo({
55
+ schemaJson: { "@type": "BlogPosting", publisher: { "@type": "Organization", name: "Acme" } },
56
+ });
57
+ expect(a.autor).toBe("Equipe Editorial Acme");
58
+ expect(a.revisor).toBeNull();
59
+ expect(a.temCadastro).toBe(false);
60
+ });
61
+ it("a prop `empresa` vence o publisher do schema", () => {
62
+ const a = autoriaDoArtigo({ schemaJson: { publisher: { "@type": "Organization", name: "Nome Velho" } } }, { empresa: "Nome Novo" });
63
+ expect(a.autor).toBe("Equipe Editorial Nome Novo");
64
+ });
65
+ it("sem schema nenhum ainda assina — nunca devolve vazio", () => {
66
+ for (const vazio of [null, undefined, "", 0, [], "texto solto"]) {
67
+ const a = autoriaDoArtigo({ schemaJson: vazio });
68
+ expect(a.autor.length).toBeGreaterThan(0);
69
+ expect(a.temCadastro).toBe(false);
70
+ }
71
+ });
72
+ it("revisor sem cargo nem registro não gera pontuação solta", () => {
73
+ const a = autoriaDoArtigo({
74
+ schemaJson: {
75
+ author: { "@type": "Organization", name: "Equipe" },
76
+ contributor: { "@type": "Person", name: "Ana Souza" },
77
+ },
78
+ });
79
+ expect(a.revisor).toBe("Ana Souza");
80
+ });
81
+ it("o selo é o texto único do grupo", () => {
82
+ expect(SELO_IA).toBe("Apoio de IA · Revisão humana");
83
+ });
84
+ });
85
+ /**
86
+ * ASSINATURA DUPLICADA — medida no siteIA em 21/10/2026. O corpo assinava
87
+ * "Dra. Ana Souza, CRC-SP 123456" (bloco `authority-signature` do motor) e o
88
+ * rodapé, "Equipe Editorial". Além de contraditório, o do corpo assina com uma
89
+ * PESSOA — a atribuição que `autoriaDoArtigo` recusa.
90
+ */
91
+ describe("corpoDoArtigo", () => {
92
+ it("remove o bloco do motor, com os divs aninhados", () => {
93
+ const html = '<p>Texto.</p><div class="authority-signature" style="x"><div><b>Dra. Ana Souza</b></div><div>CRC-SP 123456</div></div><p>Fim.</p>';
94
+ const r = corpoDoArtigo({ contentHtml: html });
95
+ expect(r).not.toContain("authority-signature");
96
+ expect(r).not.toContain("Ana Souza");
97
+ expect(r).toContain("<p>Texto.</p>");
98
+ expect(r).toContain("<p>Fim.</p>");
99
+ });
100
+ it("corpo sem o bloco volta intacto", () => {
101
+ const html = "<h2>Um</h2><p>Dois</p>";
102
+ expect(corpoDoArtigo({ contentHtml: html })).toBe(html);
103
+ });
104
+ it("corpo vazio ou nulo não quebra", () => {
105
+ expect(corpoDoArtigo({ contentHtml: null })).toBe("");
106
+ expect(corpoDoArtigo({})).toBe("");
107
+ });
108
+ it("div sem fechamento corta do bloco em diante, sem deixar lixo", () => {
109
+ const r = corpoDoArtigo({ contentHtml: '<p>A</p><div class="authority-signature"><div>B' });
110
+ expect(r).toBe("<p>A</p>");
111
+ });
112
+ });
113
+ //# sourceMappingURL=autoria.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoria.test.js","sourceRoot":"","sources":["../src/autoria.test.ts"],"names":[],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -88,4 +88,93 @@ export interface ArticleMetadata {
88
88
  */
89
89
  export declare function articleToMetadata(article: ArticleMetadataInput): ArticleMetadata;
90
90
  export type { Article, CustomMetaTag } from "./types.js";
91
+ /**
92
+ * Selo de transparência. Fecha a linha do revisor na ASSINATURA, no fim do
93
+ * artigo — nunca na barra do topo.
94
+ */
95
+ export declare const SELO_IA = "Apoio de IA \u00B7 Revis\u00E3o humana";
96
+ export interface AutoriaDoArtigo {
97
+ /** Quem assina. SEMPRE preenchido: cai para a equipe da empresa. */
98
+ autor: string;
99
+ /** "Nome, cargo, registro" do revisor, ou `null` quando não há cadastro. */
100
+ revisor: string | null;
101
+ /** ISO da revisão, quando o schema traz `dateModified`. */
102
+ revisadoEm: string | null;
103
+ /** `false` quando a autoria veio da queda, e não do schema. */
104
+ temCadastro: boolean;
105
+ }
106
+ /**
107
+ * Lê a autoria do `schemaJson` do artigo.
108
+ *
109
+ * ⚠️ `author` SÓ vale quando é `Organization`. Texto produzido com apoio de IA
110
+ * e assinado por uma PESSOA é o que os Termos do Google proíbem desde
111
+ * 30/07/2026 — então, quando o schema traz uma Person em `author`, ela é
112
+ * ignorada aqui. O profissional aparece como REVISOR, via `contributor`.
113
+ *
114
+ * NUNCA devolve vazio: sem autoria no schema, assina a equipe da empresa
115
+ * (`empresa`, ou o `publisher.name` do próprio schema). Artigo no ar sem dizer
116
+ * quem assina e sem declarar o apoio de IA não é uma saída aceitável — foi o
117
+ * que esta função existe para impedir.
118
+ */
119
+ export declare function autoriaDoArtigo(article: {
120
+ schemaJson?: unknown | null;
121
+ }, opts?: {
122
+ empresa?: string;
123
+ }): AutoriaDoArtigo;
124
+ export interface AuthorBarProps {
125
+ article: {
126
+ schemaJson?: unknown | null;
127
+ publishedAt?: string | null;
128
+ words?: number | null;
129
+ };
130
+ /** Nome do negócio, para a queda. Sem ele, usa o `publisher` do schema. */
131
+ empresa?: string;
132
+ /** Classe do contêiner. O estilo é do SITE — o SDK não traz CSS. */
133
+ className?: string;
134
+ /** Uma linha sobre a equipe, na assinatura. Omitida quando ausente. */
135
+ bioEquipe?: string;
136
+ /** Uma linha sobre o revisor, na assinatura. Omitida quando ausente. */
137
+ bioRevisor?: string;
138
+ }
139
+ /**
140
+ * Barra de autoria, logo abaixo do `<h1>`.
141
+ *
142
+ * SEMPRE renderiza (ver `autoriaDoArtigo`). Não traz CSS: o SDK não pode impor
143
+ * visual a sites que têm cada um o seu. As classes (`ia-autor` por padrão) são
144
+ * ganchos para o site vestir.
145
+ *
146
+ * O selo de IA NÃO vem aqui — ele fecha a linha do revisor em
147
+ * `<AuthorSignature>`, no fim do artigo.
148
+ */
149
+ export declare function AuthorBar({ article, empresa, className, }: AuthorBarProps): ReactElement;
150
+ /**
151
+ * Assinatura, no FIM do artigo. É aqui que o selo de IA fecha a linha do
152
+ * revisor — e aparece tanto com profissional cadastrado quanto sem.
153
+ */
154
+ export declare function AuthorSignature({ article, empresa, className, bioEquipe, bioRevisor, }: AuthorBarProps): ReactElement;
155
+ /**
156
+ * O corpo do artigo SEM a assinatura que o motor embute.
157
+ *
158
+ * O `contentHtml` traz um `<div class="authority-signature">` com nome,
159
+ * cargo e registro de um PROFISSIONAL. Quando o site também renderiza
160
+ * `<AuthorSignature>`, o artigo sai com DUAS assinaturas que se contradizem —
161
+ * medido no siteIA em 21/10/2026: o corpo assinava "Dra. Ana Souza, CRC-SP
162
+ * 123456" e o rodapé, "Equipe Editorial".
163
+ *
164
+ * Pior que a duplicata: a do corpo assina com uma PESSOA, que é justamente a
165
+ * atribuição que `autoriaDoArtigo` recusa (Termos do Google, 30/07/2026). A
166
+ * regra não alcançava ali porque aquele bloco é HTML cru, não schema.
167
+ *
168
+ * Por que remover AQUI e não no servidor: tirar do `contentHtml` mudaria o
169
+ * corpo para TODO consumidor, inclusive sites que não renderizam assinatura
170
+ * própria e ficariam sem nenhuma. Removendo no render, quem usa
171
+ * `<AuthorSignature>` fica consistente e quem não usa continua como estava.
172
+ *
173
+ * @example
174
+ * <div dangerouslySetInnerHTML={{ __html: corpoDoArtigo(article) }} />
175
+ * <AuthorSignature article={article} empresa="Acme" />
176
+ */
177
+ export declare function corpoDoArtigo(article: {
178
+ contentHtml?: string | null;
179
+ }): string;
91
180
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAW,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzD,iEAAiE;AACjE,UAAU,kBAAkB;IAC1B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,GACR,EAAE;IACD,OAAO,EAAE,kBAAkB,CAAC;CAC7B,GAAG,YAAY,GAAG,IAAI,CAYtB;AAED,mEAAmE;AACnE,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;CACzC;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE;QACV,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC5B,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,qBAAqB,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,eAAe,CAiCjB;AAGD,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * Renderiza um `<script type="application/ld+json">` com o schema JSON-LD do
4
4
  * artigo. Pode ser colocado em qualquer lugar do `<body>` — o Google lê tanto
@@ -81,4 +81,166 @@ export function articleToMetadata(article) {
81
81
  ...(Object.keys(other).length > 0 ? { other } : {}),
82
82
  };
83
83
  }
84
+ // ─────────────────────────────────────────────────────────────────────────────
85
+ // AUTORIA
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+ /**
88
+ * Selo de transparência. Fecha a linha do revisor na ASSINATURA, no fim do
89
+ * artigo — nunca na barra do topo.
90
+ */
91
+ export const SELO_IA = "Apoio de IA · Revisão humana";
92
+ const ehNo = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
93
+ const texto = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
94
+ /**
95
+ * Lê a autoria do `schemaJson` do artigo.
96
+ *
97
+ * ⚠️ `author` SÓ vale quando é `Organization`. Texto produzido com apoio de IA
98
+ * e assinado por uma PESSOA é o que os Termos do Google proíbem desde
99
+ * 30/07/2026 — então, quando o schema traz uma Person em `author`, ela é
100
+ * ignorada aqui. O profissional aparece como REVISOR, via `contributor`.
101
+ *
102
+ * NUNCA devolve vazio: sem autoria no schema, assina a equipe da empresa
103
+ * (`empresa`, ou o `publisher.name` do próprio schema). Artigo no ar sem dizer
104
+ * quem assina e sem declarar o apoio de IA não é uma saída aceitável — foi o
105
+ * que esta função existe para impedir.
106
+ */
107
+ export function autoriaDoArtigo(article, opts) {
108
+ const schema = article.schemaJson;
109
+ const nos = [];
110
+ if (ehNo(schema)) {
111
+ const grafo = schema["@graph"];
112
+ if (Array.isArray(grafo))
113
+ nos.push(...grafo.filter(ehNo));
114
+ else
115
+ nos.push(schema);
116
+ }
117
+ // Nome da empresa para a queda: o que veio por prop vence; senão, o
118
+ // `publisher` do schema, que é justamente a organização do cliente.
119
+ let empresa = texto(opts?.empresa);
120
+ if (!empresa) {
121
+ for (const no of nos) {
122
+ const pub = no.publisher;
123
+ if (ehNo(pub) && texto(pub.name)) {
124
+ empresa = texto(pub.name);
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ for (const no of nos) {
130
+ const a = no.author;
131
+ if (!ehNo(a) || a["@type"] !== "Organization")
132
+ continue;
133
+ const autor = texto(a.name);
134
+ if (!autor)
135
+ continue;
136
+ const c = no.contributor;
137
+ const revisor = ehNo(c) && texto(c.name)
138
+ ? [texto(c.name), texto(c.jobTitle), texto(c.identifier)]
139
+ .filter(Boolean)
140
+ .join(", ")
141
+ : null;
142
+ return { autor, revisor, revisadoEm: texto(no.dateModified), temCadastro: true };
143
+ }
144
+ return {
145
+ autor: empresa ? `Equipe Editorial ${empresa}` : "Equipe Editorial",
146
+ revisor: null,
147
+ revisadoEm: null,
148
+ temCadastro: false,
149
+ };
150
+ }
151
+ /** Texto da linha de revisão, igual nos dois blocos. */
152
+ function linhaDeRevisao(a, empresa) {
153
+ const quem = a.revisor
154
+ ? `Revisado por ${a.revisor}`
155
+ : `Revisado pela equipe ${empresa?.trim() || a.autor.replace(/^Equipe Editorial\s*/, "") || "editorial"}`;
156
+ return `${quem} · ${SELO_IA}`;
157
+ }
158
+ /** "21/10/2026" no fuso de quem lê. Vazio quando a data não presta. */
159
+ function dataPtBr(iso) {
160
+ if (!iso)
161
+ return "";
162
+ const d = new Date(iso);
163
+ return Number.isNaN(d.getTime()) ? "" : d.toLocaleDateString("pt-BR");
164
+ }
165
+ /** ~200 palavras por minuto, o mesmo arredondamento do resto do grupo. */
166
+ function tempoDeLeitura(words) {
167
+ return typeof words === "number" && words > 0
168
+ ? `${Math.max(1, Math.round(words / 200))} min de leitura`
169
+ : "";
170
+ }
171
+ /**
172
+ * Barra de autoria, logo abaixo do `<h1>`.
173
+ *
174
+ * SEMPRE renderiza (ver `autoriaDoArtigo`). Não traz CSS: o SDK não pode impor
175
+ * visual a sites que têm cada um o seu. As classes (`ia-autor` por padrão) são
176
+ * ganchos para o site vestir.
177
+ *
178
+ * O selo de IA NÃO vem aqui — ele fecha a linha do revisor em
179
+ * `<AuthorSignature>`, no fim do artigo.
180
+ */
181
+ export function AuthorBar({ article, empresa, className = "ia-autor", }) {
182
+ const a = autoriaDoArtigo(article, { empresa });
183
+ const meta = [dataPtBr(article.publishedAt), tempoDeLeitura(article.words)]
184
+ .filter(Boolean)
185
+ .join(" · ");
186
+ return (_jsxs("div", { className: className, children: [_jsxs("strong", { children: ["por ", a.autor] }), a.revisor ? _jsxs("span", { className: `${className}-revisao`, children: ["Revis\u00E3o: ", a.revisor] }) : null, meta ? _jsx("span", { className: `${className}-meta`, children: meta }) : null] }));
187
+ }
188
+ /**
189
+ * Assinatura, no FIM do artigo. É aqui que o selo de IA fecha a linha do
190
+ * revisor — e aparece tanto com profissional cadastrado quanto sem.
191
+ */
192
+ export function AuthorSignature({ article, empresa, className = "ia-assinatura", bioEquipe, bioRevisor, }) {
193
+ const a = autoriaDoArtigo(article, { empresa });
194
+ const nomeRevisor = a.revisor ? a.revisor.split(",")[0].trim() : null;
195
+ // Dois blocos, como a referência do grupo: QUEM ESCREVEU e QUEM REVISOU.
196
+ // O segundo sai mesmo sem profissional cadastrado — é ele que carrega o
197
+ // selo de IA, e o selo é obrigatório.
198
+ return (_jsxs(_Fragment, { children: [_jsxs("section", { className: `${className} ${className}-equipe`, "aria-label": "Quem escreveu", children: [_jsxs("p", { className: `${className}-nome`, children: ["Escrito por ", a.autor] }), bioEquipe ? _jsx("p", { className: `${className}-bio`, children: bioEquipe }) : null] }), _jsxs("section", { className: `${className} ${className}-revisor`, "aria-label": "Quem revisou", children: [nomeRevisor ? _jsx("p", { className: `${className}-nome`, children: nomeRevisor }) : null, _jsx("p", { className: `${className}-cargo`, children: linhaDeRevisao(a, empresa) }), bioRevisor ? _jsx("p", { className: `${className}-bio`, children: bioRevisor }) : null] })] }));
199
+ }
200
+ /**
201
+ * O corpo do artigo SEM a assinatura que o motor embute.
202
+ *
203
+ * O `contentHtml` traz um `<div class="authority-signature">` com nome,
204
+ * cargo e registro de um PROFISSIONAL. Quando o site também renderiza
205
+ * `<AuthorSignature>`, o artigo sai com DUAS assinaturas que se contradizem —
206
+ * medido no siteIA em 21/10/2026: o corpo assinava "Dra. Ana Souza, CRC-SP
207
+ * 123456" e o rodapé, "Equipe Editorial".
208
+ *
209
+ * Pior que a duplicata: a do corpo assina com uma PESSOA, que é justamente a
210
+ * atribuição que `autoriaDoArtigo` recusa (Termos do Google, 30/07/2026). A
211
+ * regra não alcançava ali porque aquele bloco é HTML cru, não schema.
212
+ *
213
+ * Por que remover AQUI e não no servidor: tirar do `contentHtml` mudaria o
214
+ * corpo para TODO consumidor, inclusive sites que não renderizam assinatura
215
+ * própria e ficariam sem nenhuma. Removendo no render, quem usa
216
+ * `<AuthorSignature>` fica consistente e quem não usa continua como estava.
217
+ *
218
+ * @example
219
+ * <div dangerouslySetInnerHTML={{ __html: corpoDoArtigo(article) }} />
220
+ * <AuthorSignature article={article} empresa="Acme" />
221
+ */
222
+ export function corpoDoArtigo(article) {
223
+ const html = article.contentHtml ?? "";
224
+ if (!html)
225
+ return "";
226
+ // `<div class="authority-signature" …>` até o `</div>` que o fecha, contando
227
+ // os aninhados — o bloco tem divs dentro (avatar, textos).
228
+ const abre = /<div[^>]*class="[^"]*authority-signature[^"]*"[^>]*>/i;
229
+ const m = abre.exec(html);
230
+ if (!m)
231
+ return html;
232
+ let i = m.index + m[0].length;
233
+ let nivel = 1;
234
+ const tag = /<\/?div\b[^>]*>/gi;
235
+ tag.lastIndex = i;
236
+ let t;
237
+ while ((t = tag.exec(html)) !== null) {
238
+ nivel += t[0].startsWith("</") ? -1 : 1;
239
+ if (nivel === 0)
240
+ return (html.slice(0, m.index) + html.slice(t.index + t[0].length)).trim();
241
+ }
242
+ // Sem fechamento equilibrado, corta do bloco em diante: deixar pela metade
243
+ // seria pior que remover.
244
+ return html.slice(0, m.index).trim();
245
+ }
84
246
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
- {
2
- "name": "@intelex-ia/sdk",
3
- "version": "0.2.0",
4
- "description": "SDK pra consumir a Content API da IntelexIA em sites headless (Next.js, Astro, etc.). Multiplataforma Fase 2b.",
5
- "license": "MIT",
6
- "author": "intelex-ia",
7
- "homepage": "https://github.com/intelex-ia/combo_automatico/tree/main/packages/sdk",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/intelex-ia/combo_automatico.git",
11
- "directory": "packages/sdk"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/intelex-ia/combo_automatico/issues"
15
- },
16
- "keywords": [
17
- "intelexia",
18
- "content-api",
19
- "headless",
20
- "cms",
21
- "next",
22
- "nextjs",
23
- "react",
24
- "sdk"
25
- ],
26
- "type": "module",
27
- "main": "./dist/index.js",
28
- "module": "./dist/index.js",
29
- "types": "./dist/index.d.ts",
30
- "exports": {
31
- ".": {
32
- "types": "./dist/index.d.ts",
33
- "default": "./dist/index.js"
34
- },
35
- "./react": {
36
- "types": "./dist/react.d.ts",
37
- "default": "./dist/react.js"
38
- }
39
- },
40
- "files": [
41
- "dist",
42
- "README.md"
43
- ],
44
- "scripts": {
45
- "build": "tsc",
46
- "typecheck": "tsc --noEmit"
47
- },
48
- "peerDependencies": {
49
- "react": ">=18"
50
- },
51
- "peerDependenciesMeta": {
52
- "react": {
53
- "optional": true
54
- }
55
- },
56
- "devDependencies": {
57
- "@types/react": "^18.3.12",
58
- "typescript": "^5.6.3"
59
- },
60
- "publishConfig": {
61
- "access": "public",
62
- "provenance": true
63
- }
64
- }
1
+ {
2
+ "name": "@intelex-ia/sdk",
3
+ "version": "0.4.0",
4
+ "description": "SDK pra consumir a Content API da IntelexIA em sites headless (Next.js, Astro, etc.). Multiplataforma Fase 2b.",
5
+ "license": "MIT",
6
+ "author": "intelex-ia",
7
+ "homepage": "https://github.com/intelex-ia/combo_automatico/tree/main/packages/sdk",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/intelex-ia/combo_automatico.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/intelex-ia/combo_automatico/issues"
15
+ },
16
+ "keywords": [
17
+ "intelexia",
18
+ "content-api",
19
+ "headless",
20
+ "cms",
21
+ "next",
22
+ "nextjs",
23
+ "react",
24
+ "sdk"
25
+ ],
26
+ "type": "module",
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./react": {
36
+ "types": "./dist/react.d.ts",
37
+ "default": "./dist/react.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "README.md"
43
+ ],
44
+ "scripts": {
45
+ "build": "tsc",
46
+ "typecheck": "tsc --noEmit"
47
+ },
48
+ "peerDependencies": {
49
+ "react": ">=18"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "react": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "@types/react": "^18.3.12",
58
+ "typescript": "^5.6.3"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public",
62
+ "provenance": true
63
+ }
64
+ }