﻿unit fentradas;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.DBCtrls, Vcl.Mask,
  Vcl.ComCtrls, Vcl.Grids, Vcl.DBGrids, Vcl.ExtCtrls, datos, uIAInvoiceExtractor,
  ZAbstractDataset, ZDataset, Data.DB, ZAbstractRODataset, ZAbstractTable,
  Winapi.ShellAPI, System.IOUtils;

type
  Tentradas = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    Panel5: TPanel;
    Label1: TLabel;
    tbuscar: TEdit;
    DBGrid1: TDBGrid;
    Panel3: TPanel;
    Panel4: TPanel;
    DatosProveedores: TPageControl;
    TDatosClientes: TTabSheet;
    Panel14: TPanel;
    Label2: TLabel;
    Label4: TLabel;
    DBEdit3: TDBEdit;
    Panel6: TPanel;
    Label37: TLabel;
    BtEcancelar: TButton;
    btEmodificar: TButton;
    BtEborrar: TButton;
    BtENuevo: TButton;
    BtEguardar: TButton;
    qentradas: TZReadOnlyQuery;
    entradas: TZQuery;
    entradas_detalle: TZQuery;
    entradas_detalleid: TIntegerField;
    entradas_detalleidempresa: TIntegerField;
    entradas_detalleidentrada: TIntegerField;
    entradas_detallecodigo: TWideStringField;
    entradas_detalleid_propio: TWideStringField;
    entradas_detalledescripcion: TWideStringField;
    entradas_detallecantidad: TFloatField;
    entradas_detallestock: TFloatField;
    entradas_detalleprecio: TFloatField;
    DBLookupComboBox1: TDBLookupComboBox;
    dsentradas: TDataSource;
    dsqentradas: TDataSource;
    qaux: TZReadOnlyQuery;
    DBEdit2: TDBEdit;
    DBText3: TDBText;
    DBText2: TDBText;
    DBText1: TDBText;
    qproveedores: TZReadOnlyQuery;
    qproveedoresIdempresa: TIntegerField;
    qproveedoresnombre: TWideStringField;
    qproveedoresid: TIntegerField;
    dsqproveedores: TDataSource;
    dsentradasdetalle: TDataSource;
    Label3: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    DBGrid2: TDBGrid;
    entradas_detallelote: TWideStringField;
    entradas_detalletipo_iva: TIntegerField;
    entradas_detallebase: TFloatField;
    entradas_detalletotal: TFloatField;
    entradasid: TIntegerField;
    entradasidempresa: TIntegerField;
    entradasfecha: TDateField;
    entradasidproveedor: TIntegerField;
    entradasdocumento: TWideStringField;
    entradasbase: TFloatField;
    entradastotaliva: TFloatField;
    entradastotalentrada: TFloatField;
    qentradasid: TIntegerField;
    qentradasidempresa: TIntegerField;
    qentradasfecha: TDateField;
    qentradasidproveedor: TIntegerField;
    qentradasdocumento: TWideStringField;
    qentradasbase: TFloatField;
    qentradastotaliva: TFloatField;
    qentradastotalentrada: TFloatField;
    qentradasnomproveedor: TStringField;
    DBLookupComboBox2: TDBLookupComboBox;
    qarticulospropios: TZReadOnlyQuery;
    qarticulospropioscodigo: TWideStringField;
    qarticulospropiosdescripcion: TWideStringField;
    dsarticulospropios: TDataSource;
    qarticulospropiosidempresa: TIntegerField;
    DBEdit1: TDBEdit;
    Panel7: TPanel;
    Button1: TButton;
    entradas_detalleidproveedor: TIntegerField;
    Button2: TButton;
    BtImportarDocumento: TButton;
    btVerDoc: TButton;
    procedure BtImportarDocumentoClick(Sender: TObject);
    procedure qentradasCalcFields(DataSet: TDataSet);
    procedure FormShow(Sender: TObject);
    procedure BtENuevoClick(Sender: TObject);
    procedure BtEborrarClick(Sender: TObject);
    procedure btEmodificarClick(Sender: TObject);
    procedure BtEguardarClick(Sender: TObject);
    procedure BtEcancelarClick(Sender: TObject);
    procedure dsentradasStateChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure DBGrid2KeyPress(Sender: TObject; var Key: Char);
    procedure DBGrid2DrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
    procedure DBGrid2ColExit(Sender: TObject);
    procedure entradas_detalleBeforePost(DataSet: TDataSet);
    procedure Button1Click(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure tbuscarChange(Sender: TObject);
    procedure qentradasBeforeOpen(DataSet: TDataSet);
    procedure DBGrid2KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure DBGrid1TitleClick(Column: TColumn);
    procedure Button2Click(Sender: TObject);
    procedure entradas_detalleAfterPost(DataSet: TDataSet);
    procedure btVerDocClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    var   filtro:string;
          idpropio:string;
          vertodos:integer;
    procedure AppMessage(var Msg: Tmsg; var Handled:Boolean);

  end;

var
  entradas: Tentradas;

implementation

{$R *.dfm}
uses fbuscararticulos, fbuscararticulosprov, ftraza_materiales;

procedure Tentradas.AppMessage(var Msg: Tmsg; var Handled: Boolean);
 var
  actual : TWincontrol;
 begin
 if Msg.message = WM_KEYDOWN then
    begin

    // esto es para controlar que con la flecha abajo se desplegen las
    // listas

    if Msg.wParam = VK_DOWN then
      begin
      Actual := Screen.ActiveControl;
      if Actual is TDBLookupComboBox then
            if not TDBLookupComboBox(Screen.ActiveForm.ActiveControl).listVisible then
               TDBLookupComboBox(Screen.ActiveForm.ActiveControl).DropDown;

      // si utilizas las RX
   {   if Actual is TRxDBLookupCombo then
            if not TRxDBLookupCombo(Screen.ActiveForm.ActiveControl).listVisible then
               TRxDBLookupCombo(Screen.ActiveForm.ActiveControl).DropDown;

    }
      if Actual is TComboBox then
         SendMessage(TComboBox(Screen.ActiveForm.ActiveControl).Handle,CB_SHOWDROPDOWN,-1,0);

      if (Actual is TCustomEdit) then
          Msg.wParam := VK_TAB;
      // se pueden a�adir mas controles....

      end;

    // y aqui es el control del intro.
    if Msg.wParam = VK_RETURN THEN
       begin
       Actual := Screen.ActiveControl;
       // para los edit.
       if (Actual is TCustomEdit) and
          not (Actual is TCustomMemo) then
          Msg.wParam := VK_TAB;

       // para los memo. Con ctrl + enter se salta de linea dentro del
       // memo, si solo es intro salta al siguiente control
       if (Actual is TCustomMemo) and
          (HiWord(GetKeyState(VK_CONTROL)) = 0)then
          Msg.wParam := VK_TAB;

       // para los lookup
       if Actual is TDBLookupComboBox then
          if not TDBLookupComboBox(Actual).listVisible then
             Msg.wParam := VK_TAB;

       // Si utilizas las RX
{       if Actual is TRxDBLookupCombo then
          if not TRxDBLookupCombo(Actual).listVisible then
             Msg.wParam := VK_TAB;
 }
       // Los combobox
       if Actual is TCustomComboBox then
          if not TCustomComboBox(Actual).DroppedDown then
             Msg.wParam := VK_TAB;

       // el radiobutton
       if Actual is TRadioButton then
          Msg.wParam := VK_TAB;

       // en el grid
       if Actual is TDBGrid then
          if (TDBGrid(Actual).ReadOnly) or
             (HiWord(GetKeyState(VK_CONTROL)) = 1)then
             Msg.wParam := VK_TAB;

       // o esto otro Con Ctrl + intro siguiente celda con
       // ctrl + shift + intro celda anterior
       // es complicado pero es por no liar con el intro solo
       // que siempre pasa de un control a otro.
       if Actual is TDBGrid then
          begin
          if (TDBGrid(Actual).ReadOnly) or
             (HiWord(GetKeyState(VK_CONTROL)) = 0) then
             Msg.wParam := VK_TAB
          else
             if not(HiWord(GetKeyState(VK_SHIFT)) = 0) then
                begin
                if TDBGrid(Actual).selectedindex > 0 then             { increment the field }
                   TDBGrid(Actual).selectedindex := TDBGrid(Actual).selectedindex -1
                else
                   TDBGrid(Actual).selectedindex := TDBGrid(Actual).fieldcount -1;
                end
             else
                begin
                if TDBGrid(Actual).selectedindex < (TDBGrid(Actual).fieldcount -1) then             { increment the field }
                   TDBGrid(Actual).selectedindex := TDBGrid(Actual).selectedindex +1
                else
                   TDBGrid(Actual).selectedindex := 0;
                end;
          end;


       // ListBox
       if Actual is TCustomListBox then
          Msg.wParam := VK_TAB;

       // TabControl
       if Actual is TCustomTabControl then
          Msg.wParam := VK_TAB;

       // Aqui se pondr�an todos los controles que usamos en la
       // aplicaci�n y necesitamos cambiar el tab por intro.
       // de esta forma te despreocupas de cuantos a�ades y es m�s
       // limpio el c�digo

       end;
    end;
 end;

procedure Tentradas.BtEborrarClick(Sender: TObject);
begin
   entradas.Delete;
   qentradas.Refresh;
end;

procedure Tentradas.BtEcancelarClick(Sender: TObject);
begin
   entradas.Cancel;
   entradas.MasterSource:=dsqentradas;
end;

procedure Tentradas.BtEguardarClick(Sender: TObject);
begin
    entradas.Post;
    qentradas.Refresh;
    qentradas.Locate('id',entradasid.AsInteger,[locaseinsensitive,lopartialkey]);
    entradas.MasterSource:=dsqentradas;
    entradas.Refresh;
end;

procedure Tentradas.btEmodificarClick(Sender: TObject);
begin
    entradas.edit;
    dbedit3.SetFocus;
end;

procedure Tentradas.BtENuevoClick(Sender: TObject);
begin
   entradas.MasterSource:=nil;
   entradas.append;
   entradasidempresa.Value:=dm.tempresaid.AsInteger;
   dbedit3.SetFocus;
end;

procedure Tentradas.Button1Click(Sender: TObject);
begin
  entradas_detalle.Delete;
  entradas_detalle.Refresh;
  entradas.Refresh;
end;

procedure Tentradas.Button2Click(Sender: TObject);
//var tlote,tcodigo,proveedor,documento:string;
begin
  //  tlote:=''''+entradas_detallelote.asstring+'''';
  //  tcodigo:=''''+entradas_detalleid_propio.AsString+'''';
  //  proveedor:=''''+qentradasnomproveedor.AsString+'''';
  //  documento:=''''+qentradasdocumento.AsString+'''';
    traza_materiales := Ttraza_materiales.Create(nil);
    try
      traza_materiales.qpesadas.close;
      traza_materiales.qpesadas.Prepare;
      traza_materiales.qpesadas.ParamByName('IDENTRADA').AsInteger:=entradas_detalleid.AsInteger;
      traza_materiales.qpesadas.ParamByName('LOTE').Asstring:=entradas_detalleid.AsString;
      traza_materiales.qpesadas.open;

{      traza_materiales.qpesadas2.close;
      traza_materiales.qpesadas2.SQL.Clear;
      traza_materiales.qpesadas2.SQL.Add('select '+proveedor+' as proveedor,'+documento+' as documento,'+tcodigo+' as codigo,'+tlote+' as lote,ea.id,idea,fecha,p.iddestino,origen,idorigen,parcela,lotes_detalle,ea.idalbaran,ea.idfactura as factura,c.nombre,p.pedidocliente,cd.descripcion as destino from ea_pesadas ea,pedidos p,clientes c,clientes_destinos cd');
      traza_materiales.qpesadas2.SQL.Add(' where c.idempresa=cd.idempresa and c.id=cd.idcliente and cd.codigo=p.iddestino and ea.idempresa=p.ide and ea.pedidocliente=p.id and p.idcliente=c.id and c.idempresa=ea.idempresa and ea.id in (select DISTINCT(idea) from ea_materiales where lote='+tlote+' and idaf=(select id from articulosf where codigo='+tcodigo+')) order by ea.id');
      traza_materiales.qpesadas2.open;}

      traza_materiales.showmodal();
    finally
      traza_materiales.Free;
    end;
end;

procedure Tentradas.DBGrid1TitleClick(Column: TColumn);
var
  st:ZAbstractRODataset.TSortType;
begin
  st:=qentradas.SortType;
  qentradas.SortedFields:=Column.FieldName;
  If st = stAscending then qentradas.SortType:=stDescending else qentradas.SortType:=stAscending;
  dsqentradas.DataSet.First;
end;

procedure Tentradas.DBGrid2ColExit(Sender: TObject);
begin
  if DBGrid2.SelectedField.FieldName = DBLookupComboBox2.DataField then
    DBLookupComboBox2.Visible := False;
  if (dbgrid2.SelectedField.FieldName='codigo') and (entradas_detalle.State in [dsedit,dsinsert]) and (entradas_detallecodigo.AsString<>'') then
  begin
    qaux.SQL.Clear;
    qaux.SQL.Add('select id,id_propio,descripcion,ult_precio from proveedores_articulos where idempresa='+entradasidempresa.AsString+' and id='+''''+entradas_detallecodigo.asstring+''''+' and idproveedor='+entradasidproveedor.asstring);
    qaux.Open;
    if qaux.RecordCount>0 then
    begin
        entradas_detalledescripcion.Value:=qaux.Fields[2].AsString;
        entradas_detalleid_propio.Value:=qaux.Fields[1].AsString;
        entradas_detalleprecio.Value:=qaux.Fields[3].Asfloat;
    end;
  end;
end;

procedure Tentradas.DBGrid2DrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
  if (gdFocused in State) then
  begin
    if (Column.Field.FieldName = DBLookupComboBox2.DataField) then
    with DBLookupComboBox2 do
    begin
      Left := Rect.Left + DBGrid2.Left + 2;
      Top := Rect.Top + DBGrid2.Top + 2;
      Width := Rect.Right - Rect.Left;
      Width := Rect.Right - Rect.Left;
      Height := Rect.Bottom - Rect.Top;
      Visible := True;
    end;
  end
end;

procedure Tentradas.DBGrid2KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
    if (key=115) then
    begin
      buscararticulosprov := Tbuscararticulosprov.Create(nil);
      try
        fbuscararticulosprov.filtro:=entradas_detallecodigo.AsString;
        buscararticulosprov.Edit1.Text:=entradas_detallecodigo.AsString;
        buscararticulosprov.showmodal();
      finally
        if entradas_detalle.State in [dsbrowse] then entradas_detalle.Edit;
        entradas_detallecodigo.Value:=buscararticulosprov.qarticuloscodigo.AsString;
        entradas_detalledescripcion.value:=buscararticulosprov.qarticulosdescripcion.AsString;
        buscararticulosprov.Free;
      end;
    end;
end;

procedure Tentradas.DBGrid2KeyPress(Sender: TObject; var Key: Char);
begin
  if (key = Chr(9)) then Exit;

  if (DBGrid2.SelectedField.FieldName = DBLookupComboBox2.DataField) then
  begin
    DBLookupComboBox2.SetFocus;
    SendMessage(DBLookupComboBox2.Handle, WM_Char, word(Key), 0);
  end
end;

procedure Tentradas.dsentradasStateChange(Sender: TObject);
begin
    if entradas.State in [dsedit,dsinsert] then
    begin
       Panel14.Enabled:=true;
       btenuevo.enabled:=false;
       bteborrar.enabled:=false;
       btemodificar.enabled:=false;
       bteguardar.enabled:=true;
       btecancelar.enabled:=true;
       panel7.Visible:=false;
       dbgrid2.Visible:=false;
    end;
    if entradas.State in [dsbrowse] then
    begin
       Panel14.Enabled:=false;
       btenuevo.enabled:=true;
       bteborrar.enabled:=true;
       btemodificar.enabled:=true;
       bteguardar.enabled:=false;
       btecancelar.enabled:=false;
       panel7.Visible:=true;
       dbgrid2.Visible:=true;
    end;

end;

procedure Tentradas.entradas_detalleAfterPost(DataSet: TDataSet);
begin
    // Optimización: Uso de parámetros en lugar de concatenación de strings
    qaux.SQL.Clear;
    qaux.SQL.Add('UPDATE articulosf SET ');
    qaux.SQL.Add('  ult_preciocompra = :precio, ');
    qaux.SQL.Add('  tipoiva = (SELECT id FROM tipos_iva WHERE tipo = :tipoiva), ');
    qaux.SQL.Add('  stock = (SELECT SUM(stock) FROM entradas_detalle WHERE stock > 0 AND id_propio = :codigo AND idempresa = :idempresa), ');
    qaux.SQL.Add('  preciocoste = :precio ');
    qaux.SQL.Add('WHERE idempresa = :idempresa AND codigo = :codigo');
    
    qaux.ParamByName('precio').AsFloat := entradas_detalleprecio.AsFloat;
    qaux.ParamByName('tipoiva').AsFloat := entradas_detalletipo_iva.AsFloat; // Nota: el campo tipoiva en tipos_iva suele ser el valor (e.g. 21.0)
    qaux.ParamByName('idempresa').AsInteger := entradas_detalleidempresa.AsInteger;
    qaux.ParamByName('codigo').AsString := entradas_detalleid_propio.AsString;
    
    qaux.ExecSQL;

    // Actualizar id_propio en proveedores_articulos
    qaux.SQL.Clear;
    qaux.SQL.Add('UPDATE proveedores_articulos SET id_propio = :id_propio ');
    qaux.SQL.Add('WHERE idempresa = :idempresa AND idproveedor = :idproveedor AND id = :codigo');
    
    qaux.ParamByName('id_propio').AsString := entradas_detalleid_propio.AsString;
    qaux.ParamByName('idempresa').AsInteger := entradas_detalleidempresa.AsInteger;
    qaux.ParamByName('idproveedor').AsInteger := entradas_detalleidproveedor.AsInteger;
    qaux.ParamByName('codigo').AsString := entradas_detallecodigo.AsString;
    
    qaux.ExecSQL;

    // Refrescamos los datasets para ver los cambios en la interfaz
    entradas.Refresh;
    entradas_detalle.Refresh;
end;

procedure Tentradas.entradas_detalleBeforePost(DataSet: TDataSet);
begin
   entradas_detalleidempresa.Value:=dm.tempresaid.AsInteger;
   if entradas_detallecodigo.asstring='' then entradas_detalle.Cancel;
end;

procedure Tentradas.FormCreate(Sender: TObject);
begin
 with DBLookupComboBox2 do
 begin
   DataSource := dsentradasdetalle; // -> AdoTable1 -> DBGrid1
   ListSource := dsarticulospropios;
   DataField   := 'id_propio'; // from AdoTable1 - displayed in the DBGrid
   KeyField  := 'codigo';
   ListField := 'descripcion;codigo';

   Visible    := False;
 end;

end;

procedure Tentradas.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
    if (activecontrol is tdbgrid) then
    begin
        if Key = 13 then Key :=9;
    end;

end;

procedure Tentradas.FormKeyPress(Sender: TObject; var Key: Char);
var formatos:tformatsettings;
punto:string;
begin
    GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT, formatos);
    punto:=formatos.DecimalSeparator;
    if punto=',' then
    begin
        key:=upcase(key);
        if (key='.') and (activecontrol is tdbedit) then
        begin
           if  (activecontrol as tdbedit).Field.DataType in [ftfloat,ftCurrency,ftbcd,ftfmtbcd] then key:=','
         end;
        if (key='.') and (activecontrol is tdbgrid) then
        begin
            key:=',';
        end;
    end;
    if (activecontrol is tdbgrid) then
    begin
        if Key = #13 then Key :=#9;
    end;

    if (key=#13) then
    begin
        Perform(WM_NextDLGCtl,0,0);
        key:=#0;
    end;

end;

procedure Tentradas.FormShow(Sender: TObject);
begin
    dm.tempresa.Open;
    qentradas.Close;
    qentradas.Open;
    entradas.Close;
    entradas.Open;
    entradas_detalle.Close;
    entradas_detalle.Open;
    qproveedores.Open;
    qarticulospropios.open;
end;

procedure Tentradas.qentradasBeforeOpen(DataSet: TDataSet);
var filtrolocal:string;
begin
    if filtro<>'' then
    begin
       begin
         filtrolocal:=' where nombre like('+''''+filtro+''''+') or nombre_corto like('+''''+filtro+''''+') or id like('+''''+filtro+''''+')';
       end;
      if vertodos=0 then
      begin
          begin
            filtrolocal:=' where idproveedor in (select id from proveedores where nombre like('+''''+filtro+''''+')) or documento like('+''''+filtro+''''+') or id like('+''''+filtro+''''+')';
          end;
      end;
    end;

    if filtro='' then
    begin
      filtrolocal:='';
    end;
    qentradas.SQL.Clear;
    qentradas.SQL.Add('select * from entradas ' + filtrolocal +' order by fecha desc,idproveedor')


end;

procedure Tentradas.qentradasCalcFields(DataSet: TDataSet);
begin
    qaux.SQL.Clear;
    qaux.SQL.Add('select nombre_corto from proveedores where id='+qentradasidproveedor.asstring+' and idempresa='+qentradasidempresa.asstring);
    qaux.Open;
    qentradasnomproveedor.Value:=qaux.Fields[0].AsString;
end;


procedure Tentradas.tbuscarChange(Sender: TObject);
begin
    filtro:='%'+tbuscar.Text+'%';
    qentradas.Close;
    qentradas.open;
end;

procedure Tentradas.BtImportarDocumentoClick(Sender: TObject);
var
  Extractor: TIAInvoiceExtractor;
  Res: TIAInvoiceResult;
  idProveedor, idCabecera, I, iLineasInsertadas: Integer;
  Dlg: TOpenDialog;
  sCodigoArt, sIdArticulo, sDebugMsg, sIdPropio, sSQL, sLote, sDesc: string;
  bFaltanCodigos, bEncontrado: Boolean;
  dBase, dTotal: Double;
begin
  Dlg := TOpenDialog.Create(Self);
  try
    Dlg.Filter := 'Todos los Archivos (PDF, JPG, PNG, XML)|*.*';
    if not Dlg.Execute then Exit;

    if dm.tempresa.State = dsInactive then dm.tempresa.Open;
    
    Screen.Cursor := crHourGlass;
    Extractor := TIAInvoiceExtractor.Create;
    try
      Application.ProcessMessages;
      Res := Extractor.Extraer(Dlg.FileName, 
                               dm.tempresa.FieldByName('IAmodelo').AsString, 
                               dm.tempresa.FieldByName('apikey').AsString);
      Screen.Cursor := crDefault;
    
    if not Res.Exito then
    begin
      ShowMessage('Error de IA: ' + Res.MensajeError);
      Exit;
    end;
    
    if Res.Cabecera.ProveedorNIF = '' then
    begin
      ShowMessage('La Inteligencia Artificial no devolvió un NIF válido.');
      Exit;
    end;
    
    // 1. Proveedor
    // Normalizar NIF (quitar puntos, guiones y espacios)
    sCodigoArt := StringReplace(StringReplace(StringReplace(Res.Cabecera.ProveedorNIF, '.', '', [rfReplaceAll]), '-', '', [rfReplaceAll]), ' ', '', [rfReplaceAll]);
    
    qaux.SQL.Clear;
    qaux.SQL.Add('SELECT id FROM proveedores WHERE REPLACE(REPLACE(REPLACE(nif, ''.'', ''''), ''-'', ''''), '' '', '''') = ' + QuotedStr(sCodigoArt) + ' AND idempresa = ' + IntToStr(dm.tempresaid.AsInteger));
    qaux.Open;
    if qaux.RecordCount > 0 then
      idProveedor := qaux.FieldByName('id').AsInteger
    else
    begin
      qaux.SQL.Clear;
      qaux.SQL.Add('SELECT MAX(id) + 1 FROM proveedores WHERE Idempresa = ' + IntToStr(dm.tempresaid.AsInteger));
      qaux.Open;
      idProveedor := qaux.Fields[0].AsInteger;
      if idProveedor <= 0 then idProveedor := 1;
      
      qaux.SQL.Clear;
      qaux.SQL.Add('INSERT INTO proveedores (id, Idempresa, nif, nombre) VALUES (' +
                   IntToStr(idProveedor) + ', ' +
                   IntToStr(dm.tempresaid.AsInteger) + ', ' +
                   QuotedStr(Res.Cabecera.ProveedorNIF) + ', ' +
                   QuotedStr(Copy(Res.Cabecera.ProveedorNombre, 1, 60)) + ')');
      qaux.ExecSQL;
      qproveedores.Refresh;
    end;
    
    // 1.5 Comprobar si el documento ya ha sido importado anteriormente
    qaux.SQL.Clear;
    qaux.SQL.Add('SELECT id FROM entradas WHERE idproveedor = :prov AND documento = :doc AND idempresa = :emp');
    qaux.ParamByName('prov').AsInteger := idProveedor;
    qaux.ParamByName('doc').AsString := Res.Cabecera.NumDocumento;
    qaux.ParamByName('emp').AsInteger := dm.tempresaid.AsInteger;
    qaux.Open;
    if not qaux.IsEmpty then
    begin
      idCabecera := qaux.FieldByName('id').AsInteger;
      
      // Comprobar si ya tiene documento asociado
      qaux.SQL.Clear;
      qaux.SQL.Add('SELECT id FROM documentos WHERE identrada = :id');
      qaux.ParamByName('id').AsInteger := idCabecera;
      qaux.Open;
      
      if qaux.IsEmpty then
      begin
        // La entrada existe pero no tiene el documento escaneado asociado, vamos a guardarlo
        try
          qaux.SQL.Clear;
          qaux.SQL.Add('INSERT INTO documentos (ide, fecha, descripcion, archivo, nomarchivo, extension, identrada) ');
          qaux.SQL.Add('VALUES (:ide, :fecha, :desc, :archivo, :nom, :ext, :identrada)');
          qaux.ParamByName('ide').AsInteger := dm.tempresaid.AsInteger;
          qaux.ParamByName('fecha').AsDateTime := Date;
          qaux.ParamByName('desc').AsString := 'Factura importada IA (Post-Registro): ' + Res.Cabecera.NumDocumento;
          qaux.ParamByName('archivo').LoadFromFile(Dlg.FileName, ftBlob);
          qaux.ParamByName('nom').AsString := ExtractFileName(Dlg.FileName);
          qaux.ParamByName('ext').AsString := ExtractFileExt(Dlg.FileName);
          qaux.ParamByName('identrada').AsInteger := idCabecera;
          qaux.ExecSQL;
          ShowMessage('La factura ' + Res.Cabecera.NumDocumento + ' ya estaba registrada, pero no tenía el documento físico asociado. Se ha adjuntado el archivo correctamente a la entrada ID: ' + IntToStr(idCabecera));
        except
          on E: Exception do
            ShowMessage('Error al intentar adjuntar el documento a la entrada existente: ' + E.Message);
        end;
      end
      else
      begin
        // Ya existe la entrada Y ya tiene documento
        ShowMessage('Error: El documento ' + Res.Cabecera.NumDocumento + ' ya ha sido importado anteriormente para este proveedor (ID Entrada: ' + IntToStr(idCabecera) + ') y ya tiene un fichero asociado.');
      end;
      Exit;
    end;
    
    // 2. Cabecera Entrada
    qaux.SQL.Clear;
    qaux.SQL.Add('SELECT max(id)+1 from entradas');
    qaux.Open;

    idCabecera := qaux.Fields[0].AsInteger;
    entradas.Append;
    entradas.FieldByName('id').AsInteger := idCabecera;
    entradas.FieldByName('idempresa').AsInteger := dm.tempresaid.AsInteger;
    entradas.FieldByName('idproveedor').AsInteger := idProveedor;
    
    try
      // Intentar forzar el parser si viene del AI en formato YYYY-MM-DD
      entradas.FieldByName('fecha').AsDateTime := EncodeDate(StrToInt(Copy(Res.Cabecera.Fecha,1,4)), StrToInt(Copy(Res.Cabecera.Fecha,6,2)), StrToInt(Copy(Res.Cabecera.Fecha,9,2)));
    except
      entradas.FieldByName('fecha').AsDateTime := Date; 
    end;
    
    entradas.FieldByName('documento').AsString := Res.Cabecera.NumDocumento;
    try
      entradas.Post;
      
      // Obtener el ID real de la entrada recién creada
      qaux.Close;
      qaux.SQL.Text := 'SELECT MAX(id) FROM entradas WHERE idempresa = ' + IntToStr(dm.tempresaid.AsInteger);
      qaux.Open;
      idCabecera := qaux.Fields[0].AsInteger;

    except
      on E: Exception do
      begin
        ShowMessage('Error al crear la cabecera de la entrada: ' + E.Message);
        Exit;
      end;
    end;
    
    // 3. Comprobar si faltan códigos de proveedor en TODAS las líneas
    // Sólo abortamos si no viene NINGÚN código en toda la factura
    bFaltanCodigos := True;
    for I := 0 to Length(Res.Lineas) - 1 do
    begin
      // Ignorar líneas vacías o de transporte/proveedor
      if (Trim(Res.Lineas[I].Descripcion) = '') or 
         (UpperCase(Trim(Res.Lineas[I].Descripcion)) = UpperCase(Trim(Res.Cabecera.ProveedorNombre))) then
         Continue;

      // Si al menos una línea tiene código, importamos todo
      if Trim(Res.Lineas[I].Codigo) <> '' then
      begin
        bFaltanCodigos := False;
        Break;
      end;
    end;

    if Length(Res.Lineas) = 0 then
      bFaltanCodigos := False;

    // === MENSAJE DE DEPURACIÓN: Mostrar todos los datos extraídos ===
    sDebugMsg := '=== DATOS EXTRAÍDOS POR IA ===' + #13#10;
    sDebugMsg := sDebugMsg + 'Proveedor: ' + Res.Cabecera.ProveedorNombre + ' (NIF: ' + Res.Cabecera.ProveedorNIF + ')' + #13#10;
    sDebugMsg := sDebugMsg + 'Fecha: ' + Res.Cabecera.Fecha + #13#10;
    sDebugMsg := sDebugMsg + 'Nº Documento: ' + Res.Cabecera.NumDocumento + #13#10;
    sDebugMsg := sDebugMsg + 'Base: ' + FloatToStr(Res.Cabecera.Base) + ' | IVA (calc): ' + FloatToStr(Res.Cabecera.Total - Res.Cabecera.Base) + ' | Total: ' + FloatToStr(Res.Cabecera.Total) + #13#10;
    sDebugMsg := sDebugMsg + 'ID Proveedor asignado: ' + IntToStr(idProveedor) + #13#10;
    sDebugMsg := sDebugMsg + 'ID Cabecera creada: ' + IntToStr(idCabecera) + #13#10;
    sDebugMsg := sDebugMsg + '--- LÍNEAS DE DETALLE (' + IntToStr(Length(Res.Lineas)) + ') ---' + #13#10;
    for I := 0 to Length(Res.Lineas) - 1 do
    begin
      sDebugMsg := sDebugMsg + Format('Línea %d: Código=[%s] Desc=[%s] Cant=[%s] Precio=[%s] IVA=[%s] Lote=[%s]',
        [I + 1, Res.Lineas[I].Codigo, Res.Lineas[I].Descripcion,
         FloatToStr(Res.Lineas[I].Cantidad), FloatToStr(Res.Lineas[I].Precio),
         FloatToStr(Res.Lineas[I].TipoIVA), Res.Lineas[I].Lote]) + #13#10;
    end;
    ShowMessage(sDebugMsg);

    if bFaltanCodigos then
    begin
      ShowMessage('No hay códigos de artículos de proveedor en los detalles del documento y debe hacerse la inserción manualmente.');
    end
    else
    begin
      // 4. Lineas - Inserción via ExecuteDirect para garantizar persistencia
      iLineasInsertadas := 0;

      for I := 0 to Length(Res.Lineas) - 1 do
      begin
        // VALIDACIÓN: Evitar líneas fantasmas o que repiten el proveedor
        if (Trim(Res.Lineas[I].Descripcion) = '') or
           (UpperCase(Trim(Res.Lineas[I].Descripcion)) = UpperCase(Trim(Res.Cabecera.ProveedorNombre))) then
           Continue;

        // --- LÓGICA DE BÚSQUEDA / CREACIÓN en proveedores_articulos ---
        sCodigoArt := Trim(Res.Lineas[I].Codigo);
        sIdArticulo := sCodigoArt;
        sIdPropio := '';
        bEncontrado := False;

        if sCodigoArt <> '' then
        begin
          // 1. Buscar por código del proveedor
          qaux.Close;
          qaux.SQL.Text := 'SELECT id, id_propio FROM proveedores_articulos ' +
                           'WHERE idproveedor = ' + IntToStr(idProveedor) +
                           ' AND id = ' + QuotedStr(sCodigoArt) +
                           ' AND idempresa = ' + IntToStr(dm.tempresaid.AsInteger);
          qaux.Open;
          if not qaux.IsEmpty then
          begin
            sIdArticulo := qaux.FieldByName('id').AsString;
            if qaux.FieldByName('id_propio').IsNull or (Trim(qaux.FieldByName('id_propio').AsString) = '') then
              sIdPropio := ''
            else
              sIdPropio := Trim(qaux.FieldByName('id_propio').AsString);
            bEncontrado := True;
          end
          else
          begin
            // 2. Buscar por descripción exacta
            qaux.Close;
            qaux.SQL.Text := 'SELECT id, id_propio FROM proveedores_articulos ' +
                             'WHERE idproveedor = ' + IntToStr(idProveedor) +
                             ' AND descripcion = ' + QuotedStr(Res.Lineas[I].Descripcion) +
                             ' AND idempresa = ' + IntToStr(dm.tempresaid.AsInteger);
            qaux.Open;
            if not qaux.IsEmpty then
            begin
              sIdArticulo := qaux.FieldByName('id').AsString;
              if qaux.FieldByName('id_propio').IsNull or (Trim(qaux.FieldByName('id_propio').AsString) = '') then
                sIdPropio := ''
              else
                sIdPropio := Trim(qaux.FieldByName('id_propio').AsString);
              bEncontrado := True;
            end;
          end;

          if not bEncontrado then
          begin
            // 3. No existe: Crear registro nuevo con id_propio vacío
            sIdArticulo := sCodigoArt;
            sIdPropio := '';
            try
              sSQL := 'INSERT INTO proveedores_articulos (idempresa, idproveedor, id, id_propio, descripcion, ult_precio) VALUES (' +
                IntToStr(dm.tempresaid.AsInteger) + ', ' +
                IntToStr(idProveedor) + ', ' +
                QuotedStr(sIdArticulo) + ', ' +
                QuotedStr('') + ', ' +
                QuotedStr(Res.Lineas[I].Descripcion) + ', ' +
                StringReplace(FloatToStr(Res.Lineas[I].Precio), ',', '.', []) + ')';
              DM.DBagrigest.ExecuteDirect(sSQL);
            except
              // Ignorar errores de clave duplicada
            end;
          end;
        end;

        // --- INSERCIÓN DIRECTA del detalle ---
        sDesc := StringReplace(Res.Lineas[I].Descripcion, '''', '''''', [rfReplaceAll]);
        if Res.Lineas[I].Lote <> '' then
          sLote := Res.Lineas[I].Lote
        else
          sLote := Res.Cabecera.NumDocumento;
        dBase := Res.Lineas[I].Cantidad * Res.Lineas[I].Precio;
        dTotal := dBase * (1 + Res.Lineas[I].TipoIVA / 100);

        try
          sSQL := 'INSERT INTO entradas_detalle ' +
            '(idempresa, identrada, idproveedor, codigo, id_propio, descripcion, cantidad, precio, tipo_iva, lote, stock, base, total) VALUES (' +
            IntToStr(dm.tempresaid.AsInteger) + ', ' +
            IntToStr(idCabecera) + ', ' +
            IntToStr(idProveedor) + ', ' +
            QuotedStr(sIdArticulo) + ', ' +
            QuotedStr(sIdPropio) + ', ' +
            QuotedStr(sDesc) + ', ' +
            StringReplace(FloatToStr(Res.Lineas[I].Cantidad), ',', '.', []) + ', ' +
            StringReplace(FloatToStr(Res.Lineas[I].Precio), ',', '.', []) + ', ' +
            IntToStr(Round(Res.Lineas[I].TipoIVA)) + ', ' +
            QuotedStr(sLote) + ', ' +
            StringReplace(FloatToStr(Res.Lineas[I].Cantidad), ',', '.', []) + ', ' +
            StringReplace(FloatToStr(dBase), ',', '.', []) + ', ' +
            StringReplace(FloatToStr(dTotal), ',', '.', []) + ')';
          DM.DBagrigest.ExecuteDirect(sSQL);
          Inc(iLineasInsertadas);
        except
          on E: Exception do
          begin
            ShowMessage('Error al insertar línea ' + IntToStr(I + 1) + ': ' + E.Message + #13#10 + 'SQL: ' + sSQL);
          end;
        end;
      end;

      // Verificación: comprobar que realmente se insertaron
      qaux.Close;
      qaux.SQL.Text := 'SELECT COUNT(*) FROM entradas_detalle WHERE identrada = ' + IntToStr(idCabecera) +
                       ' AND idempresa = ' + IntToStr(dm.tempresaid.AsInteger);
      qaux.Open;

      if iLineasInsertadas = 0 then
        ShowMessage('AVISO: No se insertó ninguna línea de detalle.')
      else
        ShowMessage('Se insertaron ' + IntToStr(iLineasInsertadas) + ' líneas.' + #13#10 +
                    'Verificación BD: ' + qaux.Fields[0].AsString + ' registros en entradas_detalle para identrada=' + IntToStr(idCabecera));
    end; // Fin del if bFaltanCodigos
    
    qentradas.Refresh;
    entradas.Refresh;

    // GUARDAR EL DOCUMENTO EN LA TABLA DOCUMENTOS
    try
      qaux.SQL.Clear;
      qaux.SQL.Add('INSERT INTO documentos (ide, fecha, descripcion, archivo, nomarchivo, extension, identrada) ');
      qaux.SQL.Add('VALUES (:ide, :fecha, :desc, :archivo, :nom, :ext, :identrada)');
      qaux.ParamByName('ide').AsInteger := dm.tempresaid.AsInteger;
      qaux.ParamByName('fecha').AsDateTime := Date;
      qaux.ParamByName('desc').AsString := 'Factura importada IA: ' + Res.Cabecera.NumDocumento;
      qaux.ParamByName('archivo').LoadFromFile(Dlg.FileName, ftBlob);
      qaux.ParamByName('nom').AsString := ExtractFileName(Dlg.FileName);
      qaux.ParamByName('ext').AsString := ExtractFileExt(Dlg.FileName);
      qaux.ParamByName('identrada').AsInteger := idCabecera;
      qaux.ExecSQL;
    except
      on E: Exception do
        ShowMessage('Error al guardar el documento: ' + E.Message);
    end;

    ShowMessage('Entrada importada exitosamente desde la Inteligencia Artificial.');
    entradas_detalle.Refresh;
    qentradas.Refresh;
    tbuscar.setfocus;
    tbuscar.text:=inttostr(idCabecera);

  finally
    Extractor.Free;
    Screen.Cursor := crDefault;
  end;
  finally
    Dlg.Free;
  end;
end;


procedure Tentradas.btVerDocClick(Sender: TObject);
var
  NomArchivo: string;
  RutaTemp: string;
begin
  if (qentradas.State = dsInactive) or qentradas.IsEmpty then
  begin
    ShowMessage('documento no disponible');
    Exit;
  end;
  {qaux.SQL.Clear;
  qaux.SQL.Add('delete from documentos_tmp ');
  qaux.execsql;
  qaux.SQL.Clear;
  qaux.SQL.Add('insert into documentos_tmp (select * FROM documentos WHERE identrada = :id)');
  qaux.ParamByName('id').AsInteger := qentradas.FieldByName('id').AsInteger;
  qaux.execsql;   }

  qaux.SQL.Clear;
  qaux.SQL.Add('SELECT archivo, nomarchivo, extension FROM documentos WHERE identrada = :id');
  qaux.ParamByName('id').AsInteger := qentradas.FieldByName('id').AsInteger;
  qaux.Open;

  if not qaux.IsEmpty then
  begin
    NomArchivo := qaux.FieldByName('nomarchivo').AsString;
    // Si no tiene nombre guardado, le damos uno por defecto con la extensión
    if NomArchivo = '' then 
      NomArchivo := 'Factura_' + qentradas.FieldByName('id').AsString + qaux.FieldByName('extension').AsString;
    
    // Si no tiene extensión, la añadimos desde el campo extension
    if (ExtractFileExt(NomArchivo) = '') and (qaux.FieldByName('extension').AsString <> '') then
      NomArchivo := NomArchivo + qaux.FieldByName('extension').AsString;

    RutaTemp := TPath.Combine(TPath.GetTempPath, NomArchivo);
    
    try
      TBlobField(qaux.FieldByName('archivo')).SaveToFile(RutaTemp);
      ShellExecute(0, 'open', PChar(RutaTemp), nil, nil, SW_SHOWNORMAL);
    except
      on E: Exception do
        ShowMessage('Error al abrir el documento: ' + E.Message);
    end;
  end
  else
    ShowMessage('documento no disponible');
end;

end.
