Primera version funcional

This commit is contained in:
Marklogo 2026-02-17 21:06:48 +01:00
commit 4ba9bc6869
7 changed files with 226 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__/
*.pyc
*.pyo
*.pdf
.env
.venv

0
README.md Normal file
View File

0
certificado/.gitkeep Normal file
View File

BIN
certificado/certificado.p12 Normal file

Binary file not shown.

63
docs/campos Normal file
View File

@ -0,0 +1,63 @@
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

138
main.py Normal file
View File

@ -0,0 +1,138 @@
import argparse
import re
import os
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="pyhanko")
import getpass
from pypdf import PdfReader, PdfWriter
from datetime import datetime
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
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.args = self._parsear_argumentos()
self.num_dias = self._obtener_num_dias()
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.")
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="./docs/Plantilla.pdf"):
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()
# --- FLUJO DE GUARDADO Y FIRMA ---
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()

19
requirements.txt Normal file
View File

@ -0,0 +1,19 @@
asn1crypto==1.5.1
certifi==2026.1.4
cffi==2.0.0
charset-normalizer==3.4.4
cryptography==46.0.5
fonttools==4.61.1
idna==3.11
lxml==6.0.2
oscrypto==1.3.0
pycparser==3.0
pyHanko==0.33.0
pyhanko-certvalidator==0.29.1
pypdf==6.7.0
PyYAML==6.0.3
requests==2.32.5
tzlocal==5.3.1
uharfbuzz==0.53.3
uritools==6.0.1
urllib3==2.6.3