crowdata / app /utils /pdf_generator.py
YOSOYYONOSOYOTRO's picture
Upload folder using huggingface_hub (part 2)
4223796 verified
Raw
History Blame Contribute Delete
81 kB
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()