Plataforma técnica · Sevastopol
Company View Island
Islands Sevastopol Administración
Propósito
Section titled “Propósito”CompanyViewIsland mantiene los datos legales del tenant: razón social, RUT, dirección, contacto, año de inicio de actividades, giros comerciales SII, bandera de PPM estratégico y estado activa/inactiva. Hay un único registro por tenant — la island es un formulario UPDATE; nunca crea ni elimina.
Ubicación
Section titled “Ubicación”Directorysevastopol/src/components/islands/admin/
- CompanyViewIsland.tsx — componente único, internamente
CompanyView
- CompanyViewIsland.tsx — componente único, internamente
Estado
Section titled “Estado”const { tenantId } = useActiveTenant((id) => void fetchData(id));const [companyData, setCompanyData] = createSignal<CompanyConfig | null>(null);const [giros, setGiros] = createSignal<GiroComercial[]>([]);const [isLoading, setLoading] = createSignal(true);const [isSaving, setSaving] = createSignal(false);| Signal | Tipo | Uso |
|---|---|---|
companyData | CompanyConfig | null | Registro único del tenant (null si no existe o falló la carga). |
giros | GiroComercial[] | Catálogo SII de giros, cargado al montar desde /api/parameters/giros-comerciales. |
isLoading | boolean | Bloqueo durante carga inicial. |
isSaving | boolean | Bloqueo durante PUT. |
Al montar dispara dos fetches:
onMount(() => { void fetchGiros(); if (tenantId()) void fetchData(tenantId());});fetchGiros no requiere tenant — el catálogo SII es global. fetchData sí lo requiere; sin tenant la vista queda en estado vacío.
async function fetchData(tid: string) { if (!tid) return; setLoading(true); try { const response = await authenticatedFetch(`${API_BASE}?tenant_id=${tid}`); if (!response.ok) throw new Error("Error loading company config"); setCompanyData(await response.json()); } catch (error) { toast.push("Error al cargar configuración de empresa", "error"); setCompanyData(null); }}Helpers de normalización
Section titled “Helpers de normalización”Tres helpers tipan estrictamente el formulario:
const safeString = (value: unknown) => String(value ?? "").trim();const optionalString = (value: string) => safeString(value) || null;const parseOptionalNumber = (value: string, mode = "float") => { const normalized = safeString(value).replace(",", "."); if (!normalized) return null; const parsed = mode === "int" ? parseInt(normalized, 10) : parseFloat(normalized); return Number.isFinite(parsed) ? parsed : null;};safeString: convierte cualquier valor a string trim-eado; nunca tira pornull/undefined.optionalString: vacío →null(deja claro al backend que el campo no fue provisto, no que se borró).parseOptionalNumber: acepta coma decimal latina; rechazaNaN/Infinity.
Métricas en pills
Section titled “Métricas en pills”| Pill | Origen |
|---|---|
| Tenant | Primeros 8 chars del tenantId. |
| Inventario | companyData?.giro_codigo?.length — cuántos giros tiene marcados. |
| Inicio | año_inicio_actividades formateado. |
| PPM | usar_ppm_estrategico ? "Estratégico" : "Base" (tono success vs warning). |
| Estado | activa ? "Activa" : "Inactiva". |
Giros multi-select
Section titled “Giros multi-select”giro_codigo es un array de strings. El toggle agrega o quita el código del array; si queda vacío, se setea null (no []):
function toggleInventoryGiro(code: string) { const current = companyData(); if (!current) return; const selected = current.giro_codigo ?? []; const next = selected.includes(code) ? selected.filter((item) => item !== code) : [...selected, code]; updateField("giro_codigo", next.length > 0 ? next : null);}El catálogo giros viene del SII vía /api/parameters/giros-comerciales con { codigo, descripcion }.
Guardado
Section titled “Guardado”Single PUT con el CompanyConfig completo:
const payload: UpdateCompanyDTO = { ...data };const response = await authenticatedFetch(`${API_BASE}?tenant_id=${tid}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),});setCompanyData(await response.json());La respuesta del backend reemplaza companyData en memoria — útil si el backend normaliza o recalcula campos derivados.
Sin validación de cliente — el backend valida formato de RUT (^\d{7,8}-[\dkK]$), email, y existencia de los giros contra el catálogo SII.
Endpoints consumidos
Section titled “Endpoints consumidos”| Método | Ruta | Operación |
|---|---|---|
GET | /api/admin/company?tenant_id=... | Registro único de la empresa. |
PUT | /api/admin/company?tenant_id=... body UpdateCompanyDTO | Guarda cambios completos. |
GET | /api/parameters/giros-comerciales | Catálogo SII (sin tenant). |
Reglas de UI
Section titled “Reglas de UI”| Regla | Motivo |
|---|---|
| Un solo registro UPDATE-only | El tenant es la empresa; alta y baja se gestionan en Command › Tenants. |
giro_codigo vacío → null | El backend distingue “sin giros” (null) de “lista vacía” ([]); usar null evita ambigüedad. |
| Sin validación de cliente | El backend valida formato RUT, email y giros; duplicar en cliente desincronizaría las reglas. |
| Carga de giros sin tenant | El catálogo SII es global; no esperar al selector. |
Toggle sobre giros con next.length > 0 ? next : null | Idem null vs []. |
Actualizar companyData con la respuesta del PUT | El backend puede normalizar; reflejarlo evita “guardé X pero veo Y”. |
Helpers safeString/optionalString/parseOptionalNumber | Disciplinan los tipos: nunca strings vacíos llegan al backend, números mal formados son rechazados antes. |