Plataforma técnica · Sevastopol
Bootstrap Cierre Island
Islands Sevastopol Administración
Propósito
Section titled “Propósito”BootstrapCierreIsland ejecuta la inicialización de saldos de apertura de un ejercicio contable para el tenant activo. Es una operación crítica: no tiene reversa desde la UI y solo puede dispararla SUPER_ADMIN. La UI verifica el rol vía /api/auth/validate antes de mostrar el formulario.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/admin/
- BootstrapCierreIsland.tsx — componente único, internamente
BootstrapCierreView
- BootstrapCierreIsland.tsx — componente único, internamente
Hooks y estado global
Section titled “Hooks y estado global”const { tenantId } = useActiveTenant();const [role, setRole] = createSignal<string | null>(null);const isSuperAdmin = () => role() === "SUPER_ADMIN";useActiveTenant aporta el tenant; el rol viene de un fetch independiente al iniciar:
async function fetchRole() { const r = await authenticatedFetch("/api/auth/validate", { method: "POST" }); if (!r.ok) throw new Error("No se pudo validar la sesión"); const data = await r.json(); setRole(data?.user?.role ?? null);}Si role !== "SUPER_ADMIN", la island renderiza 403 en lugar del formulario.
Signals locales:
| Signal | Tipo | Uso |
|---|---|---|
role | string | null | Resultado de /api/auth/validate. |
anio | string | Selección del año (default año actual - 1). |
mes | string | Selección del mes (default "12"). |
isLoading | boolean | Validando sesión inicial. |
confirmOpen | boolean | Modal de confirmación abierto. |
isSubmitting | boolean | Bloqueo del botón mientras se ejecuta. |
Selectores de período
Section titled “Selectores de período”El año cubre una ventana de 6 valores centrada en hoy menos 3:
const AÑO_ACTUAL = new Date().getFullYear();const ANIOS = Array.from({ length: 6 }, (_, i) => { const y = AÑO_ACTUAL - 3 + i; return { id: String(y), nombre: String(y) };});Los meses son 1..12 con etiqueta "01 — Enero", etc.
Default sugerido: año anterior + diciembre. La caja de texto del header recomienda explícitamente:
“Selecciona el período de apertura. Se recomienda 12 del año anterior al ejercicio que vas a iniciar.”
Flujo de ejecución
Section titled “Flujo de ejecución”async function ejecutarBootstrap() { const tid = tenantId(); if (!tid) { toast.push("Selecciona un tenant", "error"); return; } setSubmitting(true); try { const r = await authenticatedFetch( `/api/reportes/bootstrap-cierre?tenant_id=${tid}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ anio: parseInt(anio()), mes: parseInt(mes()) }), }, ); if (!r.ok) throw new Error((await r.json())?.error || "Falló el bootstrap"); toast.push(`Bootstrap ejecutado para ${anio()}-${mes().padStart(2, "0")}`, "ok"); setConfirmOpen(false); } catch (e: any) { toast.push(e.message, "error"); } finally { setSubmitting(false); }}El click en Ejecutar bootstrap abre el modal de confirmación; el botón de confirmar es workspaceDangerButtonClass para enfatizar la criticidad. Tras ejecutar, el toast muestra el período procesado y el modal se cierra.
Estados visibles
Section titled “Estados visibles”| Estado UI | Cuándo | Render |
|---|---|---|
| Validando sesión | isLoading === true | WorkspaceSectionCard con texto “Validando sesión…“. |
| 403 | !isSuperAdmin | WorkspaceSectionCard con eyebrow "403", mensaje “No tienes permisos”. |
| Listo | isSuperAdmin && tenantId | Formulario con dos <Select> + botón. |
| Sin tenant | isSuperAdmin && !tenantId | Botón deshabilitado, pill “Sin seleccionar” en tono warning. |
Endpoints consumidos
Section titled “Endpoints consumidos”| Método | Ruta | Operación |
|---|---|---|
POST | /api/auth/validate | Devuelve { user: { role } } — valida rol del usuario actual. |
POST | /api/reportes/bootstrap-cierre?tenant_id=... body { anio, mes } | Inicializa saldos de apertura del ejercicio. |
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
| Validar rol al montar | Mostrar el formulario sin rol válido confundiría; el backend igual rechazaría. |
| Doble confirm (botón → modal → botón danger) | La operación es crítica y no reversible. |
Botón en tono danger en el modal | Lenguaje visual claro de “no es CRUD normal”. |
| Default año anterior + diciembre | El bootstrap típico inicia el ejercicio siguiente con saldos del cierre anterior. |
Sin useActivePeriod | El período del workspace es para reportes operativos; bootstrap usa selección explícita para evitar confusión. |
| Tenant obligatorio antes de enviar | El backend rechazaría sin tenant; el botón se deshabilita para feedback inmediato. |
| Aviso explícito de “no reversible” en el note del header | El operador debe leer la consecuencia antes de hacer click. |