feat: comentario al completar turno, Google Sheets corregido, scheduling 8:30-17 5 slots, mejoras responsive
Deploy / deploy (push) Failing after 12m35s

- Agrega campo comentario opcional al marcar turno COMPLETADO (textarea en UI, guardado en DB, escrito en col N del sheet)
- Corrige mapeo Google Sheets: email→I, telefono→J, vacíos en K-M, comentario→N
- Actualiza scheduling: inicio 8:30, fin 17:00, máx 5 turnos simultáneos
- entrypoint.sh: migrate deploy → db push para aplicar cambios de schema automáticamente
- Historial cliente: tarjetas visuales con header, fecha y observaciones destacadas
- Dashboard/turnos cliente: mejoras responsive mobile
- Fix display duración: 0.5 hs → "30 min"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Beldor
2026-06-22 21:57:27 -03:00
parent e556e2f16b
commit c2b0e39b06
9 changed files with 146 additions and 73 deletions
+27 -20
View File
@@ -1,21 +1,30 @@
import { prisma } from "./prisma";
const SLOTS_PARALELOS = 4; // 2 jefes × 2 autos c/u
const HORA_INICIO = 8;
const HORA_FIN = 18;
const SLOTS_PARALELOS = 5;
const INICIO_H = 8;
const INICIO_M = 30;
const FIN_H = 17;
const FIN_M = 0;
const INICIO_MINUTOS = INICIO_H * 60 + INICIO_M; // 510
const FIN_MINUTOS = FIN_H * 60 + FIN_M; // 1020
function esDiaHabil(fecha: Date): boolean {
const dia = fecha.getDay(); // 0=Dom, 6=Sab
const dia = fecha.getDay();
return dia >= 1 && dia <= 5;
}
function horasDentroDeJornada(inicio: Date, fin: Date): boolean {
const h1 = inicio.getHours() + inicio.getMinutes() / 60;
const h2 = fin.getHours() + fin.getMinutes() / 60;
return h1 >= HORA_INICIO && h2 <= HORA_FIN;
function minutosDelDia(fecha: Date): number {
return fecha.getHours() * 60 + fecha.getMinutes();
}
function horasDentroDeJornada(inicio: Date, fin: Date): boolean {
return (
minutosDelDia(inicio) >= INICIO_MINUTOS &&
minutosDelDia(fin) <= FIN_MINUTOS &&
inicio.toDateString() === fin.toDateString()
);
}
// Devuelve cuántos turnos activos se superponen con el rango dado
async function turnosSolapados(inicio: Date, fin: Date): Promise<number> {
const count = await prisma.turno.count({
where: {
@@ -34,7 +43,6 @@ export interface SlotDisponible {
fechaFin: Date;
}
// Busca los próximos N slots disponibles para una duración dada (en horas)
export async function obtenerSlotsDisponibles(
duracionHoras: number,
diasAdelante = 14
@@ -47,29 +55,29 @@ export async function obtenerSlotsDisponibles(
fechaLimite.setDate(fechaLimite.getDate() + diasAdelante);
let cursor = new Date(ahora);
// Empezar desde el siguiente slot redondo (media hora)
// Avanzar al siguiente slot de :00 o :30
cursor.setMinutes(cursor.getMinutes() >= 30 ? 60 : 30, 0, 0);
while (cursor < fechaLimite && slots.length < 20) {
if (!esDiaHabil(cursor)) {
cursor.setDate(cursor.getDate() + 1);
cursor.setHours(HORA_INICIO, 0, 0, 0);
cursor.setHours(INICIO_H, INICIO_M, 0, 0);
continue;
}
const hInicio = cursor.getHours() + cursor.getMinutes() / 60;
if (hInicio < HORA_INICIO) {
cursor.setHours(HORA_INICIO, 0, 0, 0);
if (minutosDelDia(cursor) < INICIO_MINUTOS) {
cursor.setHours(INICIO_H, INICIO_M, 0, 0);
continue;
}
const fin = new Date(cursor.getTime() + duracionMs);
const hFin = fin.getHours() + fin.getMinutes() / 60;
if (hFin > HORA_FIN) {
// Pasar al día siguiente
if (
fin.toDateString() !== cursor.toDateString() ||
minutosDelDia(fin) > FIN_MINUTOS
) {
cursor.setDate(cursor.getDate() + 1);
cursor.setHours(HORA_INICIO, 0, 0, 0);
cursor.setHours(INICIO_H, INICIO_M, 0, 0);
continue;
}
@@ -78,7 +86,6 @@ export async function obtenerSlotsDisponibles(
slots.push({ fechaInicio: new Date(cursor), fechaFin: new Date(fin) });
}
// Avanzar 30 minutos
cursor = new Date(cursor.getTime() + 30 * 60 * 1000);
}