
    sz0i                     $   S r SSKrSSKJr  SSKrSSKrSSKJr  SSKJ	r	J
r
JrJr  SSKJrJr  SSKJrJrJrJrJrJrJrJrJr  SSKJr  \" 5         \R8                  " S	5      \R8                  " S
5      \R8                  " S5      SS.rS\R8                  " S5      \R8                  " S5      \R8                  " S5      SS/\R8                  " S5      SSSSS.r/ SQ/ SQ/ SQS.r " S S5      r  " S  S!5      r! " S" S#5      r"S$ r#S% r$S& r%S' r&S( r'g))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_actualizacionFTP_HOSTFTP_USERFTP_PASSW/domains/sycelephant.com/public_html/file/SyC/IngenieroControl/RespaldoActualizarScore/)hostuserpasswordremote_pathF	TENANT_IDONEDRIVE_CLIENT_IDONEDRIVE_CLIENT_SECRETzhttp://localhost:5000/callbackzFiles.ReadWriteONEDRIVE_USERNAME.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
NeedByDateFechaAnticipoLiberacionCXP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ComentarioLINEOvEEUUClienteEEUU)$ProdCodeComentarioSyCChkFechaSyCConsideradaPreasignacionConsideradaMaterialesu   FechaProducciónu   ComentarioProducciónu   AvanceProducciónu   FechaPlaneaciónEstadoFechau   ComentarioPlaneaciónu   FechaInicialPlaneaciónMotivoInicialFechaMatAvisoDeTerminacionTerminadoConFaltanteu   FechaActualizaciónMaterialFaltante	Auxiliar1	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                   6    \ rS rSrSrS rS rS rS rS r	Sr
g	)

FTPManagerz   u   
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                     [         S   U l        [         S   U l        [         S   U l        [         S   U l        S U l        g )Nr   r   r   r   )
FTP_CONFIGr   r   r   r   ftpselfs    tC:\Users\victor.barrera\Documents\proyectos\elepV3\Elep\src\App\SupyCtrol_Module\IngenieroControl\ActualizarScore.py__init__FTPManager.__init__   s:    v&	v&	":.%m4    c                 \    [        S5        [        R                  " U R                  5      U l        U R                  R                  U R                  U R                  5        [        S5        g! [         a3  n[        S[        U5       35        [        S[        U5       35      eSnAff = f)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   es     r   conectarFTPManager.conectar   s    	A'(zz$)),DHHHNN499dmm445 	A1#a&:;6s1vh?@@	As   A+A. .
B+8.B&&B+c                 	    [        S5         U R                  R                  S5        [        S5         U R                  R                  S5        [        S5         U R                  R                  S	5        [        S
5         U R                  R                  S5        [        S5         U R                  R                  S5        [        S5        / nU R                  R	                  SUR
                  5        [        SSR                  USS 5       35        SU;   d  SU;   a  [        S5        O[        S5        [        S5          U R                  R                  S5        [        S5         U R                  R                  S 5        [        S!5         U R                  R                  S%5        [        S&5        U R                  R                  5       n[        S*5        [        S+U 35        X0R                  :X  a  [        S,5        g0[        S-5        [        S.U R                   35        [        S/U 35        g0!    GN= f! [         a  n[        SU 35         SnAgSnAff = f! [         a  n[        SU 35         SnAgSnAff = f! [         a  n[        SU 35         SnAgSnAff = f! [         a  n[        SU 35         SnAgSnAff = f! [        R                   a     U R                  R                  S5        U R                  R                  S5        U R                  R                  S5        [        S5         GN! [         a  n[        SU 35         SnA gSnAff = ff = f! [        R                   a     U R                  R                  S5        U R                  R                  S"5        U R                  R                  S"5        [        S#5         GNU! [         a  n[        S$U 35         SnA gSnAff = ff = f! [        R                   a     U R                  R                  S 5        U R                  R                  S'5        U R                  R                  S'5        [        S(5         GN! [         a  n[        S)U 35         SnA gSnAff = ff = f! [         a!  n[        S1[        U5       35         SnAgSnAff = f)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,    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: RespaldoActualizarScore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   itemsubicacion_actuals       r   crear_estructura_carpetas$FTPManager.crear_estructura_carpetas   s   s	@AS!-/
Z()+789;CDEGHIJL ""65<<81$))E"1I2F1GHI&-5*@fhVXBD!LM13!]^>@!vwEG  $xx||~46+,<+=>?  #3#33CE 	 VXd&6&6%789&6%789Q  =aSAB  DQCHI  @DE,  9!=> $$ !!HHLL!LMHHLL'HHLL'57  !8<= !! $$ !!HHLL!PQHHLL!34HHLL!34BD  !EaSIJ !! $$ !!HHLL!abHHLL!:;HHLL!:;IK  !LQCPQ !!2  	?AxHI	s  R2 &H< &I &I) &J *BJ3 /R2 0J3 R2 &K /&M6 &P <AR2 
1R2 <I>R2 
I&I!R2 !I&&R2 )
J3JR2 JR2 
J0J+&R2 +J00R2 3
K=KR2 KR2 M3-AM	R2 
M/M*$M3(R2 *M//M33R2 6PAO*'R2 *
P4PPR2 PPR2 R/)ARR2 
R+R& R/$R2 &R++R//R2 2
S<SSc                     U R                   c  U R                  5         U R                  5         [        S5        [        SU 35        [        SU R                   35        [        US5       nU R                   R                  SU 3U5        SSS5        [        SU 35        [        SU S	35        g
! , (       d  f       N,= f! [         a3  n[        S[        U5       35        [        S[        U5       35      eSnAff = f)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   
local_pathremote_filenamefiler   s        r   subir_archivoFTPManager.subir_archivo  s    	?xx **,/1K012K 0 0123j$'4##eO+<$=tD ( 5o5FGHhixhyy{|} ('  	?6s1vh?@4SVH=>>	?s0   A*C , B2%C 2
C <C 
D .C;;D c                      U R                   b&  U R                   R                  5         [        S5        gg!    U R                   b  U R                   R                  5          g g!     g= f= f)u   Cierra la conexión FTPNu   ✓ Desconectado de FTP)r   quitr   closer   s    r   desconectarFTPManager.desconectar@  s^    		xx#/0 $	88'HHNN$ (s    26 A,'A$$A)&A,)A,)r   r   r   r   r   N)__name__
__module____qualname____firstlineno____doc__r   r   r   r   r   __static_attributes__ r   r   r   r   z   s$    A(||#?Jr   r   c                   <    \ rS rSrSrS rS rS rS rS r	S r
S	rg
)OneDriveManageriR  u   
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                 V   SS K nSS Kn[        U l        U R                  S   U l        U R                  S   R                  U R                  S   S9nUR                  U R                  S   U R                  S   US9U l        U R                  S	   U l        U R                  5       U l
        g )
Nr   r,   r+   r$   r$   r%   r&   r%   client_credentialr+   r-   )msalosONEDRIVE_CONFIGconfigr,   formatConfidentialClientApplicationmsal_appr-   _load_token_cachetoken_cache)r   r   r   r+   s       r   r   OneDriveManager.__init__\  s    %[1 KK,33kk+. 4 
	 ::kk+."kk/: ; 
 !%,> ?113r   c                    SSK nSSKnUR                  R                  U R                  5      (       a2   [        U R                  S5       nUR                  U5      sSSS5        $ 0 $ ! , (       d  f       0 $ = f!   0 s $ = f)z"Carga tokens guardados del archivor   Nr)r   jsonpathexistsr-   r   load)r   r   r   fs       r   r   !OneDriveManager._load_token_cacher  sm    77>>$//00$//599Q< 65 		 65 		s)   A9 
A'	A9 '
A61A9 6A9 9A?c                 
   SSK n [        U R                  S5       nUR                  X5        SSS5        [	        SU R                   35        g! , (       d  f       N'= f! [
         a  n[	        SU 35         SnAgSnAff = f)zGuarda tokens en archivo localr   Nwu     ✓ Tokens guardados en u)     ⚠️ No se pudo guardar token cache: )r   r   r-   dumpr   r   )r   token_responser   r   r   s        r   _save_token_cache!OneDriveManager._save_token_cache  st    	Cd++S1Q		., 2.t/D/D.EFG 21  	C=aSABB	Cs-   A  A A  
AA   
B*A==Bc                    U R                   (       a*  SU R                   ;   a  [        S5        U R                   S   $ U R                   (       av  SU R                   ;   af  [        S5        U R                  R                  U R                   S   U R                  S   S9nSU;   a!  [        S5        U R                  U5        US   $ [        S5      e)	ui   
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/login)r   r   r   acquire_token_by_refresh_tokenr   r  r   )r   results     r   obtener_access_token$OneDriveManager.obtener_access_token  s     $2B2B B01##N33 43C3C C:;]]AA"..?{{8, B F
 '9:&&v.n-- 4
 	
r   c                 v   SSK nSSU 30nU R                  S   nU R                   SU 3nUR                  XSSS9nUR                  S	:X  a/  UR                  5       n[        S
UR                  S5       35        U$ UR                  S:X  a  [        S5      e[        SUR                   SUR                   35      e)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datas           r   buscar_archivoOneDriveManager.buscar_archivo  s     w|n5
 ++i(  0	:<<b<A3&==?DODHHV$4#567N!!S(QSS78L8L7MSQYQ^Q^P_`aar   c                     SSK nSU 3SS.nU R                   SU S3nUR                  UUUSS	9nUR                  S
;   a  g[	        SUR                   SUR
                   35      e)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  s           r   actualizar_archivo"OneDriveManager.actualizar_archivo  s      '|n5_

   0	B<<	   
 :-;H<P<P;QQTU]UbUbTcdeer   )r   r,   r   r   r-   N)r   r   r   r   r   r   r   r  r  r  r"  r   r   r   r   r   r   R  s)    4,	C 
Db,fr   r   c                   f    \ rS rSrSrS rS rS rS rS r	S r
S	 rS
 rS rS rS rS rS rSrg)ScoreProcessori  uH   
Clase principal que maneja todo el proceso de actualización del Score
c           	          SU l         SU l        [        5       U l        [        R
                  " 5       U l        [        5       U l        SU l	        SU l
        SU l        SU l        SSSSSSSSS.U l        g)z9Inicializa el procesador con conexiones y configuracionesNr    F)filas_excel	filas_erpfilas_procesadasfilas_eliminadasversion_historytabla_historyrespaldo_ftponedrive_actualizado)conn_erpconn_vpsr   ftp_managerr   nowtiempo_inicior   onedrive_managerdf_score_exceldf_score_erpdf_score_finalclasificacion_columnasstatsr   s    r   r   ScoreProcessor.__init__  sz    %<%\\^ / 1 # " '+#  ! ! !$)	

r   c                     [        S5        [        S5        [        S5        [        S5        U R                  5       nUS   (       d  SSUS.$ US	   n[        S
U S35        [        S5         U R                  U5        SU R                  S'   [        S5        U R                  U5      U l        [        U R                  5      U R                  S'   [        SU R                  S    S35        [        S5        U R                  5         [        SU R                  S    S35        [        S5        U R                  5         [        S5        [        S5         [        5       U l        [        U R                  5      U R                  S'   [        SU R                  S    S35        [        S+5        U R                  5         [        U R                  5      U R                  S,'   U R                  S   U R                  S,   -
  U R                  S-'   [        S.U R                  S,    S35        [        S/5        U R                  5         [        S05        [        S15        U R!                  5         [        S25        [        S35        ["        S4   (       a!   U R%                  5         SU R                  S5'   O[        S75        [        S85        U R'                  5       n[        S5        [        S95        [        S5        U$ ! [         a-  n[        S[        U5       35        [        S5         SnAGNSnAff = f! [         aE  n[        U5      nSU;   d  SU;   a  S nOS!U;   a  S"nOS#U;   a  S$nOS%nSS&S'UU/ S(QS).S*.s SnA$ SnAff = f! [         a<  n[        S6[        U5       35        [        S5        SU R                  S5'    SnAGNSnAff = f! [         a0  n[        S:[        U5       S35        S[        U5      S;.s SnA$ SnAff = f)<us   
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ÍTICOservidor_inaccesibleTIMEOUTr  u   AUTENTICACIÓNautenticacionerror_desconocidoz No se pudo obtener datos del ERP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_errorerror_tecnicosugerencias)r=  r>  rF  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_dataframer6  lenguardar_respaldo_bdobtener_estructura_columnasr   r7  mezclar_dataframesr8  !crear_tabla_cm_score_si_no_existeactualizar_tabla_cm_scorer   actualizar_onedrivegenerar_respuesta_exitosa)r   resultado_validacionr@  r   	error_msgrF  	respuestas          r   ejecutar_actualizacion_completa.ScoreProcessor.ejecutar_actualizacion_completa  s   ~	- AB-  EF#'#5#5#7 '	2$; 4  0?L6|nBGH ;<2)),7-1

>* ;<"&"="=l"KD(+D,?,?(@DJJ}%&tzz-'@&AJK DE$$&-djj.I-J"MN EF,,.;< :;!$;$=!*-d.?.?*@

;'1$**[2I1J(STB 67##%-01D1D-EDJJ)*-1ZZ-FTfIg-gDJJ)*+DJJ7I,J+K8TU <=224./ 78**,;< CDy)?,,.9=DJJ56 FG 67668I- 34-   2I#a&RS01124  F	 /);?OS\?\!7J)+!*J%2!0J!4J  %?"8&0)2(
!	 l ! ?I#a&RS459>DJJ56?"  	/Axr:; Q 	s   AO O . K7 CO AL1 #C*O N -A	O 7
L."L)#O )L..O 1
N ;:M;5N 6O ;N  O 
O	1O>O O		O 
P%P;PPc                 @    [        S5        U R                  R                  5       n[        S5        [        S5        U R                  R                  U5      n[        S5        [        S5        U R	                  U R
                  5      n[        S[        U5      S S35        [        S	5        U R                  R                  XU5        [        S
5        [        S[        U R
                  5       S35        g! [         a  n[        S[        U5       35      eSnAff = f)z2
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   r5  r  r  dataframe_a_excel_bytesr8  rM  r"  r   r   )r   r  r*   r!  r   s        r   rS  "ScoreProcessor.actualizar_onedrive  s   	G0100EEGL() 46++::<HG,. 4566t7J7JKK*3{+;A*>gFG 45!!44\KX9:F3t22344EFG 	G<SVHEFF	Gs   C4C7 7
DDDc                     SSK Jn  U" 5       n[        R                  " USS9 nUR	                  USSS9  SSS5        UR                  S5        UR                  5       $ ! , (       d  f       N/= f)	z1
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writers        r   r\  &ScoreProcessor.dataframe_a_excel_bytes  sY     	 ^^F:6&KK:UKC 7 	A{{}	 76s   A
A,c                 n     [        5       nU$ ! [         a  nSSS[        U5       3S.s SnA$ SnAff = f)uc   
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   s      r   rJ  ScoreProcessor.validar_excel  sC    
	8:I 	 ;CF8D 	s    
4/44c                     [        5       nU R                  R                  X5        U R                  R                  5         g)zj
Guarda respaldo del Excel en el servidor FTP

Args:
    ruta_archivo (str): Ruta del archivo a respaldar
N)generar_nombre_respaldor2  r   r   )r   r@  nombre_respaldos      r   rK  #ScoreProcessor.guardar_respaldo_ftp  s2     23&&|E$$&r   c                    SSK Jn  [        S5        SnU" USS9nX4R                  ;  a,  UR	                  5         [        SU SUR                   S	35      eXC   n/ nS
n[        UR                  SS95       H`  u  pUS:X  aD  [        U	5       V
Vs/ s H*  u  pU(       a  [        U5      R                  5       OSU
 3PM,     nn
nMO  UR                  U	5        Mb     UR	                  5         [        R                  " XgS9n[        S[        U5       S[        UR                  5       S35        [        S   [        S   -   [        S   -   n[        S[        U5       35        [        S[        UR                  5       35        [!        U5      [!        UR                  5      -
  nU(       ao  [        S5        [#        U5      S
S  H  n[        SU 35        M     [        U5      S:  a  [        S[        U5      S-
   S35        [        S[        U5       35      e[!        UR                  5      [!        U5      -
  nU(       ap  [        S[        U5       S35        [#        U5      S
S  H  n[        SU 35        M     [        U5      S:  a  [        S[        U5      S-
   S35        [        S5        X   n[        S[        UR                  5       S 35        [        S   n[        S!5        US
S"  H  nXR                  ;   d  M  X   R%                  5       R'                  5       nX   R)                  5       R'                  5       n[        U5      nUS:  a  [        S#U S$U S%U S&U S'3	5        M  [        S(U S$U S%U S)35        M     [        U5      S":  a  [        S*[        U5      S"-
   S+35        S,UR                  ;   al  XS,   R)                  5          S,   R+                  S-5      n[        U5      S:  a6  [        S.5        UR-                  5        H  u  nn[        S/U S$U 35        M     [        5         [/        U5      nU$ s  snn
f )0u   
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)values_onlyColumn_)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`  rz  r   
sheetnamesr   r   	enumerate	iter_rowsr   stripr   rf  	DataFramerM  r  CLASIFICACION_COLUMNASsetsortedisnasumnotnaheadr   limpiar_dataframe_para_sql)r   r@  rz  NOMBRE_HOJA_EXCELwbwsr  r  idxrowicellrk  columnas_esperadascolumnas_faltantescolcolumnas_extracolumnas_formuladasvalores_nullvalores_no_nulltotalvalores_ejemplovals                          r   rL  %ScoreProcessor.leer_excel_a_dataframe  sM    	+@A
 & <48 MM1HHJ 12 3))+ 8[\  " !",,4,"@AHCaxW`adWefWeGA3t9??,GA3-GWef C  B 	
 \\$0$SWIXc"**o5FiPQ #>2"#456"#789 	 	+C0B,C+DEF*3rzz?*;<= !!34s2::F=?01#26&' 7%&+S);%<r%A$B.QR''*+='>&?A  RZZ3/A+BB;C<O;PPRSTn-cr2&' 3>"R'S%82%=$>nMN=? #+C

O+<<QRS
 55IJBC&r*Cjj !w||~113"$'--/"5"5"7B!#LR/@%
S_R``fghHSEO+<AeWHMN + "#a'Js#67!;<<UVW 

" I!4!4!67	BGGJO?#a'?A / 5 5 7HCIcU"SE23 !8 	
 (+	q gs   1Qc                    [        5       nUS:X  a  SnSnORSU 3n[        U5      nU R                  R                  R	                  5       n[        Xe5      nU(       a  UnSnOUS-   nSnSU 3nU(       a  [        5       n	[        X)5        O[        U5        [        U R                  U5        X R                  S'   XR                  S'   g)	zH
Guarda respaldo del Excel en ScoreHistory_X con versionado inteligente
r      TScoreHistory_Fr,  r-  N)r   r   r6  r  tolistcomparar_estructura_columnasr   r   r   r   r:  )
r   version_actualversion_usarcrear_nuevatabla_actualcolumnas_historycolumnas_excelson_igualestabla_destino
estructuras
             r   rN  "ScoreProcessor.guardar_respaldo_bdX  s    
 89QLK +>*:;L5lC!0088??AN6~XK-#  .1" (~657J$\> =) 	D//? )5

$%&3

?#r   c                     [         U l        [        S[        U R                  S   5       35        [        S[        U R                  S   5       35        [        S[        U R                  S   5       35        g)uX   
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  r9  r   rM  r   s    r   rO  *ScoreProcessor.obtener_estructura_columnas  sq    
 '=#"3t'B'B>'R#S"TUV%c$*E*EFW*X&Y%Z[\)#d.I.IJ^._*`)abcr   c                    / nU R                   S   nU R                   S   nU R                   S   n[        S[        U R                  5       S35        U R                  R	                  5        H  u  pVUS   n0 nU H  n	XR
                  ;   d  M  Xi   X'   M     U R                  U R                  S   U:H     n
[        U
5      S:  aP  U
R                  S   n
U H  n	XR
                  ;   d  M  X   X'   M     U H  n	XR
                  ;   d  M  X   X'   M     OU H  n	SX'   M	     U H  n	SX'   M	     UR                  U5        M     [        R                  " U5      U l        [        S	[        U R                  5       S
35        g)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...rA   r   Nu     ✓ Mezcla completada: z filas resultantes)r9  r   rM  r7  iterrowsrd  r6  ilocr   rf  r  r8  )r   filas_scorer   r   r   r  fila_erpordernum_line
fila_scorer  
fila_excels              r   rP  !ScoreProcessor.mezclar_dataframes  s    22>B556GH!889MNc$"3"3455FGH!..779MC$_5M J $..(&.mJO $
 ,,##O4EJ :"'__Q/
*C...*4/
 + .C...*4/
 .
 +C&*JO + .C&*JO . z*I :N !ll;7)#d.A.A*B)CCUVWr   c                 ~    [        5       nU(       d!  [        S5        [        5       n[        U5        g[        S5        g)z4
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   exister  s      r   rQ  0ScoreProcessor.crear_tabla_cm_score_si_no_existe  s1     *+=>57J ,./r   c                 F    [        S5        [        U R                  S5        g)z3
Actualiza la tabla CM_Score con los datos finales
rb  N)r   r   r8  r   s    r   rR  (ScoreProcessor.actualizar_tabla_cm_score  s    
 	z" 	D//<r   c                 ~   [        U R                  5      n[        R                  " 5       R	                  S5      nSU R
                  S   (       a  SOS SU R
                  S   (       a  SOS S	U R
                  S
    SU R
                  S    SU R
                  S    SU R
                  S    SU R
                  S    SU R
                  S    SU SU S3nSSU R
                  S
   U R
                  S   U R
                  S   U R
                  S   U R
                  S   U R
                  S   U R
                  S   UUU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/  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=  rr  r*  r)  r(  r+  r,  r-  r.  tiempo_ejecucion	timestampresumen)calcular_tiempo_ejecucionr4  r   r3  strftimer:  )r   r  r  r  s       r   rT  (ScoreProcessor.generar_respuesta_exitosa  sv    5T5G5GHLLN++,?@	 )-

>(BuH\] ^ $(::.D#E%4P Q ZZ 234 5**[)* +JJ}-. /ZZ 234 5 **%678 9

**_
%	& '!" #K -4 8 $

+= >K0::m4 $

+= >#zz*;<!ZZ8 JJ~6 0"
 	
r   )
r9  r0  r1  r7  r6  r8  r2  r5  r:  r4  N)r   r   r   r   r   r   rX  rS  r\  rJ  rK  rL  rN  rO  rP  rQ  rR  rT  r   r   r   r   r%  r%    sQ    
8EPG> &	'CJ,4\	d:Xx0=0
r   r%  c                  V    [         R                  " 5       R                  S5      n SU  S3$ )za
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   r3  r  )r  s    r   rv  rv    s*     ''(;<II;e$$r   c                   ^ SSK mSSKJn  [        S5        U R                  TR                  " U 5      S5      n U R
                   H@  nX   R                  S:X  d  M  [        SU 35        U4S jnX   R                  U5      X'   MB     U R
                   H-  nX   R                  S:X  d  M  X   R                  S	 5      X'   M/     [        S
5        U $ )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                 h  > U b  TR                   " U 5      (       a  g[        U TR                  5      (       aC  U R                  5       nUR                  S:X  a!  UR
                  S:X  a  UR                  S:X  a  gU$ [        U [        5      (       a"  U R                  5       S:X  a  gSU ;   d  SU ;   a  gU $ )u"   Convierte fechas inválidas a NoneNil  r  r'  z
1900-01-01z
1900/01/01)	r  
isinstance	Timestampto_pydatetimeyearmonthdayr   r  )xfecharf  s     r   limpiar_fecha1limpiar_dataframe_para_sql.<locals>.limpiar_fecha>  s     9

 a..OO-E zzT)ekkQ.>599PQ># !L a%%wwyB# $q(LA,=# r   objectc                     U b)  [        U [        5      (       a  U R                  5       S:X  a  S $ [        U [        5      (       a  U R                  5       $ U $ )Nr'  )r  r   r  )r  s    r   <lambda>,limpiar_dataframe_para_sql.<locals>.<lambda>c  sU    19As1C1C	UW$  Hmwxy{~mm`a`g`g`i  H  FG  Hr   u     ✓ Datos limpiados)pandasr   r   wherer  r  dtypeapply)rk  r   r  r  rf  s       @r   r  r  %  s     !	
)* 
"((2,	%B zz7==,,4SE:;> gmmM2BGG L zz7==H$gmm HBG  

!"Ir   c                 Z    [        [        U 5      5      n[        [        U5      5      nX#:H  $ )z
Compara dos listas de columnas

Args:
    cols1 (list): Primera lista
    cols2 (list): Segunda lista
    
Returns:
    bool: True si son iguales
)r  r  )cols1cols2set1set2s       r   r  r  k  s(     ve}Dve}D<r   c                     [         R                  " 5       nX-
  nUR                  5       n[        US-  5      n[        US-  5      nU SU S3$ )z
Calcula tiempo transcurrido

Args:
    tiempo_inicio (datetime): Tiempo de inicio
    
Returns:
    str: Tiempo formateado (ej: "2m 34s")
<   zm s)r   r3  total_secondsint)r4  
tiempo_findeltasegundosminutossegss         r   r  r  }  sT     J&E""$H(b.!Gx"}DYba  r   c                     U R                  SS/S9S 5       nU R                  SS/S9S 5       nU R                  S5      S	 5       nU R                  S
5      S 5       ng)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5      $ )zP
Renderiza la vista HTML principal

Returns:
    render_template: Template HTML
z/SupYCtrol/IngenieroControl/ActualizarScore.html)r   r   r   r   vista_actualizar_score<ejecutar_actualizacion_score.<locals>.vista_actualizar_score  s     PQQr   z./SyC/IngenieroControl/ActualizarScore/ejecutarPOSTc                       [        5       n U R                  5       nUS   (       a  [        U5      S4$ [        U5      S4$ ! [         a&  n[        SS[	        U5       3S.5      S4s SnA$ SnAff = f)	um   
Ejecuta el proceso completo de actualización del Score

Returns:
    jsonify: Respuesta JSON con resultado
r=  r    Fu   Error crítico: rI  rp  N)r%  rX  r   r   r   )	processorrs  r   s      r   ejecutar_actualizacion<ejecutar_actualizacion_score.<locals>.ejecutar_actualizacion  s    	&(I "AACI#y)3..y)3.. 	 +CF84   	s"   0A  A   
A0
A+%A0+A0z/onedrive/loginc                      SSK n [        S   R                  [        S   S9nU R                  [        S   [        S   US9nUR	                  [        S	   [        S
   S9n[        U5      $ )u    Inicia el flujo de autorizaciónr   Nr+   r$   r   r%   r&   r   r(   r'   )r(   r'   )r   r   r   r   get_authorization_request_urlr	   )r   r+   app_msalauth_urls       r   onedrive_login4ejecutar_actualizacion_score.<locals>.onedrive_login  s     	#K077%k2 8 
	 55%k2-o> 6 
 99"8,(8 : 

 !!r   z	/callbackc                     SSK n SSKn[        R                  R	                  S5      nU(       d  g[
        S   R                  [
        S   S9nU R                  [
        S   [
        S	   US
9nUR                  U[
        S   [
        S   S9nSU;   a.  [        [
        S   S5       nUR                  XV5        SSS5        gSUR	                  SU5       3S4$ ! , (       d  f       g= f)u/   Callback que recibe el código de autorizaciónr   Ncode)u.   Error: No se recibió código de autorizaciónr  r+   r$   r   r%   r&   r   r(   r'   )r   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: error_descriptionrp  )r   r   r   argsr  r   r   r   #acquire_token_by_authorization_coder   r   )r   r   r   r+   r  r  r   s          r   onedrive_callback7ejecutar_actualizacion_score.<locals>.onedrive_callback  s     	||'H#K077%k2 8 
	 55%k2-o> 6 
 =="8,(8 > 
 V#o&893?1		&$ @	 /vzz:Mv/V.WXZ]]] @?	s   $C
C$N)route)appmailr  r  r  r  s         r   ejecutar_actualizacion_scorer	    s     	YY6YHR IR 	YY?&YR S2 	YY !" "", 	YY{*^ *^r   )(r   r   dotenvr   r  rf  r   r   flaskr   r   r   r	   Consultas_SQL.conexionr
   r   7Consultas_SQL.SupYCtrol.IngDeControl.ActualizarScoreSQLr   r   r   r   r   r   r   r   r   2App.SupyCtrol_Module.IngenieroControl.ValidarScorer   getenvr   r   r  r   r   r%  rv  r  r  r  r	  r   r   r   <module>r     s5   
     = = D
 
 
 `
  IIj!IIj!		*%l	
  ;'/0YY78 5 	
 ))/0 @ A3 47D 	7! NQ QpEf EfVw
 w
z%CL$!0o^r   