a
    !9i&                    @   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 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/e0e1e2e0dddZ3e0e1e2e0dddZ4ee e1ee1 e2edddZ5e0dddZ6e1e1e7e2e1e0dd d!Z8d"d# Z9d$d% Z:d&d' Z;dS )(    )apprender_templaterequestsessionjsonifyredirecturl_for)wrapsNdatetime)ListDict)Path)&consultar_docs_por_oportunidad_y_tarea)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yz }  z6tdt|   t dt| dd	fW  Y d
} ~ S d
} ~ 0 0 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    >/var/www/html/src/App/Ventas_Module/Cotiz/CotizEspSolicitud.pyget_cotiz_config4   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)flaskr4   r   boolr)   r*   r3   r0   r0   r1   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u rtddddfW S td|ddfW S  ty } z6tdt|  tdt|ddfW  Y d}~S d}~0 0 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.   r0   r0   r1   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 nn ty  } z>td,t|  tdt|d-ddddfW  Y d.}~S d.}~0  tyd } zLtd/t|  tdd0d1d2d3t|d4d'id5dd6fW  Y d.}~S d.}~0  ty } z:td7t|  tdd8d9d:ddd;fW  Y d.}~S d.}~0  ty } zHtjd<t| dd= tdd>d?d:t  d@dd;fW  Y d.}~S d.}~0 0 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%   rA   messager?   FzLa oportunidad z" no existe en la BD de prototipos.ZPROTOTYPE_NOT_FOUNDinforD   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')rD   rG   rH   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rD   rG   rH   	timestamp)r   r@   r)   r*   r,   rE   floatr   r   r   Zget_opportunity_context
ValueErrorrS   r-   KeyErrorr'   replaceConnectionErrorr+   r   now	isoformat)rA   Zopportunity_number_rawZnumero_oportunidad_floatoportunidadZcrm_managerZcontexto_crmZoportunidad_mapeadar.   r/   r0   r1   buscar_oportunidad   s    






	
	
z/cotiz_esp_solicitud.<locals>.buscar_oportunidadz&/Ventas/Cotiz/EspecialSolicitud/Enviarc                  S   s  zt jrdt jv 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 nb ty } zHtjd%t| dd& tdd'd(d)t  d*dd+fW  Y d,}~S d,}~0 0 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_REQUESTrS   rF   r&   r;   user_idz2Usuario no autenticado intentando enviar solicitudzUsuario no autenticadoZNOT_AUTHENTICATEDi  u:   Procesando solicitud de cotización especial para UserID: validrD   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)ri   rj   rk   rl   rg   )r%   rD   rA   r?   rG   rH   status_codeu)   Error inesperado en envío de solicitud: rV   rX   rY   r'   rZ   r(   N)r   content_type"procesar_form_data_con_archivos_v3r@   r,   rS   r   r   r*   rE   validar_estructura_solicitudprocesar_solicitud_cotizacionr+   r'   r-   r   ra   rb   )rA   re   Zvalidation_resultZresultado_procesamientorD   Zarchivos_countr.   r0   r0   r1   enviar_solicitud  s    	



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






	
z-cotiz_esp_solicitud.<locals>.enviar_solicitudz3/Ventas/Cotiz/EspecialSolicitud/DiagnosticoArchivosGETc               
   S   sx   z$t  } td| t  ddfW S  tyr } z6tdt|  tdt|ddfW  Y d}~S d}~0 0 dS )	ux   
        Endpoint para verificar la configuración del sistema de archivos
        Solo para desarrollo/testing
        T)r%   Zdiagnosticor[   r?   u   Error en diagnóstico: Fr&   r(   N)	r   r   r   ra   rb   r+   r,   r'   r-   )Zconfig_resultr.   r0   r0   r1   diagnostico_archivos  s"    
z1cotiz_esp_solicitud.<locals>.diagnostico_archivosN)route)r   mailr2   r7   rB   rd   rr   rt   r0   r/   r1   cotiz_esp_solicitud+   s    	{


5 
frw   c              
   C   s   zd| vrdddW S d| vr*dddW S d| vr>dddW S | d }g d	}|D ],}| |d
 sRdd| dd  W S qR| d rt| d dkrdddW S dddW S  t y } z*tdt|  dddW  Y d}~S d}~0 0 dS )u   
    Valida la estructura de los datos recibidos
    
    Args:
        data (dict): Datos de la solicitud
    
    Returns:
        dict: Resultado de validación
    rc   FzFaltan datos de la oportunidad)rf   rD   formularioGeneralz#Faltan datos del formulario generalformulariosEspecificosu(   Faltan datos de formularios específicos)rO   rP   rR   rN   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-   )rA   rc   Zcampos_obligatoriosZcampor.   r0   r0   r1   rp     s$    
rp   c              
   C   s  z\| d d }zt |}W n ty } zt|}|dr|dd }td| d|  dd	d
dddW  Y d}~W S |dr|dd }td| d|  ddddddW  Y d}~W S |W Y d}~n
d}~0 0 |r|d d }| d| }td| d|  n(d}| d| }td| d|  t| d ||}	|	d sf|	W S g }
g }t	| d ||}|d r|

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

|d  ||d  qd!}g }d"| v r| d" rt| d" d!krt| d" ||
|}|d rj|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y  } z&td.| d&t|  W Y d}~n
d}~0 0 td/| d0t|
 d1t| d2|  d3||
|||d4W S  ty } z0td5t|  dd6d7d8d9dW  Y d}~S d}~0 0 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
    rc   rO   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_CLOSEDrS   r;   )r%   rD   rG   rH   rm   Nz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_PROCESSrE   i  Version-u   Creando nueva versión z para oportunidad zCreando nueva oportunidad u
    versión r%   rx   form_idtasksz*Tareas generadas tras formulario general: ry   r   archivostotal_archivos_subidosrl   zDocumentos procesados para : 	 archivosz!Error procesando documentos para rD   )rA   ri   rC   nueva_versionrj   rk   re   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%   ri   rj   rk   rg   rl   z%Error en procesamiento de solicitud: z&Error interno al procesar la solicitudPROCESSING_ERRORr'   r(   )r   r]   r-   
startswithsplitr,   rS   rE   r   procesar_formulario_generalappendextendprocesar_formulario_especificor{   "procesar_documentos_con_formids_v2*enviar_notificacion_cotizacion_especial_v2r+   r'   )rA   re   rC   Zoportunidad_existenter.   	error_msgstatusr   ri   Zresultado_oportunidadrj   rk   Zresultado_general
formularioZresultado_formr   docs_ids_generadosZresultado_docsZresultado_correor0   r0   r1   rq   (  s    

	


&

0*	rq   )formulario_datari   re   returnc              
   C   s  zt d}t|||}|d s$|W S |d }d| v 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	y } z.t
d
t|  dddddW  Y d}~S d}~0 0 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%   rD   rG   rH   N)r   r   r   r   r   r   r   r,   rE   r+   r'   r-   )r   ri   re   quotation_type_idresultado_headr   resultado_details
debe_crearr   seller_user_id	user_nameresultado_taskr.   r0   r0   r1   r     sJ    
r   c              
   C   sD  z|  dd}t|}t|||}|d s0|W S |d }d| v 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
y> } z<tdt|  dd|  dd dddW  Y d}~S d}~0 0 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
    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,   rE   r+   r'   r-   )r   ri   re   tipo_formularior   r   r   r   r   r   r   r   r   r.   r0   r0   r1   r     sL    
r   )archivos_formulariosri   rj   re   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u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||v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yF } z|dF|
ddG d=t| }tj	|dHdI |
|
ddG|dJd' |d(  d7  < |d)  t|
dg 7  < W Y d}~q~d}~0 0 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yH } zPdgt| }tj	|dHdI dOdhdi||dd| r(t| nddjdkW  Y d}~S d}~0 0 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}   formularioTiporN   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   ri   filesre   
created_byr%   rg   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 rD   Zupload_completo_fallido)r   r'   r   detalleszError procesando formulario ZdesconocidoTrV   Zexcepcion_procesamiento
tiempo_finr   Zduracion_total_segundosr   F"   No se pudo subir ningún archivo. z errores en formulariosZUPLOAD_FAILED)r%   rD   rG   r   erroreszProcesados r    en rh   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%   rD   r   rl   r   r   r   z%Error general procesando documentos: z#Error interno procesando documentosr   )error_generalr   r   r   )r%   rD   rG   technical_errorr   )r   r   ra   r,   rE   r{   r*   endswithrS   r'   r   upload_form_files_v2r    r+   r-   total_seconds)r   ri   rj   re   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_messager0   r0   r1   r   W  sV   











4
	

r   )r   c                 C   s  z<| 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 }h d"}||vr<td|j d| d#| d$ |
|j d#| d$ qz`|d |d%}|d t
|dkrtd|j d| d& |
|j d& W qW n` ty } zFtd'|j d(t|  |
|j d) W Y d*}~qW Y d*}~n
d*}~0 0 |	| td+|j d,|d% d-d. q|
rhtd/| d(t
|
 d0 |
D ]}td1|  qP|	rTtd2d3 |	D }|	D ]}|d q||||	t
|	||
d4 td5| d(t
|	 d6|d% d-d7 qT|r0||d8< td9d3 |D }td:d3 |D }td;| d6|d% d% d d< n
td= |W S  tjy~ } z$td>t|  i W  Y d*}~S d*}~0  ty } z(tjd?t| d@dA i W  Y d*}~S d*}~0 0 d*S )Bu   
    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
    rA   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íorN   z: sin nombre de archivoz: sin nombrer   u   : archivo vacíoi       z: muy grande (r   zMB)>   z.docz.pngz.jpgz.docxz.xlsxz.pdfz.xlsz.jpegu   : 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u r| d tjp,| p,d V  qdS )r   N)seekosSEEK_ENDtell.0archivor0   r0   r1   	<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   Nr0   r   formr0   r0   r1   r         c                 s   s   | ]}|d  V  qdS )r   Nr0   r   r0   r0   r1   r     r   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: TrV   )r   r*   jsonloadsr,   rE   itemsr   getlistdebugr{   	enumeraterS   r   filenamerz   r   r   r   r   r   suffixlowerreadr+   r'   r-   sumJSONDecodeError)r   	json_datarA   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_archivosr0   r0   r1   ro   H  s    




"
 


"
"	*$
ro   )r   ri   r   re   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 ]v\}}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yZ } zpd7| d!t| }tj|dd8 |
t|d9d| |d:t|d; |
d d:dd |
d d:< W Y d}~qd}~0 0 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y } zddZt| }tj|dd8 dd[d\|g g d|rht |ndd|rzt |nd|d]d^W  Y d}~S d}~0 0 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%   rD   rg   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: rD   ZDOCS_HEAD_ERROR)r%   rD   rG   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_completarA   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 rV   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  r0   r   r0   r0   r1   
<listcomp>  r   z(upload_form_files_v2.<locals>.<listcomp>)	r%   rD   rg   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%   rD   rG   r   rg   r   r   r   )r{   r,   rE   Z)Consultas_SQL.Utilities.DocsManagementSQLr
  r   ra   r   r   rS   r   r*   r   r   r   r   r   r   r   r   r'   r+   r-   getattrr   r   )r   ri   r   re   r   r  Zorigenr
  Zdocs_head_resultr   r   rg   r   indexr  r   r  r  r  	resultador.   r%   rD   extcount
error_typer0   r0   r1   r     s    

0



(4



r   c              
   C   s  z|  dg }dddddd}|D ]}| dd }| dd}d|v rdd|v rd| d	v |d
< q"d|v rv||d< q"d|v r||d< q"d|v sd|v r||d< q"d|v r"d|v r"||d< q"|W S  ty
 } z0tdt|  ddddddW  Y d}~S d}~0 0 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   FrN   )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,   rS   r-   )Zformulario_generalr   Zdatos_extraidosr"  Zpregunta_textor#  r.   r0   r0   r1    extraer_datos_formulario_general  s<    




r&  c           	      C   s   t | |}g }|D ]n}|d }|d }|r>| dr>d}nd}|rX| rX| }n|rjtj|}nd}||||d q|S )zt
    Construye el arreglo archivos_adjuntos a partir de la info en BD
    para la oportunidad y task indicados.
          )zhttp://zhttps://urllocalrN   )r   rutar   )r   r   r   rz   r   pathbasenamer   )	Zoportunity_idr   rowsarchivos_adjuntosrowr  r+  r   r   r0   r0   r1   obtener_archivos_adjuntos  s&    


r1  c                 C   s  zPddl m} ddlm}	 ddlm}
 t|}g }|D ]}d|v rP|d q8d|v rd|d q8d	|v rx|d
 q8d|v r|d q8d|v r|d q8d|v r|d q8d|v r|d q8d|v r|d q8d|v r|d q8d|v 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||}td5krd6}nd|v r*d7}nd|v r:d8}n~d	|v rJd9}nnd|v rZd:}n^d|v rjd:}nNd|v rzd;}n>d|v rd;}n.d|v rd<}nd|v rd;}nd|v rd=}|
jd>r|
jd?}|d@||gg dAdBdC||dD}q|dEd5r0|dFi d1d}tdG| dH|  ntdI| dJ|dKdL  |W S  ty } z@tjdMt| d/dN d5dOt| t|gdPW  Y dQ}~S dQ}~0 0 dQS )Ru   
    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
   r3   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 Especialesrx   rc   rP   zNo especificadorQ   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TaskIdrN   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.html)zvictor.barrera.@igsa.com.mxzvictor.cervantes@igsa.com.mxzalexis.moreno@igsa.com.mx)ZTOCCi  )Ztemplate_pathasuntoZdestinatarios_adicionalesZmail_list_idtemplate_datar/  r%   r5  u&   ✅ Proceso de correo finalizado para z. Enviados: u   ❌ Error enviando correo para r   r   zError desconocidoz5Error en enviar_notificacion_cotizacion_especial_v2: rV   u'   Error interno al enviar notificación: )r%   r   r   N)Z#App.Utilities_module.MailManagementr2  r   r5   r4   r   r   r&  r*   ra   strftimer{   intr!   r1  r   r)   r,   rE   r'   r+   r-   )rA   ri   rC   r   rj   rk   re   r   r2  r   r4   r   Zformularios_nombresr   Zdatos_proyector9  r8  r  ZtaskIDdestinatarioZformID_taskZarchivosAdjuntosr4  r.   r0   r0   r1   r     s    
















r   )<r5   r   r   r   r   r   r   r   	functoolsr	   loggingr   r   typingr   r   pathlibr   r   Z=Consultas_SQL.Operaciones.Ingenieria.Cotiz.CotizCreatedIngSQLr   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,   rw   rp   rq   dictr-   r;  r   r   r   ro   listr   r&  r1  r   r0   r0   r0   r1   <module>   sF   $@
   Y' #FG
 r  P1$