Spaces:
Paused
Paused
| """ | |
| Report Service — Orquestador principal de reportes. | |
| Usa orchestrator y registry para ejecutar scrapers y construir reportes. | |
| """ | |
| import asyncio | |
| import logging | |
| import re | |
| import uuid | |
| from datetime import datetime, timezone | |
| from typing import Any | |
| from sqlalchemy import select, insert | |
| from app.database import get_db | |
| from app.config import get_settings | |
| from app.reports.models import SearchHistory, MonitorTask, ReportCache | |
| from app.reports.schemas import ( | |
| ScoreHistorial, ScoreCrediticio, | |
| PersonReport, CompanyReport, Identificacion, Contacto, Domicilio, | |
| DatosFiscales, DatosFinancieros, DatosSocietarios, DatosPatrimoniales, | |
| DatosJudiciales, PublicacionBO, BcraHistorial, ChequeRechazado, | |
| CausaJudicial, Vehiculo, InfraccionTransito, MarcaINPI, Vinculo, | |
| DatosPrevisionales, DatosAcademicos, TituloAcademico, IndicadorRiesgo, | |
| Inmueble, Inhibicion, DatosRegistroCivil, GroupReport, VehicleReport, | |
| PropertyReport, ReportMeta, ActividadFiscal, CompanyIdentificacion, RedSocial, | |
| DatosIGJ, ViasSalud, ContratoEstado, MatriculaProfesional, | |
| DatosBilleterasVirtuales, BilleteraVirtual, TimelineEvent, DeudorAlimentario, Monotributo | |
| ) | |
| from app.cache.redis_client import cache_get, cache_set | |
| from app.scrapers.base import AntiBotBlockedError | |
| from app.utils.scoring import compute_score, compute_company_score, compute_group_score | |
| from app.reports.orchestrator import ScraperOrchestrator | |
| from app.reports.registry import registry, ScraperCategory, ScraperDef | |
| from app.reports.fallback import apply_fallbacks | |
| from app.reports.builders import ( | |
| build_person_report, build_company_report, build_vehicle_report, | |
| build_property_report, build_group_report | |
| ) | |
| SITUACIONES_BCRA = { | |
| 1: "Normal", | |
| 2: "Con seguimiento especial / Riesgo bajo", | |
| 3: "Con problemas / Riesgo medio", | |
| 4: "Con alto riesgo de insolvencia / Riesgo alto", | |
| 5: "Irrecuperable", | |
| 6: "Irrecuperable por disposición técnica", | |
| } | |
| logger = logging.getLogger(__name__) | |
| # Register all scrapers in the registry | |
| def _register_scrapers(): | |
| """Registra todos los scrapers disponibles.""" | |
| from app.scrapers.arca_afip import ArcaAfipScraper | |
| from app.scrapers.bcra import BcraScraper | |
| from app.scrapers.boletin_oficial import BoletinOficialScraper | |
| from app.scrapers.igj import IgjScraper | |
| from app.scrapers.poder_judicial import PoderJudicialScraper | |
| from app.scrapers.dnrpa import DnrpaScraper | |
| from app.scrapers.sinai import SinaiScraper | |
| from app.scrapers.inpi import InpiScraper | |
| from app.scrapers.anses import AnsesScraper | |
| from app.scrapers.redes_sociales import RedesSocialesScraper | |
| from app.scrapers.telefonia import TelefoniaScraper | |
| from app.scrapers.renaper import RenaperScraper | |
| from app.scrapers.boletines_provinciales import BoletinesProvincialesScraper | |
| from app.scrapers.colegios_profesionales import ColegiosProfesionalesScraper | |
| from app.scrapers.ruido import RuidoScraper | |
| from app.scrapers.compras_estatales import ComprasEstatalesScraper | |
| from app.scrapers.padron_electoral import PadronElectoralScraper | |
| from app.scrapers.sgarhu import SgarhuScraper | |
| from app.scrapers.siscop import SiscopScraper | |
| from app.scrapers.arba_automotores import ArbaAutomotoresScraper | |
| from app.scrapers.arba_catastro import ArbaCatastroScraper | |
| from app.scrapers.cnv import CnvScraper | |
| from app.scrapers.uif import UifScraper | |
| from app.scrapers.juba import JubaScraper | |
| from app.scrapers.renaper_facial import RenaperFacialScraper | |
| from app.scrapers.name_search import NameSearchScraper | |
| from app.scrapers.google_images import GoogleImagesScraper | |
| from app.scrapers.infracciones import InfraccionesScraper | |
| from app.scrapers.inhibiciones import InhibicionesScraper | |
| from app.scrapers.poder_judicial_provincial import PoderJudicialProvincialScraper | |
| from app.scrapers.contratar import ContratarScraper | |
| from app.scrapers.deudores_alimentarios import DeudoresAlimentariosScraper | |
| from app.scrapers.carto_arba import CartoArbaScraper | |
| from app.scrapers.archive_org import ArchiveOrgScraper | |
| from app.scrapers.monotributo_historial import MonotributoHistorialScraper | |
| from app.scrapers.registro_conductores import RegistroConductoresScraper | |
| from app.scrapers.timeline_boa import TimelineBoletinScraper | |
| from app.scrapers.billeteras_virtuales import BilleterasVirtualesScraper | |
| from app.scrapers.compras_estatales import ComprasEstatalesScraper | |
| # CORE - Críticos | |
| registry.register(ScraperDef( | |
| name="arca_afip", display_name="ARCA/AFIP", | |
| category=ScraperCategory.CORE, instance=ArcaAfipScraper(), | |
| timeout=60, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="bcra", display_name="BCRA", | |
| category=ScraperCategory.CORE, instance=BcraScraper(), | |
| timeout=30, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="dnrpa", display_name="DNRPA", | |
| category=ScraperCategory.CORE, instance=DnrpaScraper(), | |
| timeout=90, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="poder_judicial", display_name="Poder Judicial", | |
| category=ScraperCategory.CORE, instance=PoderJudicialScraper(), | |
| timeout=120, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="renaper", display_name="RENAPER", | |
| category=ScraperCategory.CORE, instance=RenaperScraper(), | |
| timeout=60, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="igj", display_name="IGJ", | |
| category=ScraperCategory.CORE, instance=IgjScraper(), | |
| timeout=60, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="inpi", display_name="INPI", | |
| category=ScraperCategory.CORE, instance=InpiScraper(), | |
| timeout=60, critical=True | |
| )) | |
| registry.register(ScraperDef( | |
| name="uif", display_name="UIF/PEPs", | |
| category=ScraperCategory.CORE, instance=UifScraper(), | |
| timeout=60, critical=True | |
| )) | |
| # FINANCIAL | |
| registry.register(ScraperDef( | |
| name="arba_automotores", display_name="ARBA Automotores", | |
| category=ScraperCategory.FINANCIAL, instance=ArbaAutomotoresScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="arba_catastro", display_name="ARBA Catastro", | |
| category=ScraperCategory.FINANCIAL, instance=ArbaCatastroScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="boletin_oficial", display_name="Boletín Oficial", | |
| category=ScraperCategory.FINANCIAL, instance=BoletinOficialScraper(), | |
| timeout=30, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="boletines_provinciales", display_name="Boletines Provinciales", | |
| category=ScraperCategory.FINANCIAL, instance=BoletinesProvincialesScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="anses", display_name="ANSES", | |
| category=ScraperCategory.FINANCIAL, instance=AnsesScraper(), | |
| timeout=120, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="cnv", display_name="CNV", | |
| category=ScraperCategory.FINANCIAL, instance=CnvScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="compras_estatales", display_name="COMPR.AR", | |
| category=ScraperCategory.FINANCIAL, instance=ComprasEstatalesScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="monotributo_historial", display_name="Monotributo Historial", | |
| category=ScraperCategory.FINANCIAL, instance=MonotributoHistorialScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="registro_conductores", display_name="Registro Conductores", | |
| category=ScraperCategory.FINANCIAL, instance=RegistroConductoresScraper(), | |
| timeout=60, critical=False | |
| )) | |
| # JUDICIAL | |
| registry.register(ScraperDef( | |
| name="infracciones", display_name="Infracciones", | |
| category=ScraperCategory.JUDICIAL, instance=InfraccionesScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="deudores_alimentarios", display_name="Deudores Alimentarios", | |
| category=ScraperCategory.JUDICIAL, instance=DeudoresAlimentariosScraper(), | |
| timeout=120, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="juba", display_name="JUBA", | |
| category=ScraperCategory.JUDICIAL, instance=JubaScraper(), | |
| timeout=120, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="poder_judicial_provincial", display_name="Poder Judicial Provincial", | |
| category=ScraperCategory.JUDICIAL, instance=PoderJudicialProvincialScraper(), | |
| timeout=120, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="inhibiciones", display_name="Inhibiciones", | |
| category=ScraperCategory.JUDICIAL, instance=InhibicionesScraper(), | |
| timeout=60, critical=False | |
| )) | |
| # PATRIMONIAL | |
| registry.register(ScraperDef( | |
| name="arba_catastro", display_name="ARBA Catastro", | |
| category=ScraperCategory.PATRIMONIAL, instance=ArbaCatastroScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="carto_arba", display_name="CARTO ARBA", | |
| category=ScraperCategory.PATRIMONIAL, instance=CartoArbaScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="sinai", display_name="SINAI", | |
| category=ScraperCategory.PATRIMONIAL, instance=SinaiScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="siscop", display_name="SISCOP", | |
| category=ScraperCategory.PATRIMONIAL, instance=SiscopScraper(), | |
| timeout=60, critical=False | |
| )) | |
| # OSINT | |
| registry.register(ScraperDef( | |
| name="google_images", display_name="Google Images", | |
| category=ScraperCategory.OSINT, instance=GoogleImagesScraper(), | |
| timeout=30, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="redes_sociales", display_name="Redes Sociales", | |
| category=ScraperCategory.OSINT, instance=RedesSocialesScraper(), | |
| timeout=30, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="telefonia", display_name="Teléfono", | |
| category=ScraperCategory.OSINT, instance=TelefoniaScraper(), | |
| timeout=30, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="name_search", display_name="Name Search", | |
| category=ScraperCategory.OSINT, instance=NameSearchScraper(), | |
| timeout=60, critical=False | |
| )) | |
| # OTHER | |
| registry.register(ScraperDef( | |
| name="billeteras_virtuales", display_name="Billeteras Virtuales", | |
| category=ScraperCategory.OTHER, instance=BilleterasVirtualesScraper(), | |
| timeout=30, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="ruido", display_name="RUIDO", | |
| category=ScraperCategory.OTHER, instance=RuidoScraper(), | |
| timeout=120, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="sgarhu", display_name="SGARHU", | |
| category=ScraperCategory.OTHER, instance=SgarhuScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="contratar", display_name="CONTRATAR", | |
| category=ScraperCategory.OTHER, instance=ContratarScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="colegios_profesionales", display_name="Colegios Profesionales", | |
| category=ScraperCategory.OTHER, instance=ColegiosProfesionalesScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="ruido_ssalud", display_name="RUIDO Salud", | |
| category=ScraperCategory.OTHER, instance=SgarhuScraper(), # alias | |
| timeout=120, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="renaper_facial", display_name="RENAPER Facial", | |
| category=ScraperCategory.OTHER, instance=RenaperFacialScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="timeline_boa", display_name="Timeline BO", | |
| category=ScraperCategory.OTHER, instance=TimelineBoletinScraper(), | |
| timeout=30, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="archive_org", display_name="Archive.org", | |
| category=ScraperCategory.OTHER, instance=ArchiveOrgScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="padron_electoral", display_name="Padrón Electoral", | |
| category=ScraperCategory.OTHER, instance=PadronElectoralScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="compras_estatales", display_name="COMPR.AR/Contrataciones", | |
| category=ScraperCategory.OTHER, instance=ComprasEstatalesScraper(), | |
| timeout=60, critical=False | |
| )) | |
| registry.register(ScraperDef( | |
| name="cnv", display_name="CNV", | |
| category=ScraperCategory.OTHER, instance=CnvScraper(), | |
| timeout=60, critical=False | |
| )) | |
| # Initialize registry on import | |
| _register_scrapers() | |
| async def get_person_report( | |
| cuit: str, | |
| skip_redes: bool = False, | |
| force_refresh: bool = False, | |
| ) -> PersonReport: | |
| """Genera reporte completo de persona física.""" | |
| # Orchestrate scrapers | |
| orchestrator = ScraperOrchestrator("persona", cuit, force_refresh) | |
| results = await orchestrator.run() | |
| # Extract data from results | |
| data = {r.name: r.data for r in results.values() if r.data} | |
| # Build report | |
| report = await build_person_report(cuit, data, results) | |
| # Save to cache | |
| await _save_report_cache("persona", cuit, report) | |
| return report | |
| async def get_company_report( | |
| cuit: str, | |
| force_refresh: bool = False, | |
| ) -> CompanyReport: | |
| """Genera reporte completo de empresa.""" | |
| orchestrator = ScraperOrchestrator("empresa", cuit, force_refresh) | |
| results = await orchestrator.run() | |
| data = {r.name: r.data for r in results.values() if r.data} | |
| report = await build_company_report(cuit, data, results) | |
| await _save_report_cache("empresa", cuit, report) | |
| return report | |
| async def get_vehicle_report( | |
| dominio: str, | |
| force_refresh: bool = False, | |
| ) -> VehicleReport: | |
| """Genera reporte de vehículo.""" | |
| orchestrator = ScraperOrchestrator("vehiculo", dominio, force_refresh) | |
| results = await orchestrator.run() | |
| data = {r.name: r.data for r in results.values() if r.data} | |
| report = await build_vehicle_report(dominio, data, results) | |
| await _save_report_cache("vehiculo", dominio, report) | |
| return report | |
| async def get_property_report( | |
| calle: str, numero: str, localidad: str, provincia: str, | |
| force_refresh: bool = False, | |
| ) -> PropertyReport: | |
| """Genera reporte de inmueble.""" | |
| identifier = f"{calle} {numero} {localidad} {provincia}" | |
| orchestrator = ScraperOrchestrator("propiedad", identifier, force_refresh) | |
| results = await orchestrator.run() | |
| data = {r.name: r.data for r in results.values() if r.data} | |
| report = await build_property_report(calle, numero, localidad, provincia, data, results) | |
| await _save_report_cache("propiedad", identifier, report) | |
| return report | |
| async def get_group_report( | |
| cuit: str, | |
| force_refresh: bool = False, | |
| ) -> GroupReport: | |
| """Genera reporte de grupo económico.""" | |
| # Primero persona | |
| person_report = await get_person_report(cuit, force_refresh=force_refresh) | |
| # Luego empresas asociadas (desde IGJ) | |
| igj_data = person_report.patrimonial.datos_igj | |
| empresas_vinculadas = [] | |
| if igj_data and igj_data.socios_directivos: | |
| for socio in igj_data.socios_directivos: | |
| cuil = socio.get("cuil") | |
| if cuil and cuil != cuit: | |
| try: | |
| emp_report = await get_company_report(cuil, force_refresh=force_refresh) | |
| empresas_vinculadas.append(emp_report) | |
| except Exception as e: | |
| logger.warning(f"Failed to get company report for {cuil}: {e}") | |
| # Vehículos | |
| # ... lógica similar | |
| return GroupReport( | |
| persona=person_report, | |
| empresas_vinculadas=empresas_vinculadas, | |
| vehiculos=[], | |
| inmuebles=[], | |
| score_grupo=compute_group_score(person_report, empresas_vinculadas, [], []), | |
| ) | |
| async def _save_report_cache(report_type: str, identifier: str, report: Any) -> None: | |
| """Guarda reporte en caché PostgreSQL.""" | |
| from app.reports.models import ReportCache | |
| import json | |
| async for db in get_db(): | |
| try: | |
| await db.execute( | |
| insert(ReportCache).values( | |
| cache_key=f"{report_type}:{identifier}", | |
| report_type=report_type, | |
| identifier=identifier, | |
| data=json.dumps(report.model_dump() if hasattr(report, 'model_dump') else report), | |
| sources_used=list(report.model_fields.keys()) if hasattr(report, 'model_fields') else [], | |
| expires_at=datetime.now(timezone.utc).replace(hour=23, minute=59, second=59), | |
| hit_count=0, | |
| ) | |
| ) | |
| await db.commit() | |
| except Exception as e: | |
| logger.warning(f"Error saving report cache: {e}") | |
| finally: | |
| break |