a
    0i                     @   s^  d Z ddlZddlmZ ddlZddlZddlmZ ddlm	Z	m
Z
mZmZ ddlmZmZ ddlmZmZmZmZmZmZmZmZmZ ddlmZ e  ed	ed
edddZdedededddgeddddddZg dg dg ddZG dd dZ G d d! d!Z!G d"d# d#Z"d$d% Z#d&d' Z$d(d) Z%d*d+ Z&d,d- Z'dS ).u   
Módulo completo de actualización del Score.
Maneja el flujo completo desde la validación del Excel hasta la 
actualización de la tabla CM_Score en la base de datos.
    N)load_dotenvdatetime)requestjsonifyrender_templateredirect)get_connectionget_connectionERP)	obtener_datos_score_erpobtener_estructura_validadorverificar_tabla_cm_scoreobtener_ultima_version_historyobtener_columnas_tablacrear_tabla_cm_scorecrear_tabla_scorehistorytruncate_tablainsertar_bulk_score) validar_score_para_actualizacionZFTP_HOSTZFTP_USERZFTP_PASSW/domains/sycelephant.com/public_html/file/SyC/IngenieroControl/RespaldoActualizarScore/)hostuserpasswordremote_pathFZ	TENANT_IDONEDRIVE_CLIENT_IDONEDRIVE_CLIENT_SECRETzhttp://localhost:5000/callbackzFiles.ReadWriteONEDRIVE_USERNAMEZ.EWDk3kJdEcdJivAlcKPi8woBTgMWG_qfQ1rYYo0Jcy5QbQz-https://login.microsoftonline.com/{tenant_id}z https://graph.microsoft.com/v1.0zonedrive_token_cache.json)enabled	tenant_id	client_idclient_secretredirect_uriscopes
user_emailfile_id	authority	graph_urltoken_cache_file)FOrderNumDepartamentoVendedorName
TotalLines	OrderLinePartNumLineDesc	CapacidadVoltajeTipoRevisionNumDecriptionProdCaseta	OrderDate
NeedByDateZFechaAnticipoLiberacionCXPZTerminosOrderNum&Line	ProjectIDJobNum2Revision_OVRevision_ProjectRevision_JobFechaDeCierreInsumosDemandadosInsumosEmitidosFechaDeTermino	PartNum_MDescription_MPartClass_MRefCategory_MQtyPer_MIssuedQty_MDemandado_MOnhandQty_MEn_PO_MNoPO_MEnRequisicion_MNoRequisicion_M	PartNum_GDescription_GPartClass_GRefCategory_GQtyPer_GIssuedQty_GDemandado_GOnhandQty_GEn_PO_GNoPO_GEnRequisicion_GNoRequisicion_G	PartNum_TDescription_TPartClass_TRefCategory_TQtyPer_TIssuedQty_TOnhandQty_T	PartNum_RDescription_RPartClass_RRefCategory_RQtyPer_RIssuedQty_ROnhandQty_RComentarioLINEZOvEEUUZClienteEEUU)$ProdCodeComentarioSyCChkFechaSyCConsideradaPreasignacionConsideradaMaterialesu   FechaProducciónu   ComentarioProducciónu   AvanceProducciónu   FechaPlaneaciónEstadoFechau   ComentarioPlaneaciónu   FechaInicialPlaneaciónZMotivoInicialFechaMatAvisoDeTerminacionTerminadoConFaltanteu   FechaActualizaciónMaterialFaltanteZ	Auxiliar1Z	Auxiliar2ComentarioCalidadPreasignado_M	NoSerie_MReq_M	Comment_M
FechaReq_MPO_MCantidad_Pedida_MPreasignado_G	NoSerie_GReq_G	Comment_G
FechaReq_GPO_GCantidad_Pedida_G)FechaVentasFechaSimulacionesFechaMGAvanceDeSurtimientoAvaceDeEmisiones
Faltante_MAlternativa_MEn_PO_Altern_MNoPO_Alern_M	Estatus_MFecha_Llegada_M
Faltante_GAlternativa_GEn_PO_Altern_GNoPO_Alern_G	Estatus_GFecha_Llegada_G)columnas_erpcolumnas_manualcolumnas_formuladoc                   @   s8   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d ZdS )
FTPManageru   
    Gestiona la conexión y operaciones con el servidor FTP
    para guardar respaldos del archivo Excel
    
    ✅ Versión actualizada con navegación paso a paso
    c                 C   s2   t d | _t d | _t d | _t d | _d | _d S )Nr   r   r   r   )
FTP_CONFIGr   r   r   r   ftpself r   J/var/www/html/src/App/SupyCtrol_Module/IngenieroControl/ActualizarScore.py__init__   s
    



zFTPManager.__init__c              
   C   s   z6t d t| j| _| j| j| j t d W dS  ty~ } z0t dt	|  tdt	| W Y d}~n
d}~0 0 dS )u   
        Establece conexión con el servidor FTP
        
        Returns:
            bool: True si la conexión fue exitosa
            
        Raises:
            Exception: Si hay error de conexión
        zConectando a FTP...u    ✓ Conectado a FTP exitosamenteTu   ✗ Error al conectar a FTP: u   Error de conexión FTP: N)
printftplibFTPr   r   loginr   r   	Exceptionstr)r   er   r   r   conectar   s    
zFTPManager.conectarc                 C   sH  zt d z| jd t d W n   Y n0 z| jd t d W n6 ty } zt d|  W Y d}~W dS d}~0 0 z| jd	 t d
 W n6 ty } zt d|  W Y d}~W dS d}~0 0 z| jd t d W n8 ty" } zt d|  W Y d}~W dS d}~0 0 zv| jd t d g }| jd|j t dd|dd   d|v s~d|v rt d nt d t d W n8 ty } zt d|  W Y d}~W dS d}~0 0 z| jd t d W n tjyn   z0| jd | j	d | jd t d W n: tyh } z t d|  W Y d}~Y W dS d}~0 0 Y n0 z| jd  t d! W n tjy
   z0| jd | j	d" | jd" t d# W n: ty } z t d$|  W Y d}~Y W dS d}~0 0 Y n0 z| jd% t d& W n tjy   z0| jd  | j	d' | jd' t d( W n: ty } z t d)|  W Y d}~Y W dS d}~0 0 Y n0 | j
 }t d* t d+|  || jkrt d, n&t d- t d.| j  t d/|  W d0S  tyB } z t d1t|  W Y d}~dS d}~0 0 dS )2u   
        Crea la estructura de carpetas navegando paso a paso
        
        Se asegura de estar en el file correcto (el que tiene Formatos, Operaciones)
        
        Returns:
            bool: True si la estructura está lista
        z,Verificando estructura de carpetas en FTP.../u     ✓ En la raíz FTPz/domainsu     ✓ En /domainsu%     ✗ No se pudo acceder a /domains: NFz/domains/sycelephant.comu!     ✓ En /domains/sycelephant.comu,     ✗ No se pudo acceder a sycelephant.com: z$/domains/sycelephant.com/public_htmlu-     ✓ En /domains/sycelephant.com/public_htmlu(     ✗ No se pudo acceder a public_html: z)/domains/sycelephant.com/public_html/fileu2     ✓ En /domains/sycelephant.com/public_html/fileNLSTu     📂 Contenido actual: z,    ZFormatosOperacionesuJ     ✓ Confirmado: Estamos en el file CORRECTO (tiene Formatos/Operaciones)u:     ⚠ ADVERTENCIA: Este file no tiene Formatos/Operacionesu&     ⚠ Puede que sea el file equivocadou!     ✗ No se pudo acceder a file: z-/domains/sycelephant.com/public_html/file/SyCu     ✓ Carpeta existe: SyCSyCu     ✓ Carpeta creada: SyCu     ✗ No se pudo crear SyC: z>/domains/sycelephant.com/public_html/file/SyC/IngenieroControlu&     ✓ Carpeta existe: IngenieroControlIngenieroControlu&     ✓ Carpeta creada: IngenieroControlu)     ✗ No se pudo crear IngenieroControl: r   u-     ✓ Carpeta existe: RespaldoActualizarScoreZRespaldoActualizarScoreu-     ✓ Carpeta creada: RespaldoActualizarScoreu0     ✗ No se pudo crear RespaldoActualizarScore: u    ✓ Estructura de carpetas listau   ✓ Ubicación actual: u+   ✓ CONFIRMADO: Estamos en la ruta correctau>   ⚠ ADVERTENCIA: Ubicación actual no coincide con remote_pathz   Esperado: z   Actual:   Tu+   ✗ Error al crear estructura de carpetas: )r   r   cwdr   	retrlinesappendjoinr   
error_permmkdpwdr   r   )r   r   itemsZubicacion_actualr   r   r   crear_estructura_carpetas   s    	
"""

z$FTPManager.crear_estructura_carpetasc              
   C   s   z| j du r|   |   td td|  td| j  t|d$}| j d| | W d   n1 sv0    Y  td|  td| d	 W d
S  ty } z0tdt|  tdt| W Y d}~n
d}~0 0 dS )u\  
        Sube un archivo al servidor FTP
        
        Args:
            local_path (str): Ruta local del archivo
            remote_filename (str): Nombre del archivo en el servidor
            
        Returns:
            bool: True si se subió correctamente
            
        Raises:
            Exception: Si hay error al subir
        Nz
Subiendo archivo a FTP...z  Archivo: z  Destino: rbzSTOR u!   ✓ Archivo subido exitosamente: uT   ✓ URL: https://sycelephantt.com/file/SyC/IngenieroControl/RespaldoActualizarScore/
Tu"   ✗ Error al subir archivo a FTP: zError al subir a FTP: )	r   r   r   r   r   open
storbinaryr   r   )r   Z
local_pathZremote_filenamefiler   r   r   r   subir_archivo  s    
2zFTPManager.subir_archivoc              	   C   sX   z | j dur| j   td W n2   z| j dur>| j   W n   Y n0 Y n0 dS )u   Cierra la conexión FTPNu   ✓ Desconectado de FTP)r   quitr   closer   r   r   r   desconectar@  s    


zFTPManager.desconectarN)	__name__
__module____qualname____doc__r   r   r   r   r   r   r   r   r   r   z   s   ~%r   c                   @   s@   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d Zdd Z	dS )OneDriveManageru  
    Gestiona actualización automática del Score en SharePoint
    usando Microsoft Authentication Library (MSAL) con Azure AD
    
    ✅ Maneja refresh tokens automáticamente
    ✅ Guarda tokens en archivo local
    ✅ No requiere regenerar tokens manualmente
    c                 C   sr   dd l }dd l}t| _| jd | _| jd j| jd d}|j| jd | jd |d| _| jd	 | _| 	 | _
d S )
Nr   r&   r%   r   r   r   r    r   Zclient_credentialr%   r'   )msalosONEDRIVE_CONFIGconfigr&   formatConfidentialClientApplicationmsal_appr'   _load_token_cachetoken_cache)r   r   r   r%   r   r   r   r   \  s    
zOneDriveManager.__init__c                 C   sp   ddl }ddl}|j| jrlz<t| jd}||W  d   W S 1 sN0    Y  W n   i  Y S 0 i S )z"Carga tokens guardados del archivor   Nr)r   jsonpathexistsr'   r   load)r   r   r   fr   r   r   r   r  s    .
z!OneDriveManager._load_token_cachec              
   C   s   ddl }zLt| jd}||| W d   n1 s80    Y  td| j  W n2 ty } ztd|  W Y d}~n
d}~0 0 dS )zGuarda tokens en archivo localr   Nwu     ✓ Tokens guardados en u)     ⚠️ No se pudo guardar token cache: )r   r   r'   dumpr   r   )r   Ztoken_responser   r   r   r   r   r   _save_token_cache  s    *z!OneDriveManager._save_token_cachec                 C   s   | j r"d| j v r"td | j d S | j rxd| j v rxtd | jj| j d | jd d}d|v rxtd | | |d S tdd	S )
u   
        Obtiene access token (renueva automáticamente si es necesario)
        
        Returns:
            str: Access token válido
        access_tokenu     ✓ Usando token del cacherefresh_tokenz&  Renovando token con refresh_token...r"   )r   r"   u!     ✓ Token renovado exitosamenteu   
❌ NO HAY TOKEN VÁLIDO

Necesitas autorizar la aplicación primero.
Ejecuta el script de autorización: python autorizar_onedrive.py
O visita: http://localhost:5000/loginN)r   r   r   Zacquire_token_by_refresh_tokenr   r   r   )r   resultr   r   r   obtener_access_token  s     

z$OneDriveManager.obtener_access_tokenc                 C   s   ddl }dd| i}| jd }| j d| }|j||dd}|jd	krj| }td
|d  |S |jdkr~tdntd|j d|j dS )u    Obtiene información del archivor   NAuthorizationBearer r$   /me/drive/items/   )headerstimeout   u     ✓ Archivo: namei  z3Archivo no encontrado. Verifica file_id y permisos.zError al buscar archivo:  - )	requestsr   r&   getstatus_coder   r   r   text)r   r   r   r   r$   urlresponsedatar   r   r   buscar_archivo  s    




zOneDriveManager.buscar_archivoc                 C   sf   ddl }d| dd}| j d| d}|j|||dd	}|jd
v rJdS td|j d|j dS )z.Actualiza el contenido del archivo en OneDriver   Nr   zAapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet)r   zContent-Typer   z/contentx   )r   r   r   )r      TzError al actualizar archivo: r   )r   r&   putr   r   r   )r   r   r$   excel_bytesr   r   r   r   r   r   r   actualizar_archivo  s    
z"OneDriveManager.actualizar_archivoN)
r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   R  s   	"r   c                   @   sx   e Zd ZdZdd Zdd Zdd Zdd	 Zd
d Zdd Z	dd Z
dd Zdd Zdd Zdd Zdd Zdd ZdS )ScoreProcessoruP   
    Clase principal que maneja todo el proceso de actualización del Score
    c              	   C   sZ   d| _ d| _t | _t | _t | _d| _	d| _
d| _d| _ddddddddd| _dS )z9Inicializa el procesador con conexiones y configuracionesNr    F)filas_excel	filas_erpfilas_procesadasfilas_eliminadasversion_historytabla_historyrespaldo_ftponedrive_actualizado)Zconn_erpZconn_vpsr   ftp_managerr   nowtiempo_inicior   onedrive_managerdf_score_exceldf_score_erpdf_score_finalclasificacion_columnasstatsr   r   r   r   r     s$    
zScoreProcessor.__init__c              
   C   sn  zt d t d t d t d |  }|d sBdd|dW S |d	 }t d
| d t d z| | d| jd< W n> ty } z&t dt|  t d W Y d}~n
d}~0 0 t d | || _t| j| jd< t d| jd  d t d | 	  t d| jd  d t d | 
  t d t d z2t | _t| j| jd< t d| jd  d W n ty } znt|}d|v sd|v rd }n$d!|v rd"}nd#|v rd$}nd%}dd&d'||g d(d)d*W  Y d}~W S d}~0 0 t d+ |   t| j| jd,< | jd | jd,  | jd-< t d.| jd,  d t d/ |   t d0 t d1 |   t d2 t d3 td4 rz|   d| jd5< W nJ ty } z0t d6t|  t d d| jd5< W Y d}~n
d}~0 0 nt d7 t d8 |  }t d t d9 t d |W S  tyh } z.t d:t| d dt|d;W  Y d}~S d}~0 0 dS )<u   
        Ejecuta el flujo completo de actualización del Score
        
        Returns:
            dict: Resultado del proceso con estadísticas
        zQ
================================================================================u-   INICIANDO PROCESO DE ACTUALIZACIÓN DEL SCOREzQ================================================================================
z1PASO 1/11: Validando Excel con ValidarScore.py...successFu   Validación de Excel fallida)r  errordetallesruta_archivou"   ✓ Validación exitosa. Archivo: r   z'PASO 2/11: Guardando respaldo en FTP...Tr   u1   ⚠ Warning: No se pudo guardar respaldo en FTP: zContinuando proceso...
Nz'PASO 3/11: Leyendo Excel a DataFrame...r   u   ✓ Excel leído: z filas
z0PASO 4/11: Guardando respaldo en ScoreHistory...u   ✓ Respaldo guardado en r   z1PASO 5/11: Obteniendo estructura del validador...u&   ✓ Estructura obtenida y clasificada
z&PASO 6/11: Obteniendo datos del ERP...r   u   ✓ Datos ERP obtenidos: u   ERROR DE CONEXIÓN AL ERPu   ERROR CRÍTICOZservidor_inaccesibleTIMEOUTr   u   AUTENTICACIÓNZautenticacionZerror_desconocidoz No se pudo obtener datos del ERPZconexion_erp_fallida)u<   Verifique que el servidor SQL Server del ERP esté encendidoz-Verifique conectividad de red al servidor ERPz2Verifique que SQL Server acepte conexiones remotasu*   Verifique que Named Pipes esté habilitadoz*Contacte al administrador del servidor ERP)
tipo_errorZerror_tecnicoZsugerencias)r  r  r  r  z"PASO 7/11: Mezclando DataFrames...r   r   u   ✓ Mezcla completada: z(PASO 8/11: Verificando tabla CM_Score...u   ✓ Tabla CM_Score lista
z#PASO 9/11: Actualizando CM_Score...u&   ✓ CM_Score actualizada exitosamente
z/PASO 10/11: Actualizando archivo en OneDrive...r   r   u-   ⚠ Warning: No se pudo actualizar OneDrive: u-   ⚠ OneDrive deshabilitado en configuración
z"PASO 11/11: Generando respuesta...zPROCESO COMPLETADO EXITOSAMENTEu   
✗ ERROR EN EL PROCESO: r  r  )r   validar_excelguardar_respaldo_ftpr  r   r   leer_excel_a_dataframer  lenguardar_respaldo_bdobtener_estructura_columnasr   r  mezclar_dataframesr  !crear_tabla_cm_score_si_no_existeactualizar_tabla_cm_scorer   actualizar_onedrivegenerar_respuesta_exitosa)r   Zresultado_validacionr	  r   	error_msgr  Z	respuestar   r   r   ejecutar_actualizacion_completa  s    



"z.ScoreProcessor.ejecutar_actualizacion_completac              
   C   s   zt d | j }t d t d | j|}t d t d | | j}t dt|dd t d	 | j||| t d
 t dt| j d W dS  ty } ztdt	| W Y d}~n
d}~0 0 dS )zB
        Actualiza archivo en OneDrive con df_score_final
        z  Obteniendo access token...u     ✓ Token obtenidoz   Buscando archivo Score.xlsx...u     ✓ Archivo encontradoz   Generando Excel actualizado...u     ✓ Excel generado (,z bytes)z   Subiendo archivo a OneDrive...u%     ✓ Archivo actualizado en OneDriveu     ✓ z filas escritas
TzError al actualizar OneDrive: N)
r   r   r   r   dataframe_a_excel_bytesr  r  r   r   r   )r   r   r$   r   r   r   r   r   r    s"    
z"ScoreProcessor.actualizar_onedrivec                 C   sb   ddl m} | }tj|dd }|j|ddd W d   n1 sF0    Y  |d | S )	zA
        Convierte DataFrame a bytes de Excel en memoria
        r   )BytesIOopenpyxl)engineCM_ScoreF)
sheet_nameindexN)ior  pdExcelWriterto_excelseekread)r   dfr  outputwriterr   r   r   r    s    .
z&ScoreProcessor.dataframe_a_excel_bytesc              
   C   sL   zt  }|W S  tyF } z"dddt| dW  Y d}~S d}~0 0 dS )u   
        Valida el Excel usando el módulo ValidarScore.py
        
        Returns:
            dict: Resultado de la validación
        F  u   Error al invocar validación: )r  statusmensajeN)r   r   r   )r   	resultador   r   r   r   r    s    zScoreProcessor.validar_excelc                 C   s"   t  }| j|| | j  dS )z
        Guarda respaldo del Excel en el servidor FTP
        
        Args:
            ruta_archivo (str): Ruta del archivo a respaldar
        N)generar_nombre_respaldor   r   r   )r   r	  Znombre_respaldor   r   r   r    s    z#ScoreProcessor.guardar_respaldo_ftpc                 C   sl  ddl m} td d}||dd}||jvrN|  td| d|j d	|| }g }d
}t|jddD ].\}}	|dkrdd t|	D }qn||	 qn|  t	j
||d}
tdt|
 dt|
j d td td  td  }tdt|  tdt|
j  t|t|
j }|rtd t|d
d D ]}td|  qBt|dkr~tdt|d  d tdt| t|
jt| }|rtdt| d t|d
d D ]}td|  qt|dkrtdt|d  d td |
| }
td t|
j d! td }td" |d
d# D ]}||
jv rJ|
|   }|
|   }t|
}|dkrtd$| d%| d&| d'| d(	 ntd)| d%| d&| d* qJt|d#krtd+t|d#  d, d-|
jv rZ|
|
d-   d- d.}t|dkrZtd/ | D ]\}}td0| d%|  q:t  t|
}
|
S )1uI  
        Lee el archivo Excel y lo convierte a DataFrame
        
        - Lee valores calculados (data_only=True) en lugar de fórmulas
        
        Args:
            ruta_archivo (str): Ruta del archivo Excel
            
        Returns:
            pd.DataFrame: DataFrame con los datos del Excel (123 columnas)
        r   )load_workbooku0     Leyendo Excel (solo valores, sin fórmulas)...ScoreV2T)	data_onlyu   ❌ La hoja 'z/' no existe en el Excel.
   Hojas disponibles: zI
   Verifica el nombre de la hoja en ValidarScore.py y ActualizarScore.pyN)Zvalues_onlyc                 S   s*   g | ]"\}}|rt | nd | qS )ZColumn_)r   strip).0icellr   r   r   
<listcomp>      z9ScoreProcessor.leer_excel_a_dataframe.<locals>.<listcomp>)columnsu     ✓ Excel leído: z filas, z	 columnasr   r   r   u     🔍 Columnas esperadas: u     🔍 Columnas en Excel: u(   
  ❌ ERROR: Faltan columnas esperadas:
   z      - z      ... y u    columnas mászDEl Excel no tiene todas las columnas esperadas.
Columnas faltantes: u&   
  ⚠️  Columnas extra detectadas (z):u)     ℹ️  Estas columnas serán ignoradasu     ✓ DataFrame filtrado a z columnas esperadas
u2     🔍 Verificando valores en columnas formuladas:r   u       ⚠️  z: r   z
 valores (z NULL)u       ✓ z valoresz
    ... y u    columnas formuladas másr      u&   
  📅 Ejemplo de valores en FechaMG:z	    Fila )r  r0  r   
sheetnamesr   r   	enumerate	iter_rowsr   r#  	DataFramer  r9  CLASIFICACION_COLUMNASsetsortedisnasumnotnaheadr   limpiar_dataframe_para_sql)r   r	  r0  ZNOMBRE_HOJA_EXCELwbwsr   r   idxrowr(  Zcolumnas_esperadasZcolumnas_faltantescolZcolumnas_extraZcolumnas_formuladasZvalores_nullZvalores_no_nulltotalZvalores_ejemplovalr   r   r   r    s    
 
$ z%ScoreProcessor.leer_excel_a_dataframec           
      C   s   t  }|dkrd}d}nBd| }t|}| jj }t||}|rN|}d}n|d }d}d| }|rzt }	t||	 nt| t	| j| || j
d< || j
d< dS )	zX
        Guarda respaldo del Excel en ScoreHistory_X con versionado inteligente
        r      TZScoreHistory_Fr   r   N)r   r   r  r9  tolistcomparar_estructura_columnasr   r   r   r   r  )
r   Zversion_actualZversion_usarZcrear_nuevaZtabla_actualZcolumnas_historycolumnas_excelZson_igualesZtabla_destino
estructurar   r   r   r  X  s*    



z"ScoreProcessor.guardar_respaldo_bdc                 C   sR   t | _tdt| jd   tdt| jd   tdt| jd   dS )uh   
        Obtiene y asigna la clasificación de columnas (ahora usando clasificación estática)
        z  - Columnas ERP: r   z  - Columnas Manual: r   z  - Columnas Formuladas: r   N)r@  r  r   r  r   r   r   r   r    s    z*ScoreProcessor.obtener_estructura_columnasc                 C   s8  g }| j d }| j d }| j d }tdt| j d | j D ]\}}|d }i }|D ]}	|	|jv rZ||	 ||	< qZ| j| jd |k }
t|
dkr|
jd }
|D ]}	|	|
jv r|
|	 ||	< q|D ]}	|	|
jv r|
|	 ||	< qn$|D ]}	d||	< q|D ]}	d||	< q|| qBt	
|| _td	t| j d
 dS )u$  
        Mezcla df_score_excel y df_score_erp según las reglas de negocio
        
        REGLA PRINCIPAL:
        - df_score_erp "manda" → solo filas que existen en ERP
        - Columnas ERP: siempre del ERP
        - Columnas Manual/Formuladas: del Excel si existe, NULL si no
        r   r   r   z  Procesando z filas del ERP...r9   r   Nu     ✓ Mezcla completada: z filas resultantes)r  r   r  r  iterrowsr!  r  ilocr   r#  r?  r  )r   Zfilas_scorer   r   r   rJ  Zfila_erpZordernum_lineZ
fila_scorerL  
fila_excelr   r   r   r    s:    	








z!ScoreProcessor.mezclar_dataframesc                 C   s.   t  }|s"td t }t| ntd dS )zD
        Verifica si CM_Score existe y la crea si no existe
        u)     Tabla CM_Score no existe, creándola...z  Tabla CM_Score ya existeN)r   r   r   r   )r   ZexisterS  r   r   r   r    s    
z0ScoreProcessor.crear_tabla_cm_score_si_no_existec                 C   s   t d t| jd dS )zC
        Actualiza la tabla CM_Score con los datos finales
        r  N)r   r   r  r   r   r   r   r    s    z(ScoreProcessor.actualizar_tabla_cm_scorec                 C   s   t | j}t d}d| jd r(dnd d| jd r<dnd d	| jd
  d| jd  d| jd  d| jd  d| jd  d| jd  d| d| d}dd| jd
 | jd | jd | jd | jd | jd | jd |||dS )u   
        Genera la respuesta JSON de éxito con estadísticas
        
        Returns:
            dict: Respuesta completa
        z%Y-%m-%d %H:%M:%SuZ   
=== RESUMEN DE ACTUALIZACIÓN ===

✅ Validación exitosa
✅ Respaldo guardado en FTP: r   u   Síu   No (sin conexión)ut   
✅ Datos del ERP obtenidos
✅ Mezcla de datos completada
✅ Tabla CM_Score actualizada
# 'OneDrive actualizado: r   ZNou,   '


📊 Estadísticas:
- Filas procesadas: r   z
- Filas del ERP: r   z
- Filas del Excel: r   z
- Filas eliminadas: r   u.   

🗄️  Respaldo:
- Versión ScoreHistory: r   z

- Tabla: r   u   

⏱️  Tiempo: u   
📅 Fecha: r   TzScore actualizado correctamente)r  r-  r   r   r   r   r   r   r   tiempo_ejecucion	timestampresumen)calcular_tiempo_ejecucionr   r   r   strftimer  )r   rW  rX  rY  r   r   r   r    sH    
z(ScoreProcessor.generar_respuesta_exitosaN)r   r   r   r   r   r  r  r  r  r  r  r  r  r  r  r  r  r   r   r   r   r     s     	 .<
r   c                  C   s   t  d} d|  dS )zu
    Genera nombre con formato: Score_YYYY-MM-DD_HH-MM-SS.xlsx
    
    Returns:
        str: Nombre del archivo
    z%Y-%m-%d_%H-%M-%SScore_z.xlsx)r   r   r[  )rX  r   r   r   r/    s    r/  c                    s   ddl  ddlm} td |  | d} | jD ]>}| | jdkr4td|   fdd}| | || |< q4| jD ](}| | jd	krz| | d
d | |< qztd | S )u   
    Limpia DataFrame antes de insertar en SQL
    
    ✅ VERSIÓN CORREGIDA: Maneja correctamente fechas 1900-01-01
    
    Args:
        df (pd.DataFrame): DataFrame a limpiar
        
    Returns:
        pd.DataFrame: DataFrame limpio
    r   Nr   z  Limpiando datos para SQL...zdatetime64[ns]z     Limpiando columna de fecha: c                    s   | du s  | rdS t|  jrP|  }|jdkrL|jdkrL|jdkrLdS |S t| tr~|  dkrjdS d| v szd| v r~dS | S )u"   Convierte fechas inválidas a NoneNil  rO  r   z
1900-01-01z
1900/01/01)	rC  
isinstance	Timestampto_pydatetimeyearmonthdayr   r3  )xZfechar#  r   r   limpiar_fecha>  s    
z1limpiar_dataframe_para_sql.<locals>.limpiar_fechaobjectc                 S   s8   | d u st | tr"|  dkr"d S t | tr4|  S | S )Nr   )r]  r   r3  )rc  r   r   r   <lambda>c  r8  z,limpiar_dataframe_para_sql.<locals>.<lambda>u     ✓ Datos limpiados)pandasr   r   whererE  r9  dtypeapply)r(  r   rL  re  r   rd  r   rG  %  s     


rG  c                 C   s    t t| }t t|}||kS )z
    Compara dos listas de columnas
    
    Args:
        cols1 (list): Primera lista
        cols2 (list): Segunda lista
        
    Returns:
        bool: True si son iguales
    )rA  rB  )Zcols1Zcols2Zset1Zset2r   r   r   rQ  k  s    rQ  c                 C   s@   t  }||  }| }t|d }t|d }| d| dS )z
    Calcula tiempo transcurrido
    
    Args:
        tiempo_inicio (datetime): Tiempo de inicio
        
    Returns:
        str: Tiempo formateado (ej: "2m 34s")
    <   zm s)r   r   total_secondsint)r   Z
tiempo_findeltaZsegundosZminutosZsegsr   r   r   rZ  }  s    
rZ  c                 C   sX   | j ddgddd }| j ddgddd	 }|  d
dd }|  ddd }dS )u   
    Registra las rutas de actualización del Score en Flask
    
    Args:
        app: Instancia de Flask
        mail: Instancia de Flask-Mail
    z%/SyC/IngenieroControl/ActualizarScoreGET)methodsc                   S   s   t dS )zx
        Renderiza la vista HTML principal
        
        Returns:
            render_template: Template HTML
        z/SupYCtrol/IngenieroControl/ActualizarScore.html)r   r   r   r   r   vista_actualizar_score  s    z<ejecutar_actualizacion_score.<locals>.vista_actualizar_scorez./SyC/IngenieroControl/ActualizarScore/ejecutarPOSTc               
   S   s|   z6t  } |  }|d r&t|dfW S t|dfW S W n@ tyv } z(tddt| ddfW  Y d}~S d}~0 0 dS )	u   
        Ejecuta el proceso completo de actualización del Score
        
        Returns:
            jsonify: Respuesta JSON con resultado
        r  r     Fu   Error crítico: r  r+  N)r   r  r   r   r   )	processorr.  r   r   r   r   ejecutar_actualizacion  s    z<ejecutar_actualizacion_score.<locals>.ejecutar_actualizacionz/onedrive/loginc                  S   sR   ddl } td jtd d}| jtd td |d}|jtd	 td
 d}t|S )u    Inicia el flujo de autorizaciónr   Nr%   r   r   r   r    r   r"   r!   )r"   r!   )r   r   r   r   Zget_authorization_request_urlr   )r   r%   app_msalZauth_urlr   r   r   onedrive_login  s    z4ejecutar_actualizacion_score.<locals>.onedrive_loginz	/callbackc                  S   s   ddl } ddl}tjd}|s$dS td jtd d}| jtd td	 |d
}|j|td td d}d|v rt	td d}|
|| W d   n1 s0    Y  dS d|d| dfS dS )u/   Callback que recibe el código de autorizaciónr   Ncode)u.   Error: No se recibió código de autorizaciónru  r%   r   r   r   r    r   r"   r!   )rz  r"   r!   r   r'   r   u  
            <html>
                <body style="font-family: Arial; padding: 50px; text-align: center;">
                    <h1 style="color: green;">✅ Autorización Exitosa</h1>
                    <p>OneDrive ha sido autorizado correctamente.</p>
                    <p>Los tokens se han guardado y se renovarán automáticamente.</p>
                    <p>Puedes cerrar esta ventana.</p>
                </body>
            </html>
            zError al obtener tokens: Zerror_descriptionr+  )r   r   r   argsr   r   r   r   Z#acquire_token_by_authorization_coder   r   )r   r   rz  r%   rx  r   r   r   r   r   onedrive_callback  s.    *z7ejecutar_actualizacion_score.<locals>.onedrive_callbackN)route)appmailrs  rw  ry  r|  r   r   r   ejecutar_actualizacion_score  s    	
	

r  )(r   r   dotenvr   rh  r#  r   r   flaskr   r   r   r   Consultas_SQL.conexionr	   r
   Z7Consultas_SQL.SupYCtrol.IngDeControl.ActualizarScoreSQLr   r   r   r   r   r   r   r   r   2App.SupyCtrol_Module.IngenieroControl.ValidarScorer   getenvr   r   r@  r   r   r   r/  rG  rQ  rZ  r  r   r   r   r   <module>   s\   ,
"
' Y     AF