import argparse import re import os import json import warnings import getpass from datetime import datetime from pypdf import PdfReader, PdfWriter from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from pyhanko.sign import signers, fields from pyhanko.stamp import TextStampStyle from pyhanko.pdf_utils.layout import SimpleBoxLayoutRule, AxisAlignment from pyhanko.pdf_utils.text import TextBoxStyle # Ignorar avisos de pyhanko warnings.filterwarnings("ignore", category=UserWarning, module="pyhanko") class SolicitudRRHH: def __init__(self): self.base_path = os.path.dirname(os.path.abspath(__file__)) self.config = self._cargar_configuracion() self.cert_path = os.path.join(self.base_path, self.config["rutas"]["certificado"]) self.datos_personales = self.config["datos_personales"] self.args = self._parsear_argumentos() self.num_dias = self._obtener_num_dias() def _cargar_configuracion(self): config_path = os.path.join(self.base_path, "docs/config.json") if not os.path.exists(config_path): raise FileNotFoundError(f"No se encuentra el archivo de configuración: {config_path}") with open(config_path, 'r', encoding='utf-8') as f: return json.load(f) def _validar_fecha(self, fecha_str): patron_fecha = r"\d{2}/\d{2}/\d{2}" patron_completo = rf"^{patron_fecha}((-{patron_fecha})|(,{patron_fecha})*)?$" if re.match(patron_completo, fecha_str): return fecha_str raise argparse.ArgumentTypeError(f"Formato de fecha inválido: {fecha_str}") def _parsear_argumentos(self): parser = argparse.ArgumentParser(description="Automatización de solicitudes RRHH") parser.add_argument("tipo", choices=["ap", "vacaciones"], help="Tipo de solicitud") parser.add_argument("fecha", type=self._validar_fecha, help="Fecha, rango o lista") parser.add_argument("--dias", type=int, help="Número de días hábiles") return parser.parse_args() def _obtener_num_dias(self): if self.args.tipo == "vacaciones": if self.args.dias: return self.args.dias while True: try: return int(input("👉 Introduce el Nº de días hábiles de vacaciones: ")) except ValueError: print("❌ Introduce un número entero válido.") return None def _obtener_passw_cert(self): return str(getpass.getpass(prompt="👉 Introduce la contraseña del certificado: ")).encode("utf-8") def _firmar_pdf(self, archivo_entrada, archivo_salida): if not os.path.exists(self.cert_path): print(f"⚠️ No se encontró el certificado en {self.cert_path}. Se guardará sin firmar.") os.rename(archivo_entrada, archivo_salida) return with open(archivo_entrada, 'rb') as inf: w = IncrementalPdfFileWriter(inf) signer = signers.SimpleSigner.load_pkcs12( pfx_file=self.cert_path, passphrase=self._obtener_passw_cert() ) estilo = TextStampStyle( border_width=0, text_box_style=TextBoxStyle(font_size=10), inner_content_layout=SimpleBoxLayoutRule(x_align=AxisAlignment.ALIGN_MIN, y_align=AxisAlignment.ALIGN_MAX), stamp_text='Firmado por: \n%(signer)s\nFecha: %(ts)s' ) pdf_signer = signers.PdfSigner( signature_meta=signers.PdfSignatureMetadata(field_name='Firma_Visible'), signer=signer, stamp_style=estilo, new_field_spec=fields.SigFieldSpec( 'Firma_Visible', box=(350, 20, 550, 100), on_page=1 ) ) with open(archivo_salida, 'wb') as outf: pdf_signer.sign_pdf(w, output=outf) def generar_pdf(self): path_plantilla = os.path.join(self.base_path, self.config["rutas"]["plantilla"]) reader = PdfReader(path_plantilla) writer = PdfWriter() writer.clone_reader_document_root(reader) fechas_raw = self.args.fecha if "-" in fechas_raw: f = fechas_raw.split("-") texto_fechas = f"Del {f[0]} al {f[1]}" elif "," in fechas_raw: texto_fechas = f"{fechas_raw.replace(',', ', ')}" else: texto_fechas = fechas_raw campos_finales = {**self.datos_personales} if self.args.tipo == "ap": campos_finales["moscoso"] = "/Yes" else: campos_finales["diasHabiles"] = "/Yes" campos_finales["NumDiasHabiles"] = str(self.num_dias) campos_finales["PERÍODO DE TIEMPO POR EL QUE SE SOLICITA"] = texto_fechas campos_finales["Fecha"] = datetime.now().strftime("%d/%m/%Y") for page in writer.pages: writer.update_page_form_field_values(page, campos_finales) writer.set_need_appearances_writer() nombre_final = self._generar_nombre_archivo() temp_file = "temp_solicitud.pdf" with open(temp_file, "wb") as f: writer.write(f) self._firmar_pdf(temp_file, nombre_final) if os.path.exists(temp_file): os.remove(temp_file) print(f"✅ Proceso completado con éxito: {nombre_final}") def _generar_nombre_archivo(self): tipo_limpio = self.args.tipo.upper() primera_fecha = re.split(r'[,-]', self.args.fecha)[0].replace("/", "-") return f"{self.datos_personales['Apellido 1']}_{self.datos_personales['Apellido 2']}_{self.datos_personales['Nombre']}_{tipo_limpio}_{primera_fecha}.pdf" if __name__ == "__main__": solicitud = SolicitudRRHH() solicitud.generar_pdf()