@credithub/harlan-components 1.127.0 → 1.129.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.
@@ -37,7 +37,12 @@ var useChartData = function (consultaSerasa, consultaBoaVista) {
37
37
  var _g = useState(false), isProcessing = _g[0], setIsProcessing = _g[1];
38
38
  var _h = useState(false), dataReady = _h[0], setDataReady = _h[1];
39
39
  var lastProgress = useRef(0);
40
- var lastProcessedDocument = useRef(null);
40
+ /**
41
+ * Evita reprocessar em vão. Inclui totais do IEPTB (globalData) para atualizar o pie
42
+ * quando o JSON do DOCUMENTHISTORY for o mesmo; a série `protestos` do gráfico vem
43
+ * só do histórico em `processData` (sem override com IEPTB).
44
+ */
45
+ var lastProcessSnapshotKey = useRef(null);
41
46
  var processingTimeout = useRef(null);
42
47
  var ctxHistory = useContext(Queries.GraficosAnaliticos);
43
48
  var ctxProtestos = useContext(Queries.Protestos);
@@ -182,6 +187,7 @@ var useChartData = function (consultaSerasa, consultaBoaVista) {
182
187
  if (ctxHistory.type === RequestStatus.Loading ||
183
188
  ctxProtestos.type === RequestStatus.Loading ||
184
189
  ctxCCF.type === RequestStatus.Loading) {
190
+ lastProcessSnapshotKey.current = null;
185
191
  setDataReady(false);
186
192
  setErrorState(null);
187
193
  setIsProcessing(false);
@@ -191,20 +197,25 @@ var useChartData = function (consultaSerasa, consultaBoaVista) {
191
197
  setErrorState(((_a = ctxHistory.error) === null || _a === void 0 ? void 0 : _a.message) || 'Erro ao consultar histórico');
192
198
  setIsProcessing(false);
193
199
  setDataReady(false);
194
- lastProcessedDocument.current = null;
200
+ lastProcessSnapshotKey.current = null;
195
201
  return;
196
202
  }
197
203
  // Só processa quando TODOS os contextos estiverem prontos
198
204
  if (allContextsReady && ctxHistory.document) {
199
205
  var documentSnapshot_1 = ctxHistory.document;
200
- // Evita processar o mesmo documento múltiplas vezes
201
- if (lastProcessedDocument.current === documentSnapshot_1) {
206
+ var processSnapshotKey_1 = JSON.stringify({
207
+ doc: documentSnapshot_1,
208
+ totalProtestos: totalProtestos !== null && totalProtestos !== void 0 ? totalProtestos : null,
209
+ valorTotalProtestos: valorTotalProtestos !== null && valorTotalProtestos !== void 0 ? valorTotalProtestos : null,
210
+ ultimaOcorrenciaProtestos: ultimaOcorrenciaProtestos !== null && ultimaOcorrenciaProtestos !== void 0 ? ultimaOcorrenciaProtestos : null,
211
+ quantidadeProcessosFromPJ: quantidadeProcessosFromPJ !== null && quantidadeProcessosFromPJ !== void 0 ? quantidadeProcessosFromPJ : null
212
+ });
213
+ if (lastProcessSnapshotKey.current === processSnapshotKey_1) {
202
214
  return;
203
215
  }
204
216
  // Marca como processando
205
217
  setIsProcessing(true);
206
218
  setDataReady(false);
207
- lastProcessedDocument.current = documentSnapshot_1;
208
219
  // Usa setTimeout para permitir que a UI atualize o estado de loading
209
220
  processingTimeout.current = setTimeout(function () {
210
221
  try {
@@ -217,6 +228,7 @@ var useChartData = function (consultaSerasa, consultaBoaVista) {
217
228
  var quantidadeProcessos = quantidadeProcessosFromPJ !== null && quantidadeProcessosFromPJ !== void 0 ? quantidadeProcessosFromPJ : getLastQuantidadeProcessosFromHistory(parsed);
218
229
  var processed = processData(structuredClone(parsed), quantidadeProcessos, totalProtestos, valorTotalProtestos, ultimaOcorrenciaProtestos);
219
230
  setData(processed);
231
+ lastProcessSnapshotKey.current = processSnapshotKey_1;
220
232
  setIsProcessing(false);
221
233
  setDataReady(true);
222
234
  // Garante que o progresso chegue a 100% quando dados estão prontos
@@ -227,6 +239,7 @@ var useChartData = function (consultaSerasa, consultaBoaVista) {
227
239
  setErrorState('Erro ao processar os dados');
228
240
  setIsProcessing(false);
229
241
  setDataReady(false);
242
+ lastProcessSnapshotKey.current = null;
230
243
  }
231
244
  processingTimeout.current = null;
232
245
  }, 0);
@@ -20,7 +20,7 @@ var useLiminarProtestosDoPassado = function (_a) {
20
20
  var posthog = useContext(PostHogContext).posthog;
21
21
  var _c = useGlobalData(), globalData = _c.data, setData = _c.setData;
22
22
  var fetch = useCallback(function () { return __awaiter(void 0, void 0, void 0, function () {
23
- var data, parsedData, _a, _b, normalizedData, numerosChave, protestosDoPassado, ocultos;
23
+ var data, parsedData, _a, _b, normalizedData, protestosDoPassado, instrumentosParaEvento, idsSomenteComChave;
24
24
  return __generator(this, function (_c) {
25
25
  switch (_c.label) {
26
26
  case 0: return [4 /*yield*/, client.request("SELECT FROM 'Protestos'.'History'", {
@@ -36,25 +36,27 @@ var useLiminarProtestosDoPassado = function (_a) {
36
36
  var _a;
37
37
  return (__assign(__assign({}, item), { cpfCnpj: (_a = item.cpfCnpj) !== null && _a !== void 0 ? _a : documento }));
38
38
  });
39
- numerosChave = normalizedData
40
- .filter(function (item) { return item.missingAt; })
41
- .map(function (item) { return item.chave; });
42
39
  protestosDoPassado = normalizedData.filter(function (item) { return item.missingAt; });
43
- ocultos = protestosDoPassado.map(function (item) { return item.chave; });
40
+ instrumentosParaEvento = protestosDoPassado.map(function (item) {
41
+ return item.chave != null && String(item.chave).trim() !== ''
42
+ ? String(item.chave)
43
+ : '(sem chave)';
44
+ });
45
+ idsSomenteComChave = instrumentosParaEvento.filter(function (c) { return c !== '(sem chave)'; });
44
46
  setData(function (prev) { return (__assign(__assign({}, prev), { protestosPagosBaixados: {
45
47
  isLoaded: true,
46
48
  protestos: protestosDoPassado
47
49
  } })); });
48
- if (ocultos.length > 0 && posthog) {
50
+ if (protestosDoPassado.length > 0 && posthog) {
49
51
  posthog.capture('liminar_protestos_ocultos', {
50
52
  documento: documento,
51
- quantidade: ocultos.length,
52
- instrumentos: ocultos
53
+ quantidade: protestosDoPassado.length,
54
+ instrumentos: instrumentosParaEvento
53
55
  });
54
56
  }
55
57
  return [2 /*return*/, {
56
- possuiIndiciosDeLiminarProtestosDoPassado: ocultos.length > 0,
57
- protestosDoPassadoIds: ocultos
58
+ possuiIndiciosDeLiminarProtestosDoPassado: protestosDoPassado.length > 0,
59
+ protestosDoPassadoIds: idsSomenteComChave
58
60
  }];
59
61
  }
60
62
  });
@@ -1,5 +1,4 @@
1
1
  import ProtestosIcon from '../../assets/icones/protestos';
2
- import { useGlobalData } from '../../contexts/globalDataContext';
3
2
  import { formatMoney } from '../../utils/number';
4
3
  import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
5
4
  import AddItemField from '../common/addItem';
@@ -9,7 +8,6 @@ import Section from '../section';
9
8
  import { Queries, RequestStatus } from '../webservice';
10
9
  var ProtestosSP = function () {
11
10
  var ctxLiminar = useContext(Queries.Liminar);
12
- var setData = useGlobalData().setData;
13
11
  var _a = useState(false), dataUpdated = _a[0], setDataUpdated = _a[1];
14
12
  var _b = useState(false), hideSection = _b[0], setHideSection = _b[1];
15
13
  var prevCtxTypeRef = useRef(null);
@@ -28,7 +26,7 @@ var ProtestosSP = function () {
28
26
  setHideSection(true);
29
27
  }
30
28
  prevCtxTypeRef.current = ctxLiminar.type;
31
- }, [ctxLiminar.type, ctxLiminar.document, dataUpdated, setData]);
29
+ }, [ctxLiminar.type, ctxLiminar.document, dataUpdated]);
32
30
  var possuiProtestosSp = useMemo(function () {
33
31
  var _a;
34
32
  if ((ctxLiminar === null || ctxLiminar === void 0 ? void 0 : ctxLiminar.type) === RequestStatus.Success)
@@ -1,4 +1,8 @@
1
1
  import type { ProtestoHistoricoItem } from '@/@types/domain/protestosPagosBaixadosTypes';
2
2
  import type { Consulta } from '../../components/chart/types/iChart';
3
- export declare function adaptToUI(items: ProtestoHistoricoItem[]): ProtestoHistoricoItem[];
3
+ /** Linha normalizada para UI, com `rowId` único para listas React. */
4
+ export type ProtestoHistoricoUiRow = ProtestoHistoricoItem & {
5
+ rowId: string;
6
+ };
7
+ export declare function adaptToUI(items: ProtestoHistoricoItem[]): ProtestoHistoricoUiRow[];
4
8
  export declare function buildChartSeries(items: ProtestoHistoricoItem[]): Consulta[];
@@ -2,16 +2,31 @@ import { __assign } from "tslib";
2
2
  // Pré-compila regexes fora do hot path
3
3
  var ISO_RE = /^\d{4}-\d{2}-\d{2}/;
4
4
  var BR_RE = /^(\d{2})\/(\d{2})\/(\d{4})/;
5
+ function assignStableRowIds(items) {
6
+ var baseKeys = items.map(function (i, idx) {
7
+ var c = i.chave;
8
+ if (c != null && String(c).trim() !== '')
9
+ return String(c);
10
+ return "protesto-historico-".concat(idx);
11
+ });
12
+ var counts = new Map();
13
+ var rowIds = baseKeys.map(function (base) {
14
+ var _a;
15
+ var n = ((_a = counts.get(base)) !== null && _a !== void 0 ? _a : 0) + 1;
16
+ counts.set(base, n);
17
+ return n === 1 ? base : "".concat(base, "__").concat(n);
18
+ });
19
+ return { rowIds: rowIds };
20
+ }
5
21
  export function adaptToUI(items) {
6
- // Se realmente não quiser adaptar nada, pode remover esta função e usar items direto.
7
- // Aqui normalizo datas vazias para string vazia e default para campos opcionais.
8
- return items.map(function (i) {
22
+ var rowIds = assignStableRowIds(items).rowIds;
23
+ return items.map(function (i, idx) {
9
24
  var _a, _b, _c, _d, _e;
10
25
  return (__assign(__assign({}, i), { dataProtesto: (_b = (_a = i.dataProtesto) !== null && _a !== void 0 ? _a : i.data) !== null && _b !== void 0 ? _b : '', nomeCedente: (_c = i.nomeCedente) !== null && _c !== void 0 ? _c : '', nomeApresentante: (_d = i.nomeApresentante) !== null && _d !== void 0 ? _d : '', valor: Number.isFinite(i.valor)
11
26
  ? i.valor
12
27
  : safeNumber((_e = i.valor) !== null && _e !== void 0 ? _e : i.valorProtestado), custas: Number.isFinite(i.custas)
13
28
  ? i.custas
14
- : safeNumber(i.custas) }));
29
+ : safeNumber(i.custas), rowId: rowIds[idx] }));
15
30
  });
16
31
  }
17
32
  export function buildChartSeries(items) {
@@ -77,7 +77,7 @@ var ProtestosPagosBaixados = function (_a) {
77
77
  : "Foram encontrados ".concat((_l = (_k = data === null || data === void 0 ? void 0 : data.protestosPagosBaixados) === null || _k === void 0 ? void 0 : _k.protestos) === null || _l === void 0 ? void 0 : _l.length, " protestos")), variant: ((_o = (_m = data === null || data === void 0 ? void 0 : data.protestosPagosBaixados) === null || _m === void 0 ? void 0 : _m.protestos) === null || _o === void 0 ? void 0 : _o.length) ? 'error' : 'default', icon: ProtestosIcon, onSuccess: function () {
78
78
  var protestosAdaptados = adaptToUI(items);
79
79
  var totalProtestos = protestosAdaptados.length;
80
- var children = totalProtestos ? (React.createElement(Result, null, protestosAdaptados.map(function (protesto) { return (React.createElement(ProtestoHistoricoItemComponent, { key: protesto.chave, protesto: protesto })); }))) : null;
80
+ var children = totalProtestos ? (React.createElement(Result, null, protestosAdaptados.map(function (protesto) { return (React.createElement(ProtestoHistoricoItemComponent, { key: protesto.rowId, protesto: protesto })); }))) : null;
81
81
  return {
82
82
  children: children
83
83
  };
@@ -5,14 +5,12 @@ import { useSafeQuery } from '../../hooks/useSafeQuery';
5
5
  import { isDocumentoPF } from '../../utils/string';
6
6
  import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
7
7
  import AddItemField from '../common/addItem';
8
+ import { formatInadPercentDisplay, inadCountPer100, resolveSerasaInadPercent } from './scoreSerasaProbUtils';
8
9
  import { ConsultasComplementaresContext } from '../consultasComplementares';
9
10
  import { Result, ResultContent } from '../interface/result';
10
11
  import StatusMessage from '../interface/statusMessage';
11
12
  import Section from '../section';
12
13
  import { RequestStatus } from '../webservice';
13
- var parseProb = function (raw) {
14
- return typeof raw === 'number' ? raw : Number(String(raw).replace(',', '.')) || 0;
15
- };
16
14
  var ConsultaScoreSerasa = function (_a) {
17
15
  var _b, _c, _d, _e, _f, _g;
18
16
  var documento = _a.documento;
@@ -21,7 +19,14 @@ var ConsultaScoreSerasa = function (_a) {
21
19
  var _h = useState(false), dataUpdated = _h[0], setDataUpdated = _h[1];
22
20
  var _j = useSafeQuery("SELECT FROM 'ScoreSerasa'.'Consulta'", { documento: documento }, (_c = (_b = consultasComplementaresContext === null || consultasComplementaresContext === void 0 ? void 0 : consultasComplementaresContext.consultasComplementares) === null || _b === void 0 ? void 0 : _b.scoreSerasa) === null || _c === void 0 ? void 0 : _c.consultaRealizada), response = _j.response, isLoading = _j.isLoading, error = _j.error, loadingProgress = _j.loadingProgress;
23
21
  var scoreData = (_e = (_d = response === null || response === void 0 ? void 0 : response.document) === null || _d === void 0 ? void 0 : _d.dados) === null || _e === void 0 ? void 0 : _e.score;
24
- var inadProb = parseProb(scoreData === null || scoreData === void 0 ? void 0 : scoreData.probabilidade_inadimplencia);
22
+ var inadResolvido = useMemo(function () {
23
+ return (scoreData === null || scoreData === void 0 ? void 0 : scoreData.score_calculado) === 'S'
24
+ ? resolveSerasaInadPercent({
25
+ pontuacao: scoreData.pontuacao,
26
+ probabilidade_inadimplencia: scoreData.probabilidade_inadimplencia
27
+ })
28
+ : null;
29
+ }, [scoreData]);
25
30
  var scoreRef = useRef(null);
26
31
  var consultaRealizada = useMemo(function () {
27
32
  var _a, _b;
@@ -61,8 +66,12 @@ var ConsultaScoreSerasa = function (_a) {
61
66
  progress: loadingProgress
62
67
  }, title: "Score Serasa", subtitle: "Consulta de Score", icon: ScoreBoaVistaIcon, description: !error && (React.createElement(StatusMessage, { type: scoreData ? 'success' : 'default' }, description)), onSuccess: function () { return (React.createElement(Result, null, scoreData && (React.createElement(ResultContent, { desktop: "repeat(4, 1fr)", tablet: "repeat(3, 1fr)", mobile: "1fr 1fr" },
63
68
  React.createElement(AddItemField, { name: "Score", value: String(scoreData.pontuacao) }),
64
- React.createElement(AddItemField, { name: "Probabilidade de Inadimpl\u00EAncia", value: "".concat(scoreData.probabilidade_inadimplencia, "%") }),
65
- React.createElement(AddItemField, { name: "An\u00E1lise", value: "DE CADA 100 ".concat(isDocumentoPF(documento) ? 'PESSOAS' : 'EMPRESAS', " CLASSIFICADAS NESTA CLASSE DE SCORE, \u00C9 PROV\u00C1VEL QUE ").concat(Math.floor(inadProb), " APRESENTEM D\u00C9BITOS NO MERCADO NOS PR\u00D3XIMOS 6 MESES.") }))))); }, isError: function (err) { return ({
69
+ React.createElement(AddItemField, { name: "Probabilidade de Inadimpl\u00EAncia", value: inadResolvido
70
+ ? formatInadPercentDisplay(inadResolvido.percent0to100)
71
+ : '—' }),
72
+ React.createElement(AddItemField, { name: "An\u00E1lise", value: inadResolvido
73
+ ? "DE CADA 100 ".concat(isDocumentoPF(documento) ? 'PESSOAS' : 'EMPRESAS', " CLASSIFICADAS NESTA CLASSE DE SCORE, \u00C9 PROV\u00C1VEL QUE ").concat(inadCountPer100(inadResolvido.percent0to100), " APRESENTEM D\u00C9BITOS NO MERCADO NOS PR\u00D3XIMOS 6 MESES.")
74
+ : '—' }))))); }, isError: function (err) { return ({
66
75
  children: React.createElement(React.Fragment, null),
67
76
  description: (React.createElement(StatusMessage, { type: "error" },
68
77
  "Erro ao realizar a consulta: ",
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Escala 0–1000 do Score Serasa; fora de faixa é ajustada (clamp).
3
+ */
4
+ export declare function parsePonto1000(pontuacao: unknown): number;
5
+ /**
6
+ * A API preenche probabilidade (0–100) ou vem vazia.
7
+ */
8
+ export declare function isProbFromApiValid(raw: unknown): boolean;
9
+ export declare function inadFromFallbackPontuacao(p: number): number;
10
+ /**
11
+ * Número inteiro 0–100 na frase "de cada 100" (arredondamento do % resolvido).
12
+ */
13
+ export declare function inadCountPer100(percent0to100: number): number;
14
+ /**
15
+ * "99,80%" com duas decimais (formato local).
16
+ */
17
+ export declare function formatInadPercentDisplay(percent0to100: number): string;
18
+ export declare function resolveSerasaInadPercent(rules: {
19
+ pontuacao: unknown;
20
+ probabilidade_inadimplencia: unknown;
21
+ }): {
22
+ percent0to100: number;
23
+ fromApi: boolean;
24
+ };
@@ -0,0 +1,66 @@
1
+ var SCORE_MAX = 1000;
2
+ var parseNumberFlexible = function (raw) {
3
+ if (raw === null || raw === undefined)
4
+ return null;
5
+ if (typeof raw === 'number') {
6
+ return Number.isFinite(raw) ? raw : null;
7
+ }
8
+ var s = String(raw).trim();
9
+ if (s === '')
10
+ return null;
11
+ var n = Number(s.replace(',', '.'));
12
+ return Number.isFinite(n) ? n : null;
13
+ };
14
+ /**
15
+ * Escala 0–1000 do Score Serasa; fora de faixa é ajustada (clamp).
16
+ */
17
+ export function parsePonto1000(pontuacao) {
18
+ var n = parseNumberFlexible(pontuacao);
19
+ if (n === null)
20
+ return 0;
21
+ return Math.min(SCORE_MAX, Math.max(0, n));
22
+ }
23
+ /**
24
+ * A API preenche probabilidade (0–100) ou vem vazia.
25
+ */
26
+ export function isProbFromApiValid(raw) {
27
+ if (raw === null || raw === undefined)
28
+ return false;
29
+ if (typeof raw === 'string' && raw.trim() === '')
30
+ return false;
31
+ var n = parseNumberFlexible(raw);
32
+ return n !== null;
33
+ }
34
+ var parseApiProbPercent = function (raw) {
35
+ var n = parseNumberFlexible(raw);
36
+ return n === null ? 0 : n;
37
+ };
38
+ export function inadFromFallbackPontuacao(p) {
39
+ return ((SCORE_MAX - p) / SCORE_MAX) * 100;
40
+ }
41
+ /**
42
+ * Número inteiro 0–100 na frase "de cada 100" (arredondamento do % resolvido).
43
+ */
44
+ export function inadCountPer100(percent0to100) {
45
+ return Math.min(100, Math.max(0, Math.round(percent0to100)));
46
+ }
47
+ var percentFormatter = new Intl.NumberFormat('pt-BR', {
48
+ minimumFractionDigits: 2,
49
+ maximumFractionDigits: 2
50
+ });
51
+ /**
52
+ * "99,80%" com duas decimais (formato local).
53
+ */
54
+ export function formatInadPercentDisplay(percent0to100) {
55
+ return "".concat(percentFormatter.format(percent0to100), "%");
56
+ }
57
+ export function resolveSerasaInadPercent(rules) {
58
+ var p = parsePonto1000(rules.pontuacao);
59
+ if (isProbFromApiValid(rules.probabilidade_inadimplencia)) {
60
+ return {
61
+ percent0to100: parseApiProbPercent(rules.probabilidade_inadimplencia),
62
+ fromApi: true
63
+ };
64
+ }
65
+ return { percent0to100: inadFromFallbackPontuacao(p), fromApi: false };
66
+ }
@@ -1,6 +1,6 @@
1
1
  import { __assign } from "tslib";
2
2
  // src/contexts/createDataContext.tsx
3
- import React, { createContext, useContext, useState } from 'react';
3
+ import React, { createContext, useCallback, useContext, useState } from 'react';
4
4
  function createDataContext(defaultValue) {
5
5
  if (defaultValue === void 0) { defaultValue = {}; }
6
6
  var DataContext = createContext({
@@ -25,12 +25,12 @@ function createDataContext(defaultValue) {
25
25
  var DataProvider = function (_a) {
26
26
  var children = _a.children;
27
27
  var _b = useState(createProxy(defaultValue)), data = _b[0], setData = _b[1];
28
- var updateData = function (update) {
28
+ var updateData = useCallback(function (update) {
29
29
  setData(function (prevData) {
30
30
  var newState = typeof update === 'function' ? update(prevData) : update;
31
31
  return createProxy(__assign(__assign({}, prevData), newState));
32
32
  });
33
- };
33
+ }, []);
34
34
  return (React.createElement(DataContext.Provider, { value: { data: data, setData: updateData } }, children));
35
35
  };
36
36
  var useData = function () { return useContext(DataContext); };