@brasil-conjunto/core 0.1.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 ADDED
@@ -0,0 +1,246 @@
1
+ # portal-core
2
+
3
+ **A constituicao do sistema.** Biblioteca compartilhada que define o dominio do negocio — tipos, schemas, migracoes, validacao e constantes. Tudo que os outros workspaces precisam concordar passa por aqui.
4
+
5
+ ---
6
+
7
+ ## Responsabilidade
8
+
9
+ Portal-core e o **contrato unico** entre todos os workspaces do monorepo. Ele define:
10
+
11
+ - **O que e um servico publico** (tipos TypeScript)
12
+ - **Como um servico deve ser validado** (schemas Zod)
13
+ - **Como o banco de dados e estruturado** (migracoes SQL)
14
+ - **Quais documentos existem** (constantes padronizadas)
15
+ - **Como normalizar texto** (funcoes utilitarias)
16
+
17
+ Se dois workspaces precisam concordar sobre a forma de um dado, a definicao vive aqui.
18
+
19
+ ### O que portal-core FAZ
20
+
21
+ - Define interfaces TypeScript (`PublicService`, `DocumentRequired`, `CostItem`, `ServiceVariant`, etc.)
22
+ - Exporta schemas Zod para validacao em runtime
23
+ - Contem migracoes SQL para o Supabase/PostgreSQL
24
+ - Padroniza codigos de documentos (`CPF`, `RG`, `CRLV_VEICULO`, `LAUDO_MEDICO_DETRAN`, etc.)
25
+ - Fornece funcoes puras de normalizacao (slugify, sanitize, etc.)
26
+
27
+ ### O que portal-core NAO FAZ
28
+
29
+ - Nao acessa banco de dados — define schemas, nao executa queries
30
+ - Nao tem dependencia de framework (sem Next.js, sem Playwright, sem AI SDK)
31
+ - Nao tem UI, componentes ou paginas
32
+ - Nao faz IO (sem HTTP, sem filesystem, sem crawling)
33
+
34
+ ---
35
+
36
+ ## Arquitetura Interna
37
+
38
+ ```
39
+ packages/portal-core/
40
+ ├── src/
41
+ │ ├── types/ # Interfaces e tipos TypeScript
42
+ │ │ ├── service.ts # PublicService, ServiceVariant, ServiceSummary
43
+ │ │ ├── document.ts # DocumentRequired, DocumentType, DocumentWithLink
44
+ │ │ ├── cost.ts # CostItem, CostType
45
+ │ │ ├── link.ts # LinkItem
46
+ │ │ ├── draft.ts # ServiceDraft, ServiceDraftStatus, ServiceDraftSource
47
+ │ │ ├── location.ts # Location, LocationLevel
48
+ │ │ ├── agency.ts # Agency
49
+ │ │ ├── report.ts # Report, ReportStatus
50
+ │ │ ├── crawl.ts # CrawlTarget, CrawlRun, CrawlJob
51
+ │ │ └── index.ts # Re-exporta tudo
52
+ │ │
53
+ │ ├── schemas/ # Schemas Zod (validacao em runtime)
54
+ │ │ ├── service.schema.ts # Valida PublicService
55
+ │ │ ├── document.schema.ts # Valida DocumentRequired
56
+ │ │ ├── cost.schema.ts # Valida CostItem
57
+ │ │ ├── draft.schema.ts # Valida ServiceDraft
58
+ │ │ └── index.ts
59
+ │ │
60
+ │ ├── constants/ # Valores fixos e enums
61
+ │ │ ├── document-codes.ts # CPF, RG, CRLV_VEICULO, LAUDO_MEDICO_DETRAN...
62
+ │ │ ├── levels.ts # federal, estadual, municipal
63
+ │ │ ├── service-types.ts # online, presencial, misto
64
+ │ │ ├── categories.ts # Identificacao, Veiculo, Medico, Residencia...
65
+ │ │ └── index.ts
66
+ │ │
67
+ │ └── utils/ # Funcoes puras utilitarias
68
+ │ ├── slugify.ts # Gera slug a partir de nome
69
+ │ ├── sanitize.ts # Limpa HTML, normaliza whitespace
70
+ │ ├── text.ts # Remocao de acentos, lowercase, trim
71
+ │ ├── validation.ts # Helpers de validacao (isValidUrl, isGovBr, etc.)
72
+ │ └── index.ts
73
+
74
+ ├── migrations/ # Migracoes SQL Supabase
75
+ │ ├── 001_create_locations.sql
76
+ │ ├── 002_create_agencies.sql
77
+ │ ├── 003_create_services.sql
78
+ │ ├── 004_create_documents.sql
79
+ │ ├── 005_create_reports.sql
80
+ │ ├── 006_create_crawl.sql
81
+ │ └── ...
82
+
83
+ ├── package.json
84
+ ├── tsconfig.json
85
+ └── README.md
86
+ ```
87
+
88
+ ### Principios de Organizacao
89
+
90
+ - **Um arquivo por tipo de entidade** — `service.ts` nao mistura com `document.ts`
91
+ - **Schemas espelham tipos** — para cada `types/service.ts` existe um `schemas/service.schema.ts`
92
+ - **Constantes sao exaustivas** — todos os codigos padrao documentados em um unico lugar
93
+ - **Utils sao funcoes puras** — sem side effects, sem IO, sem estado
94
+
95
+ ---
96
+
97
+ ## Integracoes
98
+
99
+ ### Quem importa portal-core
100
+
101
+ ```
102
+ portal-core
103
+ ▲ ▲
104
+ │ │
105
+ portal-collector portal-da-pendencia
106
+ ```
107
+
108
+ | Workspace | O que importa | Para que |
109
+ |-----------|--------------|---------|
110
+ | `portal-collector` | Types, Schemas, Constants, Utils | Validar dados extraidos contra o contrato. Usar codigos padrao de documentos. Normalizar texto antes de salvar. |
111
+ | `portal-da-pendencia` | Types, Constants | Tipar queries, tipar props de componentes, exibir nomes padrao de documentos. |
112
+
113
+ ### Quem portal-core importa
114
+
115
+ **Ninguem.** Portal-core e a raiz da arvore de dependencias. Nao tem dependencias internas. Qualquer dependencia circular aqui quebra a arquitetura.
116
+
117
+ ### Banco de dados
118
+
119
+ Portal-core **define** o schema (via migracoes SQL), mas **nao acessa** o banco. As migracoes sao executadas pelo Supabase CLI ou por scripts em `infra/`.
120
+
121
+ ---
122
+
123
+ ## Como Usar
124
+
125
+ ### Instalacao (workspace)
126
+
127
+ No `package.json` de qualquer workspace:
128
+
129
+ ```json
130
+ {
131
+ "dependencies": {
132
+ "@portal/core": "workspace:*"
133
+ }
134
+ }
135
+ ```
136
+
137
+ ### Importar tipos
138
+
139
+ ```typescript
140
+ import type { PublicService, DocumentRequired, CostItem } from "@portal/core/types";
141
+ ```
142
+
143
+ ### Validar dados
144
+
145
+ ```typescript
146
+ import { serviceSchema } from "@portal/core/schemas";
147
+
148
+ const result = serviceSchema.safeParse(rawData);
149
+ if (!result.success) {
150
+ console.error("Dados invalidos:", result.error);
151
+ }
152
+ ```
153
+
154
+ ### Usar constantes
155
+
156
+ ```typescript
157
+ import { DOCUMENT_CODES, SERVICE_LEVELS } from "@portal/core/constants";
158
+
159
+ // DOCUMENT_CODES.CPF === "CPF"
160
+ // DOCUMENT_CODES.CRLV_VEICULO === "CRLV_VEICULO"
161
+ // SERVICE_LEVELS === ["federal", "estadual", "municipal"]
162
+ ```
163
+
164
+ ### Normalizar texto
165
+
166
+ ```typescript
167
+ import { slugify, sanitizeHtml } from "@portal/core/utils";
168
+
169
+ slugify("Renovação de CNH — DETRAN-SC");
170
+ // → "renovacao-de-cnh-detran-sc"
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Rodar Migracoes
176
+
177
+ ```bash
178
+ # Via Supabase CLI (local)
179
+ supabase db push --db-url $DATABASE_URL
180
+
181
+ # Via script de setup
182
+ pnpm --filter @portal/core db:migrate
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Testes
188
+
189
+ ```bash
190
+ pnpm --filter @portal/core test
191
+ ```
192
+
193
+ Testes cobrem:
194
+ - Schemas Zod aceitam dados validos e rejeitam invalidos
195
+ - Funcoes de normalizacao produzem output esperado
196
+ - Constantes sao exaustivas e sem duplicatas
197
+ - Slugify lida com acentos, caracteres especiais e edge cases
198
+
199
+ ---
200
+
201
+ ## Plano de Expansao
202
+
203
+ ### Fase 1 — MVP (Agora)
204
+ - Tipos que espelham o schema atual do banco (`public_services`, `document_types`, etc.)
205
+ - Schemas Zod basicos para validacao
206
+ - Constantes de codigos de documentos
207
+ - Migracoes existentes migradas de `portal-da-pendencia/supabase/migrations/`
208
+
209
+ ### Fase 2 — Modelo Hierarquico
210
+ - Adicionar tipos para `service_master` → `service_variants` (servico base + variacoes por localidade)
211
+ - Migracoes para reestruturar tabelas
212
+ - Schemas para validacao de variantes
213
+ - Tipo `Location` com hierarquia (pais → estado → municipio)
214
+
215
+ ### Fase 3 — Versionamento
216
+ - Tipos para `service_versions` (snapshot historico)
217
+ - Tipos para `service_checks` (monitoramento)
218
+ - Schema de diff entre versoes
219
+
220
+ ### Fase 4 — Pacote NPM Privado
221
+ - Publicar como `@portal/core` no registry privado
222
+ - Versionamento semantico (semver)
223
+ - Changelog automatico
224
+ - Usado por repos independentes apos separacao do monorepo
225
+
226
+ ---
227
+
228
+ ## Riscos e Mitigacoes
229
+
230
+ | Risco | Impacto | Mitigacao |
231
+ |-------|---------|----------|
232
+ | Mudanca no Core quebra Collector e App simultaneamente | Alto | Testes automaticos em CI para ambos workspaces quando Core muda. Nao mergear sem green build. |
233
+ | Schema diverge do banco real | Medio | Migracoes como unica fonte de verdade. Nunca alterar banco manualmente. |
234
+ | Tipos ficam desatualizados em relacao ao schema | Medio | Gerar tipos a partir do schema (ou manter sincronia via testes). |
235
+ | Over-engineering: Core vira monolito de utilidades | Baixo | Regra: so entra no Core se dois ou mais workspaces precisam. Se so um usa, fica local. |
236
+ | Constantes incompletas (documento novo nao cadastrado) | Baixo | Zod rejeita codigos desconhecidos. Falha ruidosa forca cadastro. |
237
+
238
+ ---
239
+
240
+ ## Regras de Ouro
241
+
242
+ 1. **Se so um workspace precisa, nao vai pro Core.** Core e compartilhado, nao lixeira.
243
+ 2. **Toda mudanca no Core exige testes nos consumidores.** CI deve rodar testes de Collector e App quando Core muda.
244
+ 3. **Migracoes sao append-only.** Nunca editar uma migracao existente. Criar nova para corrigir.
245
+ 4. **Tipos e schemas andam juntos.** Nao existe tipo sem schema correspondente.
246
+ 5. **Zero dependencias de framework.** Core importa Zod e nada mais. Sem Next, sem Playwright, sem Supabase client.
@@ -0,0 +1,2 @@
1
+ export { linkItemSchema, costItemSchema, stepLinkSchema, stepItemSchema, documentRequiredSchema, publicServiceSchema, } from "./service.schema.js";
2
+ export type { PublicServiceInput } from "./service.schema.js";
@@ -0,0 +1 @@
1
+ export { linkItemSchema, costItemSchema, stepLinkSchema, stepItemSchema, documentRequiredSchema, publicServiceSchema, } from "./service.schema.js";
@@ -0,0 +1,283 @@
1
+ import { z } from "zod";
2
+ export declare const linkItemSchema: z.ZodObject<{
3
+ url: z.ZodString;
4
+ label: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ url: string;
7
+ label: string;
8
+ }, {
9
+ url: string;
10
+ label: string;
11
+ }>;
12
+ export declare const costItemSchema: z.ZodObject<{
13
+ description: z.ZodString;
14
+ type: z.ZodEnum<["fixo", "faixa", "gratuito", "variavel"]>;
15
+ value: z.ZodNullable<z.ZodNumber>;
16
+ valueMin: z.ZodNullable<z.ZodNumber>;
17
+ valueMax: z.ZodNullable<z.ZodNumber>;
18
+ required: z.ZodBoolean;
19
+ note: z.ZodNullable<z.ZodString>;
20
+ link: z.ZodNullable<z.ZodString>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ value: number | null;
23
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
24
+ description: string;
25
+ valueMin: number | null;
26
+ valueMax: number | null;
27
+ required: boolean;
28
+ note: string | null;
29
+ link: string | null;
30
+ }, {
31
+ value: number | null;
32
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
33
+ description: string;
34
+ valueMin: number | null;
35
+ valueMax: number | null;
36
+ required: boolean;
37
+ note: string | null;
38
+ link: string | null;
39
+ }>;
40
+ export declare const stepLinkSchema: z.ZodObject<{
41
+ anchor: z.ZodString;
42
+ url: z.ZodString;
43
+ }, "strip", z.ZodTypeAny, {
44
+ url: string;
45
+ anchor: string;
46
+ }, {
47
+ url: string;
48
+ anchor: string;
49
+ }>;
50
+ export declare const stepItemSchema: z.ZodObject<{
51
+ text: z.ZodString;
52
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
53
+ anchor: z.ZodString;
54
+ url: z.ZodString;
55
+ }, "strip", z.ZodTypeAny, {
56
+ url: string;
57
+ anchor: string;
58
+ }, {
59
+ url: string;
60
+ anchor: string;
61
+ }>, "many">>;
62
+ }, "strip", z.ZodTypeAny, {
63
+ text: string;
64
+ links?: {
65
+ url: string;
66
+ anchor: string;
67
+ }[] | undefined;
68
+ }, {
69
+ text: string;
70
+ links?: {
71
+ url: string;
72
+ anchor: string;
73
+ }[] | undefined;
74
+ }>;
75
+ export declare const documentRequiredSchema: z.ZodObject<{
76
+ standard_code: z.ZodString;
77
+ name: z.ZodString;
78
+ category: z.ZodString;
79
+ required: z.ZodBoolean;
80
+ condition: z.ZodNullable<z.ZodString>;
81
+ link: z.ZodOptional<z.ZodNullable<z.ZodString>>;
82
+ }, "strip", z.ZodTypeAny, {
83
+ required: boolean;
84
+ standard_code: string;
85
+ name: string;
86
+ category: string;
87
+ condition: string | null;
88
+ link?: string | null | undefined;
89
+ }, {
90
+ required: boolean;
91
+ standard_code: string;
92
+ name: string;
93
+ category: string;
94
+ condition: string | null;
95
+ link?: string | null | undefined;
96
+ }>;
97
+ export declare const publicServiceSchema: z.ZodObject<{
98
+ slug: z.ZodString;
99
+ name: z.ZodString;
100
+ agency: z.ZodString;
101
+ level: z.ZodEnum<["federal", "estadual", "municipal"]>;
102
+ state: z.ZodString;
103
+ municipality: z.ZodString;
104
+ url: z.ZodString;
105
+ type: z.ZodEnum<["online", "presencial", "misto"]>;
106
+ steps: z.ZodEffects<z.ZodArray<z.ZodObject<{
107
+ text: z.ZodString;
108
+ links: z.ZodOptional<z.ZodArray<z.ZodObject<{
109
+ anchor: z.ZodString;
110
+ url: z.ZodString;
111
+ }, "strip", z.ZodTypeAny, {
112
+ url: string;
113
+ anchor: string;
114
+ }, {
115
+ url: string;
116
+ anchor: string;
117
+ }>, "many">>;
118
+ }, "strip", z.ZodTypeAny, {
119
+ text: string;
120
+ links?: {
121
+ url: string;
122
+ anchor: string;
123
+ }[] | undefined;
124
+ }, {
125
+ text: string;
126
+ links?: {
127
+ url: string;
128
+ anchor: string;
129
+ }[] | undefined;
130
+ }>, "many">, {
131
+ text: string;
132
+ links?: {
133
+ url: string;
134
+ anchor: string;
135
+ }[] | undefined;
136
+ }[], unknown>;
137
+ links: z.ZodArray<z.ZodObject<{
138
+ url: z.ZodString;
139
+ label: z.ZodString;
140
+ }, "strip", z.ZodTypeAny, {
141
+ url: string;
142
+ label: string;
143
+ }, {
144
+ url: string;
145
+ label: string;
146
+ }>, "many">;
147
+ documents: z.ZodArray<z.ZodObject<{
148
+ standard_code: z.ZodString;
149
+ name: z.ZodString;
150
+ category: z.ZodString;
151
+ required: z.ZodBoolean;
152
+ condition: z.ZodNullable<z.ZodString>;
153
+ link: z.ZodOptional<z.ZodNullable<z.ZodString>>;
154
+ }, "strip", z.ZodTypeAny, {
155
+ required: boolean;
156
+ standard_code: string;
157
+ name: string;
158
+ category: string;
159
+ condition: string | null;
160
+ link?: string | null | undefined;
161
+ }, {
162
+ required: boolean;
163
+ standard_code: string;
164
+ name: string;
165
+ category: string;
166
+ condition: string | null;
167
+ link?: string | null | undefined;
168
+ }>, "many">;
169
+ costs: z.ZodArray<z.ZodObject<{
170
+ description: z.ZodString;
171
+ type: z.ZodEnum<["fixo", "faixa", "gratuito", "variavel"]>;
172
+ value: z.ZodNullable<z.ZodNumber>;
173
+ valueMin: z.ZodNullable<z.ZodNumber>;
174
+ valueMax: z.ZodNullable<z.ZodNumber>;
175
+ required: z.ZodBoolean;
176
+ note: z.ZodNullable<z.ZodString>;
177
+ link: z.ZodNullable<z.ZodString>;
178
+ }, "strip", z.ZodTypeAny, {
179
+ value: number | null;
180
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
181
+ description: string;
182
+ valueMin: number | null;
183
+ valueMax: number | null;
184
+ required: boolean;
185
+ note: string | null;
186
+ link: string | null;
187
+ }, {
188
+ value: number | null;
189
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
190
+ description: string;
191
+ valueMin: number | null;
192
+ valueMax: number | null;
193
+ required: boolean;
194
+ note: string | null;
195
+ link: string | null;
196
+ }>, "many">;
197
+ keywords: z.ZodArray<z.ZodString, "many">;
198
+ finalidade: z.ZodOptional<z.ZodNullable<z.ZodString>>;
199
+ tempoEletronico: z.ZodOptional<z.ZodNullable<z.ZodString>>;
200
+ tempoFisico: z.ZodOptional<z.ZodNullable<z.ZodString>>;
201
+ contato: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
202
+ }, "strip", z.ZodTypeAny, {
203
+ url: string;
204
+ type: "online" | "presencial" | "misto";
205
+ links: {
206
+ url: string;
207
+ label: string;
208
+ }[];
209
+ name: string;
210
+ slug: string;
211
+ agency: string;
212
+ level: "federal" | "estadual" | "municipal";
213
+ state: string;
214
+ municipality: string;
215
+ steps: {
216
+ text: string;
217
+ links?: {
218
+ url: string;
219
+ anchor: string;
220
+ }[] | undefined;
221
+ }[];
222
+ documents: {
223
+ required: boolean;
224
+ standard_code: string;
225
+ name: string;
226
+ category: string;
227
+ condition: string | null;
228
+ link?: string | null | undefined;
229
+ }[];
230
+ costs: {
231
+ value: number | null;
232
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
233
+ description: string;
234
+ valueMin: number | null;
235
+ valueMax: number | null;
236
+ required: boolean;
237
+ note: string | null;
238
+ link: string | null;
239
+ }[];
240
+ keywords: string[];
241
+ finalidade?: string | null | undefined;
242
+ tempoEletronico?: string | null | undefined;
243
+ tempoFisico?: string | null | undefined;
244
+ contato?: string[] | undefined;
245
+ }, {
246
+ url: string;
247
+ type: "online" | "presencial" | "misto";
248
+ links: {
249
+ url: string;
250
+ label: string;
251
+ }[];
252
+ name: string;
253
+ slug: string;
254
+ agency: string;
255
+ level: "federal" | "estadual" | "municipal";
256
+ state: string;
257
+ municipality: string;
258
+ documents: {
259
+ required: boolean;
260
+ standard_code: string;
261
+ name: string;
262
+ category: string;
263
+ condition: string | null;
264
+ link?: string | null | undefined;
265
+ }[];
266
+ costs: {
267
+ value: number | null;
268
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
269
+ description: string;
270
+ valueMin: number | null;
271
+ valueMax: number | null;
272
+ required: boolean;
273
+ note: string | null;
274
+ link: string | null;
275
+ }[];
276
+ keywords: string[];
277
+ steps?: unknown;
278
+ finalidade?: string | null | undefined;
279
+ tempoEletronico?: string | null | undefined;
280
+ tempoFisico?: string | null | undefined;
281
+ contato?: string[] | undefined;
282
+ }>;
283
+ export type PublicServiceInput = z.infer<typeof publicServiceSchema>;
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ export const linkItemSchema = z.object({
3
+ url: z.string().url(),
4
+ label: z.string(),
5
+ });
6
+ export const costItemSchema = z.object({
7
+ description: z.string(),
8
+ type: z.enum(["fixo", "faixa", "gratuito", "variavel"]),
9
+ value: z.number().nullable(),
10
+ valueMin: z.number().nullable(),
11
+ valueMax: z.number().nullable(),
12
+ required: z.boolean(),
13
+ note: z.string().nullable(),
14
+ link: z.string().nullable(),
15
+ });
16
+ export const stepLinkSchema = z.object({
17
+ anchor: z.string(),
18
+ url: z.string().url(),
19
+ });
20
+ export const stepItemSchema = z.object({
21
+ text: z.string(),
22
+ links: z.array(stepLinkSchema).optional(),
23
+ });
24
+ export const documentRequiredSchema = z.object({
25
+ standard_code: z.string(),
26
+ name: z.string(),
27
+ category: z.string(),
28
+ required: z.boolean(),
29
+ condition: z.string().nullable(),
30
+ link: z.string().url().nullable().optional(),
31
+ });
32
+ /** Coerce legacy steps (string[] or old { text, link? }[]) to StepItem[] for backward compatibility. */
33
+ const stepsSchema = z.preprocess((val) => {
34
+ if (!Array.isArray(val) || val.length === 0)
35
+ return val;
36
+ const first = val[0];
37
+ if (typeof first === "string") {
38
+ return val.map((s) => ({ text: s }));
39
+ }
40
+ if (first && typeof first === "object" && "link" in first && "text" in first) {
41
+ return val.map((s) => s.link ? { text: s.text, links: [{ anchor: "clique aqui", url: s.link }] } : { text: s.text });
42
+ }
43
+ return val;
44
+ }, z.array(stepItemSchema));
45
+ export const publicServiceSchema = z.object({
46
+ slug: z.string().min(1),
47
+ name: z.string().min(1),
48
+ agency: z.string().min(1),
49
+ level: z.enum(["federal", "estadual", "municipal"]),
50
+ state: z.string().min(1),
51
+ municipality: z.string().min(1),
52
+ url: z.string().url(),
53
+ type: z.enum(["online", "presencial", "misto"]),
54
+ steps: stepsSchema,
55
+ links: z.array(linkItemSchema),
56
+ documents: z.array(documentRequiredSchema),
57
+ costs: z.array(costItemSchema),
58
+ keywords: z.array(z.string()),
59
+ finalidade: z.string().nullable().optional(),
60
+ tempoEletronico: z.string().nullable().optional(),
61
+ tempoFisico: z.string().nullable().optional(),
62
+ contato: z.array(z.string()).optional(),
63
+ });
@@ -0,0 +1 @@
1
+ export type { LinkItem, CostItem, StepLink, StepItem, DocumentRequired, PublicService, } from "./service.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Service types — shared contract for public services across workspaces.
3
+ */
4
+ export interface LinkItem {
5
+ url: string;
6
+ label: string;
7
+ }
8
+ export interface CostItem {
9
+ description: string;
10
+ type: "fixo" | "faixa" | "gratuito" | "variavel";
11
+ value: number | null;
12
+ valueMin: number | null;
13
+ valueMax: number | null;
14
+ required: boolean;
15
+ note: string | null;
16
+ link: string | null;
17
+ }
18
+ /** Anchor is the substring in the step text to turn into a link (e.g. "clique aqui"); url is the redirect. */
19
+ export interface StepLink {
20
+ anchor: string;
21
+ url: string;
22
+ }
23
+ export interface StepItem {
24
+ text: string;
25
+ links?: StepLink[];
26
+ }
27
+ export interface DocumentRequired {
28
+ standard_code: string;
29
+ name: string;
30
+ category: string;
31
+ required: boolean;
32
+ condition: string | null;
33
+ link?: string | null;
34
+ }
35
+ export interface PublicService {
36
+ slug: string;
37
+ name: string;
38
+ agency: string;
39
+ level: "federal" | "estadual" | "municipal";
40
+ state: string;
41
+ municipality: string;
42
+ url: string;
43
+ type: "online" | "presencial" | "misto";
44
+ steps: StepItem[];
45
+ links: LinkItem[];
46
+ documents: DocumentRequired[];
47
+ costs: CostItem[];
48
+ keywords: string[];
49
+ finalidade?: string | null;
50
+ tempoEletronico?: string | null;
51
+ tempoFisico?: string | null;
52
+ contato?: string[];
53
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Service types — shared contract for public services across workspaces.
3
+ */
4
+ export {};
@@ -0,0 +1 @@
1
+ export { slugify } from "./slugify.js";
@@ -0,0 +1 @@
1
+ export { slugify } from "./slugify.js";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Slugify for service slugs (normalize NFD, strip diacritics, lowercase, dashes).
3
+ */
4
+ export declare function slugify(text: string): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Slugify for service slugs (normalize NFD, strip diacritics, lowercase, dashes).
3
+ */
4
+ export function slugify(text) {
5
+ return text
6
+ .normalize("NFD")
7
+ .replace(/\p{Diacritic}/gu, "")
8
+ .toLowerCase()
9
+ .trim()
10
+ .replace(/[^\p{L}\p{N}\s-]/gu, "")
11
+ .replace(/\s+/g, "-")
12
+ .replace(/-+/g, "-")
13
+ .replace(/^-|-$/g, "");
14
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@brasil-conjunto/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "files": ["dist"],
6
+ "exports": {
7
+ "./types": {
8
+ "types": "./dist/types/index.d.ts",
9
+ "import": "./dist/types/index.js",
10
+ "default": "./dist/types/index.js"
11
+ },
12
+ "./schemas": {
13
+ "types": "./dist/schemas/index.d.ts",
14
+ "import": "./dist/schemas/index.js",
15
+ "default": "./dist/schemas/index.js"
16
+ },
17
+ "./utils": {
18
+ "types": "./dist/utils/index.d.ts",
19
+ "import": "./dist/utils/index.js",
20
+ "default": "./dist/utils/index.js"
21
+ }
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.build.json",
25
+ "prepublishOnly": "npm run build",
26
+ "test": "vitest run"
27
+ },
28
+ "dependencies": {
29
+ "zod": "^3.23.0"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5.6.0",
33
+ "vitest": "^2.1.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ }
38
+ }