diff --git a/apps/web/src/lib/components/ponto/RegistroPonto.svelte b/apps/web/src/lib/components/ponto/RegistroPonto.svelte
index 6d23edc..f8b1feb 100644
--- a/apps/web/src/lib/components/ponto/RegistroPonto.svelte
+++ b/apps/web/src/lib/components/ponto/RegistroPonto.svelte
@@ -62,6 +62,12 @@
funcionarioId && dataHoje ? { funcionarioId, data: dataHoje } : 'skip'
);
+ // Query para obter status atual do funcionário (férias/licença)
+ const funcionarioStatusQuery = useQuery(
+ api.funcionarios.getCurrent,
+ currentUser?.data ? {} : 'skip'
+ );
+
// Estados
let mostrandoWebcam = $state(false);
let registrando = $state(false);
@@ -86,6 +92,12 @@
const registrosHoje = $derived(registrosHojeQuery?.data || []);
const config = $derived(configQuery?.data);
+ // Status de férias/licença do funcionário
+ const funcionarioStatus = $derived(funcionarioStatusQuery?.data);
+ const statusFerias = $derived(funcionarioStatus?.statusFerias ?? 'ativo');
+ const emFerias = $derived(statusFerias === 'em_ferias');
+ const emLicenca = $derived(statusFerias === 'em_licenca');
+
const proximoTipo = $derived.by(() => {
if (registrosHoje.length === 0) {
return 'entrada';
@@ -209,6 +221,16 @@
return;
}
+ // Verificar se funcionário está em férias ou licença
+ if (emFerias || emLicenca) {
+ mensagemErroModal = 'Registro de ponto não permitido';
+ detalhesErroModal = emFerias
+ ? 'Seu status atual é "Em Férias". Durante o período de férias não é permitido registrar ponto.'
+ : 'Seu status atual é "Em Licença". Durante o período de licença não é permitido registrar ponto.';
+ mostrarModalErro = true;
+ return;
+ }
+
// Verificar permissões antes de registrar
const permissoes = await verificarPermissoes();
if (!permissoes.localizacao || !permissoes.webcam) {
@@ -824,6 +846,8 @@
!coletandoInfo &&
config !== undefined &&
!estaDispensado &&
+ !emFerias &&
+ !emLicenca &&
temFuncionarioAssociado
);
});
@@ -1051,6 +1075,25 @@
{/if}
+
+ {#if temFuncionarioAssociado && (emFerias || emLicenca)}
+
+
+
+
Status do Funcionário
+
+ {#if emFerias}
+ Seu status atual é Em Férias. Durante o período de férias não é permitido
+ registrar ponto.
+ {:else}
+ Seu status atual é Em Licença. Durante o período de licença não é permitido
+ registrar ponto.
+ {/if}
+
+
+
+ {/if}
+
{#if registrando}
@@ -1098,6 +1145,12 @@
{:else if estaDispensado}
Registro Indisponível
+ {:else if emFerias}
+
+ Em Férias
+ {:else if emLicenca}
+
+ Em Licença
{:else if proximoTipo === 'entrada' || proximoTipo === 'retorno_almoco'}
Registrar Entrada
diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte
index 32f9cdc..8b250e3 100644
--- a/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte
+++ b/apps/web/src/routes/(dashboard)/recursos-humanos/atestados-licencas/+page.svelte
@@ -132,13 +132,37 @@
});
// Funções auxiliares
+ /**
+ * Converte string de data no formato "YYYY-MM-DD" (com ou sem parte de hora)
+ * para um objeto Date no fuso local, evitando o problema de "um dia a menos"
+ * causado por `new Date('YYYY-MM-DD')` em timezones como UTC-3.
+ */
+ function parseDataLocal(data: string): Date {
+ if (!data) return new Date(NaN);
+
+ // Garante que só a parte da data seja usada (descarta "T...").
+ const apenasData = data.substring(0, 10);
+ const [anoStr, mesStr, diaStr] = apenasData.split('-');
+ const ano = Number(anoStr);
+ const mes = Number(mesStr);
+ const dia = Number(diaStr);
+
+ if (!ano || !mes || !dia) {
+ return new Date(NaN);
+ }
+
+ return new Date(ano, mes - 1, dia);
+ }
+
function formatarData(data: string) {
- return new Date(data).toLocaleDateString('pt-BR');
+ const date = parseDataLocal(data);
+ if (isNaN(date.getTime())) return '';
+ return date.toLocaleDateString('pt-BR');
}
function calcularDias(dataInicio: string, dataFim: string): number {
- const inicio = new Date(dataInicio);
- const fim = new Date(dataFim);
+ const inicio = parseDataLocal(dataInicio);
+ const fim = parseDataLocal(dataFim);
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
}
@@ -508,7 +532,7 @@
!licencaMaternidade.ehProrrogacao &&
!licencaMaternidade.dataFim
) {
- const inicio = new Date(licencaMaternidade.dataInicio);
+ const inicio = parseDataLocal(licencaMaternidade.dataInicio);
if (!isNaN(inicio.getTime())) {
inicio.setDate(inicio.getDate() + 120); // 120 dias
licencaMaternidade.dataFim = inicio.toISOString().split('T')[0];
@@ -522,7 +546,7 @@
licencaPaternidade.dataInicio &&
!licencaPaternidade.dataFim
) {
- const inicio = new Date(licencaPaternidade.dataInicio);
+ const inicio = parseDataLocal(licencaPaternidade.dataInicio);
if (!isNaN(inicio.getTime())) {
inicio.setDate(inicio.getDate() + 20); // 20 dias
licencaPaternidade.dataFim = inicio.toISOString().split('T')[0];
@@ -574,11 +598,11 @@
// Filtro por período
if (filtroDataInicio && filtroDataFim) {
- const inicio = new Date(filtroDataInicio);
- const fim = new Date(filtroDataFim);
+ const inicio = parseDataLocal(filtroDataInicio);
+ const fim = parseDataLocal(filtroDataFim);
atestados = atestados.filter((a) => {
- const aInicio = new Date(a.dataInicio);
- const aFim = new Date(a.dataFim);
+ const aInicio = parseDataLocal(a.dataInicio);
+ const aFim = parseDataLocal(a.dataFim);
return (
(aInicio >= inicio && aInicio <= fim) ||
(aFim >= inicio && aFim <= fim) ||
@@ -586,8 +610,8 @@
);
});
licencas = licencas.filter((l) => {
- const lInicio = new Date(l.dataInicio);
- const lFim = new Date(l.dataFim);
+ const lInicio = parseDataLocal(l.dataInicio);
+ const lFim = parseDataLocal(l.dataFim);
return (
(lInicio >= inicio && lInicio <= fim) ||
(lFim >= inicio && lFim <= fim) ||
@@ -645,11 +669,11 @@
// Filtro por período
if (relatorioPeriodoInicio && relatorioPeriodoFim) {
- const inicio = new Date(relatorioPeriodoInicio);
- const fim = new Date(relatorioPeriodoFim);
+ const inicio = parseDataLocal(relatorioPeriodoInicio);
+ const fim = parseDataLocal(relatorioPeriodoFim);
atestados = atestados.filter((a) => {
- const aInicio = new Date(a.dataInicio);
- const aFim = new Date(a.dataFim);
+ const aInicio = parseDataLocal(a.dataInicio);
+ const aFim = parseDataLocal(a.dataFim);
return (
(aInicio >= inicio && aInicio <= fim) ||
(aFim >= inicio && aFim <= fim) ||
@@ -657,8 +681,8 @@
);
});
licencas = licencas.filter((l) => {
- const lInicio = new Date(l.dataInicio);
- const lFim = new Date(l.dataFim);
+ const lInicio = parseDataLocal(l.dataInicio);
+ const lFim = parseDataLocal(l.dataFim);
return (
(lInicio >= inicio && lInicio <= fim) ||
(lFim >= inicio && lFim <= fim) ||
@@ -759,7 +783,7 @@
doc.setFontSize(11);
doc.setTextColor(0, 0, 0);
doc.text(
- `Período: ${format(new Date(relatorioPeriodoInicio), 'dd/MM/yyyy', { locale: ptBR })} até ${format(new Date(relatorioPeriodoFim), 'dd/MM/yyyy', { locale: ptBR })}`,
+ `Período: ${format(parseDataLocal(relatorioPeriodoInicio), 'dd/MM/yyyy', { locale: ptBR })} até ${format(parseDataLocal(relatorioPeriodoFim), 'dd/MM/yyyy', { locale: ptBR })}`,
105,
yPosition,
{ align: 'center' }
diff --git a/packages/backend/convex/ferias.ts b/packages/backend/convex/ferias.ts
index 13fbdfd..2d8f314 100644
--- a/packages/backend/convex/ferias.ts
+++ b/packages/backend/convex/ferias.ts
@@ -705,6 +705,19 @@ export const atualizarStatus = mutation({
await ctx.db.patch(registro._id, updateData);
}
+ // Recalcular imediatamente o status de férias/licença do funcionário
+ // para refletir o cancelamento (ou outra mudança) sem depender apenas do cron diário
+ try {
+ await ctx.runMutation(internal.ferias.atualizarStatusFuncionario, {
+ funcionarioId: registro.funcionarioId
+ });
+ } catch (error) {
+ console.error(
+ '[ferias.atualizarStatus] Erro ao atualizar statusFerias do funcionário:',
+ error
+ );
+ }
+
return null;
}
});
diff --git a/packages/backend/convex/pontos.ts b/packages/backend/convex/pontos.ts
index 59a7217..1a66441 100644
--- a/packages/backend/convex/pontos.ts
+++ b/packages/backend/convex/pontos.ts
@@ -551,12 +551,21 @@ export const registrarPonto = mutation({
throw new Error('Usuário não possui funcionário associado');
}
- // Verificar se funcionário está ativo
+ // Verificar se funcionário existe
const funcionario = await ctx.db.get(usuario.funcionarioId);
if (!funcionario) {
throw new Error('Funcionário não encontrado');
}
+ // Bloquear registro de ponto para funcionários em férias ou licença
+ if (funcionario.statusFerias === 'em_ferias') {
+ throw new Error('Não é possível registrar ponto: funcionário está em férias.');
+ }
+
+ if (funcionario.statusFerias === 'em_licenca') {
+ throw new Error('Não é possível registrar ponto: funcionário está em licença.');
+ }
+
// Obter configuração de ponto
const config = await ctx.db
.query('configuracaoPonto')