""" 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