From 240be1157bc009612b3ab50b052b7af16a7750e4 Mon Sep 17 00:00:00 2001 From: Marklogo Date: Tue, 17 Feb 2026 21:57:44 +0100 Subject: [PATCH] Primera version funcional --- README.md | 17 ++++++++++++- docs/campos | 63 ------------------------------------------------ docs/config.json | 13 ++++++++++ main.py | 47 +++++++++++++++++++++--------------- 4 files changed, 57 insertions(+), 83 deletions(-) delete mode 100644 docs/campos create mode 100644 docs/config.json diff --git a/README.md b/README.md index bdd0edf..e247c2c 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,19 @@ pip install -r requirements.txt python main.py ``` -### Uso del script \ No newline at end of file +### Uso del script +```bash +python main.py {ap,vacaciones} fecha --dias + {ap,vacaciones} Tipo de solicitud + fecha Fecha, rango o lista + --dias nDias en caso de vacaciones el nº de dias habiles (opcional, en caso de no especificarlo se le solicita por prompt) + + Ejemplos: + python main.py ap 17/02/26 + python main.py ap 17/02/26-19/02/26 + python main.py ap 17/02/26,20/02/26,25/02/26 + + python main.py vacaciones 16/02/26-22/02/26 'Se solicitaran los dias habiles mediante el prompt' + python main.py vacaciones 16/02/26-22/02/26 --dias 5 + +```/ \ No newline at end of file diff --git a/docs/campos b/docs/campos deleted file mode 100644 index 9483403..0000000 --- a/docs/campos +++ /dev/null @@ -1,63 +0,0 @@ -DNI -Nombre -Apellido 1 -Apellido 2 -Cuerpo o escala -Área -Primergrado -segundoGrado -ausencia1hJornada -ausencia1hFraccionada -reduccion1h -reducciónMediaHora -horasSindicales -diasSindicales -diasHabiles -PermisoParto -permisoadopcion -PermisoPaternidad -fallecimiento -trasladoSin -trasladoCon -examenesFinales -examenesPrenatales -Lactancia -NacimientoPrematuro -indispensable -moscoso -matrimonio -sindicales -CursoOrganismo -CursoAGE -CursoSindicatos -CursoPracticas -TomaProvision -TomaNuevoCuerpo -jubilacion -InteresParticular -FamiliarGrave -LicenciaEstudio -LicenciaPropios -LicenciaDesarrollo -TotalEnfermedadSin -TotalEnfermedadCon -ParcialEnfermedad -GestiónOficial -GestiónPersonal -ConisionServicio -viajeOficial -PorNocturnas -FueraHorario -refuerzos -noBeneficios -curso -decision -NumDiasHabiles -nmHorasSidicales -Otros -PERÍODO DE TIEMPO POR EL QUE SE SOLICITA -Imprimir -OposicionesInterna -numDiasSindicales -Fecha -guarda \ No newline at end of file diff --git a/docs/config.json b/docs/config.json new file mode 100644 index 0000000..98d584f --- /dev/null +++ b/docs/config.json @@ -0,0 +1,13 @@ +{ + "datos_personales": { + "DNI": "33340763D", + "Nombre": "MARCOS", + "Apellido 1": "LOPEZ", + "Apellido 2": "GOMEZ", + "Cuerpo o escala": "C.TEC. AUX. DE INFORMATICA ADMON. ESTADO" + }, + "rutas": { + "plantilla": "./docs/Plantilla.pdf", + "certificado": "./certificado/certificado.p12" + } +} \ No newline at end of file diff --git a/main.py b/main.py index 9782e90..0a0c285 100644 --- a/main.py +++ b/main.py @@ -1,30 +1,38 @@ import argparse import re import os +import json import warnings -warnings.filterwarnings("ignore", category=UserWarning, module="pyhanko") import getpass -from pypdf import PdfReader, PdfWriter 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.cert_path = os.path.join(os.path.dirname(__file__), "certificado", "certificado.p12") - self.datos_personales = { - "DNI": "33340763D", - "Nombre": "MARCOS", - "Apellido 1": "LOPEZ", - "Apellido 2": "GOMEZ", - "Cuerpo o escala": "C.TEC. AUX. DE INFORMATICA ADMON. ESTADO" - } + 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})*)?$" @@ -53,20 +61,21 @@ class SolicitudRRHH: def _firmar_pdf(self, archivo_entrada, archivo_salida): if not os.path.exists(self.cert_path): - print(f"⚠️ No se encontró el certificado.") + 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()) + 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) , + 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' ) @@ -84,7 +93,8 @@ class SolicitudRRHH: with open(archivo_salida, 'wb') as outf: pdf_signer.sign_pdf(w, output=outf) - def generar_pdf(self, path_plantilla="./docs/Plantilla.pdf"): + 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) @@ -114,7 +124,6 @@ class SolicitudRRHH: writer.set_need_appearances_writer() - # --- FLUJO DE GUARDADO Y FIRMA --- nombre_final = self._generar_nombre_archivo() temp_file = "temp_solicitud.pdf" @@ -135,4 +144,4 @@ class SolicitudRRHH: if __name__ == "__main__": solicitud = SolicitudRRHH() - solicitud.generar_pdf() \ No newline at end of file + solicitud.generar_pdf()