U
    /Si)                     @   s"  d dl m Z  d dlmZ d dlZd dlZd dlmZmZmZm	Z	 d dl
mZ edZG dd dZG d	d
 d
Zdd Zeee  dddZeee dddZeee dddZeee eedddZeeeedddZeeeedddZeeeeddd Zeeed!d"d#ZdS )$    datetime)DecimalN)OptionalListDictUnionget_connectionZrecepcion_cotizacion_sqlc                   @   sd   e Zd ZdZd	eeeeeeeeeeeeeeeeeeeeeeeeeeeedddZedddZ	dS )
OpportunityAndCostingDTOuI   DTO para la información unificada de Oportunidad y Encabezado de Costeo.N
costing_idcrm_opportunity_numbercrm_contact_namecrm_contact_typecrm_assigned_salespersoncrm_contact_adresscrm_contact_coloniacrm_contact_citycrm_contact_numbercrm_contact_countrycrm_contact_legal_identifiercrm_contact_zipcrm_contact_statecrm_contact_email	case_costsale_price_listsale_price_mindiscount_max_percentrun_time_numberrun_time_typetechnical_terms_and_conditionsFinancePercentTaxCodeCurrencyCodeQ_TaxRate_FrontESQ_TaxRate_FrontENQ_Currency_FrontESQ_Currency_FrontENc                 C   s   || _ || _|| _|| _|| _|| _|| _|| _|	| _|
| _	|| _
|| _|| _|| _|| _|| _|| _|| _|| _|| _|f| _|| _|f| _|f| _|f| _|f| _|f| _|| _d S )N	CostingIDCRM_OpportunityNumberCRM_ContactNameCRM_ContactTypeCRM_AssignedSalespersonCRM_ContactAdressCRM_ContactColoniaCRM_ContactCityCRM_ContactNumberCRM_ContactCountryCRM_ContactLegalIdentifierCRM_ContactZipCRM_ContactStateCRM_ContactEmailCaseCostSalePriceListSalePriceMinDiscountMaxPercentRunTimeNumberRunTimeTypeTechnicalTermsAndConditionsr"   r#   r$   r%   r&   r'   r(   )selfr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   r'   r(    r@   kC:\Users\victor.barrera\Documents\proyectos\elepV3\Elep\src\Consultas_SQL\Ventas\Cotiz\RecepcionCotizSQL.py__init__   s8    z!OpportunityAndCostingDTO.__init__returnc                 C   s   | j | j| j| j| j| j| j| j| j| j	| j
| j| j| j| jt| jt| jt| j| j| j| jd | jdk	rxt| jnd| jd | jd | jd | jd | jd | jdS )z2Convierte el objeto a un diccionario serializable.r   Nr)   )r*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   floatr9   r:   r;   r<   r=   r>   r"   r#   r$   r%   r&   r'   r(   )r?   r@   r@   rA   to_dict>   s:    z OpportunityAndCostingDTO.to_dict)
NNNNNNNNNN)
__name__
__module____qualname____doc__strrE   intrB   dictrF   r@   r@   r@   rA   r      sN                           -r   c                   @   s   e Zd ZdZeeee dddZee	e
eeeef f  dddZee	e
eef  ddd	Zeee	e
 dd
dZeeedddZeeedddZeeedddZdS )RecepcionCotizSQLuY   Clase de servicio para manejar la lógica de recepción de datos de costeo y oportunidad.)costing_numrD   c                 C   sR  d}zt   }| }|||  | }W 5 Q R X |rt|d |d |d |d |d |d |d |d	 |d
 |d |d |d |d |d |d |d |d |d |d |d |d |d |d |d |d |d |d |d dW  5 Q R  W S W 5 Q R  W dS Q R X W n8 tk
rL } ztd |  W Y dS d}~X Y nX dS )!z{
        Consulta unificada que obtiene los datos de Q_CostingHead y Q_OpportunityCRM
        en una sola llamada.
        aO  
            SELECT TOP 1
                Q_CostingHead.CostingID,
                Q_OpportunityCRM.CRM_OpportunityNumber,
                Q_OpportunityCRM.CRM_ContactName,
                Q_OpportunityCRM.CRM_ContactType,
                Q_OpportunityCRM.CRM_AssignedSalesperson,
                Q_OpportunityCRM.CRM_ContactAdress,
                Q_OpportunityCRM.CRM_ContactColonia,
                Q_OpportunityCRM.CRM_ContactCity,
                Q_OpportunityCRM.CRM_ContactNumber,
                Q_OpportunityCRM.CRM_ContactCountry,
                Q_OpportunityCRM.CRM_ContactLegalIdentifier,
                Q_OpportunityCRM.CRM_ContactZip,
                Q_OpportunityCRM.CRM_ContactState,
                Q_OpportunityCRM.CRM_ContactEmail,
                Q_CostingHead.CaseCost,
                Q_CostingHead.SalePriceList,
                Q_CostingHead.SalePriceMin,
                Q_CostingHead.DiscountMaxPercent,
                Q_CostingHead.RunTimeNumber,
                Q_CostingHead.RunTimeType,
                Q_CostingHead.TechnicalTermsAndConditions,
                Q_CostingHead.FinancePercent,
                Q_CostingHead.TaxCode,
                Q_CostingHead.CurrencyCode,
                Q_TaxRate.FrontES as Q_TaxRate_FrontES,
                Q_TaxRate.FrontEN as Q_TaxRate_FrontEN,
                Q_Currency.FrontES as Q_Currency_FrontES,
                Q_Currency.FrontEN as Q_Currency_FrontEN
            FROM
                Q_CostingHead
            INNER JOIN
                Q_OpportunityCRM ON Q_CostingHead.CRM_OpportunityID = Q_OpportunityCRM.CRM_OpportunityID
            LEFT JOIN
                Q_TaxRate ON Q_TaxRate.TaxCode = Q_CostingHead.TaxCode 
                        AND Q_TaxRate.CurrencyCode = Q_CostingHead.CurrencyCode 
                        AND Q_TaxRate.Active >= 1
            LEFT JOIN
                Q_Currency ON Q_Currency.CurrencyCode = Q_CostingHead.CurrencyCode
                        AND Q_Currency.Active >= 1
            WHERE
                Q_CostingHead.CostingNum = ?
            ORDER BY
                Q_CostingHead.Version DESC;
        r                           	   
                                                      r   Nz#Error al obtener datos unificados: )r
   cursorexecutefetchoner   	Exceptionprint)rO   queryconnrk   rower@   r@   rA   get_unified_data_by_costing_numc   sR    .

              z1RecepcionCotizSQL.get_unified_data_by_costing_numrC   c               
   C   s   d} g }zpt  ^}| J}||  | }|D ],}||d |d t|d |d d q2W 5 Q R X W 5 Q R X |W S  tk
r } ztd|  g  W Y S d}~X Y nX dS )	ui   
        Consulta la tabla Q_TaxRate para obtener el código y la descripción de los impuestos.
        zTSELECT TaxCode, FrontES, TaxAmount, CurrencyCode    FROM Q_TaxRate WHERE Active = 1;r   rP   rQ   rR   )r#   FrontESZ	TaxAmountr$   z Error al obtener los impuestos: Nr
   rk   rl   fetchallappendrE   rn   ro   )rp   taxesrq   rk   rowsrr   rs   r@   r@   rA   	get_taxes   s"    

zRecepcionCotizSQL.get_taxesc               
   C   s   d} g }zft  T}| @}||  | }|D ]"}||d |d |d d q2W 5 Q R X W 5 Q R X |W S  tk
r } ztd|  g  W Y S d}~X Y nX dS )uw   
        Consulta la tabla Q_Currency para obtener el código, el nombre y el símbolo de las monedas activas.
        zJSELECT CurrencyCode, FrontES, CurrSymbol FROM Q_Currency WHERE Active = 1;r   rP   rQ   )r$   ru   Z
CurrSymbolzError al obtener las monedas: N)r
   rk   rl   rw   rx   rn   ro   )rp   
currenciesrq   rk   rz   rr   rs   r@   r@   rA   get_currencies   s    

z RecepcionCotizSQL.get_currenciesc                 C   s   d}g }zt  z}| f}|||  | }|D ]F}||d |d |d t|d |d t|d t|d d	 q4W 5 Q R X W 5 Q R X |W S  tk
r } ztd
|  g  W Y S d}~X Y nX dS )uN   
        Consulta Q_CostingDetail para obtener las líneas de costeo.
        a  
            SELECT 
                CostingLine,
                PartNum,
                PartDescription,
                Qty,
                UOMCode,
                UnitPrice,
                Amount
            FROM Q_CostingDetail
            WHERE CostingID = (
                SELECT TOP 1 CostingID 
                FROM Q_CostingHead 
                WHERE CostingNum = ? 
                ORDER BY Version DESC
            )
            ORDER BY CostingLine ASC;
        r   rP   rQ   rR   rS   rT   rU   )ZCostingLinePartNumPartDescriptionQtyUOMCode	UnitPriceAmountz%Error al obtener detalles de costeo: Nrv   )rO   rp   detailsrq   rk   rz   rr   rs   r@   r@   rA   get_costing_details   s*    



	z%RecepcionCotizSQL.get_costing_details)datarD   c              7   C   s  zddl m} ddlm} | d}d}t ^}| H}||| | }|d }d}	||	|f | }
|
r|
d nd}|std|  W 5 Q R  W 5 Q R  W d	S |d }|d
 r|d
 nd}|d| | }|d sd
n
|d d
 }| d}|}|r:|d| | }|r:|d r:|d }td| d|  td|d  d td|d  d td|d  d |d r|d 	 nd	}|d r|d 	 nd	}|d r|d 	 nd	}|||| d| d| d| d|d |d |d |d |d |d  |d! |d" |d# |p<d$|pDd%|pLd&| d'd| d(d| d)d||| d*d||| d+d
| d,g t
| d,g | d-| d.| jd/d0| d1d2| d3d4| d5| d6| d7| d8| d9| d:| d;| d<| d=| d>| d?| d@| dA| dBdC3}tdD|dE   |dH|}|W  5 Q R  W  5 Q R  W S Q R X W 5 Q R X W nH tk
r } z(tdG|  dd	l}|  W Y d	S d	}~X Y nX d	S )Iuw   
        Genera HTML de vista previa de la cotización SIN guardar en BD.
        Retorna el HTML renderizado.
        r   )render_templater   r*   u  
                SELECT TOP 1
                    Q_CostingHead.CostingNum,
                    Q_CostingHead.Version,
                    
                    -- Cliente
                    Q_OpportunityCRM.CRM_ContactName,
                    Q_OpportunityCRM.CRM_ContactType,
                    Q_OpportunityCRM.CRM_OpportunityNumber,
                    Q_OpportunityCRM.CRM_ContactEmail,
                    Q_OpportunityCRM.CRM_ContactNumber,
                    Q_OpportunityCRM.CRM_ContactCity,
                    Q_OpportunityCRM.CRM_ContactState,
                    Q_OpportunityCRM.CRM_ContactCountry,
                    Q_OpportunityCRM.CRM_ContactAdress,
                    
                    -- ✅ El vendedor es el UserID de la oportunidad
                    TRIM(CONCAT(
                        ISNULL(Seller.FirstName, ''), ' ',
                        ISNULL(Seller.MiddleName, ''), ' ', 
                        ISNULL(Seller.LastName, ''), ' ',
                        ISNULL(Seller.SecondLastName, '')
                    )) AS VendedorNombre,
                    ISNULL(Seller.Email, '') AS VendedorEmail,
                    ISNULL(Seller.ContactPhone, '') AS VendedorTelefono,
                    Q_CostingHead.TaxCode

                    
                FROM Q_CostingHead
                
                INNER JOIN Q_OpportunityCRM 
                    ON Q_CostingHead.CRM_OpportunityID = Q_OpportunityCRM.CRM_OpportunityID
                
                -- ✅ JOIN directo: El vendedor es Q_OpportunityCRM.UserID
                LEFT JOIN Profiles AS Seller
                    ON Q_OpportunityCRM.UserID = Seller.UserID
                
                WHERE Q_CostingHead.CostingID = ?
            r]   z
                        select TaxPercent
                        from Q_TaxRate
                        where TaxCode = ?
                    u   ❌ No se encontró CostingID: NrP   m
                        SELECT MAX(Version) FROM Q_QuotationHead WHERE QuotationNum = ?
                    r$   zk
                            SELECT FrontES FROM Q_Currency WHERE CurrencyCode = ?
                        u   💱 Moneda: z -> u   🔍 DEBUG - VendedorNombre: 'rZ   'u   🔍 DEBUG - VendedorEmail: 'r[   u    🔍 DEBUG - VendedorTelefono: 'r\   r8   r<   r=   r>   rQ   rR   rS   rT   rU   rV   rW   rX   rY   zNo asignadozventas@igsa.comzN/A	SalePriceDiscountPercentr   TotalAmountOvercostFactorQuotationLinesz%d/%m/%Yz%H:%MZIGSAz"Integradora de Servicios Avanzadosproyecto_nombrezPor definirproyecto_requerimientosZNingunocaseSelectedcase1AnticipoPercentcase1FiniquitoPercentcase2AnticipoPercentcase2FrequencyTypecase2Periodicidadcase2Cantidadcase2CantidadPeriodicidadcase3FrequencyTypecase3Periodicidadcase3Cantidadcase3CantidadPeriodicidadcase4DiasFinanciamientocase5PlazoCreditoFlag)3quotation_numversionr   caso_costeor<   r=   r>   cliente_nombretipo_contactooportunidad_crmcliente_emailcliente_telefonocliente_ciudadcliente_estadocliente_paisZcliente_direccionvendedor_asignadovendedor_emailvendedor_telefonoprecio_listadescuento_porcentajeprecio_ofertaimpuesto_codigoZimpuedesto_porcentajeprecio_totalmonedamoneda_codigofactor_sobrecostolineastotal_lineasZfecha_actualZhora_actualu   año_actualZempresaZempresa_completar   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   u    ✅ Vendedor final en template: r   *Emails/Ventas/Cotiz/PreviewCotizacion.htmlu#   ❌ Error al generar vista previa: )r   )flaskr   r   getr
   rk   rl   rm   ro   striplennowstrftimeyearrn   	traceback	print_exc)r   r   r   r   rp   rq   rk   rr   ZtaxCodeZquery2tax_rowZtax_percentr   Zcurrent_versionversion_resultZnext_versioncurrency_codeZcurrency_nameZcurrency_resultvendedor_nombrer   r   template_datahtmlrs   r   r@   r@   rA   generate_quotation_preview	  s    
+









T
2z,RecepcionCotizSQL.generate_quotation_previewc                 C   sr  z&t  }| }| d}|d| | }|s`dddW  5 Q R  W  5 Q R  W S |d }|d| | }|d sdn
|d d }| d	| }td
|  d}	||	||| d| d| dd| dd| d| d| d| d| d| d| d| d| d| d| d| d| d| d| d| d| d | d!f | d"g }
d#}|
D ]T}||||d$|d%|d&|d'|d(|d)|d*|df	 q|  td+| d, W 5 Q R X d-d.||d/W  5 Q R  W S Q R X W nD tk
rl } z$td0|  dt|d W Y S d1}~X Y nX d1S )2u   
        Crea una nueva cotización en Q_QuotationHead y Q_QuotationDetail.
        Envía notificación por correo al departamento de ingeniería.
        r*   zf
                        SELECT CostingNum FROM Q_CostingHead WHERE CostingID = ?
                    FzCostingID no encontrado)successerrorr   r   rP   -u   Creando cotización: a  
                        INSERT INTO Q_QuotationHead (
                            QuotationNum,
                            Version,
                            CaseCost,
                            SalePrice,
                            DiscountPercent,
                            OvercostFactor,
                            Amount,
                            TaxCode,
                            TotalAmount,
                            CurrencyCode,
                            Active,
                            caseSelected,
                            case1AnticipoPercent,
                            case1FiniquitoPercent,
                            case2AnticipoPercent,
                            case2FrequencyType,
                            case2Periodicidad,
                            case2Cantidad,
                            case2CantidadPeriodicidad,
                            case3FrequencyType,
                            case3Periodicidad,
                            case3Cantidad,
                            case3CantidadPeriodicidad,
                            case4DiasFinanciamiento,
                            case5PlazoCreditoFlag
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    r8   r   r   r   r   r#   r   r$   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   a  
                        INSERT INTO Q_QuotationDetail (
                            QuotationID,
                            QuotationLine,
                            CostingLineID,
                            PartNum,
                            PartDescription,
                            Qty,
                            UOMCode,
                            UnitPrice,
                            Amount
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                    ZQuotationLineZCostingLineIDr~   r   r   r   r      Cotización z creada exitosamente en BDTu   Cotización creada exitosamente)r   messageZQuotationIDVersionu   Error al crear cotización: N)	r
   rk   r   rl   rm   ro   commitrn   rK   )r   rq   rk   r   Zcosting_resultr   r   r   quotation_idZinsert_head_queryZquotation_linesZinsert_detail_queryliners   r@   r@   rA   create_quotation  s    

$


z"RecepcionCotizSQL.create_quotation)r   rD   c                 C   s   zddl m} | }| }|d|  | }|r|d rL|d  nd|d rb|d  nd|d rx|d  nddW  5 Q R  W  5 Q R  W S W 5 Q R X W 5 Q R X W n0 tk
r } ztd|  W 5 d}~X Y nX ddddS )	u!   Obtiene información del vendedorr   r	   a  
                        SELECT TOP 1
                            TRIM(CONCAT(
                                ISNULL(Seller.FirstName, ''), ' ',
                                ISNULL(Seller.MiddleName, ''), ' ', 
                                ISNULL(Seller.LastName, ''), ' ',
                                ISNULL(Seller.SecondLastName, '')
                            )) AS VendedorNombre,
                            ISNULL(Seller.Email, '') AS VendedorEmail,
                            ISNULL(Seller.ContactPhone, '') AS VendedorTelefono
                            
                        FROM Q_CostingHead
                        INNER JOIN Q_OpportunityCRM 
                            ON Q_CostingHead.CRM_OpportunityID = Q_OpportunityCRM.CRM_OpportunityID
                        LEFT JOIN Profiles AS Seller
                            ON Q_OpportunityCRM.UserID = Seller.UserID
                        WHERE Q_CostingHead.CostingID = ?
                    NrP   rQ   )nombreemailtelefonou+   ⚠️ Error al obtener info del vendedor: )Consultas_SQL.conexionr
   rk   rl   rm   r   rn   ro   )r   r
   rq   rk   rr   rs   r@   r@   rA   get_seller_infoe  s     
8 z!RecepcionCotizSQL.get_seller_infoN)rG   rH   rI   rJ   staticmethodrL   r   r   rt   r   r   rK   r   rE   r{   r}   r   rM   r   r   r   r@   r@   r@   rA   rN   `   s"   O$- S 	rN   c                 C   s>   t | trt| S t | tr$|  S tdt| j ddS )zO
    Convierte objetos Decimal y datetime a formatos compatibles con JSON.
    zObject of type z is not JSON serializableN)
isinstancer   rE   r   	isoformat	TypeErrortyperG   )objr@   r@   rA   json_serializer  s
    

r   rC   c            	   
      sr  d} t  }|std dS z:z| }||  dd |jD  | }|sdt	d W W dS  fdd|D }|D ]\}|
 D ]N\}}t|tr| ||< qt|trt|||< qt|trt|||< qqz|W W zS  tjk
r } ztd	t|   W 5 d}~X Y n: tk
rT } ztd
t|   W 5 d}~X Y nX W 5 |  td X dS )u  
    Obtiene todos los registros activos del catálogo Q_TermsType y los devuelve
    como una lista de diccionarios serializables a JSON.

    Returns:
        List[Dict]: Lista de registros de términos y condiciones.
        None: Si no se encuentran registros o ocurre un error.
    a  
        SELECT
            TermsTypeID,
            FrontES,
            FrontEN,
            Active,
            createdAt,
            CreatedBy,
            UpdatedAt,
            UpdatedBy
        FROM Q_TermsType
        WHERE Active = 1
        ORDER BY TermsTypeID ASC;
    u9   ❌ No se pudo establecer conexión con la base de datos.Nu*   🔒 Conexión a la base de datos cerrada.c                 S   s   g | ]}|d  qS r   r@   ).0colr@   r@   rA   
<listcomp>  s     z&get_TermsType_JSON.<locals>.<listcomp>u:   ⚠️ No se encontraron registros activos en Q_TermsType.c                    s   g | ]}t t |qS r@   rM   zipr   rr   columnsr@   rA   r     s     u    Error SQL al obtener términos: z(Error inesperado en get_TermsType_JSON: )r
   loggerr   closeinfork   rl   descriptionrw   warningitemsr   r   r   r   rE   boolpyodbcErrorrK   rn   )	rp   rq   rk   rz   resultsrkeyvaluers   r@   r   rA   get_TermsType_JSON  s>    	







r   )cotizacion_idrD   c              
      s  | st d dS d}d}t }|s,tdz̐zJ| }|||  dd |jD }|	 }|st 
d	|   W W dS tt||}|||  d
d |jD  | } fdd|D }	||	d}
d|
d kr|
d d }t|tr| |
d d< |
d  D ]&\}}t|trt||
d |< q|
d D ]2}| D ]"\}}t|trLt|||< qLq@|
W W S  tjk
r } z t d|  dt|   W 5 d}~X Y n: tk
r } zt dt|   W 5 d}~X Y nX W 5 |r|  t d X dS )u  
    Busca el encabezado y el detalle de una cotización usando su ID primario,
    y los devuelve en un diccionario con tipos de datos serializables a JSON.

    Args:
        cotizacion_id (str): El ID primario de la cotización (ej. "1001-1").

    Returns:
        Un diccionario con la estructura de QuotationHead y QuotationDetail,
        con tipos de datos serializables a JSON, o None si no se encuentra.
    u(   Se proporcionó un cotizacion_id vacío.Na  
        SELECT
            QuotationID, QuotationDate, QuotationNum, Version, SalePrice,
            DiscountPercent, Amount, TaxCode, TotalAmount, CurrencyCode,
            DeliveryTime, TermsAndConditions
        FROM Q_QuotationHead
        WHERE QuotationID = ?;
    a  
        SELECT
            QuotationLineID, QuotationID, QuotationLine, CostingLineID,
            PartNum, PartDescription, Qty, UOMCode, UnitPrice, Amount
        FROM Q_QuotationDetail
        WHERE QuotationID = ?
        ORDER BY QuotationLine ASC;
    u5   No se pudo establecer conexión con la base de datos.u%   Conexión a la base de datos cerrada.c                 S   s   g | ]}|d  qS r   r@   r   columnr@   r@   rA   r     s     z3get_Quote_JSON_BussinessCentral.<locals>.<listcomp>u6   No se encontró encabezado de cotización para el ID: c                 S   s   g | ]}|d  qS r   r@   r   r@   r@   rA   r     s     c                    s   g | ]}t t |qS r@   r   r   Zcolumns_detailr@   rA   r     s     )QuotationHeadQuotationDetailZQuotationDater   r   u.   Error de base de datos al buscar cotización 'z': z5Error inesperado en get_Quote_JSON_BussinessCentral: )r   r   r
   ConnectionErrorr   r   rk   rl   r   rm   r   rM   r   rw   r   r   r   r   r   rE   r   r   rK   rn   )r   Z
query_headZquery_detailrq   rk   Zcolumns_headZrow_headZquotation_head_dataZrows_detailZquotation_detail_dataZ
final_dataZdate_objr   r   r   rs   r@   r   rA   get_Quote_JSON_BussinessCentral  sZ    
		
r   c                    s  z&|  d\}}| }t| }W n& tk
rL   td|   Y dS X d}t }|sdtdzrz| |	 }|
|||f dd |jD }| }|std|   W 5 Q R  W W dS i }g }	|D ]j}
tt||
 |s fd	d
 D }| |d< |	|d< |	 d  d  d  d  d  d d q|W  5 Q R  W W S Q R X W nt tjk
r } ztdt|   W 5 d}~X Y n: tk
r } ztdt|   W 5 d}~X Y nX W 5 |r|  X dS )u   
    Busca los datos de la oportunidad, las líneas de ingeniería y el estado.
    
    Args:
        cotizacion_id (str): ID de la oportunidad (Ej: 123456-1)
    
    Returns:
        Dict: Datos de la oportunidad y las líneas o None.
    r   u$   Formato de cotizacion_id inválido: Nu  
        SELECT
            T1.CRM_OpportunityNumber,
            T1.Version,
            T1.CRM_ContactName,
            T1.CRM_ContactEmail,
            T1.Status AS StatusVenta,
            -- Asumimos una tabla de estado de ingeniería
            T2.StatusIngenieria, 
            T3.Partnum,
            T3.Description,
            T3.Quantity,
            T3.UnitPriceIngenieria, -- Precio que puso Ingeniería
            T3.DiscountVenta,       -- Descuento que puede modificar Venta
            T3.FinalPrice,
            -- Campos financieros ya calculados (si existen)
            T1.PrecioLista,
            T1.PrecioVentaIVA,
            T1.TiempoEntrega,
            T1.UnidadTiempo,
            T1.TerminosIngenieria -- Términos de Ingeniería
        FROM Q_OpportunityCRM T1
        LEFT JOIN Q_QuotationHead T2 ON T1.CRM_OpportunityID = T2.CRM_OpportunityID 
        LEFT JOIN Q_QuotationLines T3 ON T2.QuotationID = T3.QuotationID 
        WHERE 
            T1.CRM_OpportunityNumber = ? AND T1.Version = ? AND T1.Active = 1
            AND T2.StatusIngenieria IS NOT NULL -- Solo cotizaciones que han pasado por ingeniería
        ORDER BY T3.LineNum ASC
    u4   No se pudo establecer conexión con la base de datosc                 S   s   g | ]}|d  qS r   r@   r   r@   r@   rA   r   }  s     z.buscar_cotizacion_completa.<locals>.<listcomp>u-   No se encontraron datos para Cotización ID: c                    s   i | ]}|d kr| | qS )PartnumDescriptionQuantityUnitPriceIngenieriaDiscountVenta
FinalPricer@   )r   kr   r@   rA   
<dictcomp>  s       z.buscar_cotizacion_completa.<locals>.<dictcomp>r   r   r   r   r   r  r  r  r   u.   Error de base de datos al buscar cotización: z0Error inesperado en buscar_cotizacion_completa: )splitr   rL   
ValueErrorr   r   r
   r   r   rk   rl   r   rw   r   rM   r   rx   r   r   rK   rn   )r   Z	op_numberr   rp   rq   rk   r   rz   headerlinesrr   rs   r@   r  rA   buscar_cotizacion_completaA  sZ     
	  r  )r   r   user_idrD   c              
   C   s   |sdddS z@ddl m} ||}tdt| d|  d|  dd	dW S  tk
r } z*td
t|  dt|d W Y S d}~X Y nX dS )z?Actualiza campos editables (ej. Descuento) en Q_QuotationLines.Tu   No hay líneas para actualizarr   r   rP   obtener_nombre_usuariozActualizando u    líneas para z por u   Líneas actualizadasu,   Error al actualizar líneas de cotización: FN)CotizEspSolicitudSQLr  r   r   r   rn   r   rK   )r   r   r  r  	user_namers   r@   r@   rA   actualizar_lineas_cotizacion  s    
 r  )r   financierosr  rD   c           	   
   C   s(  d}t  }|sdddS  zzddlm} ||}| | }|||d |d |d	 || f |  |jd
krdddW  5 Q R  W W S t	d|   ddiW  5 Q R  W W \S Q R X W nL t
jk
r } z*tdt|  ddd W Y W S d}~X Y nX W 5 |r"|  X dS )uI   Actualiza la sección financiera en Q_OpportunityCRM (o Q_QuotationHead).a1  
        UPDATE Q_OpportunityCRM
        SET 
            DiscountPorcentaje = ?,
            IncludesIVA = ?,
            PrecioVentaIVA = ?,
            UpdatedBy = ?,
            UpdatedAt = GETDATE()
        WHERE CRM_OpportunityID = ? -- Asumiendo que el ID de la oportunidad es el cotizacion_id
    F   Error de conexión a BDr  rP   r  	descuentoZincluye_ivaZprecio_final_con_ivar   u8   Oportunidad no encontrada para actualización financieraz$Datos financieros actualizados para r   Tz'Error de BD al actualizar financieros: Error de base de datosNr
   r   r  r  rk   rl   r   rowcountr   r   r   r   r   rK   )	r   r  r  rp   rq   r  r  rk   rs   r@   r@   rA   actualizar_calculo_financiero  s4     

$( r  )r   tiempo_condicionesr  rD   c           	   
   C   s(  d}t  }|sdddS  zzddlm} ||}| | }|||d |d |d	 || f |  |jd
krdddW  5 Q R  W W S t	d|   ddiW  5 Q R  W W \S Q R X W nL t
jk
r } z*tdt|  ddd W Y W S d}~X Y nX W 5 |r"|  X dS )uM   Actualiza el tiempo de entrega y los términos técnicos en Q_OpportunityCRM.u  
        UPDATE Q_OpportunityCRM
        SET 
            TiempoEntrega = ?,
            UnidadTiempo = ?,
            TerminosVenta = ?, -- Nuevo campo para términos de Venta
            UpdatedBy = ?,
            UpdatedAt = GETDATE()
        WHERE CRM_OpportunityID = ?
    Fr  r  rP   r  Ztiempo_ejecucionZunidad_tiempoZterminos_ventar   u<   Oportunidad no encontrada para actualización de condicionesz'Tiempo y condiciones actualizados para r   Tz0Error de BD al actualizar tiempo y condiciones: r  Nr  )	r   r  r  rp   rq   r  r  rk   rs   r@   r@   rA   actualizar_tiempo_y_condiciones  s4     

$( r  )r   r  r   rD   c              
   C   sz   z,t | |}td|  d|  d|dW S  tk
rt } z*tdt|  dt|d W Y S d}~X Y nX dS )	u   
    Marca el estado final de la cotización, genera el PDF y registra el evento.
    
    Nota: La generación de PDF con Puppeteer/Node.js debería llamarse aquí.
    r   z0 marcada como ENVIADA_A_CLIENTE y PDF generado: T)r   pdf_urlu.   Error al finalizar cotización y generar PDF: Fr  N)generar_pdf_final_cotizacionr   r   rn   r   rK   )r   r  r   r  rs   r@   r@   rA   finalizar_cotizacion_y_enviar  s    
r  )r   r   rD   c                 C   s   d|  dS )uI   Simula la generación de PDF final (debería llamar a Node.js/Puppeteer).z*https://sycelephant.com/static/pdfs/cotiz/z
_final.pdfr@   )r   r   r@   r@   rA   r  1  s    r  )r   decimalr   r   loggingtypingr   r   r   r   r   r
   	getLoggerr   r   rN   r   r   rK   r   r  rL   r  r  r  r  r  r@   r@   r@   rA   <module>   s*   
R    2@fg.-