Spaces:
Paused
Paused
| from fpdf import FPDF | |
| import datetime | |
| import os | |
| import httpx | |
| import tempfile | |
| class CrowDataPDF(FPDF): | |
| def header(self): | |
| self.set_fill_color(30, 41, 59) | |
| self.rect(0, 0, 210, 32, 'F') | |
| logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png") | |
| if os.path.exists(logo_path): | |
| self.image(logo_path, x=10, y=6, w=40) | |
| self.set_font('helvetica', 'B', 22) | |
| self.set_text_color(255, 255, 255) | |
| self.set_y(6) | |
| self.set_x(55) | |
| self.cell(0, 10, 'CrowData Intelligence', new_x="LMARGIN", new_y="NEXT", align='L') | |
| self.set_font('helvetica', '', 10) | |
| self.set_text_color(203, 213, 225) | |
| self.set_x(55) | |
| self.cell(0, 5, 'Dossier de Inteligencia Consolidado - Confidencial', new_x="LMARGIN", new_y="NEXT", align='L') | |
| self.set_fill_color(16, 185, 129) | |
| self.rect(0, 31, 210, 1, 'F') | |
| self.ln(12) | |
| def footer(self): | |
| self.set_y(-15) | |
| self.set_font('helvetica', 'I', 8) | |
| self.set_text_color(100, 116, 139) | |
| fecha_gen = datetime.datetime.now().strftime("%d/%m/%Y %H:%M") | |
| self.cell(0, 10, f'Pagina {self.page_no()} | Generado por CrowData el {fecha_gen}', align='C') | |
| def clean_txt(val): | |
| if val is None: | |
| return "" | |
| s = str(val) | |
| s = s.replace("\u2013", "-").replace("\u2014", "-").replace("\u201c", '"').replace("\u201d", '"') | |
| s = s.replace("\u2018", "'").replace("\u2019", "'").replace("\u2022", "*") | |
| try: | |
| return s.encode('latin-1', 'replace').decode('latin-1') | |
| except Exception: | |
| return s | |
| def safe_float(val, default=0.0): | |
| """ | |
| Convierte valor a float de forma segura, manejando None, strings y errores. | |
| Usado para prevenir ValueError en conversiones de montos. | |
| """ | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| if val is None: | |
| return default | |
| if isinstance(val, (int, float)): | |
| return float(val) | |
| if isinstance(val, str): | |
| # Limpiar formato de moneda/puntos/comas | |
| clean_val = (val.replace('$', '') | |
| .replace(',', '') | |
| .replace('.', '') | |
| .replace('k', '000') | |
| .replace('K', '000') | |
| .strip()) | |
| try: | |
| return float(clean_val) if clean_val else default | |
| except ValueError: | |
| logger.warning(f"No se pudo convertir '{val}' a float, usando default {default}") | |
| return default | |
| logger.warning(f"Tipo inesperado para safe_float: {type(val)}, valor: {val}, usando default {default}") | |
| return default | |
| def safe_int(val, default=0): | |
| """Convierte valor a int de forma segura""" | |
| try: | |
| return int(safe_float(val, default)) | |
| except (ValueError, TypeError): | |
| return default | |
| def _section_header(pdf, title): | |
| pdf.ln(4) | |
| pdf.set_font('helvetica', 'B', 12) | |
| pdf.set_text_color(30, 41, 59) | |
| pdf.cell(0, 8, clean_txt(title), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_fill_color(226, 232, 240) | |
| pdf.rect(10, pdf.get_y(), 190, 0.3, 'F') | |
| pdf.ln(3) | |
| def _kv_line(pdf, label, value, label_w=50, font_size=9.5): | |
| pdf.set_font('helvetica', 'B', font_size) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(label_w, 6, clean_txt(label), new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', '', font_size) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 6, clean_txt(str(value) if value else "-"), new_x="LMARGIN", new_y="NEXT") | |
| def _item_bullet(pdf, text, font_size=9.5): | |
| pdf.set_x(10) | |
| pdf.set_font('helvetica', '', font_size) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.multi_cell(190, 5.5, clean_txt(text)) | |
| def _empty_notice(pdf, text): | |
| pdf.set_font('helvetica', '', 9.5) | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.cell(0, 6, clean_txt(text), new_x="LMARGIN", new_y="NEXT") | |
| def _add_photo_to_pdf(pdf, foto_url: str, x: float = 150, y: float = 35, max_w: float = 45, max_h: float = 45): | |
| """Descarga y agrega foto de perfil al PDF. Silencioso en caso de error. Timeout reducido a 3s.""" | |
| if not foto_url: | |
| return | |
| try: | |
| with httpx.Client(timeout=3, follow_redirects=True) as client: # Reducido de 10s a 3s | |
| resp = client.get(foto_url, headers={"User-Agent": "Mozilla/5.0"}) | |
| if resp.status_code != 200: | |
| return | |
| content_type = resp.headers.get("content-type", "") | |
| if "image" not in content_type and not foto_url.endswith((".jpg", ".jpeg", ".png", ".webp")): | |
| return | |
| with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp: | |
| tmp.write(resp.content) | |
| tmp_path = tmp.name | |
| try: | |
| pdf.image(tmp_path, x=x, y=y, w=max_w, h=max_h) | |
| finally: | |
| os.unlink(tmp_path) | |
| except Exception: | |
| pass | |
| pdf.ln(2) | |
| def _get_nested(obj, *keys, default=None): | |
| for k in keys: | |
| if obj is None: | |
| return default | |
| if isinstance(obj, dict): | |
| obj = obj.get(k) | |
| else: | |
| return default | |
| return obj if obj is not None else default | |
| def generate_report_pdf(data: dict, report_type: str = "persona") -> bytes: | |
| pdf = CrowDataPDF() | |
| pdf.set_auto_page_break(auto=True, margin=20) | |
| pdf.add_page() | |
| ident = data.get("identificacion", {}) | |
| rc = data.get("registro_civil", {}) | |
| fiscal = data.get("fiscal", {}) | |
| fin = data.get("financiero", {}) | |
| judicial = data.get("judicial", {}) | |
| patrimonial = data.get("patrimonial", {}) | |
| societario = data.get("societario", {}) | |
| prev = data.get("previsional", {}) | |
| salud = data.get("salud", {}) | |
| contacto = data.get("contacto", {}) | |
| meta = data.get("meta", {}) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 1: TITLE & IDENTIFICACION | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| nombre_informe = f"{ident.get('apellido', '')} {ident.get('nombres', '')}".strip() | |
| elif report_type == "vehiculo": | |
| veh = data.get("vehiculo", {}) | |
| nombre_informe = f"VehΓculo {veh.get('dominio', '')} - {veh.get('marca', '')} {veh.get('modelo', '')} ({veh.get('anio', '')})".strip() | |
| elif report_type == "propiedad": | |
| nombre_informe = data.get("direccion", "Propiedad") | |
| elif report_type == "grupo": | |
| target = data.get("target", {}) | |
| target_ident = target.get("identificacion", {}) if target else {} | |
| nombre_informe = f"Grupo EconΓ³mico: {target_ident.get('apellido', '')} {target_ident.get('nombres', '')}".strip() | |
| else: | |
| nombre_informe = ident.get('razon_social', '') or ident.get('cuit', 'Empresa') | |
| pdf.set_font('helvetica', 'B', 16) | |
| pdf.set_text_color(30, 41, 59) | |
| pdf.cell(0, 10, clean_txt(f"INFORME: {nombre_informe}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_fill_color(226, 232, 240) | |
| pdf.rect(10, pdf.get_y(), 190, 0.5, 'F') | |
| pdf.ln(5) | |
| if report_type == "persona": | |
| _kv_line(pdf, "CUIT/CUIL:", ident.get('cuil')) | |
| _kv_line(pdf, "DNI:", ident.get('dni')) | |
| _kv_line(pdf, "Sexo:", ident.get('sexo')) | |
| _kv_line(pdf, "Nacionalidad:", ident.get('nacionalidad')) | |
| fecha_nac = ident.get('fecha_nacimiento') | |
| if fecha_nac: | |
| try: | |
| parts = str(fecha_nac).split('-') | |
| if len(parts) == 3: | |
| fn = datetime.date(int(parts[0]), int(parts[1]), int(parts[2])) | |
| today = datetime.date.today() | |
| edad = today.year - fn.year - ((today.month, today.day) < (fn.month, fn.day)) | |
| fecha_fmt = fn.strftime('%d/%m/%Y') | |
| _kv_line(pdf, "Fecha Nacimiento:", f"{fecha_fmt} ({edad} anios)") | |
| else: | |
| _kv_line(pdf, "Fecha Nacimiento:", str(fecha_nac)) | |
| except Exception: | |
| _kv_line(pdf, "Fecha Nacimiento:", str(fecha_nac)) | |
| fallecido = rc.get("fallecido", False) or bool(ident.get('fecha_defuncion')) | |
| if fallecido: | |
| fecha_def = rc.get("fecha_defuncion") or ident.get('fecha_defuncion') or "Fallecido" | |
| pdf.set_font('helvetica', 'B', 10) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(50, 7, "Estado Vital:", new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', '', 10) | |
| pdf.set_text_color(185, 28, 28) | |
| pdf.cell(0, 7, clean_txt(f"FALLECIDO ({fecha_def})"), new_x="LMARGIN", new_y="NEXT") | |
| lugar_def = rc.get("lugar_defuncion") | |
| if lugar_def: | |
| _kv_line(pdf, "Lugar Defuncion:", str(lugar_def)) | |
| else: | |
| _kv_line(pdf, "Estado Vital:", "Activo / Vivo") | |
| _kv_line(pdf, "Validacion RENAPER:", "Verificado" if ident.get('validado_renaper') else "No verificado") | |
| foto_url = ident.get('foto_perfil') | |
| if foto_url: | |
| _add_photo_to_pdf(pdf, foto_url, x=155, y=40, max_w=40, max_h=40) | |
| pdf.set_font('helvetica', 'I', 7) | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.set_xy(155, 82) | |
| pdf.cell(40, 4, clean_txt(f"Foto: {ident.get('foto_perfil_fuente', 'N/A')}"), align='C') | |
| pdf.set_xy(10, pdf.get_y() + 5) | |
| elif report_type == "vehiculo": | |
| veh = data.get("vehiculo", {}) | |
| _kv_line(pdf, "Dominio:", veh.get('dominio')) | |
| _kv_line(pdf, "Marca / Modelo:", f"{veh.get('marca', '')} {veh.get('modelo', '')}") | |
| _kv_line(pdf, "AΓ±o:", str(veh.get('anio', ''))) | |
| _kv_line(pdf, "Tipo:", veh.get('tipo')) | |
| radicacion_parts = [veh.get('registro'), veh.get('localidad'), veh.get('provincia')] | |
| _kv_line(pdf, "Radicacion:", " β ".join(p for p in radicacion_parts if p)) | |
| _kv_line(pdf, "VTV:", veh.get('validez_vtv') or 'Sin datos') | |
| _kv_line(pdf, "Seguro:", 'SI' if veh.get('tiene_seguro') else 'NO') | |
| _kv_line(pdf, "Estado:", veh.get('estado') or 'Sin datos') | |
| # Titular | |
| titular = data.get("titular") or {} | |
| if titular: | |
| pdf.ln(2) | |
| pdf.set_font('helvetica', 'B', 10) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "TITULAR REGISTRADO:", new_x="LMARGIN", new_y="NEXT") | |
| nombre_titular = f"{titular.get('apellido', '')} {titular.get('nombres', '')}".strip() or 'Protegido' | |
| _kv_line(pdf, "Nombre:", nombre_titular) | |
| _kv_line(pdf, "CUIL:", titular.get('cuil')) | |
| # Prendas | |
| prendas = data.get("prendas", []) | |
| if prendas: | |
| _section_header(pdf, "GRAVAMENES Y PRENDAS") | |
| for pr in prendas: | |
| _item_bullet(pdf, f"Entidad: {pr.get('entidad', '-')} | Fecha: {pr.get('fecha', '-')} | {pr.get('descripcion', 'Prenda registrada')}") | |
| else: | |
| _section_header(pdf, "GRAVAMENES Y PRENDAS") | |
| _empty_notice(pdf, "Sin prendas o gravamenes registrados sobre el vehiculo.") | |
| # Denuncias de robo | |
| denuncias = data.get("denuncias_robo", []) | |
| if denuncias: | |
| _section_header(pdf, "DENUNCIAS DE ROBO") | |
| for den in denuncias: | |
| _item_bullet(pdf, f"Fecha: {den.get('fecha', '-')} | {den.get('descripcion', 'Denuncia registrada')}") | |
| else: | |
| _section_header(pdf, "DENUNCIAS DE ROBO") | |
| _empty_notice(pdf, "Sin denuncias de robo registradas.") | |
| # Infracciones del vehiculo | |
| inf_veh = data.get("infracciones", []) | |
| if inf_veh: | |
| _section_header(pdf, "INFRACCIONES DE TRANSITO") | |
| for inf in inf_veh: | |
| monto_inf = inf.get('monto') or 0 | |
| _item_bullet(pdf, f"Acta: {inf.get('acta', '-')} | Fecha: {inf.get('fecha', '-')} | Motivo: {inf.get('motivo', '-')} | Monto: ${safe_float(monto_inf):,.0f} | {inf.get('jurisdiccion', '')}") | |
| else: | |
| _section_header(pdf, "INFRACCIONES DE TRANSITO") | |
| _empty_notice(pdf, "Sin infracciones de transito registradas.") | |
| # Indicadores de riesgo | |
| riesgos = data.get("riesgo", []) | |
| _section_header(pdf, "INDICADORES DE RIESGO") | |
| if riesgos: | |
| for r in riesgos: | |
| _item_bullet(pdf, f"[{r.get('nivel', 'INFO').upper()}] {r.get('descripcion', '')} - {r.get('hallazgo', '')}") | |
| else: | |
| _empty_notice(pdf, "Sin indicadores de riesgo detectados.") | |
| elif report_type == "propiedad": | |
| _kv_line(pdf, "Direccion:", data.get('direccion')) | |
| cat = data.get("datos_catastrales") or {} | |
| if cat: | |
| _kv_line(pdf, "Partida:", cat.get('partida')) | |
| _kv_line(pdf, "Nomenclatura:", cat.get('nomenclatura')) | |
| _kv_line(pdf, "Partido:", cat.get('partido_nombre')) | |
| _kv_line(pdf, "Tipo Inmueble:", cat.get('tipo_inmueble')) | |
| sup = cat.get('superficie_m2') | |
| if sup: | |
| _kv_line(pdf, "Superficie:", f"{sup:,.0f} m2") | |
| vf = cat.get('valuacion_fiscal') | |
| if vf: | |
| _kv_line(pdf, "Valuacion Fiscal (ARBA):", f"${safe_float(vf):,.0f}") | |
| bi = cat.get('base_imponible') | |
| if bi: | |
| _kv_line(pdf, "Base Imponible:", f"${safe_float(bi):,.0f}") | |
| ve = data.get("valor_estimado") | |
| if ve: | |
| _kv_line(pdf, "Valor Mercado Estimado:", f"${safe_float(ve):,.0f}") | |
| geo = data.get("geolocalizacion") or {} | |
| if geo.get('lat') and geo.get('lng'): | |
| _kv_line(pdf, "Geolocalizacion:", f"Lat: {geo.get('lat')} | Lng: {geo.get('lng')}") | |
| # Titulares | |
| titulares = data.get("titulares_detectados", []) | |
| if titulares: | |
| _section_header(pdf, "PROPIETARIOS / TITULARES DETECTADOS") | |
| for t in titulares: | |
| nombre_t = f"{t.get('apellido', '')} {t.get('nombres', '')}".strip() or 'Titular detectado' | |
| cuil_t = t.get('cuil', '') | |
| cuil_str = f" | CUIL: {cuil_t}" if cuil_t else '' | |
| cruzado = ' [Posible Propietario]' if t.get('cruzado_con_busqueda') else '' | |
| _item_bullet(pdf, f"{nombre_t}{cuil_str}{cruzado}") | |
| # BoletΓn | |
| bo_prop = data.get("historial_boletin", []) | |
| if bo_prop: | |
| _section_header(pdf, "PUBLICACIONES EN BOLETIN OFICIAL") | |
| for pub in bo_prop[:10]: | |
| texto = pub.get('snippet') or pub.get('texto') or pub.get('titulo') or '' | |
| if len(texto) > 200: | |
| texto = texto[:200] + "..." | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 5, clean_txt(f"{pub.get('fecha', 'S/F')} - {pub.get('tipo', pub.get('seccion', 'BO'))}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 8.5) | |
| pdf.multi_cell(190, 4, clean_txt(f" {texto}")) | |
| pdf.ln(2) | |
| # Deudas ARBA | |
| deudas = data.get("deudas_impositivas") or {} | |
| if deudas.get('con_deuda'): | |
| _section_header(pdf, "DEUDAS IMPOSITIVAS (ARBA)") | |
| monto_d = deudas.get('monto_total', 0) | |
| _kv_line(pdf, "Monto Total Adeudado:", f"${safe_float(monto_d):,.0f}") | |
| for per in (deudas.get('periodos_adeudados') or [])[:10]: | |
| if isinstance(per, dict): | |
| _item_bullet(pdf, f"Periodo: {per.get('periodo', '')} | Monto: ${safe_float(per.get('monto', 0)):,.0f}") | |
| # Riesgos | |
| riesgos_prop = data.get("riesgo", []) | |
| _section_header(pdf, "INDICADORES DE RIESGO") | |
| if riesgos_prop: | |
| for r in riesgos_prop: | |
| _item_bullet(pdf, f"[{r.get('nivel', 'INFO').upper()}] {r.get('descripcion', '')} - {r.get('hallazgo', '')}") | |
| else: | |
| _empty_notice(pdf, "Sin indicadores de riesgo detectados para este inmueble.") | |
| else: | |
| _kv_line(pdf, "CUIT Empresa:", ident.get('cuit')) | |
| _kv_line(pdf, "Tipo Societario:", ident.get('tipo_societario')) | |
| _kv_line(pdf, "Fecha Constitucion:", ident.get('fecha_constitucion')) | |
| _kv_line(pdf, "Nombre Fantasia:", ident.get('nombre_fantasia')) | |
| igj = data.get("igj", {}) | |
| if igj and igj.get("numero_inscripcion"): | |
| _kv_line(pdf, "Nro Inscripcion IGJ:", igj.get("numero_inscripcion")) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # GRUPO: RESUMEN DEL GRUPO ECONOMICO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "grupo": | |
| empresas = data.get("empresas_vinculadas", []) | |
| vehiculos = data.get("vehiculos_vinculados", []) | |
| riesgos_con = data.get("riesgo_consolidado", []) | |
| patrimonio = data.get("total_patrimonio_estimado") | |
| _section_header(pdf, "RESUMEN DEL GRUPO ECONOMICO") | |
| _kv_line(pdf, "Empresas integradas:", str(len(empresas))) | |
| _kv_line(pdf, "Vehiculos registrados:", str(len(vehiculos))) | |
| if patrimonio: | |
| _kv_line(pdf, "Patrimonio estimado:", f"${safe_float(patrimonio):,.0f}") | |
| if empresas: | |
| _section_header(pdf, "SOCIEDADES / EMPRESAS VINCULADAS") | |
| for emp in empresas: | |
| emp_id = emp.get("identificacion", {}) if isinstance(emp, dict) else {} | |
| razon = emp_id.get("razon_social", "") if isinstance(emp_id, dict) else "" | |
| cuit_e = emp_id.get("cuit", "") if isinstance(emp_id, dict) else "" | |
| tipo = emp_id.get("tipo_societario", "") if isinstance(emp_id, dict) else "" | |
| score_e = emp.get("score", {}) if isinstance(emp, dict) else {} | |
| nivel_e = score_e.get("nivel", "") if isinstance(score_e, dict) else "" | |
| _item_bullet(pdf, f"{razon} ({cuit_e}) - {tipo} - Score: {nivel_e}") | |
| if vehiculos: | |
| _section_header(pdf, "VEHICULOS DEL GRUPO") | |
| for vh in vehiculos: | |
| vh_data = vh.get("vehiculo", {}) if isinstance(vh, dict) else {} | |
| dominio = vh_data.get("dominio", "") if isinstance(vh_data, dict) else "" | |
| marca = vh_data.get("marca", "") if isinstance(vh_data, dict) else "" | |
| modelo = vh_data.get("modelo", "") if isinstance(vh_data, dict) else "" | |
| anio = vh_data.get("anio", "") if isinstance(vh_data, dict) else "" | |
| _item_bullet(pdf, f"{dominio} - {marca} {modelo} ({anio})") | |
| if riesgos_con: | |
| _section_header(pdf, "RIESGO CONSOLIDADO DEL GRUPO") | |
| for r in riesgos_con: | |
| nivel_r = r.get("nivel", "INFO") if isinstance(r, dict) else "INFO" | |
| desc = r.get("descripcion", "") if isinstance(r, dict) else str(r) | |
| _item_bullet(pdf, f"[{nivel_r.upper()}] {desc}") | |
| pdf.ln(5) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 2: SCORE CREDITICIO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| score_obj = data.get("score") | |
| if score_obj: | |
| _section_header(pdf, "SCORE CREDITICIO CROWDATA") | |
| score_val = score_obj.get("valor", 60) | |
| nivel = score_obj.get("nivel", "Bueno") | |
| if nivel == "Excelente": | |
| r, g, b = 34, 197, 94 | |
| elif nivel == "Bueno": | |
| r, g, b = 59, 130, 246 | |
| elif nivel == "Regular": | |
| r, g, b = 245, 158, 11 | |
| elif nivel == "Malo": | |
| r, g, b = 239, 68, 68 | |
| else: | |
| r, g, b = 185, 28, 28 | |
| pdf.set_font('helvetica', 'B', 10) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(80, 7, "Valor:", new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', 'B', 14) | |
| pdf.set_text_color(r, g, b) | |
| pdf.cell(40, 7, clean_txt(f"{score_val} / 100"), new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', 'B', 10) | |
| pdf.cell(0, 7, clean_txt(f"Nivel: {nivel.upper()}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_fill_color(226, 232, 240) | |
| pdf.rect(10, pdf.get_y() + 1, 190, 4, 'F') | |
| pdf.set_fill_color(r, g, b) | |
| bar_w = max(1, int(190 * (score_val / 100))) | |
| pdf.rect(10, pdf.get_y() + 1, bar_w, 4, 'F') | |
| pdf.ln(7) | |
| historial = score_obj.get("historial", []) | |
| if historial: | |
| pdf.set_font('helvetica', 'B', 8) | |
| pdf.set_text_color(100, 116, 139) | |
| hist_items = [] | |
| for h in historial[-6:]: | |
| hist_items.append(f"{h.get('fecha', '')}: {h.get('valor', 0)}") | |
| pdf.cell(0, 5, clean_txt("Historial (12M): " + " | ".join(hist_items)), new_x="LMARGIN", new_y="NEXT") | |
| factores = score_obj.get("factores", []) | |
| if factores: | |
| pdf.ln(2) | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 5, "Factores del Score:", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 8.5) | |
| for f in factores[:8]: | |
| if isinstance(f, dict): | |
| impacto = f.get("impacto", 0) | |
| desc = f.get('descripcion', f.get('factor', '')) | |
| signo = "+" if impacto >= 0 else "" | |
| color = (22, 101, 52) if impacto >= 0 else (185, 28, 28) | |
| pdf.set_text_color(*color) | |
| pdf.cell(0, 4.5, clean_txt(f" {signo}{impacto}: {desc}"), new_x="LMARGIN", new_y="NEXT") | |
| else: | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.cell(0, 4.5, clean_txt(f" {f}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.ln(3) | |
| else: | |
| _empty_notice(pdf, "Sin factores de score disponibles.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 3: REGISTRO CIVIL (persona) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| estado_civil = rc.get("estado_civil") | |
| conyuge = rc.get("conyuge_nombre") | |
| fecha_mat = rc.get("fecha_matrimonio") | |
| if estado_civil or conyuge or fecha_mat: | |
| _section_header(pdf, "REGISTRO CIVIL") | |
| if estado_civil: | |
| _kv_line(pdf, "Estado Civil:", estado_civil) | |
| if conyuge: | |
| _kv_line(pdf, "Conyuge:", conyuge) | |
| if fecha_mat: | |
| _kv_line(pdf, "Fecha Matrimonio:", fecha_mat) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 4: INDICADORES DE RIESGO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| riesgos = data.get("riesgo", []) | |
| _section_header(pdf, "INDICADORES DE RIESGO DETECTADOS") | |
| if riesgos: | |
| for r in riesgos: | |
| nivel_r = r.get('nivel', 'INFO').upper() | |
| desc_r = r.get('descripcion', '') | |
| hallazgo_r = r.get('hallazgo', '') | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(153, 27, 27) | |
| pdf.multi_cell(190, 5.5, clean_txt(f"[{nivel_r}] {desc_r} ({hallazgo_r})")) | |
| pdf.ln(1) | |
| else: | |
| _empty_notice(pdf, "Sin indicadores de riesgo detectados.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 5: PEP / UIF | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| peps = data.get("peps", []) | |
| _section_header(pdf, "PERSONA EXPUESTA POLITICAMENTE (PEP / UIF)") | |
| if peps: | |
| for p in peps: | |
| cargo = p.get('cargo', 'Funcionario') | |
| jurisdiccion = p.get('jurisdiccion', 'Nacional') | |
| fecha_p = p.get('fecha_presentacion', 'Reciente') | |
| pdf.set_font('helvetica', '', 9.5) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.multi_cell(190, 5.5, clean_txt(f"Cargo: {cargo} | Jurisdiccion: {jurisdiccion} | DDJJ: {fecha_p}")) | |
| pdf.ln(1) | |
| else: | |
| _empty_notice(pdf, "No registra como Persona Expuesta Politicamente (PEP) en los registros oficiales.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 6: DOMICILIOS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| domicilios = contacto.get("domicilios", []) or data.get("historial_domicilios", []) | |
| if report_type == "empresa" and data.get("igj", {}).get("domicilio_legal"): | |
| igj_dom = data["igj"]["domicilio_legal"] | |
| if igj_dom: | |
| domicilios = [igj_dom] + domicilios | |
| _section_header(pdf, "DOMICILIOS REGISTRADOS") | |
| if domicilios: | |
| for d in domicilios: | |
| piso_dpto = "" | |
| if d.get("piso") or d.get("dpto"): | |
| piso_dpto = f" Piso {d.get('piso', '')} Dpto {d.get('dpto', '')}" | |
| full_dom = f"{d.get('calle', '')} {d.get('numero', '')}{piso_dpto}, {d.get('localidad', '')}, {d.get('provincia', '')} (CP: {d.get('cp', '')})" | |
| tipo = f"[{d.get('tipo', 'Otros').upper()}]" | |
| _item_bullet(pdf, f"{tipo} {full_dom}") | |
| else: | |
| _empty_notice(pdf, "Sin domicilios registrados.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 7: MAPA DE UBICACION | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "MAPA DE UBICACION") | |
| if domicilios: | |
| for d in domicilios: | |
| prov = d.get('provincia', '') | |
| loc = d.get('localidad', '') | |
| cp = d.get('cp', '') | |
| calle = d.get('calle', '') | |
| num = d.get('numero', '') | |
| texto = f"{calle} {num}" | |
| if loc: | |
| texto += f" - {loc}" | |
| if prov: | |
| texto += f", {prov}" | |
| if cp: | |
| texto += f" (CP: {cp})" | |
| _item_bullet(pdf, texto) | |
| else: | |
| _empty_notice(pdf, "Sin datos de ubicacion disponibles.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 8: CONTACTO Y REDES (persona) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| telefonos = data.get("telefonos", []) | |
| redes = data.get("redes_sociales", []) | |
| pagina_web = ident.get("pagina_web") | |
| email_contacto = ident.get("email_contacto") | |
| linkedin = ident.get("linkedin") | |
| if telefonos or redes or pagina_web or email_contacto or linkedin: | |
| _section_header(pdf, "CONTACTO Y REDES OSINT") | |
| if pagina_web: | |
| _item_bullet(pdf, f"Web: {pagina_web}") | |
| if email_contacto: | |
| _item_bullet(pdf, f"Email: {email_contacto}") | |
| if linkedin: | |
| _item_bullet(pdf, f"LinkedIn: {linkedin}") | |
| if telefonos: | |
| _item_bullet(pdf, f"Telefonos detectados: {', '.join(str(t) for t in telefonos)}") | |
| for r in redes: | |
| _item_bullet(pdf, f"{r.get('plataforma', 'Social')}: {r.get('usuario', '')} ({r.get('url', '')})") | |
| foto = ident.get("foto_perfil") | |
| if foto: | |
| _item_bullet(pdf, f"Foto de perfil detectada: {foto}") | |
| else: | |
| _section_header(pdf, "CONTACTO Y REDES OSINT") | |
| _empty_notice(pdf, "Sin datos de contacto o redes sociales detectados.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 9: SITUACION FISCAL (AFIP) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "SITUACION FISCAL (AFIP)") | |
| if fiscal: | |
| _kv_line(pdf, "Estado AFIP:", fiscal.get("estado_afip")) | |
| _kv_line(pdf, "Condicion IVA:", fiscal.get("condicion_iva")) | |
| _kv_line(pdf, "Inicio Actividad:", fiscal.get("fecha_inicio_actividad")) | |
| mono = fiscal.get("monotributo") | |
| if isinstance(mono, dict) and mono.get("categoria"): | |
| mono_str = f"Categoria {mono.get('categoria')}" | |
| if mono.get("actividad_principal"): | |
| mono_str += f" - {mono.get('actividad_principal')}" | |
| if mono.get("fecha_alta"): | |
| mono_str += f" (Alta: {mono.get('fecha_alta')})" | |
| _kv_line(pdf, "Monotributo:", mono_str) | |
| actividades = fiscal.get("actividades", []) | |
| if actividades: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Actividades Economicas Declaradas:", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for act in actividades: | |
| principal = "[PRINCIPAL]" if act.get("es_principal") else "" | |
| _item_bullet(pdf, f"CLAE {act.get('codigo_clae', '')} - {act.get('descripcion', '')} {principal}", 9) | |
| else: | |
| _empty_notice(pdf, "Sin datos fiscales disponibles.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 10: SITUACION FINANCIERA (BCRA) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "SITUACION FINANCIERA (BCRA)") | |
| if fin: | |
| sit_val = fin.get("bcra_situacion_actual") | |
| sit_actual = sit_val if sit_val is not None else 1 | |
| labels_sit = {1: "Normal", 2: "Seguimiento especial", 3: "Con problemas", | |
| 4: "Alto riesgo", 5: "Irrecuperable", 6: "Irrecuperable por disp. tecnica"} | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(50, 6, "Situacion Actual BCRA:", new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', '', 9.5) | |
| if sit_actual >= 3: | |
| pdf.set_text_color(185, 28, 28) | |
| else: | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 6, clean_txt(f"{sit_actual} - {labels_sit.get(sit_actual, 'Normal')}"), new_x="LMARGIN", new_y="NEXT") | |
| total_deuda = fin.get("total_deuda_miles") | |
| dias_max = fin.get("dias_atraso_max") | |
| if total_deuda is not None or dias_max is not None: | |
| deuda_str = f"${safe_float(total_deuda):,.0f}k" if total_deuda else "-" | |
| dias_str = f" | Max. dias atraso: {dias_max}" if dias_max else "" | |
| _kv_line(pdf, "Deuda Total:", f"{deuda_str}{dias_str}") | |
| cheques = fin.get("cheques_rechazados", []) | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(50, 6, "Cheques Rechazados:", new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', '', 9.5) | |
| if cheques: | |
| pdf.set_text_color(185, 28, 28) | |
| pdf.cell(0, 6, clean_txt(f"SI - {len(cheques)} cheque(s) rechazado(s)"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 8.5) | |
| pdf.set_text_color(15, 23, 42) | |
| for chq in cheques: | |
| monto_chq = chq.get('monto') or 0 | |
| nro = chq.get('nro_cheque') or '' | |
| nro_str = f" | Nro: {nro}" if nro else "" | |
| estado_multa = chq.get('estado_multa') or '' | |
| multa_str = f" | Multa: {estado_multa}" if estado_multa else "" | |
| en_rev = " (En revisiΓ³n)" if chq.get('en_revision') else "" | |
| proc_jud = " (Judicializado)" if chq.get('proceso_judicial') else "" | |
| estado_str = f"{chq.get('estado') or 'Rechazado'}{en_rev}{proc_jud}" | |
| # NUEVOS CAMPOS AGREGADOS | |
| denom_jur = chq.get('denom_juridica') or '' | |
| denom_str = f" | {denom_jur}" if denom_jur else "" | |
| cta_tipo = " (Cta.Personal)" if chq.get('cta_personal') else " (Cta.Empresarial)" if chq.get('cta_personal') is not None else "" | |
| _item_bullet(pdf, f"Fecha: {chq.get('fecha')} | Banco: {chq.get('banco')} | Monto: ${safe_float(monto_chq):,.2f} | Estado: {estado_str}{nro_str}{multa_str}{denom_str}{cta_tipo}", 8.5) | |
| else: | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 6, "NO registra cheques rechazados en el ultimo anio", new_x="LMARGIN", new_y="NEXT") | |
| historial = fin.get("bcra_historial", []) | |
| if historial: | |
| pdf.ln(2) | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Historial de Entidades y Deudas:", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for h in historial[:12]: | |
| monto_deuda = h.get('monto_deuda') or 0 | |
| denominacion = h.get('denominacion') or '' | |
| dias_atraso = h.get('dias_atraso') | |
| denom_str = f" ({denominacion})" if denominacion else "" | |
| dias_str = f" | Dias atraso: {dias_atraso}" if dias_atraso else "" | |
| sit_jur = h.get('situacion_juridica') or '' | |
| jur_str = f" | Juridica: {sit_jur}" if sit_jur else "" | |
| en_rev = " | En RevisiΓ³n" if h.get('en_revision') else "" | |
| proc_jud = " | Judicializado" if h.get('proceso_judicial') else "" | |
| # NUEVOS CAMPOS AGREGADOS | |
| refin_str = " | Refinanciada" if h.get('refinanciaciones') else "" | |
| recateg_str = " | Recategorizada" if h.get('recategorizacion') else "" | |
| irrec_disp_str = " | Irrec.Disp.TΓ©c." if h.get('irrec_disp_tecnica') else "" | |
| _item_bullet(pdf, f"Periodo: {h.get('periodo')} | Entidad: {h.get('entidad')}{denom_str} | Sit. {h.get('situacion')} | Deuda: ${safe_float(monto_deuda):,.0f}k{dias_str}{jur_str}{refin_str}{recateg_str}{irrec_disp_str}{en_rev}{proc_jud}", 9) | |
| else: | |
| _empty_notice(pdf, "Sin datos financieros BCRA disponibles.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 11: SOCIEDADES, CNV & MATRICULAS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| cnv_registros = data.get("cnv_registros", []) | |
| if societario and societario.get("cnv_registros"): | |
| cnv_registros = cnv_registros + societario.get("cnv_registros") | |
| matriculas = societario.get("matriculas_profesionales", []) if societario else [] | |
| _section_header(pdf, "REGISTROS SOCIETARIOS, CNV Y PROFESIONALES") | |
| has_societario = bool(cnv_registros or matriculas) | |
| if has_societario: | |
| if cnv_registros: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Registros Comision Nacional de Valores (CNV):", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for cnv in cnv_registros: | |
| _item_bullet(pdf, f"{cnv.get('razon_social') or 'CNV'} | Cat: {cnv.get('categoria')} | Matricula: {cnv.get('matricula')} | Estado: {cnv.get('estado')}", 9) | |
| pdf.ln(2) | |
| if matriculas: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Matriculas Profesionales:", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for mat in matriculas: | |
| _item_bullet(pdf, f"Consejo: {mat.get('consejo')} | Matricula: {mat.get('matricula')} | Estado: {mat.get('estado') or 'Activo'}", 9) | |
| else: | |
| _empty_notice(pdf, "Sin registros societarios, CNV ni matriculas profesionales.") | |
| igj_socios = data.get("igj", {}).get("socios_directivos", []) | |
| if report_type == "empresa" and igj_socios: | |
| pdf.ln(2) | |
| pdf.set_font('helvetica', 'B', 12) | |
| pdf.set_text_color(30, 41, 59) | |
| pdf.cell(0, 8, "SOCIOS Y DIRECTIVOS (IGJ)", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_fill_color(226, 232, 240) | |
| pdf.rect(10, pdf.get_y(), 190, 0.3, 'F') | |
| pdf.ln(3) | |
| for s in igj_socios: | |
| _item_bullet(pdf, f"{s.get('nombre')} | CUIL: {s.get('cuil')} | Rol/Cargo: {s.get('rol') or 'Socio'}") | |
| igj_data = data.get("igj", {}) | |
| objeto_social = igj_data.get("objeto_social") | |
| if objeto_social: | |
| _item_bullet(pdf, f"Objeto Social: {str(objeto_social)[:120]}") | |
| balances = igj_data.get("balances", []) | |
| if balances: | |
| for b in balances[:3]: | |
| periodo = b.get("periodo", "") if isinstance(b, dict) else "" | |
| resultado = b.get("resultado", "") if isinstance(b, dict) else "" | |
| _item_bullet(pdf, f"Balance {periodo}: {resultado}") | |
| pagina_web = ident.get("pagina_web") | |
| email_contacto = ident.get("email_contacto") | |
| linkedin = ident.get("linkedin") | |
| if pagina_web or email_contacto or linkedin: | |
| contact_parts = [] | |
| if pagina_web: | |
| contact_parts.append(f"Web: {pagina_web}") | |
| if email_contacto: | |
| contact_parts.append(f"Email: {email_contacto}") | |
| if linkedin: | |
| contact_parts.append(f"LinkedIn: {linkedin}") | |
| _item_bullet(pdf, f"Contacto: {' | '.join(contact_parts)}") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 12: BILLETERAS VIRTUALES / FINTECHS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| bv = data.get("billeteras_virtuales", {}) | |
| if bv and (bv.get("detalle", []) or bv.get("total_deuda_fintech", 0) > 0): | |
| _section_header(pdf, "BILLETERAS VIRTUALES / FINTECHS") | |
| total = bv.get("total_deuda_fintech", 0) | |
| wallets = bv.get("cantidad_wallets_con_deuda", 0) | |
| _kv_line(pdf, "Deuda total fintechs:", f"${total:,.0f}") | |
| _kv_line(pdf, "Billeteras con deuda:", str(wallets)) | |
| fintech_count = bv.get("cantidad_fintech_detectadas", 0) | |
| if fintech_count: | |
| _kv_line(pdf, "Fintechs detectadas:", str(fintech_count)) | |
| pdf.ln(2) | |
| for item in bv.get("detalle", []): | |
| marca = item.get("marca", item.get("entidad_bcra", "?")) | |
| situacion = item.get("situacion", 1) | |
| monto = item.get("monto", 0) or 0 | |
| desc = item.get("situacion_desc", "") | |
| rubro = item.get("rubro", "") | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(180, 83, 9) | |
| pdf.multi_cell(190, 5.5, clean_txt(f"{marca} ({rubro}) - Sit. {situacion}: {desc}")) | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 5, clean_txt(f" Monto: ${monto:,.0f} | Periodo: {item.get('periodo', '?')}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 13: ANTECEDENTES JUDICIALES (PJN + JUBA) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "ANTECEDENTES JUDICIALES (PJN, MEV Y JUBA)") | |
| causas = judicial.get("causas", []) if judicial else [] | |
| if causas: | |
| for c in causas: | |
| exp = c.get('expediente') or 'S/N' | |
| caratula = c.get('caratula_completa') or c.get('caratula') or 'Sin caratula' | |
| fuero = c.get('fuero') or 'Ordinario' | |
| estado = c.get('estado') or 'Tramitacion' | |
| fecha = c.get('fecha') or 'Reciente' | |
| juzgado = c.get('juzgado') or 'Juzgado de turno' | |
| voces = c.get('voces') or '' | |
| magistrados = c.get('magistrados') or '' | |
| tipo_fallo = c.get('tipo_fallo') or '' | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 5, clean_txt(f"Exp: {exp} - {fecha} (Estado: {estado})"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| lineas = [f" Caratula: {caratula}", f" Fuero: {fuero} | Juzgado: {juzgado}"] | |
| if voces: | |
| lineas.append(f" Voces: {voces}") | |
| if magistrados: | |
| lineas.append(f" Magistrados: {magistrados}") | |
| if tipo_fallo: | |
| lineas.append(f" Tipo Fallo: {tipo_fallo}") | |
| pdf.multi_cell(190, 4.5, clean_txt("\n".join(lineas))) | |
| pdf.ln(2.5) | |
| else: | |
| _empty_notice(pdf, "Sin causas judiciales federales, comerciales ni provinciales (JUBA) detectadas.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 14: PATRIMONIO (VEHICULOS E INMUEBLES) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| vehiculos = patrimonial.get("vehiculos", []) if patrimonial else [] | |
| inmuebles = patrimonial.get("inmuebles", []) if patrimonial else [] | |
| if vehiculos or inmuebles: | |
| _section_header(pdf, "REGISTROS PATRIMONIALES (VEHICULOS E INMUEBLES)") | |
| if vehiculos: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Vehiculos Registrados (DNRPA / ARBA Automotores):", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for v in vehiculos: | |
| detalle_v = f"Dominio: {v.get('dominio')} | {v.get('marca', '')} {v.get('modelo', '')} ({v.get('anio', '')}) | Radicacion: {v.get('radicacion', '') or v.get('provincia', '')}" | |
| vtv = v.get('validez_vtv') | |
| seguro = "Seguro OK" if v.get('tiene_seguro') else "Sin Seguro" | |
| estado = v.get('estado') | |
| extras = " | ".join(filter(None, [f"VTV: {vtv}" if vtv else None, seguro, estado])) | |
| if extras: | |
| detalle_v += f"\n {extras}" | |
| pdf.multi_cell(190, 5.5, clean_txt(f"{detalle_v}")) | |
| pdf.ln(2) | |
| if inmuebles: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Bienes Inmuebles Registrados (ARBA Catastro / Boletin):", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for inm in inmuebles: | |
| partida = f"Partida: {inm.get('nro_partida') or 'N/A'}" | |
| matricula = f"Matricula: {inm.get('matricula') or '-'}" | |
| superficie = f"Sup: {inm.get('superficie') or '-'}" | |
| desc = inm.get('descripcion') or 'Inmueble' | |
| prov = inm.get('provincia') or 'Bs.As.' | |
| fuente = inm.get('fuente') or 'ARBA' | |
| val_val = inm.get('valuacion_fiscal') | |
| try: | |
| val_str = f"${int(val_val):,}".replace(",", ".") if val_val else "-" | |
| except Exception: | |
| val_str = "-" | |
| val_fiscal = f"Valuacion: {val_str}" | |
| _item_bullet(pdf, f"[{fuente}] {partida} | {matricula} | {desc} ({prov}) | {superficie} | {val_fiscal}", 9) | |
| deuda_patentes = patrimonial.get("deuda_patentes", []) if patrimonial else [] | |
| if deuda_patentes: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Deuda de Patentes Vehiculares (AGIP):", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for dp in deuda_patentes[:10]: | |
| anio = dp.get("anio", "") if isinstance(dp, dict) else "" | |
| cuota = dp.get("cuota", "") if isinstance(dp, dict) else "" | |
| concepto = dp.get("concepto", "") if isinstance(dp, dict) else "" | |
| importe = dp.get("importe", "") if isinstance(dp, dict) else "" | |
| _item_bullet(pdf, f"{anio} {cuota} | {concepto} | {importe}", 9) | |
| else: | |
| _section_header(pdf, "REGISTROS PATRIMONIALES (VEHICULOS E INMUEBLES)") | |
| _empty_notice(pdf, "Sin registros patrimoniales de vehiculos ni inmuebles.") | |
| pdf.ln(2) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 15: CONTRATOS ESTATALES (COMPR.AR) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "REGISTROS EN CONTRATACIONES DEL ESTADO (COMPR.AR)") | |
| compras = data.get("compras_estatales", []) | |
| if compras: | |
| for comp in compras: | |
| registro = comp.get('registro') or '' | |
| reg_str = f" | Registro: {registro}" if registro else "" | |
| _item_bullet(pdf, f"Proveedor: {comp.get('razon_social') or '-'} | CUIT: {comp.get('cuit_proveedor', '')} | Inscripcion: {comp.get('estado_inscripcion', '')} | Rubro: {comp.get('rubro_principal', '')}{reg_str}") | |
| adjudicaciones = comp.get('adjudicaciones', []) | |
| contratos = comp.get('contratos', []) | |
| if adjudicaciones: | |
| pdf.set_font('helvetica', '', 8) | |
| for adj in adjudicaciones[:5]: | |
| desc = adj.get('descripcion_proceso') or adj.get('objeto') or '-' | |
| monto = adj.get('monto') or adj.get('monto_total') or '' | |
| monto_str = f" | Monto: ${safe_float(monto):,.0f}" if monto else "" | |
| org = adj.get('organismo') or adj.get('entidad') or '' | |
| org_str = f" | Org: {org}" if org else "" | |
| _item_bullet(pdf, f" Adjudicacion: {desc[:80]}{monto_str}{org_str}", 8) | |
| if contratos: | |
| pdf.set_font('helvetica', '', 8) | |
| for ct in contratos[:5]: | |
| desc = ct.get('descripcion_proceso') or ct.get('objeto') or '-' | |
| monto = ct.get('monto') or ct.get('monto_total') or '' | |
| monto_str = f" | Monto: ${safe_float(monto):,.0f}" if monto else "" | |
| fecha_c = ct.get('fecha') or ct.get('fecha_fin') or '' | |
| fecha_str = f" | Fecha: {fecha_c}" if fecha_c else "" | |
| _item_bullet(pdf, f" Contrato: {desc[:80]}{monto_str}{fecha_str}", 8) | |
| pdf.ln(1) | |
| else: | |
| _empty_notice(pdf, "No registra inscripciones ni contratos vigentes como proveedor del Estado en COMPR.AR.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 16: PREVISIONAL & SALUD (ANSES / RUIDO) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| _section_header(pdf, "PREVISIONAL Y OBRA SOCIAL (ANSES / RUIDO / SSSALUD)") | |
| if prev and (prev.get("tiene_aportes") is not None or prev.get("ultimo_empleador") or prev.get("obra_social")): | |
| _kv_line(pdf, "Aportes al dia:", "SI" if prev.get('tiene_aportes') else "NO") | |
| _kv_line(pdf, "Tipo Beneficiario:", prev.get('tipo_beneficiario')) | |
| _kv_line(pdf, "Ultimo Empleador:", prev.get('ultimo_empleador')) | |
| _kv_line(pdf, "Obra Social (ANSES):", prev.get('obra_social')) | |
| fecha_alta_os = prev.get('fecha_alta_obra_social') | |
| if fecha_alta_os: | |
| _kv_line(pdf, "Alta OS:", fecha_alta_os) | |
| estado_padron = prev.get('estado_padron') | |
| if estado_padron: | |
| _kv_line(pdf, "Estado Padron ANSES:", estado_padron) | |
| else: | |
| estado_padron = prev.get('estado_padron') if prev else None | |
| if estado_padron: | |
| _kv_line(pdf, "Estado Padron ANSES:", estado_padron) | |
| else: | |
| _empty_notice(pdf, "Sin datos previsionales activos en ANSES.") | |
| beneficios = prev.get("beneficios_sociales", []) if prev else [] | |
| if beneficios: | |
| _kv_line(pdf, "Beneficios Sociales:", ', '.join(str(b) for b in beneficios[:5])) | |
| jubilaciones = prev.get("jubilaciones_pensiones", []) if prev else [] | |
| if jubilaciones: | |
| _kv_line(pdf, "Jubilaciones/Pensiones:", ', '.join(str(j) for j in jubilaciones[:5])) | |
| proximo_cobro = prev.get("fecha_proximo_cobro") if prev else None | |
| lugar_cobro = prev.get("lugar_cobro") if prev else None | |
| if proximo_cobro or lugar_cobro: | |
| cobro_parts = [] | |
| if proximo_cobro: | |
| cobro_parts.append(f"Proximo cobro: {proximo_cobro}") | |
| if lugar_cobro: | |
| cobro_parts.append(f"Lugar: {lugar_cobro}") | |
| _kv_line(pdf, "Cobro:", ' | '.join(cobro_parts)) | |
| if salud and (salud.get("cobertura_activa") or (isinstance(salud.get("detalles"), dict) and salud["detalles"].get("nombre_obra_social"))): | |
| obrasocial = salud.get("detalles", {}).get("nombre_obra_social") or "Obra Social Activa" | |
| _kv_line(pdf, "Cobertura Medica SSSalud:", f"SI | {obrasocial}") | |
| else: | |
| _kv_line(pdf, "Cobertura Medica SSSalud:", "Sin cobertura de salud activa.") | |
| historial_coberturas = _get_nested(salud, "detalles", "historial_coberturas", default=[]) | |
| if historial_coberturas: | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, "Historial de Coberturas:", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| for hc in historial_coberturas: | |
| if isinstance(hc, dict): | |
| fecha_h = hc.get('fecha', '') | |
| nombre_h = hc.get('nombre_obra_social', '') or hc.get('nombre', '') | |
| estado_h = hc.get('estado', '') | |
| parts = [p for p in [fecha_h, nombre_h, estado_h] if p] | |
| _item_bullet(pdf, " | ".join(parts), 9) | |
| else: | |
| _item_bullet(pdf, str(hc), 9) | |
| seccion_electoral = ident.get("seccion_electoral") | |
| lugar_votacion = ident.get("lugar_votacion") | |
| mesa_votacion = ident.get("mesa_votacion") | |
| if seccion_electoral or lugar_votacion or mesa_votacion: | |
| pdf.ln(2) | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 5, "PADRON ELECTORAL:", new_x="LMARGIN", new_y="NEXT") | |
| _kv_line(pdf, "Seccion:", seccion_electoral or "-") | |
| _kv_line(pdf, "Lugar de votacion:", lugar_votacion or "-") | |
| _kv_line(pdf, "Mesa:", mesa_votacion or "-") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 17: DEUDORES ALIMENTARIOS (siempre mostrar) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "DEUDORES ALIMENTARIOS MOROSOS (DAM)") | |
| deudores = data.get("deudores_alimentarios") | |
| if deudores and isinstance(deudores, dict): | |
| resultado = deudores.get("resultado") or "" | |
| if resultado and resultado != "NO_REGISTRADO": | |
| nombre_dam = deudores.get("nombre_completo") or "-" | |
| dni_dam = deudores.get("dni") or "-" | |
| _kv_line(pdf, "Nombre:", nombre_dam) | |
| _kv_line(pdf, "DNI:", dni_dam) | |
| _kv_line(pdf, "Resultado:", resultado) | |
| else: | |
| _empty_notice(pdf, "Resultado: NO_REGISTRADO - No se registra como deudor alimentario moroso.") | |
| else: | |
| _empty_notice(pdf, "Sin datos de deudores alimentarios morosos.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 18: BOLETIN OFICIAL | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _section_header(pdf, "PUBLICACIONES EN BOLETINES OFICIALES") | |
| boletines = data.get("boletin_oficial", []) | |
| if boletines: | |
| for pub in boletines[:15]: | |
| fecha = pub.get('fecha') or 'S/F' | |
| secc = pub.get('seccion') or 'BORA' | |
| tipo = pub.get('tipo') or '' | |
| texto = pub.get('texto') or '' | |
| if len(texto) > 250: | |
| texto = texto[:250] + "..." | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| header = f"{fecha} - Secc: {secc}" | |
| if tipo: | |
| header += f" - {tipo}" | |
| pdf.cell(0, 5, clean_txt(header), new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font('helvetica', '', 8.5) | |
| pdf.multi_cell(190, 4, clean_txt(f" {texto}")) | |
| pdf.ln(2) | |
| else: | |
| _empty_notice(pdf, "Sin publicaciones en Boletin Oficial Nacional ni Provinciales detectadas.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 19: INFRACCIONES DE TRANSITO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| infracciones = data.get("infracciones_transito", []) | |
| if report_type == "persona": | |
| _section_header(pdf, "INFRACCIONES DE TRANSITO (SINAI / ANSV)") | |
| if infracciones: | |
| for inf in infracciones: | |
| monto_inf = inf.get('monto') or 0 | |
| dominio = inf.get('dominio') or '' | |
| dom_str = f" | Dominio: {dominio}" if dominio else "" | |
| nro_causa = inf.get('nro_causa') or '' | |
| causa_str = f" | Causa: {nro_causa}" if nro_causa else "" | |
| venc = inf.get('vencimiento') or '' | |
| venc_str = f" | Vence: {venc}" if venc else "" | |
| _item_bullet(pdf, f"Acta: {inf.get('acta') or 'S/N'} | Fecha: {inf.get('fecha', '-')} | " | |
| f"Motivo: {inf.get('motivo', '-')} | Monto: ${safe_float(monto_inf):,.0f} | " | |
| f"Estado: {inf.get('estado', '-')} | Jurisdiccion: {inf.get('jurisdiccion', '-')}{dom_str}{causa_str}{venc_str}") | |
| else: | |
| _empty_notice(pdf, "Sin infracciones de transito registradas en ANSV / SINAI.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 20: MARCAS E INPI | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| marcas = data.get("marcas_inpi", []) | |
| _section_header(pdf, "MARCAS Y PATENTES REGISTRADAS (INPI)") | |
| if marcas: | |
| for m in marcas: | |
| acta = m.get('acta') or '' | |
| acta_str = f" | Acta: {acta}" if acta else "" | |
| titulares = m.get('titulares') or '' | |
| tit_str = f" | Titulares: {titulares[:50]}" if titulares else "" | |
| tipo_marca = m.get('tipo_marca') or '' | |
| tipo_str = f" | Tipo: {tipo_marca}" if tipo_marca else "" | |
| nro_res = m.get('numero_resolucion') or '' | |
| res_str = f" | Resol.: {nro_res}" if nro_res else "" | |
| _item_bullet(pdf, f"{m.get('denominacion', '-')} | Clase: {m.get('clase', '-')} | " | |
| f"Estado: {m.get('estado', '-')} | Vencimiento: {m.get('fecha_vencimiento', '-')}{acta_str}{tipo_str}{res_str}{tit_str}") | |
| else: | |
| _empty_notice(pdf, "Sin marcas o patentes registradas en INPI.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 21: INHIBICIONES Y EMBARGOS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| inhibiciones = ( | |
| patrimonial.get("inhibiciones", []) or | |
| (judicial.get("inhibiciones_embargos", []) if judicial else []) or | |
| [] | |
| ) | |
| _section_header(pdf, "INHIBICIONES Y EMBARGOS") | |
| if inhibiciones: | |
| for inh in inhibiciones: | |
| if isinstance(inh, dict): | |
| tipo = inh.get('tipo', 'INHIBICION') | |
| fecha = inh.get('fecha', '') | |
| organismo = inh.get('organismo', 'Juzgado') | |
| descripcion = inh.get('descripcion', '') | |
| else: | |
| tipo = getattr(inh, 'tipo', 'INHIBICION') | |
| fecha = getattr(inh, 'fecha', '') | |
| organismo = getattr(inh, 'organismo', 'Juzgado') | |
| descripcion = getattr(inh, 'descripcion', '') | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(153, 27, 27) | |
| pdf.multi_cell(190, 5, clean_txt(f"[{str(tipo).upper()}] - {fecha or 'Fecha no disponible'} - {organismo}")) | |
| if descripcion: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.multi_cell(190, 5, clean_txt(f" {descripcion}")) | |
| pdf.ln(2) | |
| else: | |
| _empty_notice(pdf, "Sin inhibiciones ni embargos activos detectados.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 21b: ANTECEDENTES PENALES | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| ant_penales = data.get("antecedentes_penales", []) | |
| _section_header(pdf, "ANTECEDENTES PENALES") | |
| if ant_penales: | |
| for ap in ant_penales: | |
| tipo = ap.get('tipo', 'Antecedente') if isinstance(ap, dict) else 'Antecedente' | |
| fecha = ap.get('fecha', '') if isinstance(ap, dict) else '' | |
| delito = ap.get('delito', '') if isinstance(ap, dict) else '' | |
| estado = ap.get('estado', '') if isinstance(ap, dict) else '' | |
| desc = ap.get('descripcion', ap.get('detalle', '')) if isinstance(ap, dict) else '' | |
| organismo = ap.get('organismo', '') if isinstance(ap, dict) else '' | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(153, 27, 27) | |
| pdf.multi_cell(190, 5, clean_txt(f"[{str(tipo).upper()}] - {fecha or 'Sin fecha'} - {organismo or ''}")) | |
| if delito: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 5, clean_txt(f" Delito: {delito}"), new_x="LMARGIN", new_y="NEXT") | |
| if desc: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.multi_cell(190, 5, clean_txt(f" {desc}")) | |
| if estado: | |
| pdf.set_font('helvetica', 'I', 8.5) | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.cell(0, 5, clean_txt(f" Estado: {estado}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(2) | |
| else: | |
| _empty_notice(pdf, "Sin antecedentes penales detectados en los registros consultados.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 21c: CONCURSOS Y QUIEBRAS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| concursos = (societario.get("concursos", []) if societario else []) | |
| quiebras = (societario.get("quiebras", []) if societario else []) | |
| all_cq = concursos + quiebras | |
| _section_header(pdf, "CONCURSOS Y QUIEBRAS") | |
| if all_cq: | |
| for cq in all_cq: | |
| if isinstance(cq, dict): | |
| tipo = cq.get('tipo', 'Concurso') | |
| fecha = cq.get('fecha', '') | |
| empresa = cq.get('empresa', '') | |
| cuit_emp = cq.get('cuit_empresa', '') | |
| desc = cq.get('descripcion', cq.get('detalle', '')) | |
| else: | |
| tipo = getattr(cq, 'tipo', 'Concurso') | |
| fecha = getattr(cq, 'fecha', '') | |
| empresa = getattr(cq, 'empresa', '') | |
| cuit_emp = getattr(cq, 'cuit_empresa', '') | |
| desc = getattr(cq, 'descripcion', getattr(cq, 'detalle', '')) | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(146, 64, 14) | |
| pdf.multi_cell(190, 5, clean_txt(f"[{str(tipo).upper()}] - {fecha or 'Sin fecha'}")) | |
| if empresa: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| emp_str = f" Empresa: {empresa}" | |
| if cuit_emp: | |
| emp_str += f" (CUIT: {cuit_emp})" | |
| pdf.cell(0, 5, clean_txt(emp_str), new_x="LMARGIN", new_y="NEXT") | |
| if desc: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.multi_cell(190, 5, clean_txt(f" {desc}")) | |
| pdf.ln(2) | |
| else: | |
| _empty_notice(pdf, "Sin concursos ni quiebras registrados.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 21d: PARTICIPACIONES SOCIETARIAS (IGJ) - persona | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| part = societario.get("participaciones", []) if societario else [] | |
| if not part: | |
| part = data.get("participaciones", []) | |
| _section_header(pdf, "PARTICIPACIONES SOCIETARIAS (IGJ)") | |
| if part: | |
| for p in part: | |
| if isinstance(p, dict): | |
| empresa_p = p.get('empresa', '') | |
| rol = p.get('rol', 'Socio') | |
| cuit_p = p.get('cuit', '') | |
| porcentaje = p.get('porcentaje', '') | |
| fecha_ins = p.get('fecha_inscripcion', '') | |
| detalles = p.get('detalles', p.get('descripcion', '')) | |
| else: | |
| empresa_p = getattr(p, 'empresa', '') | |
| rol = getattr(p, 'rol', 'Socio') | |
| cuit_p = getattr(p, 'cuit', '') | |
| porcentaje = getattr(p, 'porcentaje', '') | |
| fecha_ins = getattr(p, 'fecha_inscripcion', '') | |
| detalles = getattr(p, 'detalles', getattr(p, 'descripcion', '')) | |
| pdf.set_font('helvetica', 'B', 9.5) | |
| pdf.set_text_color(30, 64, 175) | |
| parts = [f"[{str(rol).upper()}]"] | |
| if empresa_p: | |
| parts.append(empresa_p) | |
| if cuit_p: | |
| parts.append(f"CUIT: {cuit_p}") | |
| pdf.multi_cell(190, 5, clean_txt(" | ".join(parts))) | |
| if porcentaje: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 5, clean_txt(f" Participacion: {porcentaje}"), new_x="LMARGIN", new_y="NEXT") | |
| if fecha_ins: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(0, 5, clean_txt(f" Inscripto: {fecha_ins}"), new_x="LMARGIN", new_y="NEXT") | |
| if detalles: | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.multi_cell(190, 5, clean_txt(f" {detalles}")) | |
| pdf.ln(2) | |
| else: | |
| _empty_notice(pdf, "Sin participaciones societarias detectadas en IGJ.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 22: MAPA DE RELACIONES | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| vinculos = data.get("vinculos", []) if report_type == "persona" else [] | |
| _section_header(pdf, "MAPA DE RELACIONES") | |
| if vinculos: | |
| pdf.set_font('helvetica', 'B', 8.5) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(65, 5, "Nombre", new_x="RIGHT", new_y="TOP") | |
| pdf.cell(45, 5, "CUIT/CUIL", new_x="RIGHT", new_y="TOP") | |
| pdf.cell(35, 5, "Tipo de Vinculo", new_x="RIGHT", new_y="TOP") | |
| pdf.cell(0, 5, "Detalle", new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_fill_color(226, 232, 240) | |
| pdf.rect(10, pdf.get_y(), 190, 0.3, 'F') | |
| pdf.ln(2) | |
| pdf.set_font('helvetica', '', 8.5) | |
| pdf.set_text_color(15, 23, 42) | |
| for v in vinculos[:10]: | |
| if isinstance(v, dict): | |
| nombre_v = v.get('nombre', '') | |
| cuit_v = v.get('cuit', '') | |
| tipo_v = v.get('tipo', '') | |
| detalle_v = v.get('detalle', '') | |
| else: | |
| nombre_v = getattr(v, 'nombre', '') | |
| cuit_v = getattr(v, 'cuit', '') | |
| tipo_v = getattr(v, 'tipo', '') | |
| detalle_v = getattr(v, 'detalle', '') | |
| pdf.cell(65, 5, clean_txt(str(nombre_v)[:30]), new_x="RIGHT", new_y="TOP") | |
| pdf.cell(45, 5, clean_txt(str(cuit_v)), new_x="RIGHT", new_y="TOP") | |
| pdf.cell(35, 5, clean_txt(str(tipo_v)[:18]), new_x="RIGHT", new_y="TOP") | |
| pdf.cell(0, 5, clean_txt(str(detalle_v)[:30]), new_x="LMARGIN", new_y="NEXT") | |
| else: | |
| _empty_notice(pdf, "Sin vinculos detectados.") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 23: FORMACION ACADEMICA (persona) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if report_type == "persona": | |
| academico = data.get("academico", {}) | |
| titulos = academico.get("titulos", []) if academico else [] | |
| certificaciones = academico.get("certificaciones", []) if academico else [] | |
| if titulos or certificaciones: | |
| _section_header(pdf, "FORMACION ACADEMICA") | |
| for t in titulos: | |
| titulo = t.get('titulo', '') if isinstance(t, dict) else getattr(t, 'titulo', '') | |
| inst = t.get('institucion', '') if isinstance(t, dict) else getattr(t, 'institucion', '') | |
| anio = t.get('anio_graduacion', '') if isinstance(t, dict) else getattr(t, 'anio_graduacion', '') | |
| nivel = t.get('nivel', 'Grado') if isinstance(t, dict) else getattr(t, 'nivel', 'Grado') | |
| _item_bullet(pdf, f"[{nivel}] {titulo} | {inst or '-'} | Graduacion: {anio or '-'}") | |
| for c in certificaciones: | |
| nombre = c.get('nombre', '') if isinstance(c, dict) else str(c) | |
| _item_bullet(pdf, f"Certificacion: {nombre}") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 24: LINEA DE TIEMPO | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| timeline = data.get("timeline", []) | |
| if timeline: | |
| _section_header(pdf, "LINEA DE TIEMPO") | |
| cat_colors = { | |
| "fiscal": (34, 197, 94), "financiero": (59, 130, 246), | |
| "judicial": (239, 68, 68), "previsional": (245, 158, 11), | |
| "patrimonial": (168, 85, 247), "general": (100, 116, 139), | |
| } | |
| for ev in timeline[:25]: | |
| fecha = ev.get("fecha", "") | |
| titulo = ev.get("titulo", "") | |
| desc = ev.get("descripcion", "") | |
| cat = ev.get("categoria", "general") | |
| r, g, b = cat_colors.get(cat, (100, 116, 139)) | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(r, g, b) | |
| pdf.cell(0, 5, clean_txt(f"[{fecha}] {titulo}"), new_x="LMARGIN", new_y="NEXT") | |
| if desc: | |
| pdf.set_font('helvetica', '', 8) | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.cell(0, 4, clean_txt(f" {desc[:160]}"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(1.5) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 25: HISTORIAL WEB (ARCHIVE.ORG) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| web_hist_obj = data.get("web_historial") | |
| if web_hist_obj: | |
| _section_header(pdf, "HISTORIAL WEB (ARCHIVE.ORG)") | |
| wh_url = web_hist_obj.get("url_consultada", "") | |
| wh_total = web_hist_obj.get("total_snapshots", 0) | |
| wh_snap = web_hist_obj.get("snapshot_mas_cercano") or {} | |
| wh_historial = web_hist_obj.get("historial", []) | |
| if wh_url: | |
| pdf.set_font('helvetica', 'B', 9) | |
| pdf.set_text_color(59, 130, 246) | |
| url_short = wh_url[:70] + "..." if len(wh_url) > 70 else wh_url | |
| pdf.cell(0, 5, clean_txt(f"URL: {url_short}"), new_x="LMARGIN", new_y="NEXT") | |
| if wh_total: | |
| snap_date = wh_snap.get("timestamp", "") if isinstance(wh_snap, dict) else "" | |
| _kv_line(pdf, "Total capturas:", str(wh_total)) | |
| if snap_date: | |
| _kv_line(pdf, "Ultima captura:", snap_date) | |
| pdf.ln(2) | |
| for wh in wh_historial[:10]: | |
| ts = wh.get("timestamp", "") or wh.get("fecha", "") | |
| status = wh.get("status", "") | |
| tipo = wh.get("tipo", "") | |
| url_w = wh.get("url_wayback", "") | |
| detail = f" {ts} | {tipo} | {status}" | |
| if url_w: | |
| detail += f" | {url_w[:60]}" | |
| pdf.set_font('helvetica', '', 8) | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.cell(0, 4, clean_txt(detail), new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(3) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # SECTION 26: BITACORA DE FUENTES CONSULTADAS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| sources = meta.get("sources", []) if meta else [] | |
| empty_sources = meta.get("empty_sources", []) if meta else [] | |
| generated_at = meta.get("generated_at", "") if meta else "" | |
| pdf.add_page() | |
| _section_header(pdf, "BITACORA DE FUENTES CONSULTADAS") | |
| n_ok = len(sources) | |
| n_fail = len(meta.get("failures", []) if meta else []) | |
| n_empty = len(empty_sources) | |
| pdf.set_font('helvetica', '', 9) | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, clean_txt(f"Resumen: {n_ok} fuentes con datos | {n_fail} fallidas | {n_empty} sin datos para este CUIT"), new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(3) | |
| all_sources = [ | |
| ("ARCA / AFIP", "Situacion fiscal, IVA, Monotributo, Actividades"), | |
| ("BCRA", "Situacion crediticia, historial, cheques rechazados"), | |
| ("Boletin Oficial", "Publicaciones oficiales nacionales y edictos"), | |
| ("Timeline BORA", "Historial de sociedades del Boletin Oficial"), | |
| ("Boletines Provinciales", "Boletines de 24 jurisdicciones provinciales"), | |
| ("IGJ", "Sociedades, directivos y sede social registrada"), | |
| ("Poder Judicial (PJN)", "Causas judiciales federales y comerciales"), | |
| ("JUBA (SCBA)", "Causas en Justicia de la Provincia de Buenos Aires"), | |
| ("Poder Judicial Provincial", "Causas judiciales provinciales"), | |
| ("DNRPA", "Vehiculos registrados a nombre de la persona"), | |
| ("SINAI / ANSV", "Infracciones de transito nacionales y PBA"), | |
| ("INPI", "Marcas y patentes comerciales registradas"), | |
| ("ANSES", "Aportes previsionales, obra social y beneficios"), | |
| ("Redes Sociales OSINT", "Perfiles publicos en LinkedIn, Google, Instagram, etc."), | |
| ("Telefonia OSINT", "Telefonos vinculados a nombre o CUIT"), | |
| ("RENAPER", "Validacion de identidad y datos del DNI"), | |
| ("RENAPER Facial", "Estado biometrico del documento nacional"), | |
| ("Colegios Profesionales", "Matriculas habilitadas en consejos profesionales"), | |
| ("SSSalud / RUIDO", "Cobertura de salud activa y obra social"), | |
| ("COMPR.AR", "Inscripcion como proveedor del Estado Nacional"), | |
| ("Padron Electoral", "Datos del padron electoral y domicilio electoral"), | |
| ("SGARHU / SIU", "Titulos universitarios oficiales registrados"), | |
| ("SISCOP / Reg. Civil", "Deteccion de fallecimiento y estado del DNI"), | |
| ("ARBA Automotores", "Deuda de patente de vehiculos en ARBA"), | |
| ("ARBA Catastro / AGIP", "Inmuebles e impositivas en PBA y CABA"), | |
| ("CNV", "Registro de agentes y fondos en Comision de Valores"), | |
| ("UIF / PEPs", "Personas Expuestas Politicamente - lista UIF"), | |
| ("Name Search OSINT", "Busqueda de nombre en fuentes de datos abiertas"), | |
| ("Infracciones", "Infracciones de transito"), | |
| ("Inhibiciones / Embargos", "Inhibiciones y embargos judiciales"), | |
| ("Billeteras Virtuales / Fintechs", "Deudas en billeteras virtuales y fintechs"), | |
| ("Google Images", "Busqueda de fotos de perfil por nombre"), | |
| ("Antecedentes Penales", "Antecedentes penales de fuente publica"), | |
| ("Concursos y Quiebras", "Concursos preventivos y quiebras"), | |
| ("Participaciones IGJ", "Participaciones societarias en sociedades"), | |
| ] | |
| failures = meta.get("failures", []) if meta else [] | |
| for i, (src_name, src_desc) in enumerate(all_sources): | |
| is_used = src_name in sources | |
| is_failed = src_name in failures | |
| is_empty = src_name in empty_sources | |
| if is_failed: | |
| fill = (254, 242, 242) | |
| prefix = "[FALLO]" | |
| color = (185, 28, 28) | |
| desc_text = f"{src_desc} - FALLO LA CONSULTA (Requiere revision)" | |
| elif is_used: | |
| fill = (245, 250, 245) | |
| prefix = "[x]" | |
| color = (22, 101, 52) | |
| desc_text = src_desc | |
| elif is_empty: | |
| fill = (255, 251, 235) | |
| prefix = "[ ]" | |
| color = (146, 64, 14) | |
| desc_text = f"{src_desc} - Sin datos para este CUIT" | |
| else: | |
| fill = (248, 248, 248) | |
| prefix = "[ ]" | |
| color = (100, 116, 139) | |
| desc_text = src_desc | |
| pdf.set_fill_color(*fill) | |
| pdf.rect(10, pdf.get_y(), 190, 7, 'F') | |
| pdf.set_text_color(*color) | |
| pdf.set_font('helvetica', 'B' if (is_used or is_failed) else '', 8.5) | |
| pdf.cell(12, 6, prefix, new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', 'B', 8.5) | |
| pdf.set_text_color(15, 23, 42) | |
| pdf.cell(50, 6, clean_txt(src_name), new_x="RIGHT", new_y="TOP") | |
| pdf.set_font('helvetica', '', 8) | |
| if is_failed: | |
| pdf.set_text_color(185, 28, 28) | |
| elif is_empty: | |
| pdf.set_text_color(146, 64, 14) | |
| else: | |
| pdf.set_text_color(71, 85, 105) | |
| pdf.cell(0, 6, clean_txt(desc_text), new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(5) | |
| pdf.set_font('helvetica', 'I', 8) | |
| pdf.set_text_color(100, 116, 139) | |
| pdf.multi_cell(190, 4.5, clean_txt( | |
| "Este informe fue generado automaticamente por CrowData Intelligence consultando fuentes " | |
| "publicas y abiertas del Estado Argentino. Los datos presentados tienen caracter informativo " | |
| "y deben ser verificados ante cada organismo competente. CrowData no se responsabiliza " | |
| "por inexactitudes en las fuentes oficiales.\n" | |
| f"Generado el: {generated_at or datetime.datetime.now().strftime('%d/%m/%Y %H:%M')} - " | |
| "CrowData (c) 2025 - Todos los derechos reservados." | |
| )) | |
| return pdf.output() | |