Primera version operativa del script
This commit is contained in:
commit
333083c3c7
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.env
|
||||
venv/
|
||||
BIN
DestinosParseados.xlsx
Normal file
BIN
DestinosParseados.xlsx
Normal file
Binary file not shown.
BIN
Listado de Vacantes.xlsx
Normal file
BIN
Listado de Vacantes.xlsx
Normal file
Binary file not shown.
32
cabs.json
Normal file
32
cabs.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"nombre_cabecera": "PUESTO\nNÚMERO",
|
||||
"n_campos": 1,
|
||||
"campos": ["PUESTO NUMERO"]
|
||||
},
|
||||
{
|
||||
"nombre_cabecera": "",
|
||||
"n_campos": 1,
|
||||
"campos": ["MINISTERIO / ORGANISMO"]
|
||||
},
|
||||
{
|
||||
"nombre_cabecera": "CENTRO DIRECTIVO/00.A.A\nCENTRO DE DESTINO",
|
||||
"n_campos": 2,
|
||||
"campos": ["CENTRO DIRECTIVO/00.A.A", "CENTRO DE DESTINO"]
|
||||
},
|
||||
{
|
||||
"nombre_cabecera": "PROVINCIA\nLOCALIDAD",
|
||||
"n_campos": 2,
|
||||
"campos": ["PROVINCIA","LOCALIDAD"]
|
||||
},
|
||||
{
|
||||
"nombre_cabecera": "PUESTO DE TRABAJO",
|
||||
"n_campos": 2,
|
||||
"campos": [ "PUESTO DE TRABAJO", "RPT COD"]
|
||||
},
|
||||
{
|
||||
"nombre_cabecera": "NIVEL C.D.\nC. ESPECÍFICO",
|
||||
"n_campos": 2,
|
||||
"campos": ["NIVEL C.D.", "C. ESPECÍFICO"]
|
||||
}
|
||||
]
|
||||
40
helpers.py
Normal file
40
helpers.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import json
|
||||
|
||||
class CabColumna:
|
||||
def __init__(self, nombre_cabecera, n_campos):
|
||||
self.nombre_cabecera = nombre_cabecera
|
||||
self.n_campos = n_campos
|
||||
self.indice=""
|
||||
self.campos = ['' for _ in range(n_campos)]
|
||||
def __repr__(self):
|
||||
return f"CabColumna({self.nombre_cabecera!r}, {self.n_campos}, {self.campos})"
|
||||
|
||||
|
||||
class ListaCabColumnas:
|
||||
def __init__(self):
|
||||
self.lista = []
|
||||
|
||||
def añadir(self, cab_columna):
|
||||
if isinstance(cab_columna, CabColumna):
|
||||
self.lista.append(cab_columna)
|
||||
|
||||
def eliminar(self, index):
|
||||
if 0 <= index < len(self.lista):
|
||||
del self.lista[index]
|
||||
|
||||
def exportar(self, ruta):
|
||||
with open(ruta, 'w', encoding='utf-8') as f:
|
||||
json.dump([{
|
||||
'nombre_cabecera': c.nombre_cabecera,
|
||||
'n_campos': c.n_campos,
|
||||
'campos': c.campos
|
||||
} for c in self.lista], f, indent=2, ensure_ascii=False)
|
||||
|
||||
def importar(self, ruta):
|
||||
with open(ruta, 'r', encoding='utf-8') as f:
|
||||
datos = json.load(f)
|
||||
self.lista = [
|
||||
CabColumna(d['nombre_cabecera'], d['n_campos']) for d in datos
|
||||
]
|
||||
for i, d in enumerate(datos):
|
||||
self.lista[i].campos = d['campos']
|
||||
218
main.py
Normal file
218
main.py
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import os
|
||||
import curses
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from colorama import init, Fore, Style
|
||||
|
||||
from helpers import CabColumna, ListaCabColumnas
|
||||
|
||||
# Inicializa colorama
|
||||
init(autoreset=True)
|
||||
|
||||
def limpiar_pantalla():
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
def seleccionar_archivo_curses(stdscr):
|
||||
curses.curs_set(0) # Ocultar cursor
|
||||
archivos = [f for f in os.listdir() if f.lower().endswith(('.xls', '.xlsx'))]
|
||||
if not archivos:
|
||||
stdscr.addstr(0, 0, "No se encontraron archivos .xls o .xlsx en el directorio actual.")
|
||||
stdscr.getch()
|
||||
return None
|
||||
|
||||
seleccion = 0
|
||||
|
||||
while True:
|
||||
stdscr.clear()
|
||||
stdscr.addstr(0, 0, "Selecciona un archivo Excel (.xls, .xlsx):\n\n")
|
||||
|
||||
for idx, archivo in enumerate(archivos):
|
||||
if idx == seleccion:
|
||||
stdscr.addstr(idx + 2, 0, f"> {archivo}", curses.A_REVERSE)
|
||||
else:
|
||||
stdscr.addstr(idx + 2, 0, f" {archivo}")
|
||||
|
||||
stdscr.addstr(len(archivos) + 3, 0, "Usa ↑↓ para moverte, Enter para seleccionar, q para salir.")
|
||||
|
||||
tecla = stdscr.getch()
|
||||
|
||||
if tecla == curses.KEY_UP and seleccion > 0:
|
||||
seleccion -= 1
|
||||
elif tecla == curses.KEY_DOWN and seleccion < len(archivos) - 1:
|
||||
seleccion += 1
|
||||
elif tecla == ord('q'):
|
||||
return None
|
||||
elif tecla == 10: # Enter
|
||||
return os.path.abspath(archivos[seleccion])
|
||||
|
||||
def seleccionar_archivo():
|
||||
return curses.wrapper(seleccionar_archivo_curses)
|
||||
|
||||
|
||||
|
||||
memoria_combinaciones = {}
|
||||
def elegir_combinacion(strtemp):
|
||||
combinaciones = []
|
||||
|
||||
if len(strtemp) == 3:
|
||||
combinaciones = [
|
||||
(' '.join(strtemp[:1]), ' '.join(strtemp[1:])),
|
||||
(' '.join(strtemp[:2]), strtemp[2])
|
||||
]
|
||||
elif len(strtemp) == 4:
|
||||
combinaciones = [
|
||||
(' '.join(strtemp[:1]), ' '.join(strtemp[1:])),
|
||||
(' '.join(strtemp[:2]), ' '.join(strtemp[2:])),
|
||||
(' '.join(strtemp[:3]), strtemp[3])
|
||||
]
|
||||
else:
|
||||
return strtemp # No intervenimos en otros casos
|
||||
|
||||
claves_posibles = [parte1 for parte1, parte2 in combinaciones]
|
||||
|
||||
for i, clave in enumerate(claves_posibles):
|
||||
if clave in memoria_combinaciones:
|
||||
return (clave, memoria_combinaciones[clave])
|
||||
|
||||
# Mostrar menú al usuario
|
||||
print("\nSe detectaron múltiples líneas. Elige cómo dividirlas:")
|
||||
for i, (parte1, parte2) in enumerate(combinaciones):
|
||||
print(f"{i+1}) Parte 1: \"{parte1}\" | Parte 2: \"{parte2}\"")
|
||||
|
||||
while True:
|
||||
opcion = input("Introduce el número de la opción deseada: ").strip()
|
||||
if opcion.isdigit() and 1 <= int(opcion) <= len(combinaciones):
|
||||
seleccion = combinaciones[int(opcion) - 1]
|
||||
clave_general = seleccion[0] # Solo recordamos la parte 1
|
||||
memoria_combinaciones[clave_general] = seleccion[1] # Guardamos parte 2 asociada
|
||||
return seleccion
|
||||
else:
|
||||
print("Opción inválida. Intenta de nuevo.")
|
||||
|
||||
def parsear_documento(archivo):
|
||||
print("\n>>> Parsear documento")
|
||||
print(f"Parseando archivo: {archivo} ...")
|
||||
|
||||
df = pd.read_excel(archivo, engine='openpyxl', header=None)
|
||||
df = df.replace(r'^\s*$', np.nan, regex=True)
|
||||
df = df.dropna(how='all').dropna(axis=1,how='all')
|
||||
|
||||
lista_cab_columnas=ListaCabColumnas()
|
||||
lista_cab_columnas.importar('cabs.json')
|
||||
|
||||
cabeceras = []
|
||||
ministerio = ''
|
||||
for cabecera in lista_cab_columnas.lista:
|
||||
if cabecera.nombre_cabecera!="": #
|
||||
cabecera.indice=df.columns[df.apply(lambda col: col.astype(str).str.contains(cabecera.nombre_cabecera)).any()].tolist()[0]
|
||||
cabeceras.extend(list(map(str, cabecera.campos)))
|
||||
df_parseado=pd.DataFrame(columns=cabeceras)
|
||||
|
||||
for index, row in df.iterrows():
|
||||
valor_celda = row.iloc[0]
|
||||
if isinstance(valor_celda, str) and lista_cab_columnas.lista[0].nombre_cabecera not in valor_celda:
|
||||
row=row.dropna()
|
||||
ministerio = ' '.join(row.astype(str))
|
||||
elif isinstance(valor_celda, str) and lista_cab_columnas.lista[0].nombre_cabecera in valor_celda:
|
||||
continue
|
||||
else:
|
||||
fila_index = len(df_parseado)
|
||||
for cabecera in lista_cab_columnas.lista:
|
||||
if cabecera.nombre_cabecera!="": #
|
||||
if len(cabecera.campos)>1:
|
||||
strtemp = row[cabecera.indice].split('\n')
|
||||
if len(strtemp) in (3, 4):
|
||||
strtemp = elegir_combinacion(strtemp)
|
||||
else:
|
||||
strtemp = [' '.join(strtemp[:-1]), strtemp[-1]]
|
||||
|
||||
for i, campo in enumerate(cabecera.campos):
|
||||
valor = strtemp[i] if i < len(strtemp) else ""
|
||||
df_parseado.at[fila_index, campo] = valor
|
||||
|
||||
|
||||
|
||||
else:
|
||||
df_parseado.at[fila_index,cabecera.campos[0]] = str(row[cabecera.indice]).replace('\n',' ')
|
||||
else:
|
||||
df_parseado.at[fila_index,cabecera.campos[0]] = ministerio
|
||||
input("\nPresiona Enter para continuar...")
|
||||
return df_parseado
|
||||
|
||||
def exportar_a_excel(documento_parseado):
|
||||
print("\n>>> Exportar a Excel")
|
||||
try:
|
||||
documento_parseado.to_excel('DestinosParseados.xlsx', index=False)
|
||||
print("Documento exportado a Excel.")
|
||||
except Exception as e:
|
||||
print(f"Error al exportar: {e}")
|
||||
input("\nPresiona Enter para continuar...")
|
||||
|
||||
def mostrar_menu(archivo, documento_parseado):
|
||||
print("=== Menú Principal ===\n")
|
||||
|
||||
print("1) Seleccionar archivo")
|
||||
|
||||
if archivo:
|
||||
print(f"2) Parsear documento {Fore.GREEN}{os.path.basename(archivo)}{Fore.RESET}")
|
||||
else:
|
||||
print(Fore.LIGHTBLACK_EX + "2) Parsear documento [desactivado]")
|
||||
|
||||
if archivo and documento_parseado is not None and not documento_parseado.empty:
|
||||
print("3) Exportar a Excel")
|
||||
else:
|
||||
print(Fore.LIGHTBLACK_EX + "3) Exportar a Excel [desactivado]")
|
||||
|
||||
print("4) Modificar archivo ya parseado")
|
||||
|
||||
print("0) Salir")
|
||||
|
||||
def main():
|
||||
archivo = None
|
||||
archivoParser = None
|
||||
documento_parseado = None
|
||||
while True:
|
||||
limpiar_pantalla()
|
||||
mostrar_menu(archivo, documento_parseado)
|
||||
opcion = input("\nSeleccione una opción: ").strip()
|
||||
|
||||
if opcion == "1":
|
||||
archivo = seleccionar_archivo()
|
||||
if archivo:
|
||||
print(f"\nArchivo seleccionado: {archivo}")
|
||||
else:
|
||||
print("\nNo se seleccionó ningún archivo.")
|
||||
documento_parseado = None
|
||||
|
||||
elif opcion == "2":
|
||||
if not archivo:
|
||||
print(Fore.RED + "Opción desactivada: debes seleccionar un archivo primero.")
|
||||
else:
|
||||
documento_parseado = parsear_documento(archivo)
|
||||
|
||||
elif opcion == "3":
|
||||
if not archivo:
|
||||
print(Fore.RED + "Opción desactivada: primero selecciona un archivo.")
|
||||
elif documento_parseado is None or documento_parseado.empty:
|
||||
print(Fore.RED + "Opción desactivada: debes parsear el archivo antes de exportar.")
|
||||
else:
|
||||
exportar_a_excel(documento_parseado)
|
||||
|
||||
elif opcion =="4":
|
||||
archivoParser = seleccionar_archivo()
|
||||
if archivo:
|
||||
print(f"\nArchivo seleccionado: {archivo}")
|
||||
else:
|
||||
print("\nNo se seleccionó ningún archivo.")
|
||||
|
||||
elif opcion == "0":
|
||||
print("\nSaliendo del programa...")
|
||||
break
|
||||
|
||||
else:
|
||||
print(Fore.RED + "Opción no válida.")
|
||||
input("\nPresiona Enter para continuar...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
requirements.txt
Normal file
20
requirements.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Bottleneck==1.5.0
|
||||
colorama==0.4.6
|
||||
defusedxml==0.7.1
|
||||
et_xmlfile==2.0.0
|
||||
llvmlite==0.44.0
|
||||
numba==0.61.2
|
||||
numexpr==2.10.2
|
||||
numpy==2.2.6
|
||||
odfpy==1.4.1
|
||||
openpyxl==3.1.5
|
||||
packaging==25.0
|
||||
pandas==2.2.3
|
||||
python-calamine==0.3.2
|
||||
python-dateutil==2.9.0.post0
|
||||
pytz==2025.2
|
||||
pyxlsb==1.0.10
|
||||
six==1.17.0
|
||||
tzdata==2025.2
|
||||
xlrd==2.0.1
|
||||
XlsxWriter==3.2.3
|
||||
Loading…
Reference in New Issue
Block a user