diff --git a/public/tramites/api.php b/public/tramites/api.php new file mode 100644 index 0000000..4b2b942 --- /dev/null +++ b/public/tramites/api.php @@ -0,0 +1,291 @@ + 'ADM', 'corp' => 'CRP', 'mig' => 'MIG', 'lit' => 'LIT']; +const STATUS_MAP = [ + 'adm' => ['Solicitado','Recibido','En Proceso','Notificado','Recurso','Culminado','Facturado'], + 'corp' => ['Solicitado','Recibido','Borrador','Versión de Firma','Firmado','Culminado','Facturado'], + 'mig' => ['Solicitado','Recibido','Presentado','Notificado','Recurso','Culminado','Facturado'], + 'lit' => ['Solicitado','Recibido','Presentado','Admitido','Contestado','Pruebas','Contrapruebas','Alegatos','Sentencia','Apelación'], +]; + +function ok($data = null): void { + echo json_encode(['ok' => true, 'data' => $data, 'user' => Auth::user()], JSON_UNESCAPED_UNICODE); + exit; +} + +function err(string $msg, int $code = 400): void { + http_response_code($code); + echo json_encode(['ok' => false, 'error' => $msg], JSON_UNESCAPED_UNICODE); + exit; +} + +function body(): array { + return json_decode((string) file_get_contents('php://input'), true) ?? []; +} + +function dept_of(array $b): string { + $d = strtolower((string)($b['departamento'] ?? $_GET['dept'] ?? '')); + if (!in_array($d, DEPTS, true)) err('Departamento inválido'); + return $d; +} + +function clean_yesno(?string $v): ?string { + if ($v === null) return null; + $v = trim($v); + if ($v === '') return null; + return in_array($v, ['Sí','No'], true) ? $v : null; +} + +function clean_date(?string $v): ?string { + if (!$v) return null; + $v = trim($v); + if ($v === '') return null; + return preg_match('/^\d{4}-\d{2}-\d{2}$/', $v) ? $v : null; +} + +function next_codigo(PDO $pdo, string $dept): string { + $pdo->beginTransaction(); + try { + $sel = $pdo->prepare('SELECT next_seq FROM tramites_seq WHERE departamento = :d FOR UPDATE'); + $sel->execute([':d' => $dept]); + $n = (int) ($sel->fetchColumn() ?: 1); + + $upd = $pdo->prepare('UPDATE tramites_seq SET next_seq = :n WHERE departamento = :d'); + $upd->execute([':n' => $n + 1, ':d' => $dept]); + + $pdo->commit(); + return sprintf('%s-%03d', PREFIX[$dept], $n); + } catch (Throwable $e) { + $pdo->rollBack(); + throw $e; + } +} + +function hydrate(PDO $pdo, array $row): array { + $tid = (int) $row['id']; + $obs = $pdo->prepare('SELECT id, texto, abogado, autor_email, fecha FROM tramites_obs WHERE tramite_id = :t ORDER BY fecha ASC'); + $obs->execute([':t' => $tid]); + $row['obs'] = $obs->fetchAll(); + + $hrs = $pdo->prepare('SELECT id, abogado, hrs, descripcion, autor_email, fecha FROM tramites_hours WHERE tramite_id = :t ORDER BY fecha ASC'); + $hrs->execute([':t' => $tid]); + $row['hours'] = array_map(function ($h) { + $h['hrs'] = (float) $h['hrs']; + return $h; + }, $hrs->fetchAll()); + + return $row; +} + +$method = $_SERVER['REQUEST_METHOD']; +$action = $_GET['action'] ?? ''; +$user = Auth::user(); +$pdo = Db::pdo(); + +try { + if ($method === 'GET' && $action === 'list') { + $rows = $pdo->query('SELECT * FROM tramites_legales ORDER BY id DESC')->fetchAll(); + $grouped = ['adm' => [], 'corp' => [], 'mig' => [], 'lit' => []]; + foreach ($rows as $r) { + $grouped[$r['departamento']][] = hydrate($pdo, $r); + } + ok($grouped); + } + + if ($method === 'POST' && $action === 'create') { + $b = body(); + $dept = dept_of($b); + + $est = (string) ($b['estatus'] ?? 'Solicitado'); + if (!in_array($est, STATUS_MAP[$dept], true)) $est = STATUS_MAP[$dept][0]; + + $codigo = next_codigo($pdo, $dept); + + $stmt = $pdo->prepare( + 'INSERT INTO tramites_legales + (codigo, departamento, cliente, abogado, estatus, instrucciones, + nombre, institucion, tipo_documento, tipo_tramite, + doc_completa, permiso_trabajo, dependientes, fecha_vencimiento, + esfera, instancia, circuito, juzgado, tipo_proceso, + incidentes, amparo, fecha_termino, creado_por) + VALUES + (:codigo, :dept, :cliente, :abogado, :estatus, :instrucciones, + :nombre, :institucion, :tipo_documento, :tipo_tramite, + :doc_completa, :permiso_trabajo, :dependientes, :fecha_vencimiento, + :esfera, :instancia, :circuito, :juzgado, :tipo_proceso, + :incidentes, :amparo, :fecha_termino, :user)' + ); + $stmt->execute([ + ':codigo' => $codigo, + ':dept' => $dept, + ':cliente' => (string)($b['cliente'] ?? ''), + ':abogado' => (string)($b['abogado'] ?? ''), + ':estatus' => $est, + ':instrucciones' => (string)($b['instrucciones'] ?? ''), + ':nombre' => (string)($b['nombre'] ?? ''), + ':institucion' => (string)($b['institucion'] ?? ''), + ':tipo_documento'=> (string)($b['tipoDocumento'] ?? ''), + ':tipo_tramite' => (string)($b['tipoTramite'] ?? ''), + ':doc_completa' => clean_yesno($b['docCompleta'] ?? null), + ':permiso_trabajo'=> clean_yesno($b['permisoTrabajo'] ?? null), + ':dependientes' => clean_yesno($b['dependientes'] ?? null), + ':fecha_vencimiento' => clean_date($b['fechaVencimiento'] ?? null), + ':esfera' => in_array($b['esfera'] ?? '', ['Civil','Penal'], true) ? $b['esfera'] : null, + ':instancia' => in_array($b['instancia'] ?? '', ['Primera','Segunda','CSJ'], true) ? $b['instancia'] : null, + ':circuito' => (string)($b['circuito'] ?? ''), + ':juzgado' => (string)($b['juzgado'] ?? ''), + ':tipo_proceso' => (string)($b['tipoProceso'] ?? ''), + ':incidentes' => clean_yesno($b['incidentes'] ?? null), + ':amparo' => clean_yesno($b['amparo'] ?? null), + ':fecha_termino' => clean_date($b['fechaTermino'] ?? null), + ':user' => $user['email'], + ]); + $id = (int) $pdo->lastInsertId(); + $row = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $row->execute([':id' => $id]); + ok(hydrate($pdo, $row->fetch())); + } + + if ($method === 'PUT' && $action === 'update') { + $b = body(); + $id = (int) ($b['_id'] ?? 0); + if (!$id) err('id requerido'); + + $cur = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $cur->execute([':id' => $id]); + $before = $cur->fetch(); + if (!$before) err('Trámite no encontrado', 404); + + $dept = $before['departamento']; + $est = (string) ($b['estatus'] ?? $before['estatus']); + if (!in_array($est, STATUS_MAP[$dept], true)) $est = $before['estatus']; + + $stmt = $pdo->prepare( + 'UPDATE tramites_legales SET + cliente=:cliente, abogado=:abogado, estatus=:estatus, instrucciones=:instrucciones, + nombre=:nombre, institucion=:institucion, + tipo_documento=:tipo_documento, tipo_tramite=:tipo_tramite, + doc_completa=:doc_completa, permiso_trabajo=:permiso_trabajo, + dependientes=:dependientes, fecha_vencimiento=:fecha_vencimiento, + esfera=:esfera, instancia=:instancia, circuito=:circuito, + juzgado=:juzgado, tipo_proceso=:tipo_proceso, + incidentes=:incidentes, amparo=:amparo, fecha_termino=:fecha_termino, + actualizado_por=:user + WHERE id=:id' + ); + $stmt->execute([ + ':cliente' => (string)($b['cliente'] ?? $before['cliente']), + ':abogado' => (string)($b['abogado'] ?? $before['abogado']), + ':estatus' => $est, + ':instrucciones' => (string)($b['instrucciones'] ?? $before['instrucciones']), + ':nombre' => (string)($b['nombre'] ?? $before['nombre']), + ':institucion' => (string)($b['institucion'] ?? $before['institucion']), + ':tipo_documento'=> (string)($b['tipoDocumento'] ?? $before['tipo_documento']), + ':tipo_tramite' => (string)($b['tipoTramite'] ?? $before['tipo_tramite']), + ':doc_completa' => clean_yesno($b['docCompleta'] ?? $before['doc_completa']), + ':permiso_trabajo'=> clean_yesno($b['permisoTrabajo'] ?? $before['permiso_trabajo']), + ':dependientes' => clean_yesno($b['dependientes'] ?? $before['dependientes']), + ':fecha_vencimiento' => clean_date($b['fechaVencimiento'] ?? $before['fecha_vencimiento']), + ':esfera' => in_array(($b['esfera'] ?? $before['esfera']), ['Civil','Penal'], true) ? ($b['esfera'] ?? $before['esfera']) : null, + ':instancia' => in_array(($b['instancia'] ?? $before['instancia']), ['Primera','Segunda','CSJ'], true) ? ($b['instancia'] ?? $before['instancia']) : null, + ':circuito' => (string)($b['circuito'] ?? $before['circuito']), + ':juzgado' => (string)($b['juzgado'] ?? $before['juzgado']), + ':tipo_proceso' => (string)($b['tipoProceso'] ?? $before['tipo_proceso']), + ':incidentes' => clean_yesno($b['incidentes'] ?? $before['incidentes']), + ':amparo' => clean_yesno($b['amparo'] ?? $before['amparo']), + ':fecha_termino' => clean_date($b['fechaTermino'] ?? $before['fecha_termino']), + ':user' => $user['email'], + ':id' => $id, + ]); + $row = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $row->execute([':id' => $id]); + ok(hydrate($pdo, $row->fetch())); + } + + if ($method === 'PATCH' && $action === 'set_status') { + $b = body(); + $id = (int) ($b['_id'] ?? 0); + $val = (string) ($b['estatus'] ?? ''); + if (!$id) err('id requerido'); + + $cur = $pdo->prepare('SELECT departamento FROM tramites_legales WHERE id = :id'); + $cur->execute([':id' => $id]); + $dept = $cur->fetchColumn(); + if (!$dept) err('Trámite no encontrado', 404); + if (!in_array($val, STATUS_MAP[$dept], true)) err('Estatus inválido para el departamento'); + + $pdo->prepare('UPDATE tramites_legales SET estatus = :s, actualizado_por = :u WHERE id = :id') + ->execute([':s' => $val, ':u' => $user['email'], ':id' => $id]); + ok(['id' => $id, 'estatus' => $val]); + } + + if ($method === 'POST' && $action === 'add_obs') { + $b = body(); + $id = (int) ($b['_id'] ?? 0); + $texto = trim((string)($b['texto'] ?? '')); + $abog = trim((string)($b['abogado'] ?? '')); + if (!$id) err('id requerido'); + if ($texto === '') err('texto requerido'); + + $pdo->prepare('INSERT INTO tramites_obs (tramite_id, texto, abogado, autor_email) VALUES (?,?,?,?)') + ->execute([$id, $texto, $abog, $user['email']]); + + $pdo->prepare('UPDATE tramites_legales SET actualizado_por = :u WHERE id = :id') + ->execute([':u' => $user['email'], ':id' => $id]); + + $row = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $row->execute([':id' => $id]); + ok(hydrate($pdo, $row->fetch())); + } + + if ($method === 'POST' && $action === 'add_hours') { + $b = body(); + $id = (int) ($b['_id'] ?? 0); + $abog = trim((string)($b['abogado'] ?? '')); + $hrs = (float) ($b['hrs'] ?? 0); + $desc = trim((string)($b['descripcion'] ?? '')); + if (!$id) err('id requerido'); + if ($hrs <= 0) err('horas debe ser > 0'); + + $pdo->prepare('INSERT INTO tramites_hours (tramite_id, abogado, hrs, descripcion, autor_email) VALUES (?,?,?,?,?)') + ->execute([$id, $abog, $hrs, $desc, $user['email']]); + + $pdo->prepare('UPDATE tramites_legales SET actualizado_por = :u WHERE id = :id') + ->execute([':u' => $user['email'], ':id' => $id]); + + $row = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); + $row->execute([':id' => $id]); + ok(hydrate($pdo, $row->fetch())); + } + + if ($method === 'DELETE' && $action === 'delete') { + $id = (int) ($_GET['id'] ?? 0); + if (!$id) err('id requerido'); + $pdo->prepare('DELETE FROM tramites_legales WHERE id = :id')->execute([':id' => $id]); + ok(['deleted' => $id]); + } + + err('Acción no reconocida', 404); + +} catch (PDOException $e) { + error_log('tramites db error: ' . $e->getMessage()); + err('Error de base de datos', 500); +} catch (Throwable $e) { + error_log('tramites error: ' . $e->getMessage()); + err('Error interno', 500); +} diff --git a/public/tramites/index.php b/public/tramites/index.php new file mode 100644 index 0000000..c26659e --- /dev/null +++ b/public/tramites/index.php @@ -0,0 +1,8 @@ + 'live', ], [ - 'slug' => 'casos', - 'title' => 'Gestión de Casos', - 'description' => 'Expedientes, plazos procesales y notas de cada asunto.', + 'slug' => 'tramites', + 'title' => 'Control de Trámites Legales', + 'description' => 'Administrativo, corporativo, migración y litigios. Estado, observaciones, horas e historial por trámite.', 'icon' => 'briefcase', - 'href' => '#', - 'status' => 'coming-soon', + 'href' => '/tramites/', + 'status' => 'live', ], [ 'slug' => 'agenda', diff --git a/views/tramites.html b/views/tramites.html new file mode 100644 index 0000000..7c4683b --- /dev/null +++ b/views/tramites.html @@ -0,0 +1,647 @@ + + + + + +Control de Trámites Legales — AALN + + + + +
+

⚖️ AALN — Control de Trámites Legales

+
+ + + Cargando… + + ← Portal + Salir +
+
+ +
+
🏛 Administrativo
+
⚖️ Corp · Com · Civil
+
✈️ Migración
+
⚔️ Litigios
+
+ +
+
+
+
+ + + +
+ + + +