unit frm_CalendarioLaboral;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  Vcl.Buttons, Vcl.Grids, Vcl.Menus, System.DateUtils, System.Math, System.UITypes,
  System.Types, Data.DB, FireDAC.Comp.Client, FireDAC.Stan.Param;

type
  TTipoDiaLaboral = (tdlLaboral, tdlFestivo, tdlVacaciones);

  TDiaCalendarioLaboral = record
    Fecha: TDate;
    DiaNumero: Integer;
    MesNumero: Integer;
    AnioNumero: Integer;
    EsMesActual: Boolean;
    EsFinDeSemana: Boolean;
    EsHoy: Boolean;
    TipoDia: TTipoDiaLaboral;
    IdFestivo: Integer;
    TipoStr: string;
    Descripcion: string;
    EmpresaId: Integer;
  end;

  TfrmCalendarioLaboral = class(TForm)
    pnlTop: TPanel;
    pnlTitulo: TPanel;
    lblTitulo: TLabel;
    lblSubtitulo: TLabel;
    pnlCards: TPanel;
    pnlCardLaborables: TPanel;
    lblLaborablesVal: TLabel;
    lblLaborablesTit: TLabel;
    lblLaborablesSub: TLabel;
    pnlCardFestivos: TPanel;
    lblFestivosVal: TLabel;
    lblFestivosTit: TLabel;
    lblFestivosSub: TLabel;
    pnlCardVacaciones: TPanel;
    lblVacacionesVal: TLabel;
    lblVacacionesTit: TLabel;
    lblVacacionesSub: TLabel;
    pnlNav: TPanel;
    btnMesAnterior: TSpeedButton;
    btnHoy: TSpeedButton;
    btnMesSiguiente: TSpeedButton;
    pnlPeriodo: TPanel;
    cboMes: TComboBox;
    cboAnio: TComboBox;
    pnlEmpresa: TPanel;
    lblEmpresa: TLabel;
    cboEmpresa: TComboBox;
    btnCopiarAnio: TSpeedButton;
    pnlLeyenda: TPanel;
    lblLeyendaFestivo: TLabel;
    lblLeyendaVacaciones: TLabel;
    pnlDiasHeader: TPanel;
    pnlCalendarioContainer: TPanel;
    gridCalendario: TDrawGrid;
    pmOpcionesDia: TPopupMenu;
    miMarcarFestivoNacional: TMenuItem;
    miMarcarFestivoAutonomico: TMenuItem;
    miMarcarFestivoLocal: TMenuItem;
    miMarcarFestivoEmpresa: TMenuItem;
    N1: TMenuItem;
    miMarcarVacacionesFijas: TMenuItem;
    N2: TMenuItem;
    miEditarDescripcion: TMenuItem;
    miDesmarcar: TMenuItem;

    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnMesAnteriorClick(Sender: TObject);
    procedure btnHoyClick(Sender: TObject);
    procedure btnMesSiguienteClick(Sender: TObject);
    procedure cboPeriodoChange(Sender: TObject);
    procedure cboEmpresaChange(Sender: TObject);
    procedure btnCopiarAnioClick(Sender: TObject);
    procedure gridCalendarioDrawCell(Sender: TObject; ACol, ARow: Longint;
      Rect: TRect; State: TGridDrawState);
    procedure gridCalendarioMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure gridCalendarioDblClick(Sender: TObject);
    procedure miMarcarFestivoNacionalClick(Sender: TObject);
    procedure miMarcarFestivoAutonomicoClick(Sender: TObject);
    procedure miMarcarFestivoLocalClick(Sender: TObject);
    procedure miMarcarFestivoEmpresaClick(Sender: TObject);
    procedure miMarcarVacacionesFijasClick(Sender: TObject);
    procedure miEditarDescripcionClick(Sender: TObject);
    procedure miDesmarcarClick(Sender: TObject);

  private
    FCurrentYear: Word;
    FCurrentMonth: Word;
    FMatrizDias: array[0..41] of TDiaCalendarioLaboral;
    FLblDias: array[0..6] of TLabel;
    FSelectedFecha: TDate;
    FSelectedDiaIdx: Integer;
    FUpdatingPeriodo: Boolean;

    procedure InicializarFiltros;
    procedure CargarEmpresas;
    procedure CargarDatosMes;
    procedure AjustarTamanosGrid;
    function GetSelectedEmpresaId: Integer;
    procedure GuardarFestivoEnBD(AFecha: TDate; const ATipo, ADesc: string; AEmpresaId: Integer);
    procedure EliminarFestivoDeBD(AFecha: TDate; AEmpresaId: Integer);
    procedure CambiarEstadoDia(AIdx: Integer);
  public
  end;

var
  frmCalendarioLaboral: TfrmCalendarioLaboral;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_CalendarioLaboralEditor;

{$R *.dfm}

const
  NOMBRES_MESES: array[1..12] of string = (
    'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
    'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
  );

  NOMBRES_DIAS: array[1..7] of string = (
    'LUNES', 'MARTES', 'MIÉRCOLES', 'JUEVES', 'VIERNES', 'SÁBADO', 'DOMINGO'
  );

procedure TfrmCalendarioLaboral.FormCreate(Sender: TObject);
var
  Y, M, D: Word;
  I: Integer;
begin
  DecodeDate(Date, Y, M, D);
  FCurrentYear := Y;
  FCurrentMonth := M;
  FSelectedFecha := Date;
  FSelectedDiaIdx := -1;
  FUpdatingPeriodo := False;

  // Aislamiento de tags
  cboMes.Tag := 999;
  cboAnio.Tag := 999;
  cboEmpresa.Tag := 999;
  btnMesAnterior.Tag := 999;
  btnMesSiguiente.Tag := 999;
  btnHoy.Tag := 999;
  btnCopiarAnio.Tag := 999;

  gridCalendario.DoubleBuffered := True;

  // Cabeceras de días de la semana
  for I := 0 to 6 do
  begin
    FLblDias[I] := TLabel.Create(Self);
    FLblDias[I].Parent := pnlDiasHeader;
    FLblDias[I].Font.Name := 'Segoe UI';
    FLblDias[I].Font.Size := 9;
    FLblDias[I].Font.Style := [fsBold];
    FLblDias[I].Font.Color := $00475569;
    FLblDias[I].Caption := NOMBRES_DIAS[I + 1];
    FLblDias[I].Alignment := taCenter;
    FLblDias[I].AutoSize := False;
    FLblDias[I].Tag := 999;
  end;

  InicializarFiltros;
end;

procedure TfrmCalendarioLaboral.FormDestroy(Sender: TObject);
begin
  if frmCalendarioLaboral = Self then
    frmCalendarioLaboral := nil;
end;

procedure TfrmCalendarioLaboral.FormShow(Sender: TObject);
begin
  AjustarTamanosGrid;
  CargarEmpresas;
  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.FormResize(Sender: TObject);
begin
  AjustarTamanosGrid;
end;

procedure TfrmCalendarioLaboral.AjustarTamanosGrid;
var
  I: Integer;
  LColWidth, LRowHeight: Integer;
begin
  if gridCalendario.ClientWidth <= 0 then Exit;

  LColWidth := gridCalendario.ClientWidth div 7;
  for I := 0 to 6 do
    gridCalendario.ColWidths[I] := LColWidth;

  if gridCalendario.ClientHeight > 0 then
  begin
    LRowHeight := gridCalendario.ClientHeight div 6;
    for I := 0 to 5 do
      gridCalendario.RowHeights[I] := LRowHeight;
  end;

  for I := 0 to 6 do
  begin
    if Assigned(FLblDias[I]) then
    begin
      FLblDias[I].Left := I * LColWidth;
      FLblDias[I].Top := 6;
      FLblDias[I].Width := LColWidth;
      FLblDias[I].Height := 18;
    end;
  end;
end;

procedure TfrmCalendarioLaboral.InicializarFiltros;
var
  I: Integer;
begin
  FUpdatingPeriodo := True;
  try
    cboMes.Items.Clear;
    for I := 1 to 12 do
      cboMes.Items.Add(NOMBRES_MESES[I]);
    cboMes.ItemIndex := FCurrentMonth - 1;

    cboAnio.Items.Clear;
    for I := FCurrentYear - 3 to FCurrentYear + 3 do
      cboAnio.Items.Add(IntToStr(I));
    cboAnio.ItemIndex := cboAnio.Items.IndexOf(IntToStr(FCurrentYear));
    if cboAnio.ItemIndex = -1 then
      cboAnio.ItemIndex := 3;
  finally
    FUpdatingPeriodo := False;
  end;
end;

procedure TfrmCalendarioLaboral.CargarEmpresas;
var
  LQry: TFDQuery;
  LDefIdx, LIdx: Integer;
  LCurrEmpId: Integer;
begin
  cboEmpresa.Items.Clear;
  cboEmpresa.Items.AddObject('[Todas las Empresas (Oficial)]', TObject(NativeInt(0)));
  LDefIdx := 0;
  LCurrEmpId := StrToIntDef(dmgMain.CurrentCompanyId, 0);

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text := 'SELECT id, nombre FROM ge_empresas WHERE activo = 1 ORDER BY nombre ASC';
    LQry.Open;
    while not LQry.Eof do
    begin
      LIdx := cboEmpresa.Items.AddObject(
        LQry.FieldByName('nombre').AsString,
        TObject(NativeInt(LQry.FieldByName('id').AsInteger))
      );
      if LQry.FieldByName('id').AsInteger = LCurrEmpId then
        LDefIdx := LIdx;
      LQry.Next;
    end;
  finally
    LQry.Free;
  end;

  if (cboEmpresa.ItemIndex < 0) and (cboEmpresa.Items.Count > 0) then
    cboEmpresa.ItemIndex := LDefIdx;
end;

function TfrmCalendarioLaboral.GetSelectedEmpresaId: Integer;
begin
  Result := 0;
  if cboEmpresa.ItemIndex >= 0 then
    Result := NativeInt(cboEmpresa.Items.Objects[cboEmpresa.ItemIndex]);
end;

procedure TfrmCalendarioLaboral.CargarDatosMes;
var
  LPrimerDiaMes, LFechaInicio, LCurFecha, LFechaMin, LFechaMax: TDate;
  LDiaSemana: Integer;
  I: Integer;
  LQry: TFDQuery;
  LEmpresaId: Integer;
  LFechaReg: TDate;
  LTipoReg, LDescReg: string;
  LIdReg, LEmpIdReg: Integer;
  LVacacionesAnio, LVacacionesMes: Integer;
  LFestivosAnio, LFestivosMes: Integer;
  LLaborablesAnio: Integer;
  LTotalDiasAnio: Integer;
  LIterFecha, LIniAnio, LFinAnio: TDate;
  LDiaSem: Integer;
  LEsFestivoOVac: Boolean;
begin
  LPrimerDiaMes := EncodeDate(FCurrentYear, FCurrentMonth, 1);
  LDiaSemana := DayOfTheWeek(LPrimerDiaMes); // 1 = Lunes .. 7 = Domingo
  LFechaInicio := IncDay(LPrimerDiaMes, -(LDiaSemana - 1));

  // Inicializar matriz de 42 días
  for I := 0 to 41 do
  begin
    LCurFecha := IncDay(LFechaInicio, I);
    FMatrizDias[I].Fecha := LCurFecha;
    FMatrizDias[I].DiaNumero := DayOf(LCurFecha);
    FMatrizDias[I].MesNumero := MonthOf(LCurFecha);
    FMatrizDias[I].AnioNumero := YearOf(LCurFecha);
    FMatrizDias[I].EsMesActual := (FMatrizDias[I].MesNumero = FCurrentMonth);
    FMatrizDias[I].EsFinDeSemana := (DayOfTheWeek(LCurFecha) in [6, 7]);
    FMatrizDias[I].EsHoy := (LCurFecha = Date);
    FMatrizDias[I].TipoDia := tdlLaboral;
    FMatrizDias[I].IdFestivo := 0;
    FMatrizDias[I].TipoStr := '';
    FMatrizDias[I].Descripcion := '';
    FMatrizDias[I].EmpresaId := 0;
  end;

  LEmpresaId := GetSelectedEmpresaId;
  LIniAnio := EncodeDate(FCurrentYear, 1, 1);
  LFinAnio := EncodeDate(FCurrentYear, 12, 31);
  LFechaMin := Min(LFechaInicio, LIniAnio);
  LFechaMax := Max(IncDay(LFechaInicio, 41), LFinAnio);

  LVacacionesAnio := 0;
  LVacacionesMes := 0;
  LFestivosAnio := 0;
  LFestivosMes := 0;

  // Consultar festivos y vacaciones
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text :=
      'SELECT id, fecha, tipo, descripcion, COALESCE(id_empresa, 0) AS id_empresa ' +
      'FROM ge_festivos ' +
      'WHERE (:empresa = 0 OR id_empresa = :empresa OR id_empresa IS NULL OR id_empresa = 0) ' +
      '  AND (fecha >= :f_ini AND fecha <= :f_fin) ' +
      'ORDER BY fecha ASC, id_empresa DESC';
    LQry.ParamByName('empresa').AsInteger := LEmpresaId;
    LQry.ParamByName('f_ini').AsDate := LFechaMin;
    LQry.ParamByName('f_fin').AsDate := LFechaMax;
    LQry.Open;

    while not LQry.Eof do
    begin
      LFechaReg := LQry.FieldByName('fecha').AsDateTime;
      LTipoReg := UpperCase(Trim(LQry.FieldByName('tipo').AsString));
      LDescReg := Trim(LQry.FieldByName('descripcion').AsString);
      LIdReg := LQry.FieldByName('id').AsInteger;
      LEmpIdReg := LQry.FieldByName('id_empresa').AsInteger;

      // Asignar a la matriz visible si corresponde
      for I := 0 to 41 do
      begin
        if SameDate(FMatrizDias[I].Fecha, LFechaReg) then
        begin
          FMatrizDias[I].IdFestivo := LIdReg;
          FMatrizDias[I].TipoStr := LQry.FieldByName('tipo').AsString;
          FMatrizDias[I].Descripcion := LDescReg;
          FMatrizDias[I].EmpresaId := LEmpIdReg;

          if Pos('VACACION', LTipoReg) > 0 then
            FMatrizDias[I].TipoDia := tdlVacaciones
          else
            FMatrizDias[I].TipoDia := tdlFestivo;

          Break;
        end;
      end;

      // Contadores del año seleccionado
      if YearOf(LFechaReg) = FCurrentYear then
      begin
        if Pos('VACACION', LTipoReg) > 0 then
        begin
          Inc(LVacacionesAnio);
          if MonthOf(LFechaReg) = FCurrentMonth then
            Inc(LVacacionesMes);
        end
        else
        begin
          Inc(LFestivosAnio);
          if MonthOf(LFechaReg) = FCurrentMonth then
            Inc(LFestivosMes);
        end;
      end;

      LQry.Next;
    end;

    // Calcular Días Laborables Estimados en el año
    // (Lunes a Viernes excluyendo Festivos y Vacaciones que caigan entre semana)
    LLaborablesAnio := 0;
    LTotalDiasAnio := DaysBetween(LFinAnio, LIniAnio) + 1;
    LIterFecha := LIniAnio;

    while LIterFecha <= LFinAnio do
    begin
      LDiaSem := DayOfTheWeek(LIterFecha);
      if LDiaSem in [1..5] then // Lunes a Viernes
      begin
        LEsFestivoOVac := False;
        LQry.First;
        while not LQry.Eof do
        begin
          if SameDate(LQry.FieldByName('fecha').AsDateTime, LIterFecha) then
          begin
            LEsFestivoOVac := True;
            Break;
          end;
          LQry.Next;
        end;

        if not LEsFestivoOVac then
          Inc(LLaborablesAnio);
      end;
      LIterFecha := IncDay(LIterFecha, 1);
    end;

  finally
    LQry.Free;
  end;

  // Actualizar Tarjetas de Estadísticas
  lblVacacionesVal.Caption := Format('%d días', [LVacacionesAnio]);
  lblVacacionesSub.Caption := Format('(%d en este mes)', [LVacacionesMes]);

  lblFestivosVal.Caption := Format('%d días', [LFestivosAnio]);
  lblFestivosSub.Caption := Format('(%d en este mes)', [LFestivosMes]);

  lblLaborablesVal.Caption := Format('%d días', [LLaborablesAnio]);
  lblLaborablesSub.Caption := Format('en el año %d', [FCurrentYear]);

  gridCalendario.Invalidate;
end;

procedure TfrmCalendarioLaboral.gridCalendarioDrawCell(Sender: TObject; ACol, ARow: Longint;
  Rect: TRect; State: TGridDrawState);
var
  LIdx: Integer;
  LDia: TDiaCalendarioLaboral;
  LCanvas: TCanvas;
  LBadgeRect, LDescRect: TRect;
  LText: string;
begin
  LIdx := ARow * 7 + ACol;
  if (LIdx < 0) or (LIdx > 41) then Exit;
  LDia := FMatrizDias[LIdx];
  LCanvas := gridCalendario.Canvas;

  // 1. Fondo de Celda
  if not LDia.EsMesActual then
    LCanvas.Brush.Color := $00F8F9FA
  else if LDia.TipoDia = tdlVacaciones then
    LCanvas.Brush.Color := $00FDF2F8 // Rosa púrpura muy suave
  else if LDia.TipoDia = tdlFestivo then
    LCanvas.Brush.Color := $00FFFBEB // Ámbar dorado muy suave
  else if LDia.EsFinDeSemana then
    LCanvas.Brush.Color := $00F1F5F9 // Gris pizarra suave para fines de semana
  else
    LCanvas.Brush.Color := clWhite;

  LCanvas.FillRect(Rect);

  // 2. Líneas divisoras sutiles
  LCanvas.Pen.Color := $00E2E8F0;
  LCanvas.Pen.Width := 1;
  LCanvas.MoveTo(Rect.Right - 1, Rect.Top);
  LCanvas.LineTo(Rect.Right - 1, Rect.Bottom);
  LCanvas.MoveTo(Rect.Left, Rect.Bottom - 1);
  LCanvas.LineTo(Rect.Right, Rect.Bottom - 1);

  // 3. Resaltar Día de Hoy
  if LDia.EsHoy then
  begin
    LCanvas.Pen.Color := $00E67300; // Azul corporativo / Cyan vibrante
    LCanvas.Pen.Width := 2;
    LCanvas.Rectangle(Rect.Left + 1, Rect.Top + 1, Rect.Right - 1, Rect.Bottom - 1);
  end;

  // 4. Número del día
  LCanvas.Font.Name := 'Segoe UI';
  LCanvas.Font.Size := 12;
  LCanvas.Font.Style := [fsBold];

  if not LDia.EsMesActual then
    LCanvas.Font.Color := $0094A3B8
  else if LDia.EsHoy then
    LCanvas.Font.Color := $00E67300
  else if LDia.TipoDia = tdlVacaciones then
    LCanvas.Font.Color := $0086198F
  else if LDia.TipoDia = tdlFestivo then
    LCanvas.Font.Color := $00B45309
  else if LDia.EsFinDeSemana then
    LCanvas.Font.Color := $0064748B
  else
    LCanvas.Font.Color := $001E293B;

  LCanvas.TextOut(Rect.Left + 8, Rect.Top + 6, IntToStr(LDia.DiaNumero));

  // Indicador de "HOY"
  if LDia.EsHoy then
  begin
    LCanvas.Font.Size := 8;
    LCanvas.Font.Style := [fsBold];
    LCanvas.Font.Color := $00E67300;
    LCanvas.TextOut(Rect.Left + 36, Rect.Top + 10, 'HOY');
  end;

  // 5. Etiqueta / Badge de Festivo o Vacaciones
  if LDia.TipoDia = tdlFestivo then
  begin
    // Pastilla Ámbar / Festivo
    LBadgeRect := TRect.Create(Rect.Left + 6, Rect.Top + 30, Rect.Right - 6, Rect.Top + 48);
    LCanvas.Brush.Color := $000284C7; // Ámbar / Naranja dorado
    LCanvas.Pen.Color := $000284C7;
    LCanvas.RoundRect(LBadgeRect.Left, LBadgeRect.Top, LBadgeRect.Right, LBadgeRect.Bottom, 6, 6);

    LCanvas.Font.Size := 8;
    LCanvas.Font.Style := [fsBold];
    LCanvas.Font.Color := clWhite;
    LText := LDia.TipoStr;
    if LText = '' then
      LText := 'FESTIVO';
    DrawText(LCanvas.Handle, PChar(LText), -1, LBadgeRect,
      DT_CENTER or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX);

    // Descripción del festivo
    if LDia.Descripcion <> '' then
    begin
      LDescRect := TRect.Create(Rect.Left + 8, LBadgeRect.Bottom + 4, Rect.Right - 8, Rect.Bottom - 4);
      LCanvas.Font.Size := 8;
      LCanvas.Font.Style := [];
      LCanvas.Font.Color := $00334155;
      DrawText(LCanvas.Handle, PChar(LDia.Descripcion), -1, LDescRect,
        DT_LEFT or DT_WORDBREAK or DT_NOPREFIX);
    end;
  end
  else if LDia.TipoDia = tdlVacaciones then
  begin
    // Pastilla Púrpura / Vacaciones Fijas
    LBadgeRect := TRect.Create(Rect.Left + 6, Rect.Top + 30, Rect.Right - 6, Rect.Top + 48);
    LCanvas.Brush.Color := $009333EA; // Púrpura
    LCanvas.Pen.Color := $009333EA;
    LCanvas.RoundRect(LBadgeRect.Left, LBadgeRect.Top, LBadgeRect.Right, LBadgeRect.Bottom, 6, 6);

    LCanvas.Font.Size := 8;
    LCanvas.Font.Style := [fsBold];
    LCanvas.Font.Color := clWhite;
    DrawText(LCanvas.Handle, 'VACACIONES FIJAS', -1, LBadgeRect,
      DT_CENTER or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX);

    // Descripción de vacaciones
    if LDia.Descripcion <> '' then
    begin
      LDescRect := TRect.Create(Rect.Left + 8, LBadgeRect.Bottom + 4, Rect.Right - 8, Rect.Bottom - 4);
      LCanvas.Font.Size := 8;
      LCanvas.Font.Style := [];
      LCanvas.Font.Color := $00334155;
      DrawText(LCanvas.Handle, PChar(LDia.Descripcion), -1, LDescRect,
        DT_LEFT or DT_WORDBREAK or DT_NOPREFIX);
    end;
  end;
end;

procedure TfrmCalendarioLaboral.gridCalendarioMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  LCol, LRow, LIdx: Integer;
begin
  gridCalendario.MouseToCell(X, Y, LCol, LRow);
  if (LCol < 0) or (LCol > 6) or (LRow < 0) or (LRow > 5) then Exit;
  LIdx := LRow * 7 + LCol;
  if (LIdx < 0) or (LIdx > 41) then Exit;

  FSelectedFecha := FMatrizDias[LIdx].Fecha;
  FSelectedDiaIdx := LIdx;

  if Button = mbRight then
  begin
    pmOpcionesDia.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
    Exit;
  end;

  if Button = mbLeft then
  begin
    // Ciclar estado: Laboral -> Festivo Oficial -> Vacaciones Fijas -> Laboral
    CambiarEstadoDia(LIdx);
  end;
end;

procedure TfrmCalendarioLaboral.gridCalendarioDblClick(Sender: TObject);
var
  LFrm: TfrmCalendarioLaboralEditor;
begin
  if (FSelectedDiaIdx < 0) or (FSelectedDiaIdx > 41) then Exit;

  LFrm := TfrmCalendarioLaboralEditor.Create(Self);
  try
    LFrm.DefaultAnio := FCurrentYear;
    LFrm.DefaultEmpresaId := GetSelectedEmpresaId;
    LFrm.FestivoId := FMatrizDias[FSelectedDiaIdx].IdFestivo;
    if LFrm.FestivoId = 0 then
    begin
      LFrm.dtpFecha.Date := FSelectedFecha;
      LFrm.dtpFechaHasta.Date := FSelectedFecha;
    end;
    if LFrm.ShowModal = mrOk then
      CargarDatosMes;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCalendarioLaboral.CambiarEstadoDia(AIdx: Integer);
var
  LDia: TDiaCalendarioLaboral;
  LEmpresaId: Integer;
begin
  LDia := FMatrizDias[AIdx];
  LEmpresaId := GetSelectedEmpresaId;

  case LDia.TipoDia of
    tdlLaboral:
    begin
      GuardarFestivoEnBD(LDia.Fecha, 'FESTIVO NACIONAL', 'Festivo Oficial', LEmpresaId);
    end;

    tdlFestivo:
    begin
      GuardarFestivoEnBD(LDia.Fecha, 'VACACIONES FIJAS', 'Vacaciones Fijas / Cierre', LEmpresaId);
    end;

    tdlVacaciones:
    begin
      EliminarFestivoDeBD(LDia.Fecha, LEmpresaId);
    end;
  end;

  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.GuardarFestivoEnBD(AFecha: TDate; const ATipo, ADesc: string; AEmpresaId: Integer);
var
  LQry: TFDQuery;
  LUserId: Integer;
  LExistingId: Integer;
begin
  LUserId := StrToIntDef(dmgMain.CurrentUserId, 1);
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;

    // Comprobar si ya existe
    LQry.SQL.Text :=
      'SELECT id FROM ge_festivos ' +
      'WHERE fecha = :fecha AND (:emp = 0 AND (id_empresa IS NULL OR id_empresa = 0) OR id_empresa = :emp) ' +
      'LIMIT 1';
    LQry.ParamByName('fecha').AsDate := AFecha;
    LQry.ParamByName('emp').AsInteger := AEmpresaId;
    LQry.Open;

    if not LQry.IsEmpty then
    begin
      LExistingId := LQry.FieldByName('id').AsInteger;
      LQry.Close;
      LQry.SQL.Text :=
        'UPDATE ge_festivos ' +
        'SET tipo = :tipo, descripcion = :desc, updated_at = NOW(), id_user_update = :user ' +
        'WHERE id = :id';
      LQry.ParamByName('tipo').AsString := ATipo;
      LQry.ParamByName('desc').AsString := ADesc;
      LQry.ParamByName('user').AsInteger := LUserId;
      LQry.ParamByName('id').AsInteger := LExistingId;
      LQry.ExecSQL;
    end
    else
    begin
      LQry.Close;
      LQry.SQL.Text :=
        'INSERT INTO ge_festivos (fecha, tipo, descripcion, id_empresa, created_at, id_user_creator) ' +
        'VALUES (:fecha, :tipo, :desc, :emp, NOW(), :user)';
      LQry.ParamByName('fecha').AsDate := AFecha;
      LQry.ParamByName('tipo').AsString := ATipo;
      LQry.ParamByName('desc').AsString := ADesc;
      if AEmpresaId > 0 then
        LQry.ParamByName('emp').AsInteger := AEmpresaId
      else
        LQry.ParamByName('emp').Clear;
      LQry.ParamByName('user').AsInteger := LUserId;
      LQry.ExecSQL;
    end;
  finally
    LQry.Free;
  end;
end;

procedure TfrmCalendarioLaboral.EliminarFestivoDeBD(AFecha: TDate; AEmpresaId: Integer);
var
  LQry: TFDQuery;
begin
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text :=
      'DELETE FROM ge_festivos ' +
      'WHERE fecha = :fecha AND (:emp = 0 AND (id_empresa IS NULL OR id_empresa = 0) OR id_empresa = :emp)';
    LQry.ParamByName('fecha').AsDate := AFecha;
    LQry.ParamByName('emp').AsInteger := AEmpresaId;
    LQry.ExecSQL;
  finally
    LQry.Free;
  end;
end;

procedure TfrmCalendarioLaboral.miMarcarFestivoNacionalClick(Sender: TObject);
begin
  if FSelectedDiaIdx >= 0 then
  begin
    GuardarFestivoEnBD(FSelectedFecha, 'FESTIVO NACIONAL', 'Festivo Nacional', GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.miMarcarFestivoAutonomicoClick(Sender: TObject);
begin
  if FSelectedDiaIdx >= 0 then
  begin
    GuardarFestivoEnBD(FSelectedFecha, 'FESTIVO AUTONOMICO', 'Festivo Autonómico', GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.miMarcarFestivoLocalClick(Sender: TObject);
begin
  if FSelectedDiaIdx >= 0 then
  begin
    GuardarFestivoEnBD(FSelectedFecha, 'FESTIVO LOCAL', 'Festivo Local', GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.miMarcarFestivoEmpresaClick(Sender: TObject);
begin
  if FSelectedDiaIdx >= 0 then
  begin
    GuardarFestivoEnBD(FSelectedFecha, 'FESTIVO EMPRESA', 'Festivo de Empresa', GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.miMarcarVacacionesFijasClick(Sender: TObject);
begin
  if FSelectedDiaIdx >= 0 then
  begin
    GuardarFestivoEnBD(FSelectedFecha, 'VACACIONES FIJAS', 'Vacaciones Fijas / Cierre', GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.miEditarDescripcionClick(Sender: TObject);
var
  LDesc, LTipo: string;
begin
  if FSelectedDiaIdx < 0 then Exit;

  LDesc := FMatrizDias[FSelectedDiaIdx].Descripcion;
  LTipo := FMatrizDias[FSelectedDiaIdx].TipoStr;
  if LTipo = '' then
    LTipo := 'FESTIVO NACIONAL';

  if InputQuery('Descripción del Día (' + FormatDateTime('dd/mm/yyyy', FSelectedFecha) + ')',
    'Motivo o Descripción:', LDesc) then
  begin
    GuardarFestivoEnBD(FSelectedFecha, LTipo, Trim(LDesc), GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.miDesmarcarClick(Sender: TObject);
begin
  if FSelectedDiaIdx >= 0 then
  begin
    EliminarFestivoDeBD(FSelectedFecha, GetSelectedEmpresaId);
    CargarDatosMes;
  end;
end;

procedure TfrmCalendarioLaboral.btnMesAnteriorClick(Sender: TObject);
begin
  if FCurrentMonth = 1 then
  begin
    FCurrentMonth := 12;
    Dec(FCurrentYear);
  end
  else
    Dec(FCurrentMonth);

  InicializarFiltros;
  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.btnMesSiguienteClick(Sender: TObject);
begin
  if FCurrentMonth = 12 then
  begin
    FCurrentMonth := 1;
    Inc(FCurrentYear);
  end
  else
    Inc(FCurrentMonth);

  InicializarFiltros;
  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.btnHoyClick(Sender: TObject);
begin
  FCurrentYear := YearOf(Date);
  FCurrentMonth := MonthOf(Date);
  InicializarFiltros;
  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.cboPeriodoChange(Sender: TObject);
begin
  if FUpdatingPeriodo then Exit;

  if cboMes.ItemIndex >= 0 then
    FCurrentMonth := cboMes.ItemIndex + 1;

  if cboAnio.ItemIndex >= 0 then
    FCurrentYear := StrToIntDef(cboAnio.Items[cboAnio.ItemIndex], FCurrentYear);

  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.cboEmpresaChange(Sender: TObject);
begin
  CargarDatosMes;
end;

procedure TfrmCalendarioLaboral.btnCopiarAnioClick(Sender: TObject);
var
  LAnioOrigen: Integer;
  LQry, LQryIns: TFDQuery;
  LCount: Integer;
  LFechaOrig, LFechaDest: TDate;
  LUserId, LEmpresaId: Integer;
  LTipo, LDesc: string;
  Y, M, D: Word;
begin
  LAnioOrigen := FCurrentYear - 1;
  if MessageDlg(Format('¿Desea copiar todos los días festivos y vacaciones del año %d al año %d?',
    [LAnioOrigen, FCurrentYear]), mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  LUserId := StrToIntDef(dmgMain.CurrentUserId, 1);
  LEmpresaId := GetSelectedEmpresaId;
  LCount := 0;

  LQry := TFDQuery.Create(nil);
  LQryIns := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQryIns.Connection := dmgMain.dbConn;

    LQry.SQL.Text :=
      'SELECT fecha, tipo, descripcion, id_empresa ' +
      'FROM ge_festivos ' +
      'WHERE YEAR(fecha) = :anio_orig ' +
      '  AND (:emp = 0 AND (id_empresa IS NULL OR id_empresa = 0) OR id_empresa = :emp)';
    LQry.ParamByName('anio_orig').AsInteger := LAnioOrigen;
    LQry.ParamByName('emp').AsInteger := LEmpresaId;
    LQry.Open;

    while not LQry.Eof do
    begin
      LFechaOrig := LQry.FieldByName('fecha').AsDateTime;
      LTipo := LQry.FieldByName('tipo').AsString;
      LDesc := LQry.FieldByName('descripcion').AsString;

      DecodeDate(LFechaOrig, Y, M, D);
      // Ajustar si 29 de febrero en año no bisiesto
      if (M = 2) and (D = 29) and (not IsLeapYear(FCurrentYear)) then
        D := 28;

      LFechaDest := EncodeDate(FCurrentYear, M, D);

      // Comprobar si ya existe en año destino
      LQryIns.Close;
      LQryIns.SQL.Text :=
        'SELECT id FROM ge_festivos ' +
        'WHERE fecha = :fecha ' +
        '  AND (:emp = 0 AND (id_empresa IS NULL OR id_empresa = 0) OR id_empresa = :emp)';
      LQryIns.ParamByName('fecha').AsDate := LFechaDest;
      LQryIns.ParamByName('emp').AsInteger := LEmpresaId;
      LQryIns.Open;

      if LQryIns.IsEmpty then
      begin
        LQryIns.Close;
        LQryIns.SQL.Text :=
          'INSERT INTO ge_festivos (fecha, tipo, descripcion, id_empresa, created_at, id_user_creator) ' +
          'VALUES (:fecha, :tipo, :desc, :emp, NOW(), :user)';
        LQryIns.ParamByName('fecha').AsDate := LFechaDest;
        LQryIns.ParamByName('tipo').AsString := LTipo;
        LQryIns.ParamByName('desc').AsString := LDesc;
        if LEmpresaId > 0 then
          LQryIns.ParamByName('emp').AsInteger := LEmpresaId
        else
          LQryIns.ParamByName('emp').Clear;
        LQryIns.ParamByName('user').AsInteger := LUserId;
        LQryIns.ExecSQL;
        Inc(LCount);
      end;

      LQry.Next;
    end;

    ShowMessage(Format('Se han copiado con éxito %d días festivos/vacaciones al año %d.', [LCount, FCurrentYear]));
    CargarDatosMes;
  finally
    LQry.Free;
    LQryIns.Free;
  end;
end;

end.
