From d394de50f9fd8559af2f9ff4ac814832ae55144a Mon Sep 17 00:00:00 2001 From: valenti Date: Wed, 27 May 2026 10:51:02 -0400 Subject: [PATCH] crstiobal fix --- .../legal_events_76415480_1779477676.json | 7 - .../legal_events_76415480_1779478085.json | 7 - .../relationships_76415480_1779477676.json | 242 --- .../relationships_76415480_1779478085.json | 242 --- .../76415480-0_20260522T192805Z.json | 820 -------- .../dashboard/BulkEvaluationProgressMeter.tsx | 10 +- .../ConsultationHistoryChart copy.tsx | 249 --- .../dashboard/ResultsTable copy.tsx | 316 ---- src/components/dashboard/ResultsTable.tsx | 166 +- .../DeepAnalysisPanel/DeepAnalysisPanel.tsx | 212 +++ src/pages/dashboard/Dashboard.tsx | 1684 ++++++++++++++--- 11 files changed, 1691 insertions(+), 2264 deletions(-) delete mode 100644 server/dequenes_responses/legal_events_76415480_1779477676.json delete mode 100644 server/dequenes_responses/legal_events_76415480_1779478085.json delete mode 100644 server/dequenes_responses/relationships_76415480_1779477676.json delete mode 100644 server/dequenes_responses/relationships_76415480_1779478085.json delete mode 100644 server/sheriff_v2_response/76415480-0_20260522T192805Z.json delete mode 100644 src/components/dashboard/ConsultationHistoryChart copy.tsx delete mode 100644 src/components/dashboard/ResultsTable copy.tsx create mode 100644 src/components/ui/DeepAnalysisPanel/DeepAnalysisPanel.tsx diff --git a/server/dequenes_responses/legal_events_76415480_1779477676.json b/server/dequenes_responses/legal_events_76415480_1779477676.json deleted file mode 100644 index c3d2c50..0000000 --- a/server/dequenes_responses/legal_events_76415480_1779477676.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rut": "76415480", - "events": [], - "cached": true, - "timestamp": "2026-05-22T19:21:16.446777", - "constitution_date": null -} \ No newline at end of file diff --git a/server/dequenes_responses/legal_events_76415480_1779478085.json b/server/dequenes_responses/legal_events_76415480_1779478085.json deleted file mode 100644 index 1961db4..0000000 --- a/server/dequenes_responses/legal_events_76415480_1779478085.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rut": "76415480", - "events": [], - "cached": true, - "timestamp": "2026-05-22T19:28:05.250827", - "constitution_date": null -} \ No newline at end of file diff --git a/server/dequenes_responses/relationships_76415480_1779477676.json b/server/dequenes_responses/relationships_76415480_1779477676.json deleted file mode 100644 index 976d21b..0000000 --- a/server/dequenes_responses/relationships_76415480_1779477676.json +++ /dev/null @@ -1,242 +0,0 @@ -{ - "pathCount": 22, - "relationships": [ - { - "source": "76415480", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2005-12-13", - "source": "SII_REP" - } - }, - { - "source": "76415480", - "target": "10474151", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6345160", - "doh_extraction_id": "461832", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6345160", - "published_at": "2004-05-22", - "url": "https://www.diariooficial.interior.gob.cl/media/2004/05/22/do-20040522.pdf" - } - }, - { - "source": "76415480", - "target": "11550027", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6345160", - "doh_extraction_id": "461832", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6345160", - "published_at": "2004-05-22", - "url": "https://www.diariooficial.interior.gob.cl/media/2004/05/22/do-20040522.pdf" - } - }, - { - "source": "76657450", - "target": "76415480", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 26, - "source": "SII_REC_COM" - } - }, - { - "source": "76052565", - "target": "76415480", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 15, - "source": "SII_REC_COM" - } - }, - { - "source": "76103603", - "target": "76415480", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 100, - "source": "SII_REC_COM" - } - }, - { - "source": "99516680", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2003-03-28", - "source": "SII_REP" - } - }, - { - "source": "76657450", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2010-01-25", - "source": "SII_REP" - } - }, - { - "source": "76145086", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2011-05-12", - "source": "SII_REP" - } - }, - { - "source": "76103603", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2010-07-07", - "source": "SII_REP" - } - }, - { - "source": "76145086", - "target": "11550027", - "label": "OWNED_BY", - "information_source": { - "match_type": "bt", - "source": "DOH", - "published_at": "2011-02-02", - "doh_id": "7208282", - "url": "https://www.diariooficial.interior.gob.cl/media/2011/02/02/do-20110202.pdf" - } - }, - { - "source": "96944720", - "target": "10474151", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2010-01-25", - "source": "SII_REP" - } - }, - { - "source": "96944720", - "target": "10474151", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6012540", - "doh_extraction_id": "585051", - "shares_percent": 40, - "source": "DOH", - "doh_id": "6012540", - "published_at": "2001-06-20", - "url": "https://www.diariooficial.interior.gob.cl/media/2001/06/20/do-20010620.pdf" - } - }, - { - "source": "76657450", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6609682", - "doh_extraction_id": "725944", - "shares_percent": 26, - "source": "DOH", - "doh_id": "6609682", - "published_at": "2006-09-21", - "url": "https://www.diariooficial.interior.gob.cl/media/2006/09/21/do-20060921.pdf" - } - }, - { - "source": "76657450", - "target": "78595830", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6609682", - "doh_extraction_id": "725944", - "shares_percent": 26, - "source": "DOH", - "doh_id": "6609682", - "published_at": "2006-09-21", - "url": "https://www.diariooficial.interior.gob.cl/media/2006/09/21/do-20060921.pdf" - } - }, - { - "source": "76657450", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 26, - "source": "SII_REC_COM" - } - }, - { - "source": "76052565", - "target": "11736590", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2009-04-02", - "source": "SII_REP" - } - }, - { - "source": "76052565", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6986535", - "doh_extraction_id": "343796", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6986535", - "published_at": "2009-03-07", - "url": "https://www.diariooficial.interior.gob.cl/media/2009/03/07/do-20090307.pdf" - } - }, - { - "source": "76052565", - "target": "78595830", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6986535", - "doh_extraction_id": "343796", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6986535", - "published_at": "2009-03-07", - "url": "https://www.diariooficial.interior.gob.cl/media/2009/03/07/do-20090307.pdf" - } - }, - { - "source": "76052565", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 35, - "source": "SII_REC_COM" - } - } - ], - "entityNames": { - "10474151": "ORELIA ANDREA WOOD FLORES", - "11550027": "CARLOS ALEJANDRO MUNOZ BAEZ", - "11736590": "EDUARDO ANDRES KAFTANSKI ARANCIBIA", - "76052565": "LINUXCENTER SOCIEDAD ANONIMA", - "76103603": "FORMACION DIGITAL SPA", - "76145086": "LINUX LATINAMERICA SOCIEDAD ANONIMA", - "76415480": "INVERSIONES WOOD Y COMPANIA LIMITADA", - "76657450": "LINUXCENTER LATINOAMERICA SOCIEDAD ANONIMA", - "77699370": "INVERSIONES KACT LIMITADA", - "78595830": "INVERSIONES WOOD LIMITADA", - "96944720": "CENTRO LINUX S A", - "99516680": "GLOBAL TRAINING S A" - }, - "_cache_info": { - "cached": true, - "cache_key": "dequienes_cache:dequienes_relationships:44e208e6756fd87de10e4da1c1fb7751b4cc887607ae3cdfd02c58cd53fc99f2", - "retrieved_at": "2026-05-22T19:21:16.436841" - } -} \ No newline at end of file diff --git a/server/dequenes_responses/relationships_76415480_1779478085.json b/server/dequenes_responses/relationships_76415480_1779478085.json deleted file mode 100644 index 577c7a0..0000000 --- a/server/dequenes_responses/relationships_76415480_1779478085.json +++ /dev/null @@ -1,242 +0,0 @@ -{ - "pathCount": 22, - "relationships": [ - { - "source": "76415480", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2005-12-13", - "source": "SII_REP" - } - }, - { - "source": "76415480", - "target": "10474151", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6345160", - "doh_extraction_id": "461832", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6345160", - "published_at": "2004-05-22", - "url": "https://www.diariooficial.interior.gob.cl/media/2004/05/22/do-20040522.pdf" - } - }, - { - "source": "76415480", - "target": "11550027", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6345160", - "doh_extraction_id": "461832", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6345160", - "published_at": "2004-05-22", - "url": "https://www.diariooficial.interior.gob.cl/media/2004/05/22/do-20040522.pdf" - } - }, - { - "source": "76657450", - "target": "76415480", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 26, - "source": "SII_REC_COM" - } - }, - { - "source": "76052565", - "target": "76415480", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 15, - "source": "SII_REC_COM" - } - }, - { - "source": "76103603", - "target": "76415480", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 100, - "source": "SII_REC_COM" - } - }, - { - "source": "99516680", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2003-03-28", - "source": "SII_REP" - } - }, - { - "source": "76657450", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2010-01-25", - "source": "SII_REP" - } - }, - { - "source": "76145086", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2011-05-12", - "source": "SII_REP" - } - }, - { - "source": "76103603", - "target": "11550027", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2010-07-07", - "source": "SII_REP" - } - }, - { - "source": "76145086", - "target": "11550027", - "label": "OWNED_BY", - "information_source": { - "match_type": "bt", - "source": "DOH", - "published_at": "2011-02-02", - "doh_id": "7208282", - "url": "https://www.diariooficial.interior.gob.cl/media/2011/02/02/do-20110202.pdf" - } - }, - { - "source": "96944720", - "target": "10474151", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2010-01-25", - "source": "SII_REP" - } - }, - { - "source": "96944720", - "target": "10474151", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6012540", - "doh_extraction_id": "585051", - "shares_percent": 40, - "source": "DOH", - "doh_id": "6012540", - "published_at": "2001-06-20", - "url": "https://www.diariooficial.interior.gob.cl/media/2001/06/20/do-20010620.pdf" - } - }, - { - "source": "76657450", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6609682", - "doh_extraction_id": "725944", - "shares_percent": 26, - "source": "DOH", - "doh_id": "6609682", - "published_at": "2006-09-21", - "url": "https://www.diariooficial.interior.gob.cl/media/2006/09/21/do-20060921.pdf" - } - }, - { - "source": "76657450", - "target": "78595830", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6609682", - "doh_extraction_id": "725944", - "shares_percent": 26, - "source": "DOH", - "doh_id": "6609682", - "published_at": "2006-09-21", - "url": "https://www.diariooficial.interior.gob.cl/media/2006/09/21/do-20060921.pdf" - } - }, - { - "source": "76657450", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 26, - "source": "SII_REC_COM" - } - }, - { - "source": "76052565", - "target": "11736590", - "label": "REPRESENTED_BY", - "information_source": { - "started_at": "2009-04-02", - "source": "SII_REP" - } - }, - { - "source": "76052565", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6986535", - "doh_extraction_id": "343796", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6986535", - "published_at": "2009-03-07", - "url": "https://www.diariooficial.interior.gob.cl/media/2009/03/07/do-20090307.pdf" - } - }, - { - "source": "76052565", - "target": "78595830", - "label": "OWNED_BY", - "information_source": { - "pub_id": "doh_id_6986535", - "doh_extraction_id": "343796", - "shares_percent": 50, - "source": "DOH", - "doh_id": "6986535", - "published_at": "2009-03-07", - "url": "https://www.diariooficial.interior.gob.cl/media/2009/03/07/do-20090307.pdf" - } - }, - { - "source": "76052565", - "target": "77699370", - "label": "OWNED_BY", - "information_source": { - "shares_percent": 35, - "source": "SII_REC_COM" - } - } - ], - "entityNames": { - "10474151": "ORELIA ANDREA WOOD FLORES", - "11550027": "CARLOS ALEJANDRO MUNOZ BAEZ", - "11736590": "EDUARDO ANDRES KAFTANSKI ARANCIBIA", - "76052565": "LINUXCENTER SOCIEDAD ANONIMA", - "76103603": "FORMACION DIGITAL SPA", - "76145086": "LINUX LATINAMERICA SOCIEDAD ANONIMA", - "76415480": "INVERSIONES WOOD Y COMPANIA LIMITADA", - "76657450": "LINUXCENTER LATINOAMERICA SOCIEDAD ANONIMA", - "77699370": "INVERSIONES KACT LIMITADA", - "78595830": "INVERSIONES WOOD LIMITADA", - "96944720": "CENTRO LINUX S A", - "99516680": "GLOBAL TRAINING S A" - }, - "_cache_info": { - "cached": true, - "cache_key": "dequienes_cache:dequienes_relationships:44e208e6756fd87de10e4da1c1fb7751b4cc887607ae3cdfd02c58cd53fc99f2", - "retrieved_at": "2026-05-22T19:28:05.240585" - } -} \ No newline at end of file diff --git a/server/sheriff_v2_response/76415480-0_20260522T192805Z.json b/server/sheriff_v2_response/76415480-0_20260522T192805Z.json deleted file mode 100644 index f932ab1..0000000 --- a/server/sheriff_v2_response/76415480-0_20260522T192805Z.json +++ /dev/null @@ -1,820 +0,0 @@ -{ - "success": true, - "cargarRut": { - "success": true, - "data": [ - "76415480-0" - ] - }, - "resumen": { - "success": true, - "data": { - "rut": "76415480-0", - "esPersonaJuridica": true, - "fechaCarga": "20-05-2026", - "identificacion": { - "razonSocial": "INVERSIONES WOOD Y COMPANIA LIMITADA", - "presentaActividades": "SI", - "inicioActividades": "13-12-2005", - "autorizadoMonedaExtranjera": "NO", - "empresaMenor": "SI", - "observaciones": "Recomendación GeneralComo recomendación general, siempre que se realicen transacciones comerciales con cualquier contribuyente, el SII aconseja verificar, en las opciones anteriores habilitadas, el timbraje del documento y que la actividad económica esté vigente en las bases de datos del Servicio. Además, se recomienda verificar que el domicilio y la actividad económica consignados en la factura o boleta que reciba, correspondan al vendedor o prestador del servicio ofrecido. Para un mayor resguardo, se recomienda efectuar el pago con cheque nominativo o vale vista a favor del proveedor, anotando al reverso el RUT del emisor y número del documento recibido.", - "notas": "Si el contribuyente correspondiente al RUT consultado, no está de acuerdo o desconoce la situación informada en esta consulta, deberá concurrir a la unidad del SII correspondiente a su domicilio para aclarar o resolver su situación.Esta consulta no constituye una certificación del comportamiento tributario del contribuyente. De esta manera, si para un RUT no aparecen observaciones, no significa que en una posterior auditoría no se detecten problemas.", - "situacionActual": "No se encuentra en la nómina de difícil fiscalización", - "actividadEconomicaVigente": [ - { - "actividades": "VENTA AL POR MENOR DE OTROS PRODUCTOS EN COMERCIOS ESPECIALIZADOS N.C.", - "codigo": "477399", - "categoria": "Primera", - "afectaIva": "Si", - "fecha": "24-11-2017" - }, - { - "actividades": "ALQUILER DE BIENES INMUEBLES AMOBLADOS O CON EQUIPOS Y MAQUINARIAS", - "codigo": "681011", - "categoria": "Primera", - "afectaIva": "Si", - "fecha": "13-12-2005" - }, - { - "actividades": "OTRAS ACTIVIDADES DE SERVICIOS DE APOYO A LAS EMPRESAS N.C.P.", - "codigo": "829900", - "categoria": "Primera", - "afectaIva": "Si", - "fecha": "24-11-2017" - } - ], - "documentosTimbrados": [ - { - "documento": "Factura Electronica", - "ultimoAnioTimbrado": "2026" - }, - { - "documento": "Factura No Afecta O Exenta Electronica", - "ultimoAnioTimbrado": "2022" - }, - { - "documento": "Boleta Electronica", - "ultimoAnioTimbrado": "2025" - }, - { - "documento": "Boleta Exenta Electronica", - "ultimoAnioTimbrado": "2025" - }, - { - "documento": "Guia Despacho Electronica", - "ultimoAnioTimbrado": "2026" - }, - { - "documento": "Nota Debito Electronica", - "ultimoAnioTimbrado": "2025" - }, - { - "documento": "Nota Credito Electronica", - "ultimoAnioTimbrado": "2026" - }, - { - "documento": "Contabilidad En Hojas Sueltas Con Nro.Un", - "ultimoAnioTimbrado": "2018" - } - ], - "cantidadActividades": 3, - "cantidadDocumentos": 8, - "ultimaActualizacion": "20-05-2026 14:30:06", - "tamanoEmpresaSii": { - "tamanoEmpresa": "Pequeña empresa", - "ventasAnuales": "10.000,01 a 25.000,00", - "cantidadTrabajadores": 7 - } - }, - "boletinComercial": { - "casosInfocom": 0, - "casosBolcom": 1, - "MontoInfocom": 0, - "MontoBolcom": 4914700, - "variacion": { - "ultimoPeriodo": "11/2025", - "periodoActual": "05/2026", - "cambioPorcentual": null, - "cambioAbsoluto": 4914700 - }, - "ultimaActualizacion": "20-05-2026 14:30:05" - }, - "judicial": { - "casosCivilesActivos": 0, - "casosCivilesPresentados": 0, - "casosCivilesConcluidos": 0, - "cobranzaActivos": 0, - "cobranzaPresentados": 0, - "cobranzaConcluidos": 0, - "laboralActivos": 2, - "laboralPresentados": 0, - "laboralConcluidos": 0, - "casosActuales": 2, - "casosArchivados": 0, - "casosConcluidos": 0, - "todosLosCasos": 2, - "casosQuiebra": 0, - "posturasActivasPresentadas": { - "demandado": 2 - }, - "ultimaActualizacionCiviles": "20-05-2026 14:30:02", - "ultimaActualizacionCobranza": "20-05-2026 14:30:02", - "ultimaActualizacionLaboral": "20-05-2026 14:30:03", - "boletinConcursal": false - }, - "cobranzaLaboral": { - "moraPrevisional": { - "casosTotales": 149, - "empleados": 4, - "variacion": { - "ultimoPeriodo": "11/2025", - "periodoActual": "05/2026", - "cambioPorcentual": 22.13, - "cambioAbsoluto": 27 - }, - "ultimaActualizacion": "20-05-2026 14:30:15" - }, - "multaLaboral": { - "totalMultas": 4, - "montoTotalMultas": 1605000, - "multasPagadas": 1, - "montoMultasPagadas": 335000, - "multasPendientes": 3, - "montoMultasPendientes": 1270000, - "variacion": { - "ultimoPeriodo": "11/2025", - "periodoActual": "05/2026", - "cambioPorcentual": 379.1, - "cambioAbsoluto": 1270000 - }, - "ultimaActualizacion": "20-05-2026 14:30:03" - }, - "boletinLaboral": { - "montoTotal": 615512, - "variacion": { - "montoTotal": 0, - "ultimoPeriodo": "11/2025", - "periodoActual": "05/2026", - "cambioPorcentual": null, - "cambioAbsoluto": 615512 - }, - "ultimaActualizacion": "20-05-2026 14:30:04" - } - }, - "legal": {}, - "deudaBancaria": { - "periodo": "04/2026", - "deudaDirectaComercial": 20900, - "deudaDirectaCastigada": 0, - "deudaDirectaHipotecaria": 0, - "deudaDirectaMorosa": 0, - "deudaDirectaVencida": 0, - "deudaDirectaVigente": 20900, - "deudaIndirectaCastigada": 0, - "deudaIndirectaVencida": 0, - "deudaIndirectaVigente": 0, - "leasingMoroso": 0, - "leasingVigente": 0, - "lineaCredito": 10, - "variacion": { - "periodo": "10/2025", - "deudaDirectaComercial": 25070, - "deudaDirectaCastigada": 0, - "deudaDirectaHipotecaria": 0, - "deudaDirectaMorosa": 0, - "deudaDirectaVencida": 0, - "deudaDirectaVigente": 25070, - "deudaIndirectaCastigada": 0, - "deudaIndirectaVencida": 0, - "deudaIndirectaVigente": 0, - "leasingMoroso": 0, - "leasingVigente": 0, - "lineaCredito": 0 - } - }, - "bienes": { - "vehiculos": { - "cantidadVehiculos": 0, - "tasacionTotal": 0, - "ultimaActualizacion": "20-05-2026 14:30:04" - }, - "propiedades": { - "cantidadPropiedades": 0, - "tasacionTotal": "0", - "ultimaActualizacion": "20-05-2026 14:30:04" - } - }, - "creditScoring": { - "ponderacionFinanciero": 100, - "maxPonderacionFinanciero": 100, - "ponderacionJudicial": 0, - "judicialBankruptcy": false, - "maxPonderacionJudicial": 100, - "ponderacionCobranzaLaboral": 0, - "maxPonderacionCobranzaLaboral": 100, - "ponderacionBoletin": 0, - "maxPonderacionBoletin": 100, - "cantidadAyudantes": 4, - "nombresAyudantes": [ - "judicial", - "commertialBulletin", - "finances", - "laborCollection" - ], - "porcentajeClasificacion": 100, - "etiquetaClasificacion": "High" - }, - "ratings": { - "etiquetasActivas": [], - "cantidadComentarios": 0, - "calificacionGlobal": { - "valor": 0, - "total": 0 - }, - "calificacionCumplimientoGlobal": { - "valor": 0, - "total": 0 - }, - "calificacionRelacionGlobal": { - "valor": 0, - "total": 0 - }, - "calificacionTransparenciaGlobal": { - "valor": 0, - "total": 0 - }, - "calificacionFiabilidadGlobal": { - "valor": 0, - "total": 0 - } - }, - "compliance": { - "nombre": "INVERSIONES WOOD Y COMPANIA LIMITADA", - "segundoNombre": "", - "apellido": "", - "segundoApellido": "", - "tipoPersona": "juridical", - "riesgo": "free", - "resumen": { - "pepChile": { - "totalResultados": 0, - "riesgo": "free" - }, - "pepChileFamiliares": { - "totalResultados": 0, - "riesgo": "free" - }, - "funcionariosPublicos": { - "totalResultados": 0, - "riesgo": "free" - }, - "listasInternacionales": { - "totalResultados": 0, - "riesgo": "free" - }, - "penal": { - "totalResultados": 0, - "riesgo": "free" - }, - "mallaSocietaria": { - "totalResultados": 1, - "riesgo": "free" - }, - "noticias": { - "totalResultados": 0, - "riesgo": "free" - } - } - }, - "incluyeEquifax": false, - "ayudantesFallidos": [] - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:905242db38143ec3533579000b3e47fbb145291386a03cb4d63395e7e0c02dfe", - "cached_at": "2026-05-22T19:28:00.725555" - } - }, - "judicial": { - "civil": { - "success": true, - "data": { - "page": 1, - "totalPages": 0, - "cantidad": 0, - "casos": [], - "ultimaActualizacion": "20-05-2026 14:30:03" - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:2f1467a0e28de6efa96b80d2254b4f3e11e9b4224efe46b3e0096c362eb166c3", - "cached_at": "2026-05-22T19:28:01.174560" - } - }, - "cobranza": { - "success": true, - "data": { - "page": 1, - "totalPages": 0, - "cantidad": 0, - "casos": [], - "ultimaActualizacion": "20-05-2026 14:30:03" - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:0d2568ab46f5249f0c512f1e77cbf07a1ebc896073fd6e31002566c644315c17", - "cached_at": "2026-05-22T19:28:01.658467" - } - }, - "laboral": { - "success": true, - "data": { - "page": 1, - "totalPages": 1, - "cantidad": 2, - "casos": [ - { - "estado": "Tramitación", - "estadoCausa": "Sin archivar", - "etapa": "Audiencia Preparatoria", - "fechaIngreso": "22-01-2026 21:00:00", - "litigantes": [ - { - "rut": "27677870-6", - "nombre": "ANABEL CHIQUINQUIRA BELLO RIOS", - "sujeto": "DTE.", - "persona": "NATURAL", - "abogadoDefensor": null - }, - { - "rut": "76415480-0", - "nombre": "INVERSIONES WOOD LTDA", - "sujeto": "DDO.", - "persona": "JURIDICA", - "abogadoDefensor": null - }, - { - "rut": "20052516-7", - "nombre": "DANIEL BASTIÁN PATRICIO VILLAGRÁN CARMONA (Poder Amplio)", - "sujeto": "AB.DTE", - "persona": "NATURAL", - "abogadoDefensor": null - } - ], - "materia": "Ordinario", - "rit": "O-564-2026", - "tribunal": "1º Juzgado de Letras del Trabajo de Santiago", - "sujeto": "DDO.", - "estadoActual": "Vigente" - }, - { - "estado": "Tramitación", - "estadoCausa": "Sin archivar", - "etapa": "Impugnación Monitorio", - "fechaIngreso": "20-01-2026 21:00:00", - "litigantes": [ - { - "rut": "20052516-7", - "nombre": "DANIEL BASTIÁN PATRICIO VILLAGRÁN CARMONA (Poder Amplio)", - "sujeto": "AB.DTE", - "persona": "NATURAL", - "abogadoDefensor": null - }, - { - "rut": "27112648-4", - "nombre": "LIDIA KARINA MARTINEZ LLOVERA", - "sujeto": "DTE.", - "persona": "NATURAL", - "abogadoDefensor": null - }, - { - "rut": "76415480-0", - "nombre": "INVERSIONES WOOD LTDA", - "sujeto": "DDO.", - "persona": "JURIDICA", - "abogadoDefensor": null - } - ], - "materia": "Monitorio", - "rit": "M-370-2026", - "tribunal": "2º Juzgado de Letras del Trabajo de Santiago", - "sujeto": "DDO.", - "estadoActual": "Vigente" - } - ], - "ultimaActualizacion": "20-05-2026 14:30:03" - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:bfd6620c08be2e05f05d20a0e99ab40092ee1d047835fc35965c382781fc5fcc", - "cached_at": "2026-05-22T19:28:02.139101" - } - } - }, - "cobranzaLaboral": { - "multaLaboral": { - "success": true, - "data": { - "page": 1, - "totalPages": 1, - "cantidad": 4, - "casos": [ - { - "enunciado": "No comparecer a citación de la Dirección del Trabajo.", - "estado": "EN TRÁMITE DE COBRANZA", - "fecha": "20-03-2026 21:00:00", - "multa": "4282/26/011-1", - "procedencia": "RECLAMO", - "um": "0.67", - "tipoUm": "IMM", - "montoClp": 335000 - }, - { - "enunciado": "No comparecer a citación de la Dirección del Trabajo.", - "estado": "EN TRÁMITE DE COBRANZA", - "fecha": "04-02-2026 21:00:00", - "multa": "8566/25/345-1", - "procedencia": "RECLAMO", - "um": "0.67", - "tipoUm": "IMM", - "montoClp": 335000 - }, - { - "enunciado": "No exhibir  toda la documentación necesaria para efectuar la conciliación", - "estado": "EN TRÁMITE DE COBRANZA", - "fecha": "04-02-2026 21:00:00", - "multa": "8566/25/345-2", - "procedencia": "RECLAMO", - "um": "1.2", - "tipoUm": "IMM", - "montoClp": 600000 - }, - { - "enunciado": "No comparecer a citación de la Dirección del Trabajo.", - "estado": "PAGADA", - "fecha": "23-02-2024 21:00:00", - "multa": "1728/23/223-1", - "procedencia": "RECLAMO", - "um": "0.67", - "tipoUm": "IMM", - "montoClp": 335000 - } - ], - "ultimaActualizacion": "20-05-2026 14:30:02" - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:fcb2a117c7fd7381cadf1ab75d36b3391f12a2f42e7eee34af44b56e129faa25", - "cached_at": "2026-05-22T19:28:02.749770" - } - }, - "moraPrevisional": { - "success": true, - "data": { - "page": 1, - "totalPages": 8, - "cantidad": 152, - "casos": [ - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "29-04-2026 20:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "02/2026", - "rutAfiliado": "11550027-9", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "30-03-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "01/2026", - "rutAfiliado": "11550027-9", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "AFP Capital", - "fecha": "30-03-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "01/2026", - "rutAfiliado": "11550027-9", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "AFP Uno", - "fecha": "27-02-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "ANABEL CHIQUINQUIRA BELLO RIOS ", - "periodo": "12/2025", - "rutAfiliado": "27677870-6", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "27-02-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "12/2025", - "rutAfiliado": "11550027-9", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "27-02-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "LIDIA KARINA MARTINEZ LLOVERA ", - "periodo": "12/2025", - "rutAfiliado": "27112648-4", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "27-02-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "ANABEL CHIQUINQUIRA BELLO RIOS ", - "periodo": "12/2025", - "rutAfiliado": "27677870-6", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "AFP Capital", - "fecha": "27-02-2026 21:00:00", - "fechaDemanda": "En proceso  de Interposición de demanda", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "12/2025", - "rutAfiliado": "11550027-9", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "AFP Modelo", - "fecha": "27-02-2026 21:00:00", - "fechaDemanda": "En proceso  de Interposición de demanda", - "nombreAfiliado": "LIDIA KARINA MARTINEZ LLOVERA ", - "periodo": "12/2025", - "rutAfiliado": "27112648-4", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "30-01-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "ANABEL CHIQUINQUIRA BELLO RIOS ", - "periodo": "11/2025", - "rutAfiliado": "27677870-6", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "AFP Capital", - "fecha": "30-01-2026 21:00:00", - "fechaDemanda": "En proceso  de Interposición de demanda", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "11/2025", - "rutAfiliado": "11550027-9", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "AFP Modelo", - "fecha": "30-01-2026 21:00:00", - "fechaDemanda": "En proceso  de Interposición de demanda", - "nombreAfiliado": "LIDIA KARINA MARTINEZ LLOVERA ", - "periodo": "11/2025", - "rutAfiliado": "27112648-4", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "30-01-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "LIDIA KARINA MARTINEZ LLOVERA ", - "periodo": "11/2025", - "rutAfiliado": "27112648-4", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "AFP Uno", - "fecha": "30-01-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "ANABEL CHIQUINQUIRA BELLO RIOS ", - "periodo": "11/2025", - "rutAfiliado": "27677870-6", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "30-01-2026 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "11/2025", - "rutAfiliado": "11550027-9", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "30-12-2025 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "ANABEL CHIQUINQUIRA BELLO RIOS ", - "periodo": "10/2025", - "rutAfiliado": "27677870-6", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "AFP Uno", - "fecha": "30-12-2025 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "ANABEL CHIQUINQUIRA BELLO RIOS ", - "periodo": "10/2025", - "rutAfiliado": "27677870-6", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "Administradora de Fondos de Cesantía, AFC", - "fecha": "30-12-2025 21:00:00", - "fechaDemanda": "Prejudicial", - "nombreAfiliado": "LIDIA KARINA MARTINEZ LLOVERA ", - "periodo": "10/2025", - "rutAfiliado": "27112648-4", - "tipoProducto": "Seguro de Cesantía" - }, - { - "afp": "AFP Modelo", - "fecha": "30-12-2025 21:00:00", - "fechaDemanda": "En proceso  de Interposición de demanda", - "nombreAfiliado": "LIDIA KARINA MARTINEZ LLOVERA ", - "periodo": "10/2025", - "rutAfiliado": "27112648-4", - "tipoProducto": "Cotización obligatorias" - }, - { - "afp": "AFP Capital", - "fecha": "30-12-2025 21:00:00", - "fechaDemanda": "En proceso  de Interposición de demanda", - "nombreAfiliado": "CARLOS ALEJANDRO MUNOZ BAEZ ", - "periodo": "10/2025", - "rutAfiliado": "11550027-9", - "tipoProducto": "Cotización obligatorias" - } - ], - "ultimaActualizacion": "20-05-2026 14:30:02" - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:29103e062b8d43f3ced521ba71f4dc1082d6054a9a09aacdefba1c4cd57c208a", - "cached_at": "2026-05-22T19:28:03.229857" - } - } - }, - "compliance": { - "success": true, - "data": { - "rut": "76415480-0", - "tipoPersona": "juridical", - "nombre": "INVERSIONES WOOD Y COMPANIA LIMITADA", - "riesgo": "free", - "sociedades": { - "coincidencias": [ - { - "nombreSociedad": "INVERSIONES WOOD Y COMPANIA LIMITADA", - "rutSociedad": "76415480-0", - "nombreSocio": null, - "rutSocio": null, - "fecha": null, - "socios": [], - "extractos": [], - "participaciones": [ - { - "razonSocial": "FORMACION DIGITAL SPA", - "rut": "76103603-3", - "tipoDeParticipacion": "Socio" - }, - { - "razonSocial": "LINUXCENTER LATINOAMERICA SOCIEDAD ANONIMA", - "rut": "76657450-5", - "tipoDeParticipacion": "Socio" - }, - { - "razonSocial": "LINUXCENTER SOCIEDAD ANONIMA", - "rut": "76052565-0", - "tipoDeParticipacion": "Socio" - }, - { - "razonSocial": "LINUX LATINAMERICA SOCIEDAD ANONIMA", - "rut": "76145086-7", - "tipoDeParticipacion": "Socio" - } - ], - "fuente": null - } - ], - "totalCoincidencias": 1 - }, - "listasInternacionales": { - "coincidencias": [], - "totalCoincidencias": 0 - }, - "penal": { - "coincidencias": [], - "totalCoincidencias": 0 - }, - "noticias": { - "coincidencias": [], - "totalCoincidencias": 0 - }, - "pepChile": { - "coincidencias": [], - "totalCoincidencias": 0 - }, - "familiaresPep": { - "coincidencias": [], - "totalCoincidencias": 0 - }, - "funcionariosPublicos": { - "coincidencias": [], - "totalCoincidencias": 0 - } - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:277735490089a998797d3e64e5706e8723087e6b1a0b16684916ed4dc69310c8", - "cached_at": "2026-05-22T19:28:03.930966" - } - }, - "legal": { - "mallaSocietaria": { - "success": true, - "data": { - "rut": "76415480-0", - "empresaEnUnDia": [], - "diarioOficial": [], - "participaciones": [ - { - "rut": "0766574505", - "society": "LINUXCENTER LATINOAMERICA SOCIEDAD ANONIMA" - }, - { - "rut": "0760525650", - "society": "LINUXCENTER SOCIEDAD ANONIMA" - }, - { - "rut": "0761450867", - "society": "LINUX LATINAMERICA SOCIEDAD ANONIMA" - }, - { - "rut": "0761036033", - "society": "FORMACION DIGITAL SPA" - } - ] - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:e501a401d1232075ba99f75666a87a548a09c121c0c86e4998c2c91525d2d052", - "cached_at": "2026-05-22T19:28:04.437179" - } - } - }, - "creditScore": { - "success": true, - "data": { - "periodo": "2026-05", - "inicioActividades": true, - "porcentajeFinanzas": 0, - "numeradorFinanzas": 0, - "denominadorFinanzas": 20900, - "ponderacionFinanzas": 100, - "porcentajeJudicial": 100, - "numeradorJudicial": 2, - "denominadorJudicial": 2, - "quiebraJudicial": false, - "ponderacionJudicial": 0, - "porcentajeMoraPrevisional": 0, - "casosMoraPrevisional": 144, - "porcentajeMultasLaborales": 79.12772585669782, - "montoMultasLaborales": 1605000, - "porcentajeBoletinLaboral": 100, - "montoBoletinLaboral": 615512, - "ponderacionCobranzaLaboral": 0, - "porcentajeBoletinComercial": 100, - "montoBoletinComercial": 4914700, - "ponderacionBoletinComercial": 0, - "cantidadHelpers": "4", - "porcentajeClasificacion": 100, - "etiquetaClasificacion": "High", - "nombresHelpers": [ - "judicial", - "commertialBulletin", - "finances", - "laborCollection" - ], - "maxPonderacionJudicial": 100, - "maxPonderacionBoletinComercial": 100, - "maxPonderacionCobranzaLaboral": 100 - }, - "_cache_info": { - "cached": false, - "cache_key": "sheriff_v2_cache:sheriff_v2_get:8169039cf1dc5a9883879d5acdfd0a2bf1eab0a1dabd62dd34c6e86c16e6ec62", - "cached_at": "2026-05-22T19:28:05.200921" - } - } -} \ No newline at end of file diff --git a/src/components/dashboard/BulkEvaluationProgressMeter.tsx b/src/components/dashboard/BulkEvaluationProgressMeter.tsx index 6b8bb3c..7d08d01 100644 --- a/src/components/dashboard/BulkEvaluationProgressMeter.tsx +++ b/src/components/dashboard/BulkEvaluationProgressMeter.tsx @@ -62,18 +62,18 @@ const BulkEvaluationProgressMeter: React.FC = () => { if (!processingEvaluation) { return ( -
-

{t('bulkProgress.title')}

+
+

{t('bulkProgress.title')}

- + {t('bulkProgress.progress')}
- + 0%
@@ -84,7 +84,7 @@ const BulkEvaluationProgressMeter: React.FC = () => { className="h-4 rounded-full bg-blue-500" >
-
+
{t('bulkProgress.completed', { completed: 0, total: 0 })}
diff --git a/src/components/dashboard/ConsultationHistoryChart copy.tsx b/src/components/dashboard/ConsultationHistoryChart copy.tsx deleted file mode 100644 index 4de51b5..0000000 --- a/src/components/dashboard/ConsultationHistoryChart copy.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, AreaChart, Area } from 'recharts'; -import { ChartBarIcon, ChartBarSquareIcon, PresentationChartLineIcon } from '@heroicons/react/24/outline'; -import { IResult as IResultBackend } from '../../../server/src/models/Result'; -import { useTranslation } from 'react-i18next'; - -interface IResult extends Omit { - _id: string; - createdAt: string; - rut?: string; -} - -interface ConsultationHistoryChartProps { - results: IResult[]; -} - -interface ChartData { - date: string; - count: number; - displayDate: string; -} - -type ChartType = 'bar' | 'line' | 'area'; - -const ConsultationHistoryChart: React.FC = ({ results }) => { - const { t, i18n } = useTranslation(); - const [chartType, setChartType] = useState('bar'); - const chartData = useMemo(() => { - // Group results by date - const dateGroups: { [key: string]: number } = {}; - - results.forEach(result => { - const date = new Date(result.createdAt); - const dateKey = date.toISOString().split('T')[0]; // YYYY-MM-DD format - dateGroups[dateKey] = (dateGroups[dateKey] || 0) + 1; - }); - - // Convert to array and sort by date - const data: ChartData[] = Object.entries(dateGroups) - .map(([date, count]) => { - const resolvedLangKey = (i18n.resolvedLanguage ?? i18n.language ?? 'es'); - const localeMap: Record = { es: 'es-ES', en: 'en-US', pt: 'pt-PT', fr: 'fr-FR' }; - const dateLocale = localeMap[resolvedLangKey] ?? 'es-ES'; - return { - date, - count, - displayDate: new Date(date).toLocaleDateString(dateLocale, { - day: '2-digit', - month: '2-digit' - }) - }; - }) - .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) - .slice(-30); // Show last 30 days - - return data; - }, [results, i18n.resolvedLanguage, i18n.language]); - - const totalConsultations = results.length; - const averagePerDay = chartData.length > 0 ? (totalConsultations / chartData.length).toFixed(1) : '0'; - const maxInDay = Math.max(...chartData.map(d => d.count), 0); - - return ( -
-
-
-
-
- -
-
-

{t('consultationChart.title')}

-

{t('consultationChart.subtitle')}

-
-
- - {/* Chart Type Selector */} -
- {t('consultationChart.chartType')}: -
- - - -
-
-
- - {/* Statistics Cards */} -
-
-
{totalConsultations}
-
{t('consultationChart.cards.totalConsultas')}
-
-
-
{averagePerDay}
-
{t('consultationChart.cards.averagePerDay')}
-
-
-
{maxInDay}
-
{t('consultationChart.cards.maxInDay')}
-
-
- - {/* Chart */} - {chartData.length > 0 ? ( -
- - {chartType === 'bar' ? ( - - - - - `${t('consultationChart.tooltip.date')}: ${label}`} - formatter={(value: number) => [value, t('consultationChart.tooltip.consultas')]} - /> - - - ) : chartType === 'line' ? ( - - - - - `${t('consultationChart.tooltip.date')}: ${label}`} - formatter={(value: number) => [value, t('consultationChart.tooltip.consultas')]} - /> - - - ) : ( - - - - - - - - - - - `${t('consultationChart.tooltip.date')}: ${label}`} - formatter={(value: number) => [value, t('consultationChart.tooltip.consultas')]} - /> - - - )} - -
- ) : ( -
- -

{t('consultationChart.empty.title')}

-

{t('consultationChart.empty.subtitle')}

-
- )} -
-
- ); -}; - -export default ConsultationHistoryChart; \ No newline at end of file diff --git a/src/components/dashboard/ResultsTable copy.tsx b/src/components/dashboard/ResultsTable copy.tsx deleted file mode 100644 index 0708bb6..0000000 --- a/src/components/dashboard/ResultsTable copy.tsx +++ /dev/null @@ -1,316 +0,0 @@ -import React, { useState } from 'react'; -import { IResult as IResultBackend } from '../../../server/src/models/Result'; -import { Link } from 'react-router-dom'; -import { MagnifyingGlassIcon, ArrowUpIcon, ArrowDownIcon, EyeIcon, CalendarIcon, BuildingOfficeIcon } from '@heroicons/react/24/outline'; -import { logger } from '../../utils/logger'; -import { useTranslation } from 'react-i18next'; - -interface IResult extends IResultBackend { - _id: string; - createdAt: string; -} - -interface ResultsTableProps { - results: IResult[]; - currentPage: number; - totalPages: number; - onPageChange: (page: number) => void; - sortField?: string; - sortDirection?: 'asc' | 'desc'; - onSort?: (field: string) => void; -} - -const ResultsTable: React.FC = ({ - results, - currentPage, - totalPages, - onPageChange, - sortField = 'createdAt', - sortDirection = 'desc', - onSort -}) => { - const [searchTerm, setSearchTerm] = useState(''); - const { t, i18n } = useTranslation(); - const localeMap: Record = { es: 'es-ES', en: 'en-US', pt: 'pt-PT', fr: 'fr-FR' }; - // Safely resolve language key; i18n.resolvedLanguage can be undefined - const resolvedLangKey = (i18n.resolvedLanguage ?? i18n.language ?? 'es'); - const dateLocale = localeMap[resolvedLangKey] ?? 'es-ES'; - - const filteredResults = results.filter(result => { - if (result.isPep) return false; - const hasRiskSummary = !!result.riskAssessment?.riskSummary?.semaphore; - if (!hasRiskSummary) return false; - const searchLower = searchTerm.toLowerCase(); - const rut = (result.rut || result.details?.rut || '').toLowerCase(); - const razonSocial = (result.details?.razonSocial || '').toLowerCase(); - return rut.includes(searchLower) || razonSocial.includes(searchLower); - }); - const getRiskLevelClass = (riskLevel: string) => { - switch (riskLevel) { - case 'red': - return 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300 border border-red-200 dark:border-red-800'; - case 'yellow': - return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800'; - case 'green': - return 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300 border border-green-200 dark:border-green-800'; - case 'orange': - return 'bg-orange-100 text-orange-800 dark:bg-orange-900/50 dark:text-orange-300 border border-orange-200 dark:border-orange-800'; - default: - return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-600'; - } - }; - - const getRiskLevelDisplayName = (riskLevel: string) => { - logger.log(33,"riskLevel",riskLevel); - switch (riskLevel) { - case 'red': - return t('risk.critical'); - case 'yellow': - return t('risk.medium'); - case 'green': - return t('risk.low'); - case 'orange': - return t('risk.high'); - default: - return riskLevel; - } - }; - - return ( -
-
-
-
-
- -
-
-

{t('dashboard.recentQueriesTitle', { defaultValue: 'Consultas Recientes' })}

-

{t('dashboard.recentQueriesSubtitle', { defaultValue: 'Historial de evaluaciones realizadas' })}

-
-
-
-
- -
- setSearchTerm(e.target.value)} - className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent sm:text-sm" - /> -
-
- {filteredResults.length > 0 ? ( - <> -
-
- - - - - - - - - - - - {filteredResults.map((result) => ( - - - - - - - - ))} - -
- - - - - - - - - {t('consultas.acciones')} -
- {result.rut || result.details?.rut} - -
- - {result.details?.razonSocial} -
-
- - {getRiskLevelDisplayName(result.riskAssessment?.riskSummary?.semaphore || t('common.notAvailable'))} - - -
- - {new Date(result.createdAt).toLocaleDateString(dateLocale)} -
-
- - - {t('consultas.viewDetail', { defaultValue: 'Ver Detalle' })} - -
-
-
-
-
- - -
-
-
-

- {t('common.page')} {currentPage} {t('common.of')}{' '} - {totalPages} - {searchTerm && ( - - ({t('common.filteredBy')}: "{searchTerm}") - - )} -

-
-
- -
-
-
- - ) : ( -
-
- -

- {searchTerm ? t('resultsTable.noResults') : t('resultsTable.noQueries')} -

-

- {searchTerm - ? t('resultsTable.noResultsMessage', { searchTerm }) - : t('resultsTable.noQueriesMessage') - } -

- {searchTerm && ( - - )} -
-
- )} -
-
- ); -}; - -export default ResultsTable; diff --git a/src/components/dashboard/ResultsTable.tsx b/src/components/dashboard/ResultsTable.tsx index bf59af1..c10be07 100644 --- a/src/components/dashboard/ResultsTable.tsx +++ b/src/components/dashboard/ResultsTable.tsx @@ -23,6 +23,8 @@ interface ResultsTableProps { totalServerResults?: number; hasActiveFilters?: boolean; totalResultsBeforeFilter?: number; + selectedRuts?: string[]; + onSelectionChange?: (ruts: string[]) => void; } const ResultsTable: React.FC = ({ @@ -35,12 +37,14 @@ const ResultsTable: React.FC = ({ onSort: externalOnSort, totalServerResults = 0, hasActiveFilters = false, - totalResultsBeforeFilter = 0 + totalResultsBeforeFilter = 0, + selectedRuts = [], + onSelectionChange, }) => { const [searchTerm, setSearchTerm] = useState(''); const [localSortField, setLocalSortField] = useState('createdAt'); const [localSortDirection, setLocalSortDirection] = useState<'asc' | 'desc'>('desc'); - + const { t, i18n } = useTranslation(); const localeMap: Record = { es: 'es-ES', en: 'en-US', pt: 'pt-PT', fr: 'fr-FR' }; const resolvedLangKey = (i18n.resolvedLanguage ?? i18n.language ?? 'es'); @@ -49,7 +53,7 @@ const ResultsTable: React.FC = ({ // Ordenar localmente los resultados de la página actual const sortedResults = useMemo(() => { const resultsCopy = [...(results || [])]; - + return resultsCopy.sort((a, b) => { let aValue: any; let bValue: any; @@ -92,7 +96,7 @@ const ResultsTable: React.FC = ({ const rut = (result.rut || result.details?.rut || '').toLowerCase(); const razonSocial = (result.details?.razonSocial || '').toLowerCase(); const matchesSearch = !searchTerm || rut.includes(searchLower) || razonSocial.includes(searchLower); - + return { ...result, isVisible: matchesSearch @@ -103,6 +107,32 @@ const ResultsTable: React.FC = ({ const hiddenCount = resultsWithVisibility.length - visibleCount; const hasTableStructure = totalPages > 0; + const visibleRuts = resultsWithVisibility + .filter(r => r.isVisible) + .map(r => r.rut || r.details?.rut || '') + .filter(Boolean); + + const allVisibleSelected = visibleRuts.length > 0 && visibleRuts.every(rut => selectedRuts.includes(rut)); + + const toggleAllVisible = () => { + if (!onSelectionChange) return; + if (allVisibleSelected) { + onSelectionChange(selectedRuts.filter(rut => !visibleRuts.includes(rut))); + } else { + const merged = Array.from(new Set([...selectedRuts, ...visibleRuts])); + onSelectionChange(merged); + } + }; + + const toggleOneRut = (rut: string) => { + if (!onSelectionChange) return; + if (selectedRuts.includes(rut)) { + onSelectionChange(selectedRuts.filter(r => r !== rut)); + } else { + onSelectionChange([...selectedRuts, rut]); + } + }; + // Manejar ordenamiento local const handleLocalSort = (field: string) => { if (localSortField === field) { @@ -240,18 +270,7 @@ const ResultsTable: React.FC = ({

{t('dashboard.recentQueriesSubtitle', { defaultValue: 'Historial de evaluaciones realizadas' })}

- {/*
-
- -
- setSearchTerm(e.target.value)} - className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent sm:text-sm" - /> -
*/} +
{hasTableStructure ? ( <> @@ -260,6 +279,14 @@ const ResultsTable: React.FC = ({ + {resultsWithVisibility.length > 0 ? ( <> - {resultsWithVisibility.map((result) => ( - - - - - - - - - ))} + {resultsWithVisibility.map((result) => { + const rutValue = result.rut || result.details?.rut || ''; + return ( + + + + + + + + + + ); + })} {visibleCount === 0 && ( - -
+ +
- {result.rut || result.details?.rut} - -
- - {result.details?.razonSocial} -
-
-
- {result?.noteTags?.map((noteTag: any, index: number) => - - {noteTag} - - )} -
-
- - {getRiskLevelDisplayName(result.riskAssessment?.riskSummary?.semaphore || t('common.notAvailable'))} - - -
- - {new Date(result.createdAt).toLocaleDateString(dateLocale)} -
-
- - - {t('consultas.viewDetail', { defaultValue: 'Ver Detalle' })} - -
+ toggleOneRut(rutValue)} + className="w-4 h-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500" + /> + + {result.rut || result.details?.rut} + +
+ + {result.details?.razonSocial} +
+
+
+ {result?.noteTags?.map((noteTag: any, index: number) => + + {noteTag} + + )} +
+
+ + {getRiskLevelDisplayName(result.riskAssessment?.riskSummary?.semaphore || t('common.notAvailable'))} + + +
+ + {new Date(result.createdAt).toLocaleDateString(dateLocale)} +
+
+ + + {t('consultas.viewDetail', { defaultValue: 'Ver Detalle' })} + +
+

@@ -407,7 +447,7 @@ const ResultsTable: React.FC = ({ ) : (

+

diff --git a/src/components/ui/DeepAnalysisPanel/DeepAnalysisPanel.tsx b/src/components/ui/DeepAnalysisPanel/DeepAnalysisPanel.tsx new file mode 100644 index 0000000..6dd41cc --- /dev/null +++ b/src/components/ui/DeepAnalysisPanel/DeepAnalysisPanel.tsx @@ -0,0 +1,212 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { DocumentArrowDownIcon, EyeIcon } from '@heroicons/react/24/outline'; + +interface ParametroCritico { + label: string; + count: number; +} + +interface DimensionCritica { + name: string; + count: number; +} + +interface RutInfo { + rut: string; + razonSocial: string; + fecha: string; + _id: string; +} + +interface DeepAnalysisData { + dimensionesCriticas: DimensionCritica[]; + parametrosCriticos: ParametroCritico[]; + rutsByParam: Record; +} + +interface Props { + data: DeepAnalysisData | null; + loadingAnalysis: boolean; + loadingExcel: boolean; + onAnalyze: () => void; + onExportExcel: () => void; +} + +const DeepAnalysisPanel: React.FC = ({ data, loadingAnalysis, loadingExcel, onAnalyze, onExportExcel }) => { + const [selectedParams, setSelectedParams] = useState([]); + const maxCount = data ? Math.max(...data.parametrosCriticos.map(p => p.count), 1) : 1; + + useEffect(() => { + if (data && selectedParams.length > 0) { + const validParams = selectedParams.filter(p => data.rutsByParam[p]); + if (validParams.length !== selectedParams.length) { + setSelectedParams(validParams); + } + } + }, [data, selectedParams]); + + const toggleParam = (label: string) => { + setSelectedParams(prev => + prev.includes(label) ? prev.filter(p => p !== label) : [...prev, label] + ); + }; + + return ( +
+
+

Análisis Profundo

+
+ + +
+
+ + {data && data.parametrosCriticos.length > 0 && ( + <> +
+ {/* Dimensiones Críticas - solo parámetros severo/Crítico con nombre y conteo */} +
+
+

Parametros severos +

+ + Crítico + +
+
    + {data.dimensionesCriticas.map((dim, i) => ( +
  • + + + {dim.name} + + 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'}`}> + {dim.count} + +
  • + ))} +
+
+ + {/* Parámetros Críticos - gráfico de barras */} +
+
+

Recurrencia por parámetro

+ + + Crítico / Alto + +
+
+ {data.parametrosCriticos.map((param, i) => ( +
+ toggleParam(param.label)} + className="w-4 h-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500 flex-shrink-0" + /> + + {param.label} + +
+
+
+ {param.count} +
+
+
+
+ ))} +
+
+
+ + {selectedParams.length > 0 && ( +
+
+

+ RUTs por Parámetro Seleccionado ({selectedParams.length}) +

+ +
+
+
+ + + + + + + + + + + + {selectedParams.flatMap(paramLabel => + (data.rutsByParam[paramLabel] || []).map((r, idx) => ( + + + + + + + + )) + )} + +
ParámetroRUTRazón SocialFechaAcciones
{paramLabel}{r.rut}{r.razonSocial} + {r.fecha ? new Date(r.fecha).toLocaleDateString('es-CL') : '-'} + + {r._id && ( + + + Ver Detalle + + )} +
+
+
+
+ )} + + )} + + {data && data.parametrosCriticos.length === 0 && ( +

No se encontraron parámetros críticos o altos.

+ )} +
+ ); +}; + +export default DeepAnalysisPanel; \ No newline at end of file diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx index 8090ede..1dc4919 100644 --- a/src/pages/dashboard/Dashboard.tsx +++ b/src/pages/dashboard/Dashboard.tsx @@ -2,14 +2,16 @@ import React, { useState, useEffect } from 'react'; import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; import { toast } from 'react-hot-toast'; +import * as XLSX from 'xlsx'; -// Componentes Dashboard import DailyJobsSummary from '../../components/dashboard/DailyJobsSummary'; import TenantUsageSummary from '../../components/dashboard/TenantUsageSummary'; +import { apiClient } from '../../services/api'; import RiskDonutChart from '../../components/dashboard/RiskDonutChart'; import ResultsTable from '../../components/dashboard/ResultsTable'; import ConsultationHistoryChart from '../../components/dashboard/ConsultationHistoryChart'; import BulkEvaluationProgressMeter from '../../components/dashboard/BulkEvaluationProgressMeter'; +import DeepAnalysisPanel from '../../components/ui/DeepAnalysisPanel/DeepAnalysisPanel'; // Utils e Iconos import { @@ -37,6 +39,30 @@ interface IResult extends Omit { noteTags?: string[]; } +const DEEP_ANALYSIS_STORAGE_KEY = 'deepAnalysisCompactData'; +const DEEP_ANALYSIS_STORAGE_VERSION = 1; +const DEEP_ANALYSIS_MAX_RUTS = 550; + +interface CompactRuleEntry { + label: string; + detected: boolean; + risk: string; + isSevero: boolean; +} + +interface CompactRutData { + rutValue: string; + rules: CompactRuleEntry[]; + causasPenalesDetected: boolean; + noticiasDetected: boolean; +} + +interface DeepAnalysisStoragePayload { + version: number; + timestamp: number; + data: CompactRutData[]; +} + // Función helper para eliminar RUTs duplicados const removeDuplicateRuts = (data: IResult[]): IResult[] => { const uniqueResults = data.reduce((acc, result) => { @@ -51,6 +77,193 @@ const removeDuplicateRuts = (data: IResult[]): IResult[] => { return Array.from(uniqueResults.values()); }; +const processDeepAnalysisFromCompact = ( + compactData: CompactRutData[], + allowedRuts: Set +) => { + const paramCounts: Record = {}; + const paramRuts: Record = {}; + const allCriticoLabels = new Set(); + const criticoCounts: Record = {}; + + for (const entry of compactData) { + if (!allowedRuts.has(entry.rutValue)) continue; + + for (const rule of entry.rules) { + if (rule.isSevero) allCriticoLabels.add(rule.label); + + if (rule.detected && (rule.risk === 'critico' || rule.risk === 'alto')) { + paramCounts[rule.label] = (paramCounts[rule.label] || 0) + 1; + if (!paramRuts[rule.label]) paramRuts[rule.label] = []; + paramRuts[rule.label].push(entry.rutValue); + if (rule.isSevero) { + criticoCounts[rule.label] = (criticoCounts[rule.label] || 0) + 1; + } + } + } + + // Causas Penales siempre se considera severo/Crítico como dimensión + allCriticoLabels.add('Causas Penales'); + if (entry.causasPenalesDetected) { + paramCounts['Causas Penales'] = (paramCounts['Causas Penales'] || 0) + 1; + if (!paramRuts['Causas Penales']) paramRuts['Causas Penales'] = []; + paramRuts['Causas Penales'].push(entry.rutValue); + criticoCounts['Causas Penales'] = (criticoCounts['Causas Penales'] || 0) + 1; + } + + if (entry.noticiasDetected) { + paramCounts['Reputación Pública y Mediática'] = + (paramCounts['Reputación Pública y Mediática'] || 0) + 1; + if (!paramRuts['Reputación Pública y Mediática']) paramRuts['Reputación Pública y Mediática'] = []; + paramRuts['Reputación Pública y Mediática'].push(entry.rutValue); + } + } + + const parametrosCriticos = Object.entries(paramCounts) + .map(([label, count]) => ({ label, count })) + .sort((a, b) => b.count - a.count); + + const dimensionesCriticas = Array.from(allCriticoLabels) + .map(name => ({ name, count: criticoCounts[name] || 0 })) + .sort((a, b) => b.count - a.count); + + return { dimensionesCriticas, parametrosCriticos, rutsByParam: paramRuts }; +}; + +// Helper: extrae lo mínimo necesario desde una respuesta cruda de un RUT +const buildCompactEntryFromResponse = (rutValue: string, d: any): CompactRutData => { + const entry: CompactRutData = { + rutValue, + rules: [], + causasPenalesDetected: false, + noticiasDetected: false, + }; + + const allRulesData = d?.riskAssessment?.allRules || {}; + for (const catKey of ['complianceRules', 'legalRules', 'capitalHumanoRules', 'financieroTributarioRules']) { + const rules: any[] = allRulesData[catKey] || []; + for (const rule of rules) { + if (rule.label === 'Inicio de Actividades') continue; + + let detected = rule.detected || false; + let risk = rule.risk || 'bajo'; + + if (rule.label === 'Deuda Previsional Presunta') { + const details = rule.details?.casos || rule.details; + const detailCount = Array.isArray(details) ? details.length + : (details && typeof details === 'object' && Object.keys(details).length > 0) ? 1 : 0; + if (detailCount > 0) { + detected = true; + risk = 'alto'; + } + } + + entry.rules.push({ + label: rule.label, + detected, + risk, + isSevero: rule.impacto === 'severo', + }); + } + } + + const todasCoincidencias = + d?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || + d?.details?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || + d?.logEntry?.filteredDetails?.compliance?.penal?.coincidencias || []; + const coincidenciasFiltradas = todasCoincidencias.filter((item: any) => { + const postura = item?.postura?.toLowerCase() || ''; + return postura === 'demandado' || postura === 'querellado' || postura === 'denunciado'; + }); + entry.causasPenalesDetected = coincidenciasFiltradas.length > 0; + + const noticiasCoincidencias = + d?.sheriffLogData?.filteredDetails?.compliance?.noticias?.coincidencias || + d?.details?.sheriffLogData?.filteredDetails?.compliance?.noticias?.coincidencias || + d?.logEntry?.filteredDetails?.compliance?.noticias?.coincidencias || []; + entry.noticiasDetected = noticiasCoincidencias.length > 0; + + return entry; +}; + + +const processDeepAnalysisFromCache = ( + cachedResponses: Array<{ rutValue: string; data: any }>, + allowedRuts: Set +) => { + const paramCounts: Record = {}; + const allCriticoLabels = new Set(); + const criticoCounts: Record = {}; + + for (const { rutValue, data: d } of cachedResponses) { + if (!allowedRuts.has(rutValue) || !d) continue; + + const allRulesData = d?.riskAssessment?.allRules || {}; + + for (const catKey of ['complianceRules', 'legalRules', 'capitalHumanoRules', 'financieroTributarioRules']) { + const rules: any[] = allRulesData[catKey] || []; + for (const rule of rules) { + if (rule.label === 'Inicio de Actividades') continue; + + let detected = rule.detected || false; + let risk = rule.risk || 'bajo'; + + if (rule.impacto === 'severo') { + allCriticoLabels.add(rule.label); + } + + if (rule.label === 'Deuda Previsional Presunta') { + const details = rule.details?.casos || rule.details; + const detailCount = Array.isArray(details) ? details.length + : (details && typeof details === 'object' && Object.keys(details).length > 0) ? 1 : 0; + if (detailCount > 0) { + detected = true; + risk = 'alto'; + } + } + + if (detected && (risk === 'critico' || risk === 'alto')) { + paramCounts[rule.label] = (paramCounts[rule.label] || 0) + 1; + if (rule.impacto === 'severo') { + criticoCounts[rule.label] = (criticoCounts[rule.label] || 0) + 1; + } + } + } + } + + allCriticoLabels.add('Causas Penales'); + const todasCoincidencias = + d?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || + d?.details?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || + d?.logEntry?.filteredDetails?.compliance?.penal?.coincidencias || []; + const coincidenciasFiltradas = todasCoincidencias.filter((item: any) => { + const postura = item?.postura?.toLowerCase() || ''; + return postura === 'demandado' || postura === 'querellado' || postura === 'denunciado'; + }); + if (coincidenciasFiltradas.length > 0) { + paramCounts['Causas Penales'] = (paramCounts['Causas Penales'] || 0) + 1; + criticoCounts['Causas Penales'] = (criticoCounts['Causas Penales'] || 0) + 1; + } + + const noticiasCoincidencias = + d?.sheriffLogData?.filteredDetails?.compliance?.noticias?.coincidencias || + d?.details?.sheriffLogData?.filteredDetails?.compliance?.noticias?.coincidencias || + d?.logEntry?.filteredDetails?.compliance?.noticias?.coincidencias || []; + if (noticiasCoincidencias.length > 0) { + paramCounts['Reputación Pública y Mediática'] = (paramCounts['Reputación Pública y Mediática'] || 0) + 1; + } + } + + const parametrosCriticos = Object.entries(paramCounts) + .map(([label, count]) => ({ label, count })) + .sort((a, b) => b.count - a.count); + + const dimensionesCriticas = Array.from(allCriticoLabels) + .map(name => ({ name, count: criticoCounts[name] || 0 })) + .sort((a, b) => b.count - a.count); + + return { dimensionesCriticas, parametrosCriticos }; +}; const Dashboard: React.FC = () => { const { user } = useAuth(); const { tenant } = useTenant(); @@ -78,23 +291,34 @@ const Dashboard: React.FC = () => { const [loadingAllData, setLoadingAllData] = useState(false); const [showRiskDropdown, setShowRiskDropdown] = useState(false); const [showTagDropdown, setShowTagDropdown] = useState(false); + const [deepAnalysisData, setDeepAnalysisData] = useState<{ dimensionesCriticas: { name: string; count: number }[]; parametrosCriticos: { label: string; count: number }[]; rutsByParam: Record> } | null>(null); + const [loadingDeepAnalysis, setLoadingDeepAnalysis] = useState(false); + + const [cachedRutResponses, setCachedRutResponses] = useState>([]); + + const [compactRutData, setCompactRutData] = useState([]); + - // Nuevo estado para manejar el modo de filtros const [allDataForFilters, setAllDataForFilters] = useState([]); const [isFilterMode, setIsFilterMode] = useState(false); - // Estado para el tipo de vista del gráfico de historial + const [chartViewType, setChartViewType] = useState<'monthly' | 'daily' | 'yearly'>('monthly'); - // Siempre mantener todos los datos para los gráficos + const [allDataForCharts, setAllDataForCharts] = useState([]); + const [selectedRuts, setSelectedRuts] = useState([]); + + useEffect(() => { + setSelectedRuts([]); + }, [currentPage]); + useEffect(() => { const fetchData = async () => { if (user && tenant && !isFilterMode) { console.log('Fetching all data to remove duplicates'); try { - // Cargar todos los datos para eliminar duplicados let allData: IResult[] = []; let page = 1; let totalP = 1; @@ -111,20 +335,16 @@ const Dashboard: React.FC = () => { page++; } while (page <= totalP); - // Eliminar RUTs duplicados, dejando solo el más reciente const uniqueData = removeDuplicateRuts(allData); - - // Ordenar por fecha descendente uniqueData.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - - // Paginar manualmente + const startIndex = (currentPage - 1) * itemsPerPage; const paginatedData = uniqueData.slice(startIndex, startIndex + itemsPerPage); - + setResults(paginatedData); setTotalPages(Math.ceil(uniqueData.length / itemsPerPage)); setAllDataForCharts(uniqueData); - + console.log('Unique records:', uniqueData.length, 'Showing page:', currentPage); } catch (error) { logger.error('Error fetching data:', error); @@ -134,6 +354,438 @@ const Dashboard: React.FC = () => { fetchData(); }, [user, tenant, currentPage, itemsPerPage, sortField, sortDirection, appliedSearch, isFilterMode]); + + useEffect(() => { + if (compactRutData.length === 0) { + setDeepAnalysisData(null); + return; + } + + const hasFilters = !!(appliedStartDate || appliedEndDate || appliedRiskLevels.length > 0 || appliedTags.length > 0 || appliedSearch); + + const dataSource = isFilterMode && allDataForFilters.length > 0 + ? allDataForFilters + : (allDataForCharts.length > 0 ? allDataForCharts : results); + + const rutInfoMap = new Map(); +for (const r of dataSource) { + const rut = r.rut || r.identifier || ''; + if (!rut) continue; + rutInfoMap.set(rut, { + razonSocial: r.details?.razonSocial || r.razonSocial || 'N/A', + fecha: r.createdAt, + _id: r._id + }); +} + +const enrichRuts = (rutsByParam: Record) => { + const enriched: Record> = {}; + for (const [label, ruts] of Object.entries(rutsByParam)) { + enriched[label] = ruts.map(rut => { + const info = rutInfoMap.get(rut); + return { + rut, + razonSocial: info?.razonSocial || 'N/A', + fecha: info?.fecha || '', + _id: info?._id || '' + }; + }); + } + return enriched; +}; + + if (!hasFilters) { + let allRuts = new Set(compactRutData.map(r => r.rutValue)); + if (selectedRuts.length > 0) { + allRuts = new Set([...allRuts].filter(r => selectedRuts.includes(r))); + } + const result = processDeepAnalysisFromCompact(compactRutData, allRuts); + setDeepAnalysisData({ + dimensionesCriticas: result.dimensionesCriticas, + parametrosCriticos: result.parametrosCriticos, + rutsByParam: enrichRuts(result.rutsByParam) + }); + return; + } + + if (dataSource.length === 0) return; + + const filteredData = applyFiltersToData(dataSource); + const tableRuts = new Set(filteredData.map(r => r.rut || r.identifier || '').filter(Boolean)); + + const allowedRuts = new Set(); + for (const { rutValue } of compactRutData) { + if (tableRuts.has(rutValue)) allowedRuts.add(rutValue); + } + + if (selectedRuts.length > 0) { + for (const r of [...allowedRuts]) { + if (!selectedRuts.includes(r)) allowedRuts.delete(r); + } + } + + const result = processDeepAnalysisFromCompact(compactRutData, allowedRuts); + setDeepAnalysisData({ + dimensionesCriticas: result.dimensionesCriticas, + parametrosCriticos: result.parametrosCriticos, + rutsByParam: enrichRuts(result.rutsByParam) + }); + }, [appliedStartDate, appliedEndDate, appliedRiskLevels, appliedTags, appliedSearch, compactRutData, allDataForCharts, results, isFilterMode, allDataForFilters, selectedRuts]); + + + const handleExportInfo = async () => { + setLoadingAllData(true); + try { + const sourceData = isFilterMode && allDataForFilters.length > 0 + ? allDataForFilters + : (allDataForCharts.length > 0 ? allDataForCharts : results); + const uniqueRuts = Array.from(new Set( + sourceData.map(r => r.rut || r.identifier || '').filter(Boolean) + )); + + if (uniqueRuts.length === 0) { + toast.error('No hay RUTs para consultar'); + setLoadingAllData(false); + return; + } + + const riskToColor: Record = { + critico: 'rojo', alto: 'naranjo', medio: 'amarillo', bajo: 'verde', no_disponible: 'gris', + }; + + const calculateSemaphore = (c: { critical: number; high: number; low: number }) => { + const total = c.critical + c.high + c.low; + if (c.critical >= 1) return 'red'; + if (c.low === total && total > 0) return 'green'; + if (total > 0 && c.high / total >= 0.5) return 'red'; + if (total > 0 && c.high / total >= 0.3) return 'orange'; + return 'yellow'; + }; + + const semaphoreToColor: Record = { red: 'rojo', orange: 'naranjo', yellow: 'amarillo', green: 'verde' }; + const semaphoreToLabel: Record = { red: 'CRÍTICO', orange: 'ALTO', yellow: 'MEDIO', green: 'BAJO' }; + + const allRuleLabelsOrdered: string[] = []; + const allRuleLabelsSet = new Set(); + const allRutResponses: Array<{ rutValue: string; data: any }> = []; + + // Reutilizar cache cuando esté disponible + const cacheMap = new Map(); + for (const c of cachedRutResponses) { + if (c.data) cacheMap.set(c.rutValue, c.data); + } + + const rutsToFetch = uniqueRuts.filter(rut => !cacheMap.has(rut)); + const cachedHits = uniqueRuts.length - rutsToFetch.length; + + if (cachedHits > 0) { + toast.loading( + `Reutilizando ${cachedHits} RUTs del análisis previo${rutsToFetch.length > 0 ? `, consultando ${rutsToFetch.length} faltantes...` : '...'}`, + { id: 'export-progress' } + ); + } + + // 1) Agregar los que ya tenemos en cache + for (const rutValue of uniqueRuts) { + if (cacheMap.has(rutValue)) { + allRutResponses.push({ rutValue, data: cacheMap.get(rutValue) }); + } + } + + // 2) Fetch de los faltantes + for (let i = 0; i < rutsToFetch.length; i++) { + const rutValue = rutsToFetch[i]; + try { + toast.loading(`Consultando RUT ${i + 1}/${rutsToFetch.length}: ${rutValue}`, { id: 'export-progress' }); + const response = await apiClient.get(`/rut/results/rut/${rutValue}`); + allRutResponses.push({ rutValue, data: response.data }); + + if (i < rutsToFetch.length - 1) { + await new Promise(resolve => setTimeout(resolve, 300)); + } + } catch (err) { + console.error(`Error RUT ${rutValue}:`, err); + allRutResponses.push({ rutValue, data: null }); + } + } + + // 3) Construir labels desde TODAS las respuestas + for (const { data: d } of allRutResponses) { + if (!d) continue; + const allRulesData = d?.riskAssessment?.allRules || {}; + for (const catKey of ['complianceRules', 'legalRules', 'capitalHumanoRules', 'financieroTributarioRules']) { + const rules: any[] = allRulesData[catKey] || []; + for (const rule of rules) { + if (!allRuleLabelsSet.has(rule.label)) { + allRuleLabelsSet.add(rule.label); + allRuleLabelsOrdered.push(rule.label); + } + } + } + } + if (!allRuleLabelsSet.has('Causas Penales')) { + allRuleLabelsSet.add('Causas Penales'); + allRuleLabelsOrdered.push('Causas Penales'); + } + if (!allRuleLabelsSet.has('Reputación Pública y Mediática')) { + allRuleLabelsSet.add('Reputación Pública y Mediática'); + allRuleLabelsOrdered.push('Reputación Pública y Mediática'); + } + + toast.dismiss('export-progress'); + toast.loading('Generando Excel...', { id: 'export-generating' }); + + // ====== A PARTIR DE AQUÍ TODO IDÉNTICO A LO QUE YA TENÍAS ====== + const fixedHeaders = [ + 'Razón Social', 'RUT', 'Fecha Evaluación', + 'Resultado Fast Check (color Semáforo)', 'Resultado Fast Check (Nivel de Riesgo)', + '# parámetros riesgo crítico', '# parámetros riesgo alto', '# parámetros sin riesgo', + ]; + + const subCols = ['Impacto', 'Detectado', 'Riesgo', 'Cant. Detalles']; + + const headerRow1: string[] = [...fixedHeaders]; + for (const label of allRuleLabelsOrdered) { + headerRow1.push(label, '', '', ''); + } + headerRow1.push('Resumen de hallazgos', '# hallazgos detectados'); + + const headerRow2: string[] = fixedHeaders.map(() => ''); + for (const _label of allRuleLabelsOrdered) { + headerRow2.push(...subCols); + } + headerRow2.push('', ''); + + const columnTotals: Record = {}; + allRuleLabelsOrdered.forEach(l => { columnTotals[l] = { detected: 0, totalDetails: 0 }; }); + let totalCritical = 0, totalHigh = 0, totalLow = 0; + + const dataRows: any[][] = []; + + for (const { rutValue, data: d } of allRutResponses) { + if (!d) { + const row: any[] = ['Error al consultar', rutValue, '', '', '', 0, 0, 0]; + allRuleLabelsOrdered.forEach(() => { row.push('', '', '', ''); }); + row.push('Error al consultar', ''); + dataRows.push(row); + continue; + } + + const razonSocial = d?.details?.razonSocial || 'N/A'; + const fechaEval = d?.createdAt ? new Date(d.createdAt).toLocaleDateString('es-CL') : 'N/A'; + const allRulesData = d?.riskAssessment?.allRules || {}; + + const ruleMap: Record = {}; + + for (const catKey of ['complianceRules', 'legalRules', 'capitalHumanoRules', 'financieroTributarioRules']) { + const rules: any[] = allRulesData[catKey] || []; + for (const rule of rules) { + let details = rule.details; + if (rule.label === 'Deuda Previsional Presunta' && details?.casos) { + details = details.casos; + } + const detailCount = Array.isArray(details) ? details.length + : (details && typeof details === 'object' && Object.keys(details).length > 0) ? 1 + : 0; + + ruleMap[rule.label] = { + detected: rule.detected || false, + risk: rule.risk || 'bajo', + impacto: rule.impacto || '', + detailCount, + details, + }; + } + } + + const todasCoincidencias = + d?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || + d?.details?.sheriffLogData?.filteredDetails?.compliance?.penal?.coincidencias || + d?.logEntry?.filteredDetails?.compliance?.penal?.coincidencias || []; + const coincidenciasFiltradas = todasCoincidencias.filter((item: any) => { + const postura = item?.postura?.toLowerCase() || ''; + return postura === 'demandado' || postura === 'querellado' || postura === 'denunciado'; + }); + ruleMap['Causas Penales'] = { + detected: coincidenciasFiltradas.length > 0, + risk: coincidenciasFiltradas.length > 0 ? 'critico' : 'bajo', + impacto: 'severo', + detailCount: coincidenciasFiltradas.length, + details: coincidenciasFiltradas, + }; + + const noticiasCoincidencias = + d?.sheriffLogData?.filteredDetails?.compliance?.noticias?.coincidencias || + d?.details?.sheriffLogData?.filteredDetails?.compliance?.noticias?.coincidencias || + d?.logEntry?.filteredDetails?.compliance?.noticias?.coincidencias || []; + ruleMap['Reputación Pública y Mediática'] = { + detected: noticiasCoincidencias.length > 0, + risk: noticiasCoincidencias.length > 0 ? 'alto' : 'bajo', + impacto: 'significativo', + detailCount: noticiasCoincidencias.length, + details: noticiasCoincidencias, + }; + + if (ruleMap['Deuda Previsional Presunta']?.detailCount > 0) { + ruleMap['Deuda Previsional Presunta'].risk = 'alto'; + ruleMap['Deuda Previsional Presunta'].detected = true; + } + + if (ruleMap['Inicio de Actividades']) { + const ia = ruleMap['Inicio de Actividades']; + ia.detected = !ia.detected; + ia.risk = ia.detected ? 'alto' : 'bajo'; + } + + let critical = 0, high = 0, low = 0; + for (const label of allRuleLabelsOrdered) { + const r = ruleMap[label]; + if (!r || r.risk === 'no_disponible') continue; + if (r.risk === 'critico') critical++; + else if (r.risk === 'alto') high++; + else low++; + } + + const semaphore = calculateSemaphore({ critical, high, low }); + totalCritical += critical; + totalHigh += high; + totalLow += low; + + const row: any[] = [ + razonSocial, rutValue, fechaEval, + semaphoreToColor[semaphore] || '', semaphoreToLabel[semaphore] || '', + critical, high, low, + ]; + + for (const label of allRuleLabelsOrdered) { + const r = ruleMap[label]; + if (r) { + const impactoCapitalized = r.impacto ? r.impacto.charAt(0).toUpperCase() + r.impacto.slice(1) : ''; + row.push( + impactoCapitalized, + r.detected ? 'Sí' : 'No', + riskToColor[r.risk] || r.risk, + r.detailCount > 0 ? r.detailCount : '', + ); + + if (r.detected && r.risk !== 'bajo' && r.risk !== 'no_disponible') { + columnTotals[label].detected++; + } + columnTotals[label].totalDetails += r.detailCount; + } else { + row.push('', '', '', ''); + } + } + + const hallazgos: string[] = []; + for (const label of allRuleLabelsOrdered) { + const r = ruleMap[label]; + if (!r || !r.detected || r.risk === 'bajo' || r.risk === 'no_disponible') continue; + + const color = riskToColor[r.risk] || r.risk; + let detail = `${label} (${color})`; + if (r.detailCount > 0) detail += `: ${r.detailCount} hallazgo${r.detailCount > 1 ? 's' : ''}`; + + if (['Multas Laborales', 'Deuda Previsional Publicada', 'Protestos y Morosidades', 'Condenas por Prácticas Antisindicales'].includes(label) && Array.isArray(r.details)) { + let monto = 0; + r.details.forEach((item: any) => { + const m = item?.monto || item?.Monto || item?.montoMulta || item?.['Monto Multa'] || item?.unpaidAmount || 0; + monto += typeof m === 'number' ? m : parseFloat(String(m).replace(/[^0-9.-]/g, '')) || 0; + }); + if (monto > 0) detail += ` | Monto: $${Math.round(monto).toLocaleString('es-CL')}`; + } + + if (['Causas Civiles', 'Causas Laborales', 'Causas de Cobranza Laboral'].includes(label) && Array.isArray(r.details)) { + let vigentes = 0, concluidas = 0; + r.details.forEach((item: any) => { + const estado = String(item?.estado || '').toLowerCase(); + if (estado.includes('vigente')) vigentes++; + else if (estado.includes('concluida') || estado.includes('concluido')) concluidas++; + }); + detail += ` | Vigentes: ${vigentes}, Concluidas: ${concluidas}`; + } + + if (label === 'Causas Penales' && Array.isArray(r.details)) { + const posturas: Record = {}; + r.details.forEach((item: any) => { + const p = (item?.postura || '').toLowerCase().trim(); + if (p) posturas[p] = (posturas[p] || 0) + 1; + }); + const parts = Object.entries(posturas).map(([k, v]) => `${k}: ${v}`); + if (parts.length > 0) detail += ` | ${parts.join(', ')}`; + } + + if (label === 'Deuda Previsional Presunta' && r.detailCount > 0) { + detail += ` | ${r.detailCount} afiliado${r.detailCount > 1 ? 's' : ''}`; + } + + hallazgos.push(detail); + } + + row.push(hallazgos.join('\n'), hallazgos.length > 0 ? hallazgos.length : ''); + dataRows.push(row); + } + + const totalsRow: any[] = [ + 'TOTALES', `${uniqueRuts.length} RUTs`, '', '', '', + totalCritical, totalHigh, totalLow, + ]; + for (const label of allRuleLabelsOrdered) { + const ct = columnTotals[label]; + totalsRow.push( + '', + ct.detected > 0 ? `${ct.detected} RUTs` : '', + '', + ct.totalDetails > 0 ? ct.totalDetails : '', + ); + } + totalsRow.push('', ''); + + const wb = XLSX.utils.book_new(); + const allRows = [headerRow1, headerRow2, ...dataRows, totalsRow]; + const ws = XLSX.utils.aoa_to_sheet(allRows); + + const merges: XLSX.Range[] = []; + let colIdx = fixedHeaders.length; + for (const _label of allRuleLabelsOrdered) { + merges.push({ + s: { r: 0, c: colIdx }, + e: { r: 0, c: colIdx + 3 }, + }); + colIdx += 4; + } + ws['!merges'] = merges; + + const colWidths: { wch: number }[] = [ + { wch: 40 }, { wch: 16 }, { wch: 16 }, + { wch: 14 }, { wch: 14 }, + { wch: 12 }, { wch: 12 }, { wch: 12 }, + ]; + for (const _label of allRuleLabelsOrdered) { + colWidths.push({ wch: 14 }, { wch: 12 }, { wch: 12 }, { wch: 14 }); + } + colWidths.push({ wch: 80 }, { wch: 12 }); + ws['!cols'] = colWidths; + + XLSX.utils.book_append_sheet(wb, ws, 'Reporte'); + + const fileName = `Reporte_FastCheck_${new Date().toISOString().slice(0, 10)}.xlsx`; + XLSX.writeFile(wb, fileName); + + toast.dismiss('export-generating'); + + const conRiesgo = dataRows.filter(r => (r[5] as number) > 0).length; + toast.success(`Excel generado: ${uniqueRuts.length} RUTs, ${conRiesgo} con riesgo crítico`); + } catch (error) { + toast.dismiss('export-progress'); + toast.dismiss('export-generating'); + console.error('Error generating Excel:', error); + toast.error('Error al generar el Excel'); + } finally { + setLoadingAllData(false); + } + }; + // Cerrar dropdowns al hacer clic fuera useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -146,7 +798,20 @@ const Dashboard: React.FC = () => { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); - + useEffect(() => { + try { + const stored = localStorage.getItem(DEEP_ANALYSIS_STORAGE_KEY); + if (!stored) return; + const parsed: DeepAnalysisStoragePayload = JSON.parse(stored); + if (parsed.version === DEEP_ANALYSIS_STORAGE_VERSION && Array.isArray(parsed.data)) { + setCompactRutData(parsed.data); + } else { + localStorage.removeItem(DEEP_ANALYSIS_STORAGE_KEY); + } + } catch (e) { + console.error('Error cargando análisis profundo cacheado:', e); + } + }, []); const applyFiltersToData = (data: IResult[]) => { return data.filter(result => { const resultDate = new Date(result.createdAt); @@ -155,16 +820,96 @@ const Dashboard: React.FC = () => { if (start && resultDate < start) return false; if (end && resultDate > end) return false; - // Filtro de riesgo - si hay niveles seleccionados, debe estar en la lista if (appliedRiskLevels.length > 0 && !appliedRiskLevels.includes(result.riskAssessment?.riskSummary?.semaphore || '')) return false; - // Filtro de tags - si hay tags seleccionados, debe tener al menos uno if (appliedTags.length > 0 && (!result.noteTags || !result.noteTags.some(tag => appliedTags.includes(tag)))) return false; return true; }); }; + const handleDeepAnalysis = async () => { + console.log('=== handleDeepAnalysis START ==='); + setLoadingDeepAnalysis(true); + try { + const rawData = isFilterMode && allDataForFilters.length > 0 + ? allDataForFilters + : (allDataForCharts.length > 0 ? allDataForCharts : results); + const sourceData = applyFiltersToData(rawData); + const allUniqueRuts = Array.from(new Set( + sourceData.map(r => r.rut || r.identifier || '').filter(Boolean) + )); + + if (allUniqueRuts.length === 0) { + toast.error('No hay RUTs para analizar'); + setLoadingDeepAnalysis(false); + return; + } + + let uniqueRuts = allUniqueRuts; + if (allUniqueRuts.length > DEEP_ANALYSIS_MAX_RUTS) { + uniqueRuts = allUniqueRuts.slice(0, DEEP_ANALYSIS_MAX_RUTS); + toast( + `El análisis profundo está limitado a ${DEEP_ANALYSIS_MAX_RUTS} RUTs por restricciones de almacenamiento. Se analizarán los primeros ${DEEP_ANALYSIS_MAX_RUTS} de ${allUniqueRuts.length}. Para todos los RUTs usa "Exportar Excel".`, + { duration: 8000, icon: '⚠️' } + ); + } + + if (uniqueRuts.length === 0) { + toast.error('No hay RUTs para analizar'); + setLoadingDeepAnalysis(false); + return; + } + + const rutResponses: Array<{ rutValue: string; data: any }> = []; + const compactEntries: CompactRutData[] = []; + + for (let i = 0; i < uniqueRuts.length; i++) { + const rutValue = uniqueRuts[i]; + try { + toast.loading(`Analizando RUT ${i + 1}/${uniqueRuts.length}: ${rutValue}`, { id: 'deep-progress' }); + + const response = await apiClient.get(`/rut/results/rut/${rutValue}`); + const d = response.data; + rutResponses.push({ rutValue, data: d }); + compactEntries.push(buildCompactEntryFromResponse(rutValue, d)); + + if (i < uniqueRuts.length - 1) { + await new Promise(resolve => setTimeout(resolve, 300)); + } + } catch (err) { + console.error(`Error RUT ${rutValue}:`, err); + } + } + + setCachedRutResponses(rutResponses); + setCompactRutData(compactEntries); + + // Persistir solo lo compacto + try { + const payload: DeepAnalysisStoragePayload = { + version: DEEP_ANALYSIS_STORAGE_VERSION, + timestamp: Date.now(), + data: compactEntries, + }; + localStorage.setItem(DEEP_ANALYSIS_STORAGE_KEY, JSON.stringify(payload)); + } catch (e) { + console.error('Error guardando análisis en localStorage:', e); + // Si excede la cuota, limpiamos para no dejar estado corrupto + try { localStorage.removeItem(DEEP_ANALYSIS_STORAGE_KEY); } catch { } + } + + toast.dismiss('deep-progress'); + toast.success('Análisis profundo completado'); + } catch (error) { + toast.dismiss('deep-progress'); + console.error('Error deep analysis:', error); + toast.error('Error al generar análisis profundo'); + } finally { + setLoadingDeepAnalysis(false); + } + }; + // Verificar si hay filtros activos (mover antes de su uso) const hasActiveFilters = !!(appliedStartDate || appliedEndDate || appliedRiskLevels.length > 0 || appliedTags.length > 0 || appliedSearch); const hasNonSearchFilters = !!(appliedStartDate || appliedEndDate || appliedRiskLevels.length > 0 || appliedTags.length > 0); @@ -175,12 +920,13 @@ const Dashboard: React.FC = () => { // Para gráficos: siempre usar todos los datos cargados const dataForCharts = allDataForCharts.length > 0 ? allDataForCharts : results; - // Solo aplicar filtros si hay filtros activos (fecha, riesgo, tags) - // En modo normal, los datos ya vienen filtrados por búsqueda desde la API const filteredResults = hasNonSearchFilters ? applyFiltersToData(baseData) : baseData; - const filteredChartsData = applyFiltersToData(dataForCharts); // Siempre aplicar filtros a los gráficos + const filteredChartsData = applyFiltersToData(dataForCharts); + + const dataAfterSelection = selectedRuts.length > 0 + ? filteredChartsData.filter(r => selectedRuts.includes(r.rut || r.identifier || '')) + : filteredChartsData; - // Paginación manual para datos filtrados (solo en modo filtro) const totalFilteredPages = Math.ceil(filteredResults.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; @@ -199,7 +945,7 @@ const Dashboard: React.FC = () => { }); const uniqueTags = Array.from(new Set(baseData.flatMap(result => result.noteTags || []))).sort(); - const dailyEvaluations = filteredChartsData.filter(r => r.createdAt.startsWith(new Date().toISOString().split('T')[0])).length; + const dailyEvaluations = dataAfterSelection.filter(r => r.createdAt.startsWith(new Date().toISOString().split('T')[0])).length; const toggleRiskLevel = (level: string) => { setRiskLevelFilters(prev => @@ -223,7 +969,6 @@ const Dashboard: React.FC = () => { setAppliedSearch(trimmedSearch); setCurrentPage(1); - // Si hay filtros de fecha, riesgo o tags, cargar todos los datos const needsAllData = startDate || endDate || riskLevelFilters.length > 0 || tagFilters.length > 0; if (needsAllData) { @@ -246,7 +991,6 @@ const Dashboard: React.FC = () => { page++; } while (page <= totalP); - // Eliminar RUTs duplicados, dejando solo el más reciente const uniqueData = removeDuplicateRuts(allData); setAllDataForFilters(uniqueData); @@ -258,7 +1002,6 @@ const Dashboard: React.FC = () => { setLoadingAllData(false); } } else { - // Solo búsqueda o sin filtros - usar paginación normal del backend setIsFilterMode(false); setAllDataForFilters([]); if (trimmedSearch) { @@ -314,7 +1057,6 @@ const Dashboard: React.FC = () => { } setCurrentPage(1); - // Verificar si aún hay filtros que requieren todos los datos setTimeout(() => { const stillNeedsAllData = (filterType === 'startDate' ? false : !!appliedStartDate) || (filterType === 'endDate' ? false : !!appliedEndDate) || @@ -358,12 +1100,10 @@ const Dashboard: React.FC = () => { }; const handleRiskChartClick = async (riskLevel: 'green' | 'yellow' | 'orange' | 'red') => { - // Actualizar los filtros de nivel de riesgo setRiskLevelFilters([riskLevel]); setAppliedRiskLevels([riskLevel]); setCurrentPage(1); - // Cargar todos los datos para aplicar el filtro setLoadingAllData(true); setIsFilterMode(true); try { @@ -383,7 +1123,6 @@ const Dashboard: React.FC = () => { page++; } while (page <= totalP); - // Eliminar RUTs duplicados, dejando solo el más reciente const uniqueData = removeDuplicateRuts(allData); setAllDataForFilters(uniqueData); @@ -409,7 +1148,6 @@ const Dashboard: React.FC = () => { page++; } while (page <= totalP); - // Eliminar RUTs duplicados, dejando solo el más reciente const uniqueAllResults = removeDuplicateRuts(allResults); const finalData = applyFiltersToData(uniqueAllResults); @@ -419,7 +1157,6 @@ const Dashboard: React.FC = () => { const margin = 15; let currentY = margin; - // Logo try { const logoImg = new Image(); logoImg.crossOrigin = 'anonymous'; @@ -435,7 +1172,6 @@ const Dashboard: React.FC = () => { pdf.text("Dashboard consultas", margin, currentY); currentY += 10; - // Mostrar filtros aplicados if (hasActiveFilters) { pdf.setFontSize(12); pdf.setFont("helvetica", "bold"); @@ -467,22 +1203,323 @@ const Dashboard: React.FC = () => { currentY += 5; } - currentY += 5; // Espacio extra después de filtros + currentY += 5; } - // Captura de Gráficos const historyEl = document.getElementById('pdf-chart-history'); if (historyEl) { const canvas = await html2canvas(historyEl, { scale: 2, backgroundColor: '#ffffff' }); - const chartWidth = (pageWidth - 2 * margin) * 0.8; // Reducido 20% + const chartWidth = (pageWidth - 2 * margin) * 0.8; const imgH = (canvas.height * chartWidth) / canvas.width; - const xOffset = ((pageWidth - 2 * margin) - chartWidth) / 2 + margin; // Centrar + const xOffset = ((pageWidth - 2 * margin) - chartWidth) / 2 + margin; pdf.addImage(canvas.toDataURL('image/jpeg', 0.8), 'JPEG', xOffset, currentY, chartWidth, imgH); currentY += imgH + 10; } + + if (deepAnalysisData) { + const deepEl = document.getElementById('pdf-deep-analysis'); + if (deepEl) { + const buttons = deepEl.querySelectorAll('button[data-pdf-hide="true"]'); + buttons.forEach(btn => (btn as HTMLElement).style.display = 'none'); + + const expandables = deepEl.querySelectorAll('[data-pdf-expand="true"]'); + const originalMaxHeights: string[] = []; + const originalOverflows: string[] = []; + expandables.forEach((el, i) => { + const htmlEl = el as HTMLElement; + originalMaxHeights[i] = htmlEl.style.maxHeight; + originalOverflows[i] = htmlEl.style.overflow; + htmlEl.style.maxHeight = 'none'; + htmlEl.style.overflow = 'visible'; + }); + + const captureOpts = { + scale: 2, + backgroundColor: '#ffffff', + useCORS: true, + logging: false, + }; + + const chartWidth = pageWidth - 2 * margin; + const fullPageHeight = pageHeight - 2 * margin; + + const PX_TO_MM = 0.22; + + // 1) Capturar parte superior (sin las cards de RUT) → ancho completo + // 1) Capturar parte superior (sin las cards de RUT ni el título de la tabla) → ancho completo + const cards = Array.from(deepEl.querySelectorAll('[data-pdf-section="rut-card"]')) as HTMLElement[]; + const originalCardDisplay: string[] = []; + cards.forEach((card, i) => { + originalCardDisplay[i] = card.style.display; + card.style.display = 'none'; + }); + + // Ocultar también el título "RUTs por Parámetro Seleccionado" para dibujarlo aparte + const tableHeaderEl = deepEl.querySelector('[data-pdf-table-header="true"]') as HTMLElement | null; + const originalTableHeaderDisplay = tableHeaderEl ? tableHeaderEl.style.display : ''; + if (tableHeaderEl) tableHeaderEl.style.display = 'none'; + + // Quitar truncate del bloque superior para que los nombres se vean completos + const topTruncated = deepEl.querySelectorAll('.truncate'); + const topTruncatedOriginals: { el: HTMLElement; original: string }[] = []; + topTruncated.forEach(t => { + const el = t as HTMLElement; + topTruncatedOriginals.push({ el, original: el.style.cssText }); + el.style.overflow = 'visible'; + el.style.textOverflow = 'clip'; + el.style.whiteSpace = 'normal'; + }); + + await new Promise(resolve => setTimeout(resolve, 80)); + const topCanvas = await html2canvas(deepEl, captureOpts); + const topImgH = (topCanvas.height * chartWidth) / topCanvas.width; + + if (currentY + topImgH > pageHeight - margin) { + pdf.addPage(); + currentY = margin; + } + pdf.addImage(topCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, topImgH); + currentY += topImgH + 8; + + // Restaurar truncate del bloque superior + topTruncatedOriginals.forEach(({ el, original }) => { + el.style.cssText = original; + }); + + // Restaurar cards y título ANTES de medir columnas + cards.forEach((card, i) => { + card.style.display = originalCardDisplay[i]; + }); + + if (tableHeaderEl) tableHeaderEl.style.display = originalTableHeaderDisplay; + + // Esperar a que el DOM reflowee antes de medir columnas + await new Promise(resolve => setTimeout(resolve, 100)); + + // 2) Capturar la tabla de cada card: thead + cada fila independientemente + // 2) Capturar la tabla de cada card: thead + cada fila independientemente + type RowCanvas = { canvas: HTMLCanvasElement; cardIndex: number }; + const headerCanvases: (HTMLCanvasElement | null)[] = []; + const rowCanvases: RowCanvas[] = []; + + // Quitar truncate dentro de las cards + const cardTruncatedEls: { el: HTMLElement; original: string }[] = []; + cards.forEach(card => { + const titles = card.querySelectorAll('.truncate'); + titles.forEach(t => { + const el = t as HTMLElement; + cardTruncatedEls.push({ el, original: el.style.cssText }); + el.style.overflow = 'visible'; + el.style.textOverflow = 'clip'; + el.style.whiteSpace = 'normal'; + }); + }); + + for (let cardIdx = 0; cardIdx < cards.length; cardIdx++) { + const card = cards[cardIdx]; + const table = card.querySelector('table') as HTMLTableElement | null; + + if (!table) { + headerCanvases.push(null); + continue; + } + + const cardWidth = card.offsetWidth; + const tableClass = table.className; + + const thead = table.querySelector('thead'); + const headerCells = thead ? Array.from(thead.querySelectorAll('th')) as HTMLTableCellElement[] : []; + let columnWidths = headerCells.map(th => th.getBoundingClientRect().width); + let totalColWidth = columnWidths.reduce((a, b) => a + b, 0); + + if (totalColWidth === 0 && headerCells.length > 0) { + columnWidths = headerCells.map(() => 1); + totalColWidth = headerCells.length; + } + + const buildColgroup = () => { + const cg = document.createElement('colgroup'); + columnWidths.forEach(w => { + const col = document.createElement('col'); + col.style.width = `${(w / totalColWidth) * 100}%`; + cg.appendChild(col); + }); + return cg; + }; + + // Capturar el thead solo + if (thead) { + const headerTable = document.createElement('table'); + headerTable.className = tableClass; + headerTable.style.width = '100%'; + headerTable.style.borderCollapse = 'collapse'; + headerTable.style.tableLayout = 'fixed'; + headerTable.appendChild(buildColgroup()); + headerTable.appendChild(thead.cloneNode(true)); + + const headerWrapper = document.createElement('div'); + headerWrapper.style.cssText = `width: ${cardWidth}px; background: #ffffff; border: 1px solid #e5e7eb; border-bottom: none; border-radius: 8px 8px 0 0; overflow: hidden; position: absolute; left: -99999px; top: 0;`; + headerWrapper.appendChild(headerTable); + document.body.appendChild(headerWrapper); + + await new Promise(r => setTimeout(r, 30)); + const headerCanvas = await html2canvas(headerWrapper, captureOpts); + headerCanvases.push(headerCanvas); + document.body.removeChild(headerWrapper); + } else { + headerCanvases.push(null); + } + + // Capturar cada fila del tbody + const trs = Array.from(table.querySelectorAll('tbody tr')) as HTMLTableRowElement[]; + for (let rIdx = 0; rIdx < trs.length; rIdx++) { + const tr = trs[rIdx]; + + const rowTable = document.createElement('table'); + rowTable.className = tableClass; + rowTable.style.width = '100%'; + rowTable.style.borderCollapse = 'collapse'; + rowTable.style.tableLayout = 'fixed'; + rowTable.appendChild(buildColgroup()); + const tbody = document.createElement('tbody'); + tbody.appendChild(tr.cloneNode(true)); + rowTable.appendChild(tbody); + + const rowWrapper = document.createElement('div'); + const isLast = rIdx === trs.length - 1; + rowWrapper.style.cssText = `width: ${cardWidth}px; background: #ffffff; border-left: 1px solid #e5e7eb; border-right: 1px solid #e5e7eb; ${isLast ? 'border-bottom: 1px solid #e5e7eb; border-radius: 0 0 8px 8px;' : ''} overflow: hidden; position: absolute; left: -99999px; top: 0;`; + rowWrapper.appendChild(rowTable); + document.body.appendChild(rowWrapper); + + await new Promise(r => setTimeout(r, 15)); + const rowCanvas = await html2canvas(rowWrapper, captureOpts); + rowCanvases.push({ canvas: rowCanvas, cardIndex: cardIdx }); + document.body.removeChild(rowWrapper); + } + } + + // Restaurar truncate + cardTruncatedEls.forEach(({ el, original }) => { + el.style.cssText = original; + }); + + // Capturar el título de la tabla por separado para mantenerlo junto a la grilla + let titleCanvas: HTMLCanvasElement | null = null; + let titleImgH = 0; + if (tableHeaderEl) { + await new Promise(r => setTimeout(r, 30)); + titleCanvas = await html2canvas(tableHeaderEl, captureOpts); + titleImgH = (titleCanvas.height * chartWidth) / titleCanvas.width; + } + + // Dibujar en el PDF: título + por cada card, header + filas con salto de página + let titleDrawn = false; + for (let cardIdx = 0; cardIdx < cards.length; cardIdx++) { + const headerCanvas = headerCanvases[cardIdx]; + const rows = rowCanvases.filter(r => r.cardIndex === cardIdx); + if (rows.length === 0) continue; + + const headerImgH = headerCanvas ? (headerCanvas.height * chartWidth) / headerCanvas.width : 0; + const firstRowH = (rows[0].canvas.height * chartWidth) / rows[0].canvas.width; + + const minBlockHeight = (titleDrawn ? 0 : titleImgH + 4) + headerImgH + firstRowH; + if (currentY + minBlockHeight > pageHeight - margin) { + pdf.addPage(); + currentY = margin; + } + + if (!titleDrawn && titleCanvas) { + pdf.addImage(titleCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, titleImgH); + currentY += titleImgH + 4; + titleDrawn = true; + } + + if (headerCanvas) { + pdf.addImage(headerCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, headerImgH); + currentY += headerImgH; + } + + for (const { canvas: rowCanvas } of rows) { + const rowImgH = (rowCanvas.height * chartWidth) / rowCanvas.width; + + if (currentY + rowImgH > pageHeight - margin) { + pdf.addPage(); + currentY = margin; + if (headerCanvas) { + pdf.addImage(headerCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, headerImgH); + currentY += headerImgH; + } + } + + pdf.addImage(rowCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, rowImgH); + currentY += rowImgH; + } + + currentY += 8; + } + + // Restaurar truncate + cardTruncatedEls.forEach(({ el, original }) => { + el.style.cssText = original; + }); + + // Dibujar en el PDF: por cada card, header + filas con salto de página + for (let cardIdx = 0; cardIdx < cards.length; cardIdx++) { + const headerCanvas = headerCanvases[cardIdx]; + const rows = rowCanvases.filter(r => r.cardIndex === cardIdx); + if (rows.length === 0) continue; + + const headerImgH = headerCanvas ? (headerCanvas.height * chartWidth) / headerCanvas.width : 0; + + // Asegurar que el header + al menos 1 fila entren en la página actual + const firstRowH = (rows[0].canvas.height * chartWidth) / rows[0].canvas.width; + if (currentY + headerImgH + firstRowH > pageHeight - margin) { + pdf.addPage(); + currentY = margin; + } + + // Dibujar header + if (headerCanvas) { + pdf.addImage(headerCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, headerImgH); + currentY += headerImgH; + } + + // Dibujar cada fila, redibujando header en cada nueva página + for (const { canvas: rowCanvas } of rows) { + const rowImgH = (rowCanvas.height * chartWidth) / rowCanvas.width; + + if (currentY + rowImgH > pageHeight - margin) { + pdf.addPage(); + currentY = margin; + if (headerCanvas) { + pdf.addImage(headerCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, headerImgH); + currentY += headerImgH; + } + } + + pdf.addImage(rowCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', margin, currentY, chartWidth, rowImgH); + currentY += rowImgH; + } + + currentY += 8; + } + + + + + + expandables.forEach((el, i) => { + const htmlEl = el as HTMLElement; + htmlEl.style.maxHeight = originalMaxHeights[i]; + htmlEl.style.overflow = originalOverflows[i]; + }); + buttons.forEach(btn => (btn as HTMLElement).style.display = ''); + } + } + const smallCharts = ['pdf-chart-risk', 'pdf-chart-daily', 'pdf-meter', 'pdf-tenant']; - const chartW = ((pageWidth - 3 * margin) / 2) * 0.8; // Reducido 20% + const chartW = ((pageWidth - 3 * margin) / 2) * 0.8; let rowMaxH = 0; for (let i = 0; i < smallCharts.length; i++) { @@ -492,14 +1529,13 @@ const Dashboard: React.FC = () => { const imgH = (canvas.height * chartW) / canvas.width; if (currentY + imgH > pageHeight - margin) { pdf.addPage(); currentY = margin + 10; } const baseXPos = (i % 2 === 0) ? margin : margin + ((pageWidth - 3 * margin) / 2) + margin; - const xOffset = baseXPos + (((pageWidth - 3 * margin) / 2) - chartW) / 2; // Centrar cada gráfico + const xOffset = baseXPos + (((pageWidth - 3 * margin) / 2) - chartW) / 2; pdf.addImage(canvas.toDataURL('image/jpeg', 0.8), 'JPEG', xOffset, currentY, chartW, imgH); rowMaxH = Math.max(rowMaxH, imgH); if (i % 2 !== 0 || i === smallCharts.length - 1) { currentY += rowMaxH + 10; rowMaxH = 0; } } } - // Tabla Detalle pdf.addPage(); currentY = margin; pdf.setFontSize(14); @@ -515,14 +1551,13 @@ const Dashboard: React.FC = () => { date: margin + 245 }; - // Header de la tabla con fondo gris pdf.setFontSize(9); pdf.setFont("helvetica", "bold"); - pdf.setFillColor(249, 250, 251); // bg-gray-50 - pdf.setDrawColor(229, 231, 235); // border-gray-200 + pdf.setFillColor(249, 250, 251); + pdf.setDrawColor(229, 231, 235); pdf.rect(margin, currentY - 5, pageWidth - 2 * margin, 8, 'FD'); - pdf.setTextColor(55, 65, 81); // text-gray-700 + pdf.setTextColor(55, 65, 81); pdf.text("RUT", cols.rut, currentY); pdf.text("RAZÓN SOCIAL", cols.name, currentY); pdf.text("TAG", cols.tag, currentY); @@ -532,7 +1567,7 @@ const Dashboard: React.FC = () => { currentY += 8; pdf.setFont("helvetica", "normal"); pdf.setFontSize(8); - pdf.setTextColor(17, 24, 39); // text-gray-900 + pdf.setTextColor(17, 24, 39); let rowIndex = 0; finalData.forEach(item => { @@ -540,7 +1575,6 @@ const Dashboard: React.FC = () => { pdf.addPage(); currentY = margin + 10; - // Repetir header en nueva página pdf.setFont("helvetica", "bold"); pdf.setFontSize(9); pdf.setFillColor(249, 250, 251); @@ -560,57 +1594,50 @@ const Dashboard: React.FC = () => { rowIndex = 0; } - // Fondo alternado para filas (hover effect simulado) if (rowIndex % 2 === 0) { - pdf.setFillColor(255, 255, 255); // bg-white + pdf.setFillColor(255, 255, 255); } else { - pdf.setFillColor(249, 250, 251); // bg-gray-50 + pdf.setFillColor(249, 250, 251); } pdf.rect(margin, currentY - 4, pageWidth - 2 * margin, 6, 'F'); const rut = item.rut || item.identifier || '-'; const razonSocial = item.details?.razonSocial || item.razonSocial || '-'; - // RUT - texto negro normal - pdf.setTextColor(17, 24, 39); // text-gray-900 + pdf.setTextColor(17, 24, 39); pdf.setFont("helvetica", "bold"); pdf.text(rut, cols.rut, currentY); - // Razón Social - texto negro normal pdf.setFont("helvetica", "normal"); pdf.text(razonSocial.substring(0, 60), cols.name, currentY); - // Tags - texto azul - pdf.setTextColor(30, 64, 175); // text-blue-800 + pdf.setTextColor(30, 64, 175); pdf.text((item.noteTags?.join(', ') || '-').substring(0, 30), cols.tag, currentY); - // Nivel de Riesgo con colores específicos const sem = item.riskAssessment?.riskSummary?.semaphore || 'N/A'; const riskLabel = sem === 'red' ? 'CRÍTICO' : sem === 'orange' ? 'ALTO' : sem === 'yellow' ? 'MEDIO' : sem === 'green' ? 'BAJO' : 'N/A'; pdf.setFont("helvetica", "bold"); if (sem === 'red') { - pdf.setTextColor(153, 27, 27); // text-red-800 + pdf.setTextColor(153, 27, 27); } else if (sem === 'orange') { - pdf.setTextColor(154, 52, 18); // text-orange-800 + pdf.setTextColor(154, 52, 18); } else if (sem === 'yellow') { - pdf.setTextColor(133, 77, 14); // text-yellow-800 + pdf.setTextColor(133, 77, 14); } else if (sem === 'green') { - pdf.setTextColor(22, 101, 52); // text-green-800 + pdf.setTextColor(22, 101, 52); } else { - pdf.setTextColor(31, 41, 55); // text-gray-800 + pdf.setTextColor(31, 41, 55); } pdf.text(riskLabel, cols.risk, currentY); - // Fecha - texto gris pdf.setFont("helvetica", "normal"); - pdf.setTextColor(107, 114, 128); // text-gray-500 + pdf.setTextColor(107, 114, 128); pdf.text(new Date(item.createdAt).toLocaleDateString(), cols.date, currentY); currentY += 6; - // Línea divisoria entre filas - pdf.setDrawColor(229, 231, 235); // border-gray-200 + pdf.setDrawColor(229, 231, 235); pdf.setLineWidth(0.1); pdf.line(margin, currentY - 4, pageWidth - margin, currentY - 4); @@ -628,288 +1655,319 @@ const Dashboard: React.FC = () => { }; return ( -
-
- {/* Panel de Filtros */} -
-
- -

Filtros de Búsqueda

-
-
-
- - setStartDate(e.target.value)} className="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white outline-none focus:ring-2 focus:ring-primary-500" /> + <> +
+
+ {/* Panel de Filtros */} +
+
+ +

Filtros de Búsqueda

-
- - setEndDate(e.target.value)} className="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white outline-none focus:ring-2 focus:ring-primary-500" /> -
-
- -
- - {showRiskDropdown && ( -
- {[ - { value: 'red', label: 'Crítico' }, - { value: 'orange', label: 'Alto' }, - { value: 'yellow', label: 'Medio' }, - { value: 'green', label: 'Bajo' } - ].map(risk => ( - - ))} -
- )} +
+
+ + setStartDate(e.target.value)} className="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white outline-none focus:ring-2 focus:ring-primary-500" /> +
+
+ + setEndDate(e.target.value)} className="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white outline-none focus:ring-2 focus:ring-primary-500" />
-
-
-
- - {showTagDropdown && ( -
- {uniqueTags.length > 0 ? ( - uniqueTags.map(tag => ( -
-
- -
- - setSearchFilter(e.target.value)} className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white outline-none" /> -
-
-
-
- - - -
-
- - {/* Filtros Activos */} - {hasActiveFilters && ( -
-
-
-

Filtros Activos:

-
- {appliedStartDate && ( - - Desde: - {new Date(appliedStartDate).toLocaleDateString()} - - - )} - {appliedEndDate && ( - - Hasta: - {new Date(appliedEndDate).toLocaleDateString()} - - - )} - {appliedRiskLevels.map(level => ( - - Riesgo: - {getRiskLevelLabel(level)} - - - ))} - {appliedTags.map(tag => ( - - Tag: - {tag} - - - ))} - {appliedSearch && ( - - Búsqueda: - "{appliedSearch}" - - + ))} +
)}
+
+ +
+ + {showTagDropdown && ( +
+ {uniqueTags.length > 0 ? ( + uniqueTags.map(tag => ( + + )) + ) : ( +
No hay tags disponibles
+ )} +
+ )} +
+
+
+ +
+ + setSearchFilter(e.target.value)} className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white outline-none" /> +
+
+
+
+ +
- )} - {/* Layout Dashboard */} -
-
- {/* Información de registros filtrados */} - {isFilterMode && ( -
-
-
-
-

- Filtros Aplicados +

+
+ +
+
+ +
+ +
+ + {/* Filtros Activos */} + {hasActiveFilters && ( +
+
+
+

Filtros Activos:

+
+ {appliedStartDate && ( + + Desde: + {new Date(appliedStartDate).toLocaleDateString()} + + + )} + {appliedEndDate && ( + + Hasta: + {new Date(appliedEndDate).toLocaleDateString()} + + + )} + {appliedRiskLevels.map(level => ( + + Riesgo: + {getRiskLevelLabel(level)} + + + ))} + {appliedTags.map(tag => ( + + Tag: + {tag} + + + ))} + {appliedSearch && ( + + Búsqueda: + "{appliedSearch}" + + + )} +
+
+ +
+
+ )} + + {/* Deep Analysis Panel */} +
+ +
+ + + + + + + + {/* Layout Dashboard */} +
+
+ {/* Información de registros filtrados */} + {isFilterMode && ( +
+
+
+
+

+ Filtros Aplicados +

+

+ Mostrando {filteredResults.length} de {allDataForFilters.length} registros totales +

+
+
+
+

+ {filteredResults.length}

-

- Mostrando {filteredResults.length} de {allDataForFilters.length} registros totales +

+ resultados

-
-

- {filteredResults.length} -

-

- resultados -

+
+ )} + + +
+
+

Historial de Consultas

+
+ + +
-
- )} - -
-
-

Historial de Consultas

-
- - - -
+
- -
-
-
-
-
-
-
+
+ +
+
+
+
-
+ ); }; -export default Dashboard; +export default Dashboard; \ No newline at end of file