U
    i!!                    @   s  d dl mZmZmZmZmZmZm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 d dlmZ d dlZd dlmZ d dlmZmZ d d	lmZ d d
lmZ d dlmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(mZ e
)dZ*dd Z+dd Z,dd Z-e.e/e0e.dddZ1e.e/e0e.dddZ2ee e/ee/ e0edddZ3e.dddZ4e/e/e5e0e/e.ddd Z6d!d" Z7d#d$ Z8dS )%    )apprender_templaterequestsessionjsonifyredirecturl_for)wrapsNdatetime)ListDict)Path)IS_PRODUCTION)verificar_configuracion upload_document_to_existing_docs)upload_document)
CRMManager)buscar_oportunidad_crminsert_CRM_CONTACTverificar_oportunidad_existentecrear_actualizar_oportunidadobtener_quotation_type_idcrear_formulario_headcrear_formulario_detailsobtener_seller_user_idobtener_nombre_usuariocrear_tarea_cotizaciondebe_crear_tareaactualizar_formulario_con_docsget_FormID_by_TaskIDr   Zcotizaciones_especialesc                    s     d fdd}  ddd } j ddgdd	d
 } j ddgd fdd} j ddgddd } j ddgddd }dS )u   
    Función principal que registra todas las rutas del módulo de cotizaciones especiales
    
    Args:
        app: Instancia de Flask
        mail: Instancia del sistema de mail
    z)/Ventas/Cotiz/EspecialSolicitud/GetConfigc               
      s   z,t d jd jd jddW S  tk
r| }  z2tdt|   t dt| dd	f W Y S d
} ~ X Y nX d
S )ub   
        Endpoint para que el frontend consulte el modo de operación (Prototipo o Real).
        T	PROTOTIPOPROTOTIPO_INTERFAZEMAIL_PROTOTYPE_MODE)successZPROTOTIPO_MODEr"   r#   u$   ❌ Error al obtener configuracion: Fr$   error  N)r   configget	Exceptionloggerr&   str)er    hC:\Users\victor.barrera\Documents\proyectos\elepV3\Elep\src\App\Ventas_Module\Cotiz\CotizEspSolicitud.pyget_cotiz_config3   s    



z-cotiz_esp_solicitud.<locals>.get_cotiz_configc                  S   s&   ddl m}  tdt| jddiS )Nr   current_appZ	prototipor!   F)flaskr3   r   boolr(   r)   r2   r/   r/   r0   is_prototipo   s
     z)cotiz_esp_solicitud.<locals>.is_prototipoz-/Ventas/Cotiz/EspecialSolicitud/InsertContactPOST)methodsc               
   S   s   t jdd} | s"tddddfS | d}| d}| d	}|rL|rL|s^tdd
ddfS z<t|||}|dkrtddddfW S td|ddfW S  tk
r } z2tdt|  tdt|ddf W Y S d}~X Y nX dS )zX
        Inserta un contacto en Q_CRM y devuelve el OpportunityNumber generado.
        T)silentFu   Se esperaba JSON válido.r%     contactNamecontactNumbercontactEmailzFaltan campos obligatorios.Nz No se pudo crear la oportunidad.r'   )r$   ZopportunityNumber   zError en InsertContact: )	r   get_jsonr   r)   r   r*   r+   r&   r,   )datar;   r<   r=   Znew_opportunityr-   r/   r/   r0   insert_contact_endpoint   sZ    


z4cotiz_esp_solicitud.<locals>.insert_contact_endpointz1/Ventas/Cotiz/EspecialSolicitud/BuscarOportunidadc                     s&  zt  } | d } jdrtd|  t|}t|}|rZtd|dddfW S tdd	| d
dddddfW S n"td|  td td jd  td jd  td jd  td td t	 jd}td |
|}|d|d|d|d|d|d |d!|d"|d#d$|d%|d&d'd'd(}td)| d* td|d+ddfW S W nx tk
r } z:td,t|  tdt|d-ddddf W Y S d.}~X Y n tk
rj } zHtd/t|  tdd0d1d2d3t|d4d'id5dd6f W Y S d.}~X Y n tk
r } z6td7t|  tdd8d9d:ddd;f W Y S d.}~X Y nd tk
r  } zDtjd<t| dd= tdd>d?d:t  d@dd;f W Y S d.}~X Y nX d.S )Az~
        Busca una oportunidad. Usa la BD local si PROTOTIPO=True.
        Se conecta al CRM real si PROTOTIPO=False.
        opportunity_numberr!   u7   ⚠️ MODO PROTOTIPO: Buscando oportunidad local SQL: Tz/Oportunidad (prototipo) encontrada en BD local.)r$   r@   messager>   FzLa oportunidad z" no existe en la BD de prototipos.ZPROTOTYPE_NOT_FOUNDinforC   code
alert_typer%   i  u,   ✅ MODO REAL: Buscando oportunidad en CRM: zB--- DEBUG: Verificando app.config ANTES de llamar a CRMManager ---zDEBUG: Valor de PROTOTIPO: zDEBUG: Valor de BC_TENANT_ID: BC_TENANT_IDzDEBUG: Valor de BC_ENV_NAME: BC_ENV_NAMEu&   --- DEBUG: Fin de la verificación ---zIntentando crear CRMManager...)r(   zCRMManager creado exitosamente.NoZContactNameZContactTypeZSalespersonNameZContactEMailAddressZColonyCityZCountyZMXZPostCodeZPhone )CRM_OpportunityNumberCRM_ContactNameZCRM_ContactTypeCRM_AssignedSalespersonCRM_ContactEmailZCRM_ContactAdressZCRM_ContactColoniaZCRM_ContactCityZCRM_ContactStateZCRM_ContactCountryZCRM_ContactZipZCRM_ContactNumberZCRM_ContactIDZCRM_ContactLegalIdentifierzOportunidad z  encontrada y mapeada desde CRM.z-Oportunidad encontrada exitosamente en el CRMz"Oportunidad no encontrada en CRM: ZCRM_OPPORTUNITY_NOT_FOUNDNu    Campo faltante en la petición: u!   Datos incompletos en la peticiónZMISSING_FIELDwarningZmissing_field')rC   rF   rG   detailsr:   u0   Error de conexión en búsqueda de oportunidad: uM   Error de conexión con la base de datos. Intente nuevamente en unos momentos.ZDATABASE_CONNECTION_ERRORr&   r'   u.   Error inesperado en búsqueda de oportunidad: exc_infoOError interno del servidor. Si el problema persiste, contacte al administrador.INTERNAL_SERVER_ERRORrC   rF   rG   	timestamp)r   r?   r(   r)   r+   rD   floatr   r   r   Zget_opportunity_context
ValueErrorrR   r,   KeyErrorr&   replaceConnectionErrorr*   r   now	isoformat)r@   Zopportunity_number_rawZnumero_oportunidad_floatoportunidadZcrm_managerZcontexto_crmZoportunidad_mapeadar-   r.   r/   r0   buscar_oportunidad   s    






	 
	
z/cotiz_esp_solicitud.<locals>.buscar_oportunidadz&/Ventas/Cotiz/EspecialSolicitud/Enviarc                  S   s  zt jrdt jkrtt } nt  } | sPtd tddddddd	fW S td
}|std tdddddddfW S t	d|  t
| }|d std|d ddddd	fW S t| |}|d rXt	d|  d}|dddkr|dd}|d| d7 }td||d |d |d |dg |dddd d!fW S td|d |d" |d# dd|d$ fW S W nd tk
r } zDtjd%t| dd& tdd'd(d)t  d*dd+f W Y S d,}~X Y nX d,S )-u   
        Procesa el envío de solicitud de cotización especial
        ACTUALIZADO: Manejo mejorado de FormData con archivos
        
        Returns:
            JSON con resultado del procesamiento
        zmultipart/form-datau*   Petición sin datos en envío de solicitudFu,   Petición inválida - no se recibieron datosZINVALID_REQUESTrR   rE   r%   r:   user_idz2Usuario no autenticado intentando enviar solicitudzUsuario no autenticadoZNOT_AUTHENTICATEDi  u:   Procesando solicitud de cotización especial para UserID: validrC   ZVALIDATION_ERRORr$   z.Solicitud procesada exitosamente para UserID: u-   Solicitud de cotización enviada exitosamentearchivos_subidosr    con z documento(s)Topportunity_idforms_createdtasks_generateddocs_ids)rh   ri   rj   rk   rf   )r$   rC   r@   r>   rF   rG   status_codeu)   Error inesperado en envío de solicitud: rU   rW   rX   r&   rY   r'   N)r   content_type"procesar_form_data_con_archivos_v3r?   r+   rR   r   r   r)   rD   validar_estructura_solicitudprocesar_solicitud_cotizacionr*   r&   r,   r   r`   ra   )r@   rd   Zvalidation_resultZresultado_procesamientorC   Zarchivos_countr-   r/   r/   r0   enviar_solicitud  s    	




	






	
z-cotiz_esp_solicitud.<locals>.enviar_solicitudz3/Ventas/Cotiz/EspecialSolicitud/DiagnosticoArchivosGETc               
   S   sz   z$t  } td| t  ddfW S  tk
rt } z2tdt|  tdt|ddf W Y S d}~X Y nX dS )	ux   
        Endpoint para verificar la configuración del sistema de archivos
        Solo para desarrollo/testing
        T)r$   ZdiagnosticorZ   r>   u   Error en diagnóstico: Fr%   r'   N)	r   r   r   r`   ra   r*   r+   r&   r,   )Zconfig_resultr-   r/   r/   r0   diagnostico_archivos  s"    
z1cotiz_esp_solicitud.<locals>.diagnostico_archivosN)route)r   mailr1   r6   rA   rc   rq   rs   r/   r.   r0   cotiz_esp_solicitud*   s    	{


5 
frv   c              
   C   s   zd| krdddW S d| kr*dddW S d| kr>dddW S | d }d	d
dg}|D ],}| |d sTdd| dd  W S qT| d rt| d dkrdddW S dddW S  tk
r } z&tdt|  ddd W Y S d}~X Y nX dS )u   
    Valida la estructura de los datos recibidos
    
    Args:
        data (dict): Datos de la solicitud
    
    Returns:
        dict: Resultado de validación
    rb   FzFaltan datos de la oportunidad)re   rC   formularioGeneralz#Faltan datos del formulario generalformulariosEspecificosu(   Faltan datos de formularios específicosrN   rO   rQ   rM   z	El campo z es obligatorior   u@   Debe seleccionar al menos un tipo de formulario para cotizaciónTu   Validación exitosau$   Error en validación de estructura: u%   Error interno en validación de datosN)r)   striplenr*   r+   r&   r,   )r@   rb   Zcampos_obligatoriosZcampor-   r/   r/   r0   ro      s$    

ro   c              
   C   s  zP| d d }zt |}W n tk
r } zt|}|dr~|dd }td| d|  dd	d
ddd W Y TW S |dr|dd }td| d|  dddddd W Y W S |W 5 d}~X Y nX |r|d d }| d| }td| d|  n(d}| d| }td| d|  t| d ||}	|	d s\|	W S g }
g }t	| d ||}|d r|

|d  ||d  td|  | d  D ]8}t|||}|d r|

|d  ||d  qd!}g }d"| krz| d" rzt| d" d!krzt| d" ||
|}|d r`|d# }|d$ }td%| d&| d' ntd(| d&|d)   z\t| ||||
|||d*}|d std+| d&|d,   ntd-| d&|d,   W n> tk
r } ztd.| d&t|  W 5 d}~X Y nX td/| d0t|
 d1t| d2|  d3||
|||d4W S  tk
r } z,td5t|  dd6d7d8d9d W Y S d}~X Y nX dS ):u3  
    Procesa la solicitud siguiendo el flujo del diagrama
    ACTUALIZADO: Gestión de documentos DESPUÉS de crear formularios + ENVÍO DE CORREO
    
    Args:
        data (dict): Datos de la solicitud
        user_id (int): ID del usuario
    
    Returns:
        dict: Resultado del procesamiento
    rb   rN   zOPPORTUNITY_CLOSED::   z(Intento de procesar oportunidad cerrada z con status Ful   El número de oportunidad ingresado ya terminó su proceso, por favor ingresar un no. de oportunidad válidoZOPPORTUNITY_CLOSEDrR   r:   )r$   rC   rF   rG   rl   zOPPORTUNITY_IN_PROCESS:z+Intento de procesar oportunidad en proceso u   Hay una solicitud previa de ingeniería por lo que no se puede generar una nueva. Comuníquese con ingeniería para que cancelen las tareas y puedan volver a asignar nuevas tareasZOPPORTUNITY_IN_PROCESSrD   i  NVersion-u   Creando nueva versión z para oportunidad zCreando nueva oportunidad u
    versión r$   rw   form_idtasksz*Tareas generadas tras formulario general: rx   r   archivostotal_archivos_subidosrk   zDocumentos procesados para : 	 archivosz!Error procesando documentos para rC   )r@   rh   rB   nueva_versionri   rj   rd   r   u,   Error enviando correo de notificación para mensajeu2   Correo de notificación enviado exitosamente para z&Error inesperado enviando correo para z"Solicitud procesada exitosamente: z	, Forms: z	, Tasks: z, Archivos: T)r$   rh   ri   rj   rf   rk   z%Error en procesamiento de solicitud: z&Error interno al procesar la solicitudPROCESSING_ERRORr&   r'   )r   r\   r,   
startswithsplitr+   rR   rD   r   procesar_formulario_generalappendextendprocesar_formulario_especificorz   "procesar_documentos_con_formids_v2*enviar_notificacion_cotizacion_especial_v2r*   r&   )r@   rd   rB   Zoportunidad_existenter-   	error_msgstatusr   rh   Zresultado_oportunidadri   rj   Zresultado_general
formularioZresultado_formr   docs_ids_generadosZresultado_docsZresultado_correor/   r/   r0   rp   '  s    

	


&

,*	rp   )formulario_datarh   rd   returnc              
   C   s  zt d}t|||}|d s$|W S |d }d| krX| d rXt|| d }|d sX|W S t|}g }|rt|}	t|}
t|	|||
}|d s|W S |d g}td|  ntd|  d||d	W S  t	k
r } z*t
d
t|  ddddd W Y S d}~X Y nX dS )a  
    Procesa el formulario general (siempre presente)
    
    Args:
        formulario_data (Dict): Datos del formulario general
        opportunity_id (str): ID de la oportunidad
        user_id (int): ID del usuario
    
    Returns:
        Dict: Resultado del procesamiento
    Generalr$   r   	preguntastask_idz(Formulario general procesado con tarea: z(Formulario general procesado SIN tarea: Tr$   r   r   z&Error al procesar formulario general: Fz$Error al procesar formulario generalr   r&   r$   rC   rF   rG   N)r   r   r   r   r   r   r   r+   rD   r*   r&   r,   )r   rh   rd   quotation_type_idresultado_headr   resultado_details
debe_crearr   seller_user_id	user_nameresultado_taskr-   r/   r/   r0   r     sJ    
r   c              
   C   sF  z|  dd}t|}t|||}|d s0|W S |d }d| krd| d rdt|| d }|d sd|W S t|}g }	|rt|}
t|}t|
|||}|d s|W S |d g}	t	d| d| d	 nt	d
| d| d	 d||	dW S  t
k
r@ } z8tdt|  dd|  dd ddd W Y S d}~X Y nX dS )u%  
    Procesa un formulario específico (plantas, UPS, etc.)
    
    Args:
        formulario_data (Dict): Datos del formulario específico
        opportunity_id (str): ID de la oportunidad
        user_id (int): ID del usuario
    
    Returns:
        Dict: Resultado del procesamiento
    Ztipor   r$   r   r   r   u,   Formulario específico procesado con tarea: z (tipo: )u,   Formulario específico procesado SIN tarea: Tr   u*   Error al procesar formulario específico: FzError al procesar formulario nombreu   específicor   r&   r   N)r)   r   r   r   r   r   r   r   r+   rD   r*   r&   r,   )r   rh   rd   tipo_formularior   r   r   r   r   r   r   r   r   r-   r/   r/   r0   r     sL    
r   )archivos_formulariosrh   ri   rd   r   c                 C   sN  zt |}dddddddt g g i d}g }g }g }ddddddd	d
dddd}	tdt|  d td|  | D ]}
|d  d7  < z|
dd}|
dd}|
dg }|
dd}td| dt| d |rt|dkrtd| d W q~|d  t|7  < d}|	||}|dk	r^|D ]}||r>|} qpq>ntd | d! |sd"| d#| d$}t	d%|  |
||d&d' |d(  d7  < |d)  t|7  < W q~td*|  t|||||d+}|d, rRt|d- }t|d. }|d/ }|d0  |7  < |d)  |7  < |d1  |d2 d1 7  < |rt||}|d, rtd3| d4|  ||d5 |< ntd6| d7|  |r||kr|
| |d8 
| |
||||||d9g |d2i d: |d;  d7  < td<| d=| d>|  |dkr|
|| d?d@|d. dA n\dB| d=|dC  }t	d%|  |
||dD|dE |d(  d7  < |d)  t|7  < W q~ tk
rD } ztdF|
ddG d=t| }tj	|dHdI |
|
ddG|dJd' |d(  d7  < |d)  t|
dg 7  < W 5 d}~X Y q~X q~t |dK< |dK |dL   |dM< ||dN< |d0 dkrt|dkrdOdPt| dQdR||dSW S dT|d0  dU}|d; dkr|dV|d;  d7 }t|dkr|dWt| dX7 }tdY tdZ|d   td[|d;   td\|d(   td]|d   td^|d0   td_|d)   td`|d1 dadb tdc|dM dadd tde|  dH||d0 ||||dfW S  tk
rH } zLdgt| }tj	|dHdI dOdhdi||dd| r(t| nddjdk W Y S d}~X Y nX dS )lu  
    Versión mejorada para procesar documentos asociándolos con FormIDs específicos
    
    Args:
        archivos_formularios (List[Dict]): Lista de formularios con sus archivos
        opportunity_id (str): ID de la oportunidad
        forms_created (list[str]): Lista de FormIDs creados
        user_id (int): ID del usuario
    
    Returns:
        dict: Resultado del procesamiento con estadísticas detalladas
    r   )formularios_procesadosformularios_exitososformularios_fallidostotal_archivos_intentadostotal_archivos_exitosostotal_archivos_fallidos   tamaño_total_mbtiempo_inicioerrores_por_formularior   mapeo_formid_docsFormSolicVisFormPTTransNacFormPTTransULFormAireFormSistCritUPSZFormGralFormBess	FormSolar
FormEneCog	FormImessFormProyecEsp)SolicVis
PTTransNac	PTTransULAireSistCritUPSr   BessSolarEnergyCogenerationImess	ProyecEspu    🚀 Procesando documentos para z formulario(s)u   📋 FormIDs disponibles: r   r|   formularioTiporM   formularioNombrer   	totalSizeu   📝 Procesando formulario:  (z
 archivos)u   ⏭️ z: sin archivos, omitiendor   Nz'El sufijo esperado para el formulario 'z
' es None.u!   No se encontró FormID para tipo z (sufijo esperado: r   u   ❌ Zform_id_no_encontrado)r   r&   
tipo_errorr   r   u   🎯 FormID encontrado: )r   rh   filesrd   
created_byr$   rf   archivos_fallidosdocs_idr   r   estadisticasu   🔗 FormID z vinculado con DocsID r   u"   ⚠️ No se pudo vincular FormID z con DocsID r   download_urls)r   r   r   archivos_exitososr   r   r   r   u   ✅ r   z archivos subidos, DocsID: z archivos fallaronZarchivos_parciales_fallidos)r   r&   r   r   zError subiendo archivos para rC   Zupload_completo_fallido)r   r&   r   ZdetalleszError procesando formulario ZdesconocidoTrU   Zexcepcion_procesamiento
tiempo_finr   Zduracion_total_segundosr   F"   No se pudo subir ningún archivo. z errores en formulariosZUPLOAD_FAILED)r$   rC   rF   r   erroreszProcesados r    en rg   z advertencia(s)u,   📊 === RESUMEN GLOBAL DE PROCESAMIENTO ===u       📋 Formularios procesados: u      ✅ Formularios exitosos: u      ❌ Formularios fallidos: u      📄 Archivos intentados: u      ✅ Archivos exitosos: u      ❌ Archivos fallidos:       📏 Tamaño total: .2f MBu      ⏱️ Duración total: 	 segundosu      🆔 DocsIDs generados: )r$   rC   r   rk   r   r   r   z%Error general procesando documentos: z#Error interno procesando documentosr   )error_generalr   r   r   )r$   rC   rF   technical_errorr   )r   r   r`   r+   rD   rz   r)   endswithrR   r&   r   upload_form_files_v2r   r*   r,   total_seconds)r   rh   ri   rd   r   Zestadisticas_globalesr   Zerrores_procesamientor   Zmapeo_formulariosr   r   Znombre_modulor   
total_sizeZform_id_buscadoZsufijo_esperador   r   Zresultado_uploadr   r   r   Zactualizar_resultador-   Zsuccess_messager/   r/   r0   r   V  sV   











0
	

r   )r   c                 C   s  zB| j d}|r t|}ni }g }dddddddd	d
dd
}td | D ]\}}d| d}| j|}t	d|  t	dt
|  g }	g }
t|D ]\}}|std|d  d| d |
d|d  d q|jr|j dkr8td|d  d| d |
d|d  d q|dtj | }|d |dkrtd|j d| d |
|j d q|dkr|d }td|j d| d|d d! |
|j d|d d! qt|jj }d"d#d$d%d&d'd(d)h}||krHtd|j d| d*| d+ |
|j d*| d+ qz`|d |d,}|d t
|dkrtd|j d| d- |
|j d- W qW nZ tk
r } z:td.|j d/t|  |
|j d0 W Y qW 5 d1}~X Y nX |	| td2|j d3|d, d4d5 q|
rntd6| d/t
|
 d7 |
D ]}td8|  qV|	rTtd9d: |	D }|	D ]}|d q||||	t
|	||
d; td<| d/t
|	 d=|d, d4d> qT|r6||d?< td@d: |D }tdAd: |D }tdB| d=|d, d, d dC n
tdD |W S  tjk
r } z tdEt|  i  W Y S d1}~X Y nD tk
r } z$tjdFt| dGdH i  W Y S d1}~X Y nX d1S )Iu   
    Versión mejorada para procesar FormData con mejor manejo de archivos
    
    Args:
        request: Objeto request de Flask
    
    Returns:
        Dict: Datos estructurados incluyendo archivos organizados por formulario
    r@   r   r   r   r   r   r   r   r   r   r   )
r   r   r   r   r   r   r   r   r   r   u'   🔍 Procesando archivos de FormData...Zfiles_z[]u!   🔍 Buscando archivos en campo: u   🔍 Archivos encontrados: u   ❌ Archivo r|   r   u   : objeto archivo vacíoArchivo u   : objeto vacíorM   z: sin nombre de archivoz: sin nombrer   u   : archivo vacíoi       z: muy grande (r   zMB)z.pdfz.docz.docxz.jpgz.jpegz.pngz.xlsxz.xlsu   : extensión no permitida (r      u   : contenido vacíou   ❌ Error leyendo archivo r   z: error de lecturaNu   ✅ Archivo válido: r   .1f KB)   ⚠️ u    archivos inválidos:z   - c                 s   s6   | ].}| d dkr| d tjp,| p,d V  qdS )r   N)seekosSEEK_ENDtell.0archivor/   r/   r0   	<genexpr>  s   z5procesar_form_data_con_archivos_v3.<locals>.<genexpr>)r   r   r   cantidadr   ZarchivosInvalidosu   📎 u    archivos válidos (z
 KB total)r   c                 s   s   | ]}|d  V  qdS )r   Nr/   r   formr/   r/   r0   r     s     c                 s   s   | ]}|d  V  qdS )r   Nr/   r   r/   r/   r0   r     s     u   📁 Total procesado: z MB)u7   📄 No se encontraron archivos válidos en el FormDatau+   ❌ Error decodificando JSON del FormData: u,   ❌ Error procesando FormData con archivos: TrU   )r   r)   jsonloadsr+   rD   itemsr   getlistdebugrz   	enumeraterR   r   filenamery   r   r   r   r   r   suffixlowerreadr*   r&   r,   sumJSONDecodeError)r   	json_datar@   r   Zform_modulesZ	tipo_formZmodulo
field_nameZarchivos_tipoZarchivos_validosZarchivos_invalidosir   	file_sizeZsize_mb	extensionZextensiones_permitidasheaderr-   r&   r   Ztotal_archivosr/   r/   r0   rn   G  s    




"
 



"	*$
rn   )r   rh   r   rd   r   r   c                 C   s  z|rt |dkr2ddg g dddddddW S tdt | d|   d	| d
|  }d}ddlm} ||||}|d sdd|d  ddW S |d }	td|	 d|   t |dddt i i d}
g }g }t|dD ]t\}}z|r|jsRd| d}t	d|  |
d| |dd |
d ddd |
d d< W q|dtj | }|d t|jj }|
d |dd |
d |< td | d
t | d!|j d"|d# d$d%	 d&|  d'| }t||	|j||d(}|d rz|
|j|d) d* |d) d+ |	|d) d, |||d- |
d.  d7  < |
d/  |7  < td0| d1|j d2|d) d+   nT|dd3}|
|j|d4|d5 |
d d4dd |
d d4< td6| d!|  W q tk
rX } zhd7| d!t| }tj|dd8 |
t|d9d| |d:t|d; |
d d:dd |
d d:< W 5 d}~X Y qX qt ||
d<< t |
d=< |
d= |
d>   |
d?< |
d/ d@ |
dA< |
d. dk}|
d< dkrdB|
d.  dC}n<|
d. dkrdD|
d<  dE}d}n|
d.  dF|
d<  dG}tdH|  dI tdJ|
d.   tdK|
d<   tdL|
dA dMdN tdO|
d? dMdP |
d rtdQ |
d  D ]"\}}tdR| d!| dS q|
d rtdT |
d  D ]"\}}tdR| d!| dU q|||||
dV ||	dWdX |D |
dY	W S  tk
r } z`dZt| }tj|dd8 dd[d\|g g d|rht |ndd|rzt |nd|d]d^ W Y S d}~X Y nX dS )_u  
    Versión mejorada para subir múltiples archivos de un formulario
    
    Args:
        form_id (str): ID del formulario
        opportunity_id (str): ID de la oportunidad
        files (list): Lista de archivos FileStorage
        user_id (int): ID del usuario
        created_by (str): Nombre del usuario
    
    Returns:
        dict: Resultado del procesamiento con estadísticas detalladas
    r   TzNo hay archivos para procesarN)total_intentadostotal_exitosostotal_fallidos   tamaño_total)r$   rC   rf   r   r   r   u   🚀 Iniciando upload de z archivos para FormID zVentas/Formularios//ZQ_SpQ_FormsHead)crear_docs_headr$   Fz#Error creando grupo de documentos: rC   ZDOCS_HEAD_ERROR)r$   rC   rF   r   u   📋 DocsHead z creado para FormID )r  r  r  r  r   archivos_por_tipoerrores_por_tipor|   r   u    está vacío o sin nombrer   Zarchivo_Zarchivo_vacio)archivo_originalr&   r   r  r  u   📄 Procesando archivo r   r   r   r   r   zArchivo de z - Oportunidad )file_objr   titledescriptionruta_completar@   doc_line_iddownload_urlremote_path)r	  r  r  r   r  r   Z	file_typeZupload_orderr  r  u   ✅ Archivo z	 subido: z -> zError desconocido en uploadZupload_failed)r	  r&   r   r   u   ❌ Error subiendo archivo zError procesando archivo rU   r   	excepcion)r	  r&   r   r  r  r   r   Zduracion_segundosr   r   zTodos los archivos (z) fueron subidos exitosamenter   z erroresz archivos subidos, z	 fallaronu#   📊 Resumen de upload para FormID r{   u      ✅ Exitosos: u      ❌ Fallidos: r   r   r   u      ⏱️ Duración: r   u      📁 Tipos de archivo:z      z archivo(s)u      ⚠️ Tipos de error:z
 error(es)r  c                 S   s   g | ]}|d  qS )r  r/   r   r/   r/   r0   
<listcomp>  s     z(upload_form_files_v2.<locals>.<listcomp>)	r$   rC   rf   r   Ztotal_procesadosr  r   r   r   z'Error general en upload_form_files_v2: z1Error interno al procesar archivos del formularioZFORM_FILES_ERROR)r  r  r  r   )r$   rC   rF   r   rf   r   r   r   )rz   r+   rD   Z)Consultas_SQL.Utilities.DocsManagementSQLr  r   r`   r   r   rR   r   r)   r   r   r   r   r   r   r   r   r&   r*   r,   getattrr   r   )r   rh   r   rd   r   r  Zorigenr  Zdocs_head_resultr   r   rf   r   indexr
  r   r   r   r  	resultador-   r$   rC   extcountZ
error_typer/   r/   r0   r     s    

0



(0



r   c              
   C   s  z|  dg }dddddd}|D ]}| dd }| dd}d|krdd|krd| d	k|d
< q"d|krv||d< q"d|kr||d< q"d|ksd|kr||d< q"d|kr"d|kr"||d< q"|W S  tk
r } z,tdt|  dddddd W Y S d}~X Y nX dS )u   
    Extrae datos específicos del formulario general para el correo
    
    Args:
        formulario_general (dict): Datos del formulario general
    
    Returns:
        dict: Datos extraídos y formateados
    r   FrM   )requiere_visitaestadociudad	direcciondescripcion_proyectopregunta	respuestaZvisitaZespecialista)u   sísiyesr  r  r  u
   direcciónr  u   informaciónZproyector  z/Error extrayendo datos del formulario general: N)r)   r   r*   r+   rR   r,   )Zformulario_generalr   Zdatos_extraidosr  Zpregunta_textor  r-   r/   r/   r0    extraer_datos_formulario_general  s<    




r!  c                 C   s  z:ddl m} ddlm}	 ddlm}
 t|}g }|D ]}d|krP|d q8d|krd|d q8d	|krx|d
 q8d|kr|d q8d|kr|d q8d|kr|d q8d|kr|d q8d|kr|d q8d|kr|d q8d|kr8|d q8t| di }|| d dd| d dd||		 
d|		 
d |t|t||||d! |d" |d# |d$ |d% d&d'| d(}d)| }|d*kr|d+| d,7 }|d% rd-| d.}d/d0d1did2}|D ]}t||d3< d4}tt|}td5krd6}nd|krd7}nd|kr,d8}n~d	|kr<d9}nnd|krLd:}n^d|kr\d:}nNd|krld;}n>d|kr|d;}n.d|krd<}nd|krd;}nd|krd=}|
jd>r|
jd?}|d@|dA|gidB|dC}q|dDd5r|dEi d1d}tdF| dG|  ntdH| dI|dJdK  |W S  tk
r } z<tjdLt| d/dM d5dNt| t|gdO W Y S dP}~X Y nX dPS )Qu   
    Versión mejorada del envío de correo de notificación.
    Corregida para:
    1. Evitar KeyError si no hay tareas.
    2. Usar current_app para acceder a la configuración (evita AttributeError).
    r   )enviar_correo_universalr
   r2   r   u   Solicitud de Visita Técnicar   z.Plantas, Tableros y Transformadores Nacionalesr   z&Plantas, Tableros y Transformadores ULr   zAires Acondicionadosr   u   Sistemas Críticos (UPS)r   ZBESSr   zSistemas Solaresr   u   Energía y Cogeneraciónr   ZiMessr   zProyectos Especialesrw   rb   rO   zNo especificadorP   zNo asignadoz%d/%m/%Yz%H:%Mr  r  r  r  r  zhttps://sycelephant.comz+https://sycelephant.com/ventas/oportunidad/)Znumero_oportunidadZcliente_nombreZvendedor_asignadoZsolicitante_nombreZfecha_solicitudZhora_solicitudZformularios_solicitadosZtotal_formulariosZtotal_tareasZform_idsversionZproyecto_estadoZproyecto_ciudadZproyecto_direccionZproyecto_descripcionr  Zurl_sistemaZurl_oportunidadu;   🔔 Nueva Solicitud de Cotización Especial - Oportunidad r|   u    (Versión r   u   🚨 URGENTE - u    - REQUIERE VISITA TÉCNICATu9   No se generaron tareas para envío de correo específico.total_destinatarios)r$   r   destinatariosZTaskIdrM   Fzvictor.barrera@igsa.com.mxzmarco.escobar@igsa.com.mxzcarlos.huitron@igsa.com.mxzjose.lopez@igsa.com.mxzrogelio.robles@igsa.com.mxzcarlos.anguiano@igsa.com.mxzdiego.rivas@igsa.com.mxzarturo.martinez@igsa.com.mxr#   EMAIL_PROTOTYPE_TOADRESSz.Emails/Ventas/Cotiz/CotizEspSolicitudMail.htmlZTOi  )Ztemplate_pathasuntoZdestinatarios_adicionalesZmail_list_idtemplate_datar$   r%  u&   ✅ Proceso de correo finalizado para z. Enviados: u   ❌ Error enviando correo para r   r   zError desconocidoz5Error en enviar_notificacion_cotizacion_especial_v2: rU   u'   Error interno al enviar notificación: )r$   r   r   N)Z#App.Utilities_module.MailManagementr"  r   r4   r3   r   r   r!  r)   r`   strftimerz   intr    r   r(   r+   rD   r&   r*   r,   )r@   rh   rB   r   ri   rj   rd   r   r"  r   r3   r   Zformularios_nombresr   Zdatos_proyector(  r'  r  ZtaskIDdestinatarioZformID_taskr$  r-   r/   r/   r0   r     s    














 
r   )9r4   r   r   r   r   r   r   r   	functoolsr	   loggingr   r   typingr   r   pathlibr   r   r(   r   Z#App.Utilities_module.DocsManagementr   r   r   Z"App.Utilities_module.CRMManagementr   Z/Consultas_SQL.Ventas.Cotiz.CotizEspSolicitudSQLr   r   r   r   r   r   r   r   r   r   r   r   r    	getLoggerr+   rv   ro   rp   dictr,   r*  r   r   r   rn   listr   r!  r   r/   r/   r/   r0   <module>   sJ   $@
   Y' #FG   r    P1