unit fentradas;

interface

uses
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.Comp.DataSet, FireDAC.Comp.Client, uFireDacHelper,
  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,
    Data.DB,  
  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: TFDQuery;
    entradas: TFDQuery;
    entradas_detalle: TFDQuery;
    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: TFDQuery;
    DBEdit2: TDBEdit;
    DBText3: TDBText;
    DBText2: TDBText;
    DBText1: TDBText;
    qproveedores: TFDQuery;
    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: TFDQuery;
    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:TFDQuerySortType;
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.
