diff --git a/public/tramites/api.php b/public/tramites/api.php index 9037a4c..aa7be47 100644 --- a/public/tramites/api.php +++ b/public/tramites/api.php @@ -357,6 +357,62 @@ try { ok(hydrate($pdo, $row->fetch())); } + if ($method === 'PUT' && $action === 'update_hours') { + $b = body(); + $hourId = (int)($b['_id'] ?? 0); + if (!$hourId) err('id requerido'); + + $sel = $pdo->prepare('SELECT id, tramite_id, autor_email, abogado, hrs, descripcion FROM tramites_hours WHERE id = :id'); + $sel->execute([':id' => $hourId]); + $row = $sel->fetch(); + if (!$row) err('Registro de horas no encontrado', 404); + + $tramiteId = (int)$row['tramite_id']; + if (!canView($pdo, $tramiteId)) err('No tienes acceso al trámite', 403); + if (!Auth::isSocio() && $row['autor_email'] !== $user['email']) { + err('Solo quien registró las horas (o un Socio/Admin) puede editarlas', 403); + } + + $abog = trim((string)($b['abogado'] ?? $row['abogado'])); + $hrs = (float) ($b['hrs'] ?? $row['hrs']); + $desc = trim((string)($b['descripcion'] ?? $row['descripcion'])); + if ($hrs <= 0) err('horas debe ser > 0'); + + $upd = $pdo->prepare('UPDATE tramites_hours SET abogado = :a, hrs = :h, descripcion = :d WHERE id = :id'); + $upd->execute([':a' => $abog, ':h' => $hrs, ':d' => $desc, ':id' => $hourId]); + + $pdo->prepare('UPDATE tramites_legales SET actualizado_por = :u WHERE id = :id') + ->execute([':u' => $user['email'], ':id' => $tramiteId]); + + $r = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $r->execute([':id' => $tramiteId]); + ok(hydrate($pdo, $r->fetch())); + } + + if ($method === 'DELETE' && $action === 'delete_hours') { + $hourId = (int)($_GET['id'] ?? 0); + if (!$hourId) err('id requerido'); + + $sel = $pdo->prepare('SELECT id, tramite_id, autor_email FROM tramites_hours WHERE id = :id'); + $sel->execute([':id' => $hourId]); + $row = $sel->fetch(); + if (!$row) err('Registro de horas no encontrado', 404); + + $tramiteId = (int)$row['tramite_id']; + if (!canView($pdo, $tramiteId)) err('No tienes acceso al trámite', 403); + if (!Auth::isSocio() && $row['autor_email'] !== $user['email']) { + err('Solo quien registró las horas (o un Socio/Admin) puede eliminarlas', 403); + } + + $pdo->prepare('DELETE FROM tramites_hours WHERE id = :id')->execute([':id' => $hourId]); + $pdo->prepare('UPDATE tramites_legales SET actualizado_por = :u WHERE id = :id') + ->execute([':u' => $user['email'], ':id' => $tramiteId]); + + $r = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $r->execute([':id' => $tramiteId]); + ok(hydrate($pdo, $r->fetch())); + } + if ($method === 'DELETE' && $action === 'delete') { if (!Auth::canDelete()) err('Tu rol no permite eliminar trámites. Solo Socios y Abogados pueden.', 403); diff --git a/sql/009_panama_timezone.sql b/sql/009_panama_timezone.sql new file mode 100644 index 0000000..fd8333c --- /dev/null +++ b/sql/009_panama_timezone.sql @@ -0,0 +1,33 @@ +-- ============================================================ +-- Donna — Schema 009: Convertir timestamps UTC → America/Panama +-- ============================================================ +-- Hasta aquí la app corrió con OS, MariaDB y PHP en UTC, pero los +-- usuarios viven en Panamá (UTC-5). Causa: filtros de fecha en frontend +-- (basados en hora local) excluían entries creados después de las +-- 19:00 hora Panama porque su DATETIME en UTC ya era del día siguiente. +-- +-- Aplicado junto con: +-- - timedatectl set-timezone America/Panama (OS) +-- - SET time_zone = '-05:00' por conexión (src/Db.php) +-- - date_default_timezone_set('America/Panama') en src/Config.php + +UPDATE users + SET first_seen_at = CASE WHEN first_seen_at IS NOT NULL THEN CONVERT_TZ(first_seen_at, '+00:00', '-05:00') ELSE first_seen_at END, + last_login_at = CASE WHEN last_login_at IS NOT NULL THEN CONVERT_TZ(last_login_at, '+00:00', '-05:00') ELSE last_login_at END; + +UPDATE escrituras_tramites + SET fecha_solicitud = CONVERT_TZ(fecha_solicitud, '+00:00', '-05:00'), + ultima_actualizacion = CONVERT_TZ(ultima_actualizacion, '+00:00', '-05:00'), + created_at = CONVERT_TZ(created_at, '+00:00', '-05:00'); + +UPDATE escrituras_historial + SET modificado_en = CONVERT_TZ(modificado_en, '+00:00', '-05:00'); + +UPDATE tramites_legales + SET fecha_solicitud = CONVERT_TZ(fecha_solicitud, '+00:00', '-05:00'), + created_at = CONVERT_TZ(created_at, '+00:00', '-05:00'), + updated_at = CONVERT_TZ(updated_at, '+00:00', '-05:00'); + +UPDATE tramites_obs SET fecha = CONVERT_TZ(fecha, '+00:00', '-05:00'); +UPDATE tramites_hours SET fecha = CONVERT_TZ(fecha, '+00:00', '-05:00'); +UPDATE tramites_involucrados SET added_at = CONVERT_TZ(added_at, '+00:00', '-05:00'); diff --git a/src/Config.php b/src/Config.php index 6fa39c5..5b90ff4 100644 --- a/src/Config.php +++ b/src/Config.php @@ -3,8 +3,13 @@ declare(strict_types=1); final class Config { private static ?array $env = null; + private static bool $tzSet = false; public static function load(string $path = '/etc/donna/donna.env'): void { + if (!self::$tzSet) { + date_default_timezone_set('America/Panama'); + self::$tzSet = true; + } if (self::$env !== null) return; if (!is_readable($path)) { http_response_code(500); diff --git a/src/Db.php b/src/Db.php index 18b2e19..9b44a99 100644 --- a/src/Db.php +++ b/src/Db.php @@ -19,6 +19,8 @@ final class Db { PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); + // Hora de Panamá (UTC-5, sin DST) — defensivo aunque el OS ya esté en Panamá + self::$pdo->exec("SET time_zone = '-05:00'"); return self::$pdo; } } diff --git a/views/tramites.html b/views/tramites.html index 9aa002e..e974449 100644 --- a/views/tramites.html +++ b/views/tramites.html @@ -103,8 +103,13 @@ tr.expanded>td{background:#fffdf5;} .add-obs{display:flex;gap:6px;margin-top:8px;} .add-obs textarea{flex:1;font-size:12px;border:1px solid var(--border);border-radius:4px;padding:5px 8px;resize:vertical;min-height:48px;} .hours-list{list-style:none;} -.hours-list li{font-size:11.5px;padding:3px 0;display:flex;justify-content:space-between;border-bottom:1px dashed #eee;} +.hours-list li{font-size:11.5px;padding:3px 0;display:flex;justify-content:space-between;align-items:center;gap:8px;border-bottom:1px dashed #eee;} .hours-list li .hl-who{color:var(--navy);font-weight:600;} +.hours-list li .hl-right{display:flex;align-items:center;gap:8px;} +.hr-actions{display:inline-flex;gap:3px;} +.hr-actions button{background:transparent;border:1px solid var(--border);border-radius:3px;padding:1px 6px;font-size:10px;cursor:pointer;color:var(--gray);line-height:1.3;} +.hr-actions .hr-edit:hover{background:var(--gold);color:var(--navy);border-color:var(--gold);} +.hr-actions .hr-del:hover{background:#e74c3c;color:#fff;border-color:#e74c3c;} .add-hours{display:flex;gap:6px;margin-top:8px;align-items:center;flex-wrap:wrap;} .add-hours select,.add-hours input{font-size:12px;padding:4px 8px;border:1px solid var(--border);border-radius:4px;} .total-hrs{font-size:11px;font-weight:700;color:var(--navy);margin-top:6px;} @@ -197,6 +202,21 @@ tr.expanded>td{background:#fffdf5;} + + +