tychat-contracts 1.6.53 → 1.6.56

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.
Files changed (37) hide show
  1. package/dist/legal-terms/default-legal-term-document.d.ts +23 -0
  2. package/dist/legal-terms/default-legal-term-document.d.ts.map +1 -0
  3. package/dist/legal-terms/default-legal-term-document.js +170 -0
  4. package/dist/legal-terms/index.d.ts +4 -0
  5. package/dist/legal-terms/index.d.ts.map +1 -1
  6. package/dist/legal-terms/index.js +4 -0
  7. package/dist/legal-terms/legal-term-body-format.d.ts +1 -1
  8. package/dist/legal-terms/legal-term-body-format.d.ts.map +1 -1
  9. package/dist/legal-terms/legal-term-body-format.js +1 -1
  10. package/dist/legal-terms/legal-term-document-compile.d.ts +3 -0
  11. package/dist/legal-terms/legal-term-document-compile.d.ts.map +1 -0
  12. package/dist/legal-terms/legal-term-document-compile.js +166 -0
  13. package/dist/legal-terms/legal-term-document-compile.spec.d.ts +2 -0
  14. package/dist/legal-terms/legal-term-document-compile.spec.d.ts.map +1 -0
  15. package/dist/legal-terms/legal-term-document-compile.spec.js +49 -0
  16. package/dist/legal-terms/legal-term-document.dto.d.ts +21 -0
  17. package/dist/legal-terms/legal-term-document.dto.d.ts.map +1 -0
  18. package/dist/legal-terms/legal-term-document.dto.js +127 -0
  19. package/dist/legal-terms/legal-term-document.types.d.ts +30 -0
  20. package/dist/legal-terms/legal-term-document.types.d.ts.map +1 -0
  21. package/dist/legal-terms/legal-term-document.types.js +14 -0
  22. package/dist/legal-terms/legal-term-preview.dto.d.ts +2 -0
  23. package/dist/legal-terms/legal-term-preview.dto.d.ts.map +1 -1
  24. package/dist/legal-terms/legal-term-preview.dto.js +14 -1
  25. package/dist/legal-terms/legal-term-template.dto.d.ts +4 -1
  26. package/dist/legal-terms/legal-term-template.dto.d.ts.map +1 -1
  27. package/dist/legal-terms/legal-term-template.dto.js +25 -6
  28. package/package.json +1 -1
  29. package/src/legal-terms/default-legal-term-document.ts +170 -0
  30. package/src/legal-terms/index.ts +4 -0
  31. package/src/legal-terms/legal-term-body-format.ts +1 -1
  32. package/src/legal-terms/legal-term-document-compile.spec.ts +53 -0
  33. package/src/legal-terms/legal-term-document-compile.ts +192 -0
  34. package/src/legal-terms/legal-term-document.dto.ts +100 -0
  35. package/src/legal-terms/legal-term-document.types.ts +42 -0
  36. package/src/legal-terms/legal-term-preview.dto.ts +13 -1
  37. package/src/legal-terms/legal-term-template.dto.ts +23 -7
@@ -0,0 +1,30 @@
1
+ /** Blocos do editor visual do termo jurídico (sem HTML do usuário). */
2
+ export declare const LEGAL_TERM_BLOCK_TYPES: readonly ["document_title", "legal_reference", "section", "paragraph", "table_side_effects", "signature_footer"];
3
+ export type LegalTermBlockType = (typeof LEGAL_TERM_BLOCK_TYPES)[number];
4
+ export type LegalTermSectionLevel = 1 | 2 | 3;
5
+ export declare const LEGAL_TERM_TEXT_ALIGNS: readonly ["left", "center", "right"];
6
+ export type LegalTermTextAlign = (typeof LEGAL_TERM_TEXT_ALIGNS)[number];
7
+ export type LegalTermDocumentBlock = {
8
+ id: string;
9
+ type: LegalTermBlockType;
10
+ /** Nível de numeração para `section` (1 → 1., 2 → 1.1., 3 → 1.1.1.). */
11
+ level?: LegalTermSectionLevel;
12
+ /** Título visível (seção) ou ignorado em tipos sem título. */
13
+ title?: string;
14
+ /** Texto com placeholders `{{ token }}`; vazio em blocos só estruturais. */
15
+ body?: string;
16
+ titleAlign?: LegalTermTextAlign;
17
+ titleBold?: boolean;
18
+ titleItalic?: boolean;
19
+ titleUnderline?: boolean;
20
+ bodyAlign?: LegalTermTextAlign;
21
+ bodyBold?: boolean;
22
+ bodyItalic?: boolean;
23
+ bodyUnderline?: boolean;
24
+ };
25
+ export type LegalTermDocument = {
26
+ version: 1;
27
+ blocks: LegalTermDocumentBlock[];
28
+ };
29
+ export declare const LEGAL_TERM_DOCUMENT_VERSION: 1;
30
+ //# sourceMappingURL=legal-term-document.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal-term-document.types.d.ts","sourceRoot":"","sources":["../../src/legal-terms/legal-term-document.types.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,eAAO,MAAM,sBAAsB,kHAOzB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE9C,eAAO,MAAM,sBAAsB,sCAAuC,CAAC;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,kBAAkB,CAAC;IACzB,wEAAwE;IACxE,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAG,CAAU,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LEGAL_TERM_DOCUMENT_VERSION = exports.LEGAL_TERM_TEXT_ALIGNS = exports.LEGAL_TERM_BLOCK_TYPES = void 0;
4
+ /** Blocos do editor visual do termo jurídico (sem HTML do usuário). */
5
+ exports.LEGAL_TERM_BLOCK_TYPES = [
6
+ 'document_title',
7
+ 'legal_reference',
8
+ 'section',
9
+ 'paragraph',
10
+ 'table_side_effects',
11
+ 'signature_footer',
12
+ ];
13
+ exports.LEGAL_TERM_TEXT_ALIGNS = ['left', 'center', 'right'];
14
+ exports.LEGAL_TERM_DOCUMENT_VERSION = 1;
@@ -1,8 +1,10 @@
1
1
  import { type LegalTermBodyFormat } from './legal-term-body-format';
2
+ import { LegalTermDocumentDto } from './legal-term-document.dto';
2
3
  declare const LEGAL_TERM_SIGNATURE_METHODS: readonly ["digital", "printed"];
3
4
  export type LegalTermSignatureMethodDto = (typeof LEGAL_TERM_SIGNATURE_METHODS)[number];
4
5
  /** Valores explícitos para substituição na pré-visualização (sem inferência heurística). */
5
6
  export declare class LegalTermPreviewRequestDto {
7
+ body_document?: LegalTermDocumentDto;
6
8
  body_html?: string;
7
9
  body_format?: LegalTermBodyFormat;
8
10
  nome_paciente?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"legal-term-preview.dto.d.ts","sourceRoot":"","sources":["../../src/legal-terms/legal-term-preview.dto.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE7F,QAAA,MAAM,4BAA4B,iCAAkC,CAAC;AACrE,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,4FAA4F;AAC5F,qBAAa,0BAA0B;IAQrC,SAAS,CAAC,EAAE,MAAM,CAAC;IAQnB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAMlC,aAAa,CAAC,EAAE,MAAM,CAAC;IAMvB,WAAW,CAAC,EAAE,MAAM,CAAC;IAMrB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAU9B,+BAA+B,CAAC,EAAE,MAAM,EAAE,CAAC;IAM3C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,gBAAgB;IAS3B,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAK9C,YAAY,EAAE,MAAM,EAAE,CAAC;IAUvB,eAAe,CAAC,EAAE,MAAM,CAAC;IAMzB,WAAW,CAAC,EAAE,MAAM,CAAC;IASrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAQrB,aAAa,CAAC,EAAE,MAAM,CAAC;IAQvB,cAAc,CAAC,EAAE,OAAO,CAAC;IAMzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAK1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAO3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAM9B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAWnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
1
+ {"version":3,"file":"legal-term-preview.dto.d.ts","sourceRoot":"","sources":["../../src/legal-terms/legal-term-preview.dto.ts"],"names":[],"mappings":"AAgBA,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,QAAA,MAAM,4BAA4B,iCAAkC,CAAC;AACrE,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,4FAA4F;AAC5F,qBAAa,0BAA0B;IAQrC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IASrC,SAAS,CAAC,EAAE,MAAM,CAAC;IAQnB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAMlC,aAAa,CAAC,EAAE,MAAM,CAAC;IAMvB,WAAW,CAAC,EAAE,MAAM,CAAC;IAMrB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAU9B,+BAA+B,CAAC,EAAE,MAAM,EAAE,CAAC;IAM3C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,gBAAgB;IAS3B,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAK9C,YAAY,EAAE,MAAM,EAAE,CAAC;IAUvB,eAAe,CAAC,EAAE,MAAM,CAAC;IAMzB,WAAW,CAAC,EAAE,MAAM,CAAC;IASrB,WAAW,CAAC,EAAE,MAAM,CAAC;IAQrB,aAAa,CAAC,EAAE,MAAM,CAAC;IAQvB,cAAc,CAAC,EAAE,OAAO,CAAC;IAMzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAK1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAO3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAM9B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAWnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
@@ -12,10 +12,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.LegalTermSendDto = exports.LegalTermPreviewRequestDto = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
14
  const class_validator_1 = require("class-validator");
15
+ const class_transformer_1 = require("class-transformer");
15
16
  const legal_term_body_format_1 = require("./legal-term-body-format");
17
+ const legal_term_document_dto_1 = require("./legal-term-document.dto");
16
18
  const LEGAL_TERM_SIGNATURE_METHODS = ['digital', 'printed'];
17
19
  /** Valores explícitos para substituição na pré-visualização (sem inferência heurística). */
18
20
  class LegalTermPreviewRequestDto {
21
+ body_document;
19
22
  body_html;
20
23
  body_format;
21
24
  nome_paciente;
@@ -27,7 +30,17 @@ class LegalTermPreviewRequestDto {
27
30
  exports.LegalTermPreviewRequestDto = LegalTermPreviewRequestDto;
28
31
  __decorate([
29
32
  (0, swagger_1.ApiPropertyOptional)({
30
- description: 'HTML atual do editor (rascunho); se omitido, usa o modelo salvo no banco',
33
+ description: 'Rascunho do documento visual; se omitido, usa o modelo salvo',
34
+ type: legal_term_document_dto_1.LegalTermDocumentDto,
35
+ }),
36
+ (0, class_validator_1.IsOptional)(),
37
+ (0, class_validator_1.ValidateNested)(),
38
+ (0, class_transformer_1.Type)(() => legal_term_document_dto_1.LegalTermDocumentDto),
39
+ __metadata("design:type", legal_term_document_dto_1.LegalTermDocumentDto)
40
+ ], LegalTermPreviewRequestDto.prototype, "body_document", void 0);
41
+ __decorate([
42
+ (0, swagger_1.ApiPropertyOptional)({
43
+ description: 'HTML legado (rascunho); ignorado se body_document for enviado',
31
44
  maxLength: 500_000,
32
45
  }),
33
46
  (0, class_validator_1.IsOptional)(),
@@ -1,14 +1,17 @@
1
1
  import { type LegalTermBodyFormat } from './legal-term-body-format';
2
+ import { LegalTermDocumentDto } from './legal-term-document.dto';
2
3
  export declare class LegalTermTemplateDto {
3
4
  id: string;
4
5
  body_html: string;
5
6
  body_format: LegalTermBodyFormat;
7
+ body_document: LegalTermDocumentDto;
6
8
  mandatory_procedure_ids: string[];
7
9
  createdAt: Date;
8
10
  updatedAt: Date;
9
11
  }
10
12
  export declare class UpsertLegalTermTemplateDto {
11
- body_html: string;
13
+ body_document: LegalTermDocumentDto;
14
+ body_html?: string;
12
15
  body_format?: LegalTermBodyFormat;
13
16
  mandatory_procedure_ids?: string[];
14
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"legal-term-template.dto.d.ts","sourceRoot":"","sources":["../../src/legal-terms/legal-term-template.dto.ts"],"names":[],"mappings":"AAUA,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE7F,qBAAa,oBAAoB;IAE/B,EAAE,EAAE,MAAM,CAAC;IAKX,SAAS,EAAE,MAAM,CAAC;IAMlB,WAAW,EAAE,mBAAmB,CAAC;IAOjC,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAGlC,SAAS,EAAE,IAAI,CAAC;IAGhB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,qBAAa,0BAA0B;IASrC,SAAS,EAAE,MAAM,CAAC;IAQlB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAWlC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;CACpC"}
1
+ {"version":3,"file":"legal-term-template.dto.d.ts","sourceRoot":"","sources":["../../src/legal-terms/legal-term-template.dto.ts"],"names":[],"mappings":"AAYA,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,qBAAa,oBAAoB;IAE/B,EAAE,EAAE,MAAM,CAAC;IAKX,SAAS,EAAE,MAAM,CAAC;IAMlB,WAAW,EAAE,mBAAmB,CAAC;IAMjC,aAAa,EAAE,oBAAoB,CAAC;IAOpC,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAGlC,SAAS,EAAE,IAAI,CAAC;IAGhB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,qBAAa,0BAA0B;IAOrC,aAAa,EAAE,oBAAoB,CAAC;IASpC,SAAS,CAAC,EAAE,MAAM,CAAC;IAQnB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAWlC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;CACpC"}
@@ -11,12 +11,15 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.UpsertLegalTermTemplateDto = exports.LegalTermTemplateDto = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
+ const class_transformer_1 = require("class-transformer");
14
15
  const class_validator_1 = require("class-validator");
15
16
  const legal_term_body_format_1 = require("./legal-term-body-format");
17
+ const legal_term_document_dto_1 = require("./legal-term-document.dto");
16
18
  class LegalTermTemplateDto {
17
19
  id;
18
20
  body_html;
19
21
  body_format;
22
+ body_document;
20
23
  mandatory_procedure_ids;
21
24
  createdAt;
22
25
  updatedAt;
@@ -28,17 +31,24 @@ __decorate([
28
31
  ], LegalTermTemplateDto.prototype, "id", void 0);
29
32
  __decorate([
30
33
  (0, swagger_1.ApiProperty)({
31
- description: 'Conteúdo do modelo (HTML, Markdown ou texto conforme body_format)',
34
+ description: 'HTML compilado a partir de body_document (cache para PDF)',
32
35
  }),
33
36
  __metadata("design:type", String)
34
37
  ], LegalTermTemplateDto.prototype, "body_html", void 0);
35
38
  __decorate([
36
39
  (0, swagger_1.ApiProperty)({
37
40
  enum: legal_term_body_format_1.LEGAL_TERM_BODY_FORMATS,
38
- description: 'Como interpretar body_html antes de gerar PDF',
41
+ description: 'structured: editor visual; demais formatos legados',
39
42
  }),
40
43
  __metadata("design:type", String)
41
44
  ], LegalTermTemplateDto.prototype, "body_format", void 0);
45
+ __decorate([
46
+ (0, swagger_1.ApiProperty)({
47
+ description: 'Documento estruturado do editor visual',
48
+ type: legal_term_document_dto_1.LegalTermDocumentDto,
49
+ }),
50
+ __metadata("design:type", legal_term_document_dto_1.LegalTermDocumentDto)
51
+ ], LegalTermTemplateDto.prototype, "body_document", void 0);
42
52
  __decorate([
43
53
  (0, swagger_1.ApiProperty)({
44
54
  type: [String],
@@ -56,6 +66,7 @@ __decorate([
56
66
  __metadata("design:type", Date)
57
67
  ], LegalTermTemplateDto.prototype, "updatedAt", void 0);
58
68
  class UpsertLegalTermTemplateDto {
69
+ body_document;
59
70
  body_html;
60
71
  body_format;
61
72
  mandatory_procedure_ids;
@@ -63,19 +74,27 @@ class UpsertLegalTermTemplateDto {
63
74
  exports.UpsertLegalTermTemplateDto = UpsertLegalTermTemplateDto;
64
75
  __decorate([
65
76
  (0, swagger_1.ApiProperty)({
66
- description: 'Corpo do termo (HTML, Markdown ou texto puro conforme body_format)',
67
- minLength: 1,
77
+ description: 'Documento do editor visual (obrigatório no fluxo structured)',
78
+ type: legal_term_document_dto_1.LegalTermDocumentDto,
79
+ }),
80
+ (0, class_validator_1.ValidateNested)(),
81
+ (0, class_transformer_1.Type)(() => legal_term_document_dto_1.LegalTermDocumentDto),
82
+ __metadata("design:type", legal_term_document_dto_1.LegalTermDocumentDto)
83
+ ], UpsertLegalTermTemplateDto.prototype, "body_document", void 0);
84
+ __decorate([
85
+ (0, swagger_1.ApiPropertyOptional)({
86
+ description: 'Ignorado no fluxo structured; servidor compila body_html',
68
87
  maxLength: 500_000,
69
88
  }),
89
+ (0, class_validator_1.IsOptional)(),
70
90
  (0, class_validator_1.IsString)(),
71
- (0, class_validator_1.MinLength)(1),
72
91
  (0, class_validator_1.MaxLength)(500_000),
73
92
  __metadata("design:type", String)
74
93
  ], UpsertLegalTermTemplateDto.prototype, "body_html", void 0);
75
94
  __decorate([
76
95
  (0, swagger_1.ApiPropertyOptional)({
77
96
  enum: legal_term_body_format_1.LEGAL_TERM_BODY_FORMATS,
78
- description: 'Padrão: html',
97
+ description: 'Padrão: structured',
79
98
  }),
80
99
  (0, class_validator_1.IsOptional)(),
81
100
  (0, class_validator_1.IsIn)(legal_term_body_format_1.LEGAL_TERM_BODY_FORMATS),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tychat-contracts",
3
- "version": "1.6.53",
3
+ "version": "1.6.56",
4
4
  "description": "DTOs compartilhados com class-validator (API e microserviços)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,170 @@
1
+ import type { LegalTermDocument, LegalTermDocumentBlock } from './legal-term-document.types';
2
+
3
+ /** IDs estáveis dos blocos do modelo CFM padrão (restauração no editor). */
4
+ export const DEFAULT_LEGAL_TERM_BLOCK_IDS = {
5
+ title: 'cfm-title',
6
+ reference: 'cfm-reference',
7
+ s1: 'cfm-s1-identificacao',
8
+ s2: 'cfm-s2-procedimento',
9
+ s3: 'cfm-s3-indicacao',
10
+ s4: 'cfm-s4-riscos',
11
+ s4Table: 'cfm-s4-riscos-table',
12
+ s5: 'cfm-s5-beneficios',
13
+ s6: 'cfm-s6-alternativas',
14
+ s7: 'cfm-s7-orientacoes',
15
+ s8: 'cfm-s8-lgpd',
16
+ s9: 'cfm-s9-consentimento',
17
+ s10: 'cfm-s10-revogacao',
18
+ signatures: 'cfm-signatures',
19
+ } as const;
20
+
21
+ function block(partial: LegalTermDocumentBlock): LegalTermDocumentBlock {
22
+ return partial;
23
+ }
24
+
25
+ /** Modelo CFM (TCLE) — texto em português; placeholders suportados pelo backend. */
26
+ export function createDefaultCfmLegalTermDocument(): LegalTermDocument {
27
+ return {
28
+ version: 1,
29
+ blocks: [
30
+ block({
31
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.title,
32
+ type: 'document_title',
33
+ title: 'Termo de consentimento livre e esclarecido',
34
+ titleAlign: 'center',
35
+ titleBold: true,
36
+ }),
37
+ block({
38
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.reference,
39
+ type: 'legal_reference',
40
+ body: 'Conforme Resolução CFM nº 2.217/2018 (Código de Ética Médica)',
41
+ bodyAlign: 'center',
42
+ }),
43
+ block({
44
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s1,
45
+ type: 'section',
46
+ level: 1,
47
+ title: 'Identificação das partes',
48
+ body: [
49
+ 'Paciente: {{ nome_paciente }}',
50
+ 'CPF: {{ cpf_formatado }}',
51
+ 'Data de Nascimento: {{ data_nascimento }}',
52
+ 'Telefone: {{ telefone_paciente }}',
53
+ 'E-mail: {{ email_paciente }}',
54
+ 'Endereço: {{ endereco_paciente }}',
55
+ 'Instituição: (preencher dados da clínica no cadastro)',
56
+ ].join('\n'),
57
+ }),
58
+ block({
59
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s2,
60
+ type: 'section',
61
+ level: 1,
62
+ title: 'Descrição do procedimento',
63
+ body: [
64
+ 'Declaro que fui devidamente informado(a), de forma clara, objetiva e compreensível, acerca da natureza, finalidade, indicação clínica, etapas, duração e métodos envolvidos no procedimento médico descrito abaixo:',
65
+ '{{ procedimentos }}',
66
+ ].join('\n\n'),
67
+ }),
68
+ block({
69
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s3,
70
+ type: 'section',
71
+ level: 1,
72
+ title: 'Indicação e justificativa',
73
+ body: 'O procedimento foi indicado com base em avaliação clínica individualizada, considerando meu histórico de saúde, sintomas apresentados e exames complementares, visando o melhor resultado terapêutico possível.',
74
+ }),
75
+ block({
76
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s4,
77
+ type: 'section',
78
+ level: 1,
79
+ title: 'Riscos e complicações possíveis',
80
+ body: [
81
+ 'Fui informado(a) de que todo procedimento médico envolve riscos inerentes, podendo ocorrer intercorrências imediatas ou tardias, previsíveis ou não, incluindo, mas não se limitando a:',
82
+ ].join('\n'),
83
+ }),
84
+ block({
85
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s4Table,
86
+ type: 'table_side_effects',
87
+ body: '',
88
+ }),
89
+ block({
90
+ id: 'cfm-s4-riscos-after',
91
+ type: 'paragraph',
92
+ body: 'Estou ciente de que não há garantia absoluta de resultados, podendo ser necessária a realização de procedimentos adicionais.',
93
+ }),
94
+ block({
95
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s5,
96
+ type: 'section',
97
+ level: 1,
98
+ title: 'Benefícios esperados',
99
+ body: 'Fui informado(a) sobre os benefícios esperados, incluindo melhora clínica, controle de sintomas e/ou prevenção de agravamentos, respeitando as limitações individuais de resposta ao tratamento.',
100
+ }),
101
+ block({
102
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s6,
103
+ type: 'section',
104
+ level: 1,
105
+ title: 'Alternativas terapêuticas',
106
+ body: 'Foram apresentadas e discutidas alternativas ao procedimento proposto, incluindo tratamentos conservadores, outras técnicas disponíveis e a possibilidade de não realização do procedimento, estando ciente das consequências dessa decisão.',
107
+ }),
108
+ block({
109
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s7,
110
+ type: 'section',
111
+ level: 1,
112
+ title: 'Orientações pré e pós-procedimento',
113
+ body: 'Recebi orientações detalhadas quanto aos cuidados necessários antes e após o procedimento, incluindo restrições, uso de medicamentos, sinais de alerta e acompanhamento médico.',
114
+ }),
115
+ block({
116
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s8,
117
+ type: 'section',
118
+ level: 1,
119
+ title: 'Confidencialidade e proteção de dados',
120
+ body: 'Autorizo o tratamento dos meus dados pessoais e sensíveis para fins médicos, assistenciais e administrativos, conforme a Lei Geral de Proteção de Dados (Lei nº 13.709/2018 - LGPD).',
121
+ }),
122
+ block({
123
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s9,
124
+ type: 'section',
125
+ level: 1,
126
+ title: 'Declaração de consentimento',
127
+ body: [
128
+ 'Declaro que:',
129
+ 'Recebi todas as informações necessárias de forma clara e adequada;',
130
+ 'Tive oportunidade de realizar perguntas e obtive respostas satisfatórias;',
131
+ 'Estou ciente dos riscos, benefícios e alternativas;',
132
+ 'Não fui submetido(a) a qualquer tipo de pressão ou indução;',
133
+ 'Autorizo, de forma livre e esclarecida, a realização do procedimento descrito.',
134
+ ].join('\n'),
135
+ }),
136
+ block({
137
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.s10,
138
+ type: 'section',
139
+ level: 1,
140
+ title: 'Direito de revogação',
141
+ body: 'Estou ciente de que posso revogar este consentimento a qualquer momento antes da realização do procedimento, sem prejuízo ao meu atendimento médico.',
142
+ }),
143
+ block({
144
+ id: DEFAULT_LEGAL_TERM_BLOCK_IDS.signatures,
145
+ type: 'signature_footer',
146
+ body: [
147
+ 'Local: (cidade da clínica)',
148
+ 'Data: {{ data_hora }}',
149
+ '',
150
+ '______________________________',
151
+ 'Paciente',
152
+ '',
153
+ '______________________________',
154
+ 'Responsável Legal',
155
+ '',
156
+ '______________________________',
157
+ 'Médico Responsável',
158
+ '',
159
+ 'Documento gerado eletronicamente. Assinatura válida conforme legislação vigente.',
160
+ ].join('\n'),
161
+ }),
162
+ ],
163
+ };
164
+ }
165
+
166
+ /** Mapa id → bloco do seed CFM (para restaurar tópicos padrão). */
167
+ export function getDefaultCfmBlocksById(): Map<string, LegalTermDocumentBlock> {
168
+ const doc = createDefaultCfmLegalTermDocument();
169
+ return new Map(doc.blocks.map((b) => [b.id, { ...b }]));
170
+ }
@@ -1,4 +1,8 @@
1
1
  export * from './legal-term-body-format';
2
+ export * from './legal-term-document.types';
3
+ export * from './legal-term-document.dto';
4
+ export * from './legal-term-document-compile';
5
+ export * from './default-legal-term-document';
2
6
  export * from './legal-term-template.dto';
3
7
  export * from './legal-term-preview.dto';
4
8
  export * from './legal-term-dispatch.dto';
@@ -1,4 +1,4 @@
1
1
  /** Formato de armazenamento do modelo; conversão para HTML/PDF é determinística por enum. */
2
- export const LEGAL_TERM_BODY_FORMATS = ['html', 'markdown', 'plain_text'] as const;
2
+ export const LEGAL_TERM_BODY_FORMATS = ['structured', 'html', 'markdown', 'plain_text'] as const;
3
3
 
4
4
  export type LegalTermBodyFormat = (typeof LEGAL_TERM_BODY_FORMATS)[number];
@@ -0,0 +1,53 @@
1
+ import { compileLegalTermDocumentToHtml } from './legal-term-document-compile';
2
+ import { createDefaultCfmLegalTermDocument } from './default-legal-term-document';
3
+ import type { LegalTermDocument } from './legal-term-document.types';
4
+
5
+ describe('compileLegalTermDocumentToHtml', () => {
6
+ it('returns empty paragraph for no blocks', () => {
7
+ expect(compileLegalTermDocumentToHtml({ version: 1, blocks: [] })).toBe('<p></p>');
8
+ });
9
+
10
+ it('numbers sections hierarchically', () => {
11
+ const doc: LegalTermDocument = {
12
+ version: 1,
13
+ blocks: [
14
+ { id: 'a', type: 'section', level: 1, title: 'ONE', body: 'a' },
15
+ { id: 'b', type: 'section', level: 2, title: 'TWO', body: 'b' },
16
+ { id: 'c', type: 'section', level: 3, title: 'THREE', body: 'c' },
17
+ { id: 'd', type: 'section', level: 1, title: 'FOUR', body: 'd' },
18
+ ],
19
+ };
20
+ const html = compileLegalTermDocumentToHtml(doc);
21
+ expect(html).toContain('1. ONE');
22
+ expect(html).toContain('1.1. TWO');
23
+ expect(html).toContain('1.1.1. THREE');
24
+ expect(html).toContain('2. FOUR');
25
+ });
26
+
27
+ it('escapes HTML in body text', () => {
28
+ const doc: LegalTermDocument = {
29
+ version: 1,
30
+ blocks: [{ id: 'x', type: 'paragraph', body: '<script>alert(1)</script>' }],
31
+ };
32
+ const html = compileLegalTermDocumentToHtml(doc);
33
+ expect(html).not.toContain('<script>');
34
+ expect(html).toContain('&lt;script&gt;');
35
+ });
36
+
37
+ it('emits table placeholder token', () => {
38
+ const doc: LegalTermDocument = {
39
+ version: 1,
40
+ blocks: [{ id: 't', type: 'table_side_effects', body: 'intro' }],
41
+ };
42
+ const html = compileLegalTermDocumentToHtml(doc);
43
+ expect(html).toContain('{{ table_side_effects_en_list }}');
44
+ expect(html).toContain('intro');
45
+ });
46
+
47
+ it('compiles default CFM document', () => {
48
+ const html = compileLegalTermDocumentToHtml(createDefaultCfmLegalTermDocument());
49
+ expect(html).toContain('Termo de consentimento livre e esclarecido');
50
+ expect(html).toContain('{{ nome_paciente }}');
51
+ expect(html).toContain('Identificação das partes');
52
+ });
53
+ });
@@ -0,0 +1,192 @@
1
+ import type {
2
+ LegalTermDocument,
3
+ LegalTermDocumentBlock,
4
+ LegalTermSectionLevel,
5
+ LegalTermTextAlign,
6
+ } from './legal-term-document.types';
7
+
8
+ type TextStyleOpts = {
9
+ align?: LegalTermTextAlign;
10
+ bold?: boolean;
11
+ italic?: boolean;
12
+ underline?: boolean;
13
+ };
14
+
15
+ function escapeHtmlText(s: string): string {
16
+ return s
17
+ .replace(/&/g, '&amp;')
18
+ .replace(/</g, '&lt;')
19
+ .replace(/>/g, '&gt;')
20
+ .replace(/"/g, '&quot;');
21
+ }
22
+
23
+ function cssTextStyle(opts: TextStyleOpts, defaults: TextStyleOpts = {}): string {
24
+ const align = opts.align ?? defaults.align ?? 'left';
25
+ const bold = opts.bold ?? defaults.bold ?? false;
26
+ const italic = opts.italic ?? defaults.italic ?? false;
27
+ const underline = opts.underline ?? defaults.underline ?? false;
28
+ const parts = [`text-align:${align}`];
29
+ if (bold) parts.push('font-weight:700');
30
+ if (italic) parts.push('font-style:italic');
31
+ if (underline) parts.push('text-decoration:underline');
32
+ return parts.join(';');
33
+ }
34
+
35
+ function textToHtmlParagraphs(text: string, opts: TextStyleOpts = {}): string {
36
+ const trimmed = text.trim();
37
+ if (!trimmed) return '';
38
+ const style = cssTextStyle(opts, { align: 'left' });
39
+ const lines = trimmed.split(/\r?\n/);
40
+ return lines
41
+ .map((line) => {
42
+ const t = line.trim();
43
+ if (!t) return '<br/>';
44
+ return `<p style="margin:0.4em 0;line-height:1.45;${style}">${escapeHtmlText(t)}</p>`;
45
+ })
46
+ .join('');
47
+ }
48
+
49
+ function formatSectionNumber(counters: [number, number, number], level: LegalTermSectionLevel): string {
50
+ if (level === 1) return `${counters[0]}.`;
51
+ if (level === 2) return `${counters[0]}.${counters[1]}.`;
52
+ return `${counters[0]}.${counters[1]}.${counters[2]}.`;
53
+ }
54
+
55
+ function bumpSectionCounters(
56
+ counters: [number, number, number],
57
+ level: LegalTermSectionLevel,
58
+ ): [number, number, number] {
59
+ const next: [number, number, number] = [...counters] as [number, number, number];
60
+ if (level === 1) {
61
+ next[0] += 1;
62
+ next[1] = 0;
63
+ next[2] = 0;
64
+ } else if (level === 2) {
65
+ next[1] += 1;
66
+ next[2] = 0;
67
+ } else {
68
+ next[2] += 1;
69
+ }
70
+ return next;
71
+ }
72
+
73
+ function compileBlock(block: LegalTermDocumentBlock, sectionCounters: [number, number, number]): {
74
+ html: string;
75
+ counters: [number, number, number];
76
+ } {
77
+ let counters = sectionCounters;
78
+ const wrap = (inner: string, extraStyle = '') =>
79
+ `<div class="legal-term-block" data-block-type="${block.type}" style="margin-bottom:0.75em;${extraStyle}">${inner}</div>`;
80
+
81
+ switch (block.type) {
82
+ case 'document_title': {
83
+ const title = (block.title ?? block.body ?? '').trim();
84
+ const style = cssTextStyle(
85
+ {
86
+ align: block.titleAlign,
87
+ bold: block.titleBold,
88
+ italic: block.titleItalic,
89
+ underline: block.titleUnderline,
90
+ },
91
+ { align: 'center', bold: true },
92
+ );
93
+ const inner = title
94
+ ? `<h1 style="font-size:1.25em;margin:0 0 0.5em;${style}">${escapeHtmlText(title)}</h1>`
95
+ : '';
96
+ return { html: wrap(inner), counters };
97
+ }
98
+ case 'legal_reference': {
99
+ const ref = (block.body ?? block.title ?? '').trim();
100
+ const inner = ref
101
+ ? textToHtmlParagraphs(ref, {
102
+ align: block.bodyAlign ?? 'center',
103
+ bold: block.bodyBold,
104
+ italic: block.bodyItalic,
105
+ underline: block.bodyUnderline,
106
+ })
107
+ : '';
108
+ return { html: wrap(inner), counters };
109
+ }
110
+ case 'section': {
111
+ const level: LegalTermSectionLevel = block.level === 2 || block.level === 3 ? block.level : 1;
112
+ counters = bumpSectionCounters(counters, level);
113
+ const num = formatSectionNumber(counters, level);
114
+ const title = (block.title ?? '').trim();
115
+ const titleStyle = cssTextStyle(
116
+ {
117
+ align: block.titleAlign,
118
+ bold: block.titleBold,
119
+ italic: block.titleItalic,
120
+ underline: block.titleUnderline,
121
+ },
122
+ { align: 'left', bold: true },
123
+ );
124
+ const bodyHtml = textToHtmlParagraphs(block.body ?? '', {
125
+ align: block.bodyAlign,
126
+ bold: block.bodyBold,
127
+ italic: block.bodyItalic,
128
+ underline: block.bodyUnderline,
129
+ });
130
+ const heading = title
131
+ ? `<h2 style="font-size:${level === 1 ? '1.05em' : '1em'};margin:0.75em 0 0.35em;${titleStyle}">${escapeHtmlText(`${num} ${title}`)}</h2>`
132
+ : '';
133
+ return { html: wrap(`${heading}${bodyHtml}`), counters };
134
+ }
135
+ case 'paragraph': {
136
+ return {
137
+ html: wrap(
138
+ textToHtmlParagraphs(block.body ?? '', {
139
+ align: block.bodyAlign,
140
+ bold: block.bodyBold,
141
+ italic: block.bodyItalic,
142
+ underline: block.bodyUnderline,
143
+ }),
144
+ ),
145
+ counters,
146
+ };
147
+ }
148
+ case 'table_side_effects': {
149
+ const intro = textToHtmlParagraphs(block.body ?? '', {
150
+ align: block.bodyAlign,
151
+ bold: block.bodyBold,
152
+ italic: block.bodyItalic,
153
+ underline: block.bodyUnderline,
154
+ });
155
+ const token = '<p>{{ table_side_effects_en_list }}</p>';
156
+ return { html: wrap(`${intro}${token}`), counters };
157
+ }
158
+ case 'signature_footer': {
159
+ return {
160
+ html: wrap(
161
+ textToHtmlParagraphs(block.body ?? '', {
162
+ align: block.bodyAlign ?? 'left',
163
+ bold: block.bodyBold,
164
+ italic: block.bodyItalic,
165
+ underline: block.bodyUnderline,
166
+ }),
167
+ 'page-break-inside:avoid;margin-top:1.5em;padding-top:0.5em;border-top:1px solid #ddd',
168
+ ),
169
+ counters,
170
+ };
171
+ }
172
+ default:
173
+ return { html: '', counters };
174
+ }
175
+ }
176
+
177
+ export function compileLegalTermDocumentToHtml(document: LegalTermDocument): string {
178
+ if (!document?.blocks?.length) {
179
+ return '<p></p>';
180
+ }
181
+ let sectionCounters: [number, number, number] = [0, 0, 0];
182
+ const parts: string[] = [
183
+ '<div class="legal-term-document" style="font-family:Roboto,Helvetica,Arial,sans-serif;font-size:11pt;color:#111">',
184
+ ];
185
+ for (const block of document.blocks) {
186
+ const { html, counters } = compileBlock(block, sectionCounters);
187
+ sectionCounters = counters;
188
+ if (html) parts.push(html);
189
+ }
190
+ parts.push('</div>');
191
+ return parts.join('').trim() || '<p></p>';
192
+ }