From 0f630560c27f0f040084a521c4092e195e20cb50 Mon Sep 17 00:00:00 2001 From: valenti Date: Tue, 2 Jun 2026 10:46:41 -0400 Subject: [PATCH] fixes --- src/components/ui/MeshTable/MeshTable.tsx | 494 ++++++++++++++++++---- src/pages/FastCheckEX.tsx | 56 ++- 2 files changed, 462 insertions(+), 88 deletions(-) diff --git a/src/components/ui/MeshTable/MeshTable.tsx b/src/components/ui/MeshTable/MeshTable.tsx index 0c06de4..c9f1734 100644 --- a/src/components/ui/MeshTable/MeshTable.tsx +++ b/src/components/ui/MeshTable/MeshTable.tsx @@ -46,9 +46,40 @@ interface OwnRow { fuente: string; } +// ── layout del mapa conceptual ────────────────────────────────────────────── +const NODE_W = 215; +const NODE_H = 88; +const COL_GAP = 72; +const ROW_GAP = 26; +const PAD = 34; + +const wrapName = (name: string, maxChars = 30, maxLines = 2): string[] => { + const words = (name || 'Sin nombre').trim().split(/\s+/) + .map(w => (w.length > maxChars ? w.slice(0, maxChars - 1) + '\u2026' : w)); + const lines: string[] = []; + let cur = ''; + for (const w of words) { + const candidate = cur ? `${cur} ${w}` : w; + if (candidate.length <= maxChars) cur = candidate; + else { + if (cur) lines.push(cur); + cur = w; + if (lines.length === maxLines) break; + } + } + if (cur && lines.length < maxLines) lines.push(cur); + const consumed = lines.join(' ').length; + if (lines.length === maxLines && (name || '').trim().length > consumed) { + const last = lines[maxLines - 1]; + lines[maxLines - 1] = (last.length > maxChars - 1 ? last.slice(0, maxChars - 1) : last) + '\u2026'; + } + return lines.length ? lines : ['Sin nombre']; +}; + const MeshTable = ({ title, height, dequienesRelationships, mainRut, forceExpanded = false }: any) => { const [showOwn, setShowOwn] = useState(true); const [expandedOwn, setExpandedOwn] = useState>(new Set()); + const [viewMode, setViewMode] = useState<'table' | 'map'>('table'); if (!dequienesRelationships) return null; @@ -189,9 +220,6 @@ const MeshTable = ({ title, height, dequienesRelationships, mainRut, forceExpand }); })(); - const containerStyle = - height !== 'auto' ? { maxHeight: height, overflowY: 'auto' as const } : {}; - const thStyle: React.CSSProperties = { border: '1px solid #e5e7eb', padding: '6px 8px', textAlign: 'left', fontSize: '12px', fontWeight: 600, color: '#374151', @@ -292,79 +320,399 @@ const MeshTable = ({ title, height, dequienesRelationships, mainRut, forceExpand .filter(r => r.parentId === null) .sort((a, b) => (b.percentage ?? -1) - (a.percentage ?? -1)); + // ── Mapa conceptual ─────────────────────────────────────────────────────── + // Usa exactamente los mismos ownRows que la tabla (dueños hacia arriba). + // Layout izquierda→derecha: N0 = RUT principal | N1 = dueños | N2..N5 = dueños-de-dueños. + const buildMapLayout = () => { + if (!ownRows.length) return null; + + const slot = NODE_H + ROW_GAP; + const cyMap = new Map(); + let rowCursor = 0; + + const place = (rowId: number) => { + const row = ownRows.find(r => r.id === rowId); + if (!row) return; + const kids = ownRows.filter(r => r.parentId === rowId); + const isExp = forceExpanded || expandedOwn.has(rowId); + const visibleKids = isExp ? kids : []; + if (!visibleKids.length) { + cyMap.set(rowId, rowCursor * slot + NODE_H / 2); + rowCursor++; + } else { + visibleKids.forEach(k => place(k.id)); + const fy = cyMap.get(visibleKids[0].id)!; + const ly = cyMap.get(visibleKids[visibleKids.length - 1].id)!; + cyMap.set(rowId, (fy + ly) / 2); + } + }; + + rootOwnRows.forEach(r => place(r.id)); + + let rootCy: number; + if (rootOwnRows.length) { + const fy = cyMap.get(rootOwnRows[0].id)!; + const ly = cyMap.get(rootOwnRows[rootOwnRows.length - 1].id)!; + rootCy = (fy + ly) / 2; + } else { + rootCy = NODE_H / 2; + } + + interface MapNode { + key: string; + level: number; + rut: string; + name: string; + percentage: number | null; + isCompany: boolean; + cx: number; + top: number; + isMain: boolean; + rowId: number | null; + hasChildren: boolean; + isExpanded: boolean; + } + interface MapEdge { + id: string; + x1: number; y1: number; + x2: number; y2: number; + pct: number | null; + } + + const colStep = NODE_W + COL_GAP; + const nodes: MapNode[] = []; + const edges: MapEdge[] = []; + + let minY = rootCy - NODE_H / 2; + let maxY = rootCy + NODE_H / 2; + let maxLvl = 0; + + const visitedIds = new Set(); + const walk = (rowId: number) => { + if (visitedIds.has(rowId)) return; + visitedIds.add(rowId); + const row = ownRows.find(r => r.id === rowId); + if (!row) return; + const cy = cyMap.get(rowId); + if (cy == null) return; + minY = Math.min(minY, cy - NODE_H / 2); + maxY = Math.max(maxY, cy + NODE_H / 2); + maxLvl = Math.max(maxLvl, row.level); + + const kids = ownRows.filter(r => r.parentId === rowId); + const isExp = forceExpanded || expandedOwn.has(rowId); + + nodes.push({ + key: `n${rowId}`, + level: row.level, + rut: row.targetRut, + name: row.targetName, + percentage: row.percentage, + isCompany: isSociedad(row.targetRut), + cx: row.level * colStep + NODE_W / 2 + PAD, + top: cy, + isMain: false, + rowId, + hasChildren: kids.length > 0, + isExpanded: isExp, + }); + + if (isExp) kids.forEach(k => walk(k.id)); + }; + rootOwnRows.forEach(r => walk(r.id)); + + const rootNode: MapNode = { + key: 'root', + level: 0, + rut: cleanMain, + name: getName(cleanMain), + percentage: null, + isCompany: isSociedad(cleanMain), + cx: NODE_W / 2 + PAD, + top: rootCy, + isMain: true, + rowId: null, + hasChildren: rootOwnRows.length > 0, + isExpanded: true, + }; + + // Normalizo Y de center -> top con padding + rootNode.top = rootNode.top - NODE_H / 2 - minY + PAD; + nodes.forEach(n => { n.top = n.top - NODE_H / 2 - minY + PAD; }); + + const allNodes = [rootNode, ...nodes]; + const byKey = new Map(allNodes.map(n => [n.key, n])); + const rowIdToKey = new Map(); + nodes.forEach(n => { if (n.rowId != null) rowIdToKey.set(n.rowId, n.key); }); + + // root -> N1 + rootOwnRows.forEach(r => { + const key = rowIdToKey.get(r.id); + if (!key) return; + const child = byKey.get(key)!; + edges.push({ + id: `root->${key}`, + x1: rootNode.cx + NODE_W / 2, + y1: rootNode.top + NODE_H / 2, + x2: child.cx - NODE_W / 2, + y2: child.top + NODE_H / 2, + pct: r.percentage, + }); + }); + + // padre expandido -> hijos + nodes.forEach(parentNode => { + if (parentNode.rowId == null || !parentNode.isExpanded) return; + const kids = ownRows.filter(r => r.parentId === parentNode.rowId); + kids.forEach(k => { + const key = rowIdToKey.get(k.id); + if (!key) return; + const child = byKey.get(key)!; + edges.push({ + id: `${parentNode.key}->${key}`, + x1: parentNode.cx + NODE_W / 2, + y1: parentNode.top + NODE_H / 2, + x2: child.cx - NODE_W / 2, + y2: child.top + NODE_H / 2, + pct: k.percentage, + }); + }); + }); + + const width = maxLvl * colStep + NODE_W + PAD * 2; + const fullHeight = maxY - minY + PAD * 2; + + return { allNodes, edges, width, fullHeight, maxLvl }; + }; + + const expandAll = () => { + const all = new Set(); + ownRows.forEach(r => { + if (ownRows.some(c => c.parentId === r.id)) all.add(r.id); + }); + setExpandedOwn(all); + }; + const collapseAll = () => setExpandedOwn(new Set()); + + const renderMap = () => { + const layout = buildMapLayout(); + if (!layout) { + return ( +

+ Sin datos de malla societaria +

+ ); + } + + const renderNode = (n: any) => { + let bg: string, border: string, badgeBg: string, badgeTx: string, sub: string, nameCol: string; + if (n.isMain) { + bg = '#2563eb'; border = '#1d4ed8'; badgeBg = '#ffffff'; + badgeTx = '#1d4ed8'; sub = '#bfdbfe'; nameCol = '#ffffff'; + } else if (n.isCompany) { + bg = '#ffffff'; border = '#60a5fa'; badgeBg = '#dbeafe'; + badgeTx = '#1d4ed8'; sub = '#6b7280'; nameCol = '#111827'; + } else { + bg = '#fefce8'; border = '#facc15'; badgeBg = '#dbeafe'; + badgeTx = '#1d4ed8'; sub = '#6b7280'; nameCol = '#111827'; + } + + const nameLines = wrapName(n.name); + const left = n.cx - NODE_W / 2; + const showToggle = n.hasChildren && !forceExpanded && !n.isMain && n.rowId != null; + + return ( + + + + N{n.level} + {n.isCompany ? 'Empresa' : 'Persona'} + + {formatRut(n.rut)} + + {nameLines.map((ln: string, i: number) => ( + {ln} + ))} + {!n.isMain && n.percentage != null && ( + + {n.percentage}% participación + + )} + {showToggle && ( + { e.stopPropagation(); toggleOwn(n.rowId); }} + > + + + {n.isExpanded ? '\u2212' : '+'} + + + )} + + ); + }; + + return ( + <> + {!forceExpanded && ( +
+ + Nivel: + + {layout.maxLvl} / {maxLevel} + + +
+ + +
+
+ )} + +
+ + + {layout.edges.map((e: any) => { + const midX = (e.x1 + e.x2) / 2; + return ( + + + {e.pct != null && ( + + + + {e.pct}% + + + )} + + ); + })} + {layout.allNodes.map(renderNode)} + +
+ + {!forceExpanded && ( +

+ Usá el botón + al costado de cada nodo para expandir sus dueños. + Hasta {MAX_DEPTH} niveles de profundidad. +

+ )} + + ); + }; + return (
-

{title}

- -
- {ownRows.length > 0 ? ( -
- setShowOwn(v => !v)} - /> - - {showOwn && ( - <> -
- {controlador && ( -
- Controlador principal identificado: - - {controlador.targetRut} {controlador.targetName} - - {controlador.level > 0 && ( - - (Nivel {controlador.level}{controlador.percentage != null ? ` - ${controlador.percentage}%` : ''}) - - )} -
- )} -
- Cobertura de la malla: - - Nivel máximo alcanzado: {maxLevel} de {MAX_DEPTH} - -
-
- -
- - - - - - - - - - - - - - - - - - - - - {renderOwnRows(rootOwnRows, { count: 0 })} - -
NivelEmpresaDueño%TipoFuente
-

- Nota: Hasta {MAX_DEPTH} niveles · Solo se expanden dueños que son sociedades · - % según DO / DOH / SII (puede haber múltiples fuentes y duplicados). -

-
- - )} -
- ) : ( -

Sin datos de malla societaria

- )} +
+

{title}

+ {/* {ownRows.length > 0 && !forceExpanded && ( + + )} */}
+ + {viewMode === 'map' && ownRows.length > 0 ? ( + renderMap() + ) : ( +
+ {ownRows.length > 0 ? ( +
+ setShowOwn(v => !v)} + /> + + {showOwn && ( + <> +
+ {controlador && ( +
+ Controlador principal identificado: + + {controlador.targetRut} {controlador.targetName} + + {controlador.level > 0 && ( + + (Nivel {controlador.level}{controlador.percentage != null ? ` - ${controlador.percentage}%` : ''}) + + )} +
+ )} +
+ Cobertura de la malla: + + Nivel máximo alcanzado: {maxLevel} de {MAX_DEPTH} + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + {renderOwnRows(rootOwnRows, { count: 0 })} + +
NivelEmpresaDueño%TipoFuente
+

+ Nota: Hasta {MAX_DEPTH} niveles · Solo se expanden dueños que son sociedades · + % según DO / DOH / SII (puede haber múltiples fuentes y duplicados). +

+
+ + )} +
+ ) : ( +

Sin datos de malla societaria

+ )} +
+ )}
); }; diff --git a/src/pages/FastCheckEX.tsx b/src/pages/FastCheckEX.tsx index 7e54c9b..1f07980 100644 --- a/src/pages/FastCheckEX.tsx +++ b/src/pages/FastCheckEX.tsx @@ -21,7 +21,7 @@ import { Dialog } from '@headlessui/react'; import { Tooltip, } from '@mui/material'; - +/* import MeshFlowChart from '../components/ui/MeshFlowChart/MeshFlowChart'; */ import { ChevronDown, ChevronUp, Download, Edit3, Save, X, FileText } from 'lucide-react'; import * as XLSX from 'xlsx'; import { useTranslation } from 'react-i18next'; @@ -337,6 +337,14 @@ const FastCheck: React.FC = () => { unpaidAmount: 'Monto', documentType: 'Tipo de Deuda', totalUTA: 'Multa UTA', + libradorName: 'Librador', + localidadName: 'Localidad', + justificationDescription: 'Justificación', + boletinNumber: 'N° Boletín', + boletinPage: 'Página Boletín', + + + }; const [count, setCount] = useState({ @@ -348,7 +356,7 @@ const FastCheck: React.FC = () => { const columnasPermitidas: Record = { 'Multas Laborales': ['motivo', 'Motivo', 'boletin', 'Boletín', 'fecha', 'Fecha', 'tipoInfraccion', 'Tipo Infracción', 'infraccion', 'Infracción', 'monto', 'Monto', 'resolucion', 'Resolución'], - 'Protestos y Morosidades': ['vencimiento', 'Vencimiento', 'monto', 'Monto', 'tipoDeuda', 'Tipo de Deuda', 'publicacion', 'Publicación', 'expirationDate', 'unpaidAmount', 'documentType',], + 'Protestos y Morosidades': ['vencimiento', 'Vencimiento', 'monto', 'Monto', 'tipoDeuda', 'Tipo de Deuda', 'publicacion', 'Publicación', 'expirationDate', 'unpaidAmount', 'documentType', 'libradorName', 'localidadName', 'justificationDescription', 'boletinNumber', 'boletinPage'], 'Condenas por Prácticas Antisindicales': ['rit', 'RIT', 'tribunal', 'Tribunal', 'fechaEjecutoria', 'Fecha ejecutoria', 'hechosCondenados', 'Hechos condenados', 'tipoDenuncia', 'Tipo de Denuncia', 'montoMulta', 'Monto Multa'], 'Proceso Sanciones Medioambientales': ['unidadFiscalizable', 'expediente', 'procesoSancionTipoNombre', 'Estado', 'fechaInicio', 'fechaTermino', 'confirmaPdC', 'multaTotalUTA', 'linkSNIFA', 'regionNombre', 'comunaNombre', 'linkSNIFA_UF', 'fechaActualizacion'], 'Sanciones Medioambientales': ['Unidad Fiscalizable', 'Expediente', 'Tipo Sanción', 'Estado', 'Fecha', 'totalUTA', 'Región',], @@ -385,12 +393,7 @@ const FastCheck: React.FC = () => { data?.details?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || data?.logEntry?.filteredDetails?.compliance?.penal?.coincidencias || []; -/* - console.warn( - '[FastCheckEX] compliance.penal.coincidencias', - data?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias - ); -*/ + const coincidenciasFiltradas = todasCoincidencias.filter((item: any) => { const postura = item?.postura?.toLowerCase() || ''; return postura === 'demandado' || postura === 'querellado' || postura === 'denunciado'; @@ -2305,6 +2308,21 @@ const FastCheck: React.FC = () => { await new Promise(resolve => setTimeout(resolve, 100)); + iframeDoc.body.appendChild(clonedElement); + + await new Promise(resolve => setTimeout(resolve, 100)); + + + clonedElement.querySelectorAll('svg').forEach((svg) => { + const r = svg.getBoundingClientRect(); + if (r.width > 0 && r.height > 0) { + svg.setAttribute('width', String(Math.ceil(r.width))); + svg.setAttribute('height', String(Math.ceil(r.height))); + } + }); + + + const canvas = await html2canvas(clonedElement, { scale: 2, useCORS: true, @@ -4180,8 +4198,13 @@ const FastCheck: React.FC = () => { tipoDeuda: 'tipoDeuda', documentType: 'tipoDeuda', publicacion: 'publicacion', + libradorName: 'libradorName', + localidadName: 'localidadName', + justificationDescription: 'justificationDescription', + boletinNumber: 'boletinNumber', + boletinPage: 'boletinPage', }; - const colOrder = ['vencimiento', 'monto', 'tipoDeuda', 'publicacion']; + const colOrder = ['vencimiento', 'monto', 'tipoDeuda', 'publicacion', 'libradorName', 'localidadName', 'justificationDescription', 'boletinNumber', 'boletinPage']; filteredDetails = details.map((row: any) => { const filtered: Record = {}; Object.keys(keyMap).forEach(srcKey => { @@ -4225,11 +4248,6 @@ const FastCheck: React.FC = () => { }
- - - - - {expandedSections.informacion && (
{/* Informazioni generali */} @@ -4250,7 +4268,7 @@ const FastCheck: React.FC = () => {

Tamaño Empresa:

-

{companyGeneralInfo.tamanoEmpresa}

+

{data?.sheriffLogData?.filteredDetails?.sheriffV2Data?.resumen?.data?.identificacion?.tamanoEmpresaSii?.tamanoEmpresa}

Rango Ventas UF:

@@ -4553,6 +4571,14 @@ const FastCheck: React.FC = () => {
)} + {/* {!exportingPdf && ( + + )} */}