22 次代碼提交 1f0ea49c91 ... 726623197a

作者 SHA1 備註 提交日期
  Jose Cienfuegos 726623197a Se agrego localhost en .env.development.local 11 月之前
  Jose Cienfuegos 744dccc989 Se quitaron columnas en el grid de condicionantes 11 月之前
  Jose Cienfuegos 700d697ea2 Se agrego imagen en login de inicio 11 月之前
  Jose Cienfuegos 02a0e9fc9b Se cambio los estilos del dashboard y de los CRUDS 1 年之前
  Jose Cienfuegos 6c4163d463 Se agrego campo idSagarhpa en modulos de municipio y estado 1 年之前
  Jose Cienfuegos c87b51023f Merge branch 'desarrollo' of https://git.miralo.xyz/SAGARHPA/Sagarhpa_2024_web into jcrdev 1 年之前
  Jose Cienfuegos 93532d3edb Merge branch 'desarrollo' of https://git.miralo.xyz/SAGARHPA/Sagarhpa_2024_web into jcrdev 1 年之前
  Jose Cienfuegos b84ca935cf Se cambio el orden del grid de la tabla nivel 1 年之前
  Jose Cienfuegos 025b692ee3 Se agrego columna para di del subproducto 1 年之前
  Jose Cienfuegos 877c074493 Se agrego modulo nivel 1 年之前
  OscarGil03 cb3c3ef394 corrección estilos wysywyg 1 年之前
  OscarGil03 03267d89a4 Wysywyg condicionantes 1 年之前
  OscarGil03 752afd3d1f Edición municipios 1 年之前
  OscarGil03 4256e9ca73 ordenar 1 年之前
  OscarGil03 f2d0a2ab93 Actualización de tabla al eliminar un registro 1 年之前
  OscarGil03 eea4198cc9 Edicion condicionantes 1 年之前
  OscarGil03 507cb960f7 Merge branch 'desarrollo' into OGdev 1 年之前
  OscarGil03 db169fb0b1 subproductos 1 年之前
  OscarGil03 788f7261f1 Merge branch 'desarrollo' into OGdev 1 年之前
  OscarGil03 c77458630e Condicionante 1 年之前
  OscarGil03 95e224ddc7 Catalogos 1 年之前
  OscarGil03 30bd952065 campo idSagarhpa 1 年之前
共有 34 個文件被更改,包括 2238 次插入492 次删除
  1. 3 2
      .env.development.local
  2. 3 3
      .env.production.local
  3. 5 0
      .firebaserc
  4. 17 0
      firebase.json
  5. 12 0
      src/components/DefaultLayout.jsx
  6. 7 10
      src/components/Tabla.jsx
  7. 3 1
      src/components/index.js
  8. 5 2
      src/components/layouts/DashboardLayout.jsx
  9. 118 1
      src/routers/routes.jsx
  10. 5 28
      src/views/auth/Ingresar.jsx
  11. 144 0
      src/views/catalogos/estados/EstadoDetalle.jsx
  12. 118 0
      src/views/catalogos/estados/Estados.jsx
  13. 55 0
      src/views/catalogos/estados/Formulario.jsx
  14. 7 0
      src/views/catalogos/estados/index.js
  15. 144 0
      src/views/catalogos/finMovilizaciones/FinMovilizacionDetalle.jsx
  16. 117 0
      src/views/catalogos/finMovilizaciones/FinMovilizaciones.jsx
  17. 55 0
      src/views/catalogos/finMovilizaciones/Formulario.jsx
  18. 7 0
      src/views/catalogos/finMovilizaciones/index.js
  19. 55 0
      src/views/catalogos/municipios/Formulario.jsx
  20. 193 0
      src/views/catalogos/municipios/MunicipioDetalle.jsx
  21. 117 0
      src/views/catalogos/municipios/Municipios.jsx
  22. 7 0
      src/views/catalogos/municipios/index.js
  23. 53 0
      src/views/catalogos/nivel/Detalle.jsx
  24. 97 0
      src/views/catalogos/nivel/Formulario.jsx
  25. 79 0
      src/views/catalogos/nivel/Listado.jsx
  26. 49 0
      src/views/catalogos/nivel/index.jsx
  27. 95 85
      src/views/catalogos/productos/ProductoDetalle.jsx
  28. 10 46
      src/views/catalogos/productos/Productos.jsx
  29. 55 0
      src/views/catalogos/tipoMovilizaciones/Formulario.jsx
  30. 144 0
      src/views/catalogos/tipoMovilizaciones/TipoMovilizacionDetalle.jsx
  31. 116 0
      src/views/catalogos/tipoMovilizaciones/TipoMovilizaciones.jsx
  32. 7 0
      src/views/catalogos/tipoMovilizaciones/index.js
  33. 323 236
      src/views/condicionantes/CondicionanteDetalle.jsx
  34. 13 78
      src/views/condicionantes/Condicionantes.jsx

+ 3 - 2
.env.development.local

@@ -1,9 +1,10 @@
 # Projecto   firebase | jwt
 VITE_PROJECT=jwt
 VITE_NOMBRE_PAGINA=
-VITE_API_URL=http://localhost:8080
+#VITE_API_URL=http://localhost:8080
+VITE_API_URL=https://sagarhpa.api.edesarrollos.info
 VITE_USR_URL=http://127.0.0.1:5173/
-VITE_API_VERSION=1.24.05.06+0
+VITE_API_VERSION=1.24.05.17+1
 
 VITE_API_MODULE=/v1/
 VITE_API_MODULE_PDF=/pdf/

+ 3 - 3
.env.production.local

@@ -1,9 +1,9 @@
 # Projecto   firebase | jwt
 VITE_PROJECT=jwt
 VITE_NOMBRE_PAGINA=
-VITE_API_URL=http://localhost:8080
-VITE_USR_URL=http://127.0.0.1:5173/
-VITE_API_VERSION=1.24.05.06+0
+VITE_API_URL=https://sagarhpa.api.edesarrollos.info
+VITE_USR_URL=https://sagarhpa.web.app/
+VITE_API_VERSION=1.24.05.17+1
 
 VITE_API_MODULE=/v1/
 VITE_API_MODULE_PDF=/pdf/

+ 5 - 0
.firebaserc

@@ -0,0 +1,5 @@
+{
+  "projects": {
+    "default": "sagarhpa-sirgea"
+  }
+}

+ 17 - 0
firebase.json

@@ -0,0 +1,17 @@
+{
+  "hosting": {
+    "public": "dist",
+    "site": "sagarhpa",
+    "ignore": [
+      "firebase.json",
+      "**/.*",
+      "**/node_modules/**"
+    ],
+    "rewrites": [
+      {
+        "source": "**",
+        "destination": "/index.html"
+      }
+    ]
+  }
+}

+ 12 - 0
src/components/DefaultLayout.jsx

@@ -0,0 +1,12 @@
+import { Layout } from "antd";
+import React from "react";
+
+const DefaultLayout = ({ children }) => {
+  return (
+    <Layout style={{ backgroundColor: "white", padding: "0 24px 24px" }}>
+      {children}
+    </Layout>
+  );
+};
+
+export default DefaultLayout;

+ 7 - 10
src/components/Tabla.jsx

@@ -1,19 +1,18 @@
-import React from "react";
+import React, { forwardRef, useImperativeHandle } from "react";
 import { Table } from "antd";
 import { useModels, useSortColumns, usePagination } from "../hooks";
 import PropTypes from "prop-types";
 import { emptyRequest } from "../constants/requests";
 
-const Tabla = ({
+const Tabla = forwardRef(({
   nameURL = "",
   expand = "",
   extraParams = null,
   columns,
   order,
-  innerRef,
   scrollX = "80vw",
   ...props
-}) => {
+}, ref) => {
   const [columnsData, setColumnsData] = React.useState([]);
   const [request, setRequest] = React.useState(emptyRequest);
 
@@ -56,11 +55,9 @@ const Tabla = ({
     }
   }, [modelsPage, setTotal]);
 
-  if (innerRef) {
-    innerRef.current = {
-      refresh,
-    };
-  }
+  useImperativeHandle(ref, () => ({
+    refresh,
+  }));
 
   return (
     <Table
@@ -75,7 +72,7 @@ const Tabla = ({
       size="small"
     />
   );
-};
+});
 
 Tabla.propTypes = {
   nameURL: PropTypes.string.isRequired,

+ 3 - 1
src/components/index.js

@@ -12,6 +12,7 @@ import ViewLoading from "./ViewLoading";
 import MediaCards from "./MediaCards";
 import InputPass from "./InputPass";
 import Selector from "./Selector";
+import DefaultLayout from "./DefaultLayout";
 
 export {
   Tabla,
@@ -27,5 +28,6 @@ export {
   ViewLoading,
   MediaCards,
   InputPass,
-  ArbolPermisos
+  ArbolPermisos,
+  DefaultLayout,
 };

+ 5 - 2
src/components/layouts/DashboardLayout.jsx

@@ -70,7 +70,7 @@ const DashboardLayout = ({ children }) => {
     },
     header: {
       padding: 0,
-      backgroundColor: '#fff',
+      backgroundColor: '#F6F6F4',
     },
     trigger: {
       color: '#333',
@@ -234,6 +234,7 @@ const DashboardLayout = ({ children }) => {
           openKeys={openKeys}
           onOpenChange={onOpenChange}
           selectedKeys={selectedKey}
+          style={{background: "#F6F6F4"}}
           items={[
             ...dashboardRoutes.map(sidebarMapper),
             {
@@ -266,7 +267,7 @@ const DashboardLayout = ({ children }) => {
                 style={dashStyles.breadcrumb}
                 items={breadcrumbItems?.map((item, index) => ({
                   title: (
-                    <Link to={item?.to}>
+                    <Link to={item?.to} style={{color: "#242424"}}>
                       {item?.icon}
                       <span> {item?.name} </span>
                     </Link>
@@ -300,7 +301,9 @@ const DashboardLayout = ({ children }) => {
           className="site-layout-background"
           style={{
             margin: 10,
+            marginRight: 10,
             minHeight: 280,
+            background: "#ffffff"
           }}
           children={children}
         />

+ 118 - 1
src/routers/routes.jsx

@@ -14,6 +14,8 @@ import {
   ApartmentOutlined,
   UnorderedListOutlined 
 } from "@ant-design/icons";
+import { BsFillCartCheckFill } from "react-icons/bs";
+
 //Íconos de Ant Design
 
 //Íconos React Icons
@@ -25,7 +27,12 @@ import { Usuarios, UsuarioDetalle } from "../views/admin/usuarios";
 
 /* CATÁLOGOS */
 import { Productos, ProductoDetalle } from "../views/catalogos/productos";
+import { Estados, EstadoDetalle } from "../views/catalogos/estados";
+import { Municipios, MunicipioDetalle } from "../views/catalogos/municipios";
+import { TipoMovilizaciones, TipoMovilizacionDetalle } from "../views/catalogos/tipoMovilizaciones";
+import { FinMovilizaciones, FinMovilizacionDetalle } from "../views/catalogos/finMovilizaciones";
 import { Condicionantes, CondicionanteDetalle } from "../views/condicionantes";
+import { Nivel, NivelDetalle} from '../views/catalogos/nivel'
 /* CATÁLOGOS */
 import { Perfil } from "../views/perfil";
 import { Modulos } from "../views/admin/permisos/modulos";
@@ -177,7 +184,7 @@ const dashboardRoutes = [
             layout: "dashboard",
             path: "/productos",
             name: "Productos",
-            icon: <ApartmentOutlined />,
+            icon: <BsFillCartCheckFill />,
             sidebar: "single",
             ver: "MENU-ADMIN",
             routes: [
@@ -195,6 +202,116 @@ const dashboardRoutes = [
               },
             ],
           },
+          {
+            layout: "dashboard",
+            path: "/estados",
+            name: "Estados",
+            icon: <ApartmentOutlined />,
+            sidebar: "single",
+            ver: "MENU-ADMIN",
+            routes: [
+              {
+                path: "/",
+                element: Estados,
+              },
+              {
+                path: "/agregar",
+                element: EstadoDetalle,
+              },
+              {
+                path: "/editar",
+                element: EstadoDetalle,
+              },
+            ],
+          },
+          {
+            layout: "dashboard",
+            path: "/municipios",
+            name: "Municipios",
+            icon: <ApartmentOutlined />,
+            sidebar: "single",
+            ver: "MENU-ADMIN",
+            routes: [
+              {
+                path: "/",
+                element: Municipios,
+              },
+              {
+                path: "/agregar",
+                element: MunicipioDetalle,
+              },
+              {
+                path: "/editar",
+                element: MunicipioDetalle,
+              },
+            ],
+          },
+          {
+            layout: "dashboard",
+            path: "/nivel",
+            name: "Nivel",
+            icon: <ApartmentOutlined />,
+            sidebar: "single",
+            ver: "MENU-ADMIN",
+            routes: [
+              {
+                path: "/",
+                element: Nivel,
+              },
+              {
+                path: "/agregar",
+                element: NivelDetalle,
+              },
+              {
+                path: "/editar",
+                element: NivelDetalle,
+              },
+            ],
+          },
+          {
+            layout: "dashboard",
+            path: "/tipoMovilizaciones",
+            name: "Tipos de Movilización",
+            icon: <ApartmentOutlined />,
+            sidebar: "single",
+            ver: "MENU-ADMIN",
+            routes: [
+              {
+                path: "/",
+                element: TipoMovilizaciones,
+              },
+              {
+                path: "/agregar",
+                element: TipoMovilizacionDetalle,
+              },
+              {
+                path: "/editar",
+                element: TipoMovilizacionDetalle,
+              },
+            ],
+          },
+          {
+            layout: "dashboard",
+            path: "/finMovilizaciones",
+            name: "Fines de Movilización",
+            icon: <ApartmentOutlined />,
+            sidebar: "single",
+            ver: "MENU-ADMIN",
+            routes: [
+              {
+                path: "/",
+                element: FinMovilizaciones,
+              },
+              {
+                path: "/agregar",
+                element: FinMovilizacionDetalle,
+              },
+              {
+                path: "/editar",
+                element: FinMovilizacionDetalle,
+              },
+            ],
+          },
         ],
       },
     ],

+ 5 - 28
src/views/auth/Ingresar.jsx

@@ -63,7 +63,7 @@ const Ingresar = () => {
   return (
     <Row gutter={[10, 10]}>
       {/* Listado Izquierdo */}
-      <Col
+     {/*  <Col
         sm={24}
         md={6}
         style={{
@@ -98,30 +98,15 @@ const Ingresar = () => {
             </i>{" "}
           </p>
         </div>
-      </Col>
+      </Col> */}
 
       {/* Formulario */}
-      <Col sm={24} md={12}>
+      <Col sm={24} md={12}  lg={24}>
         <div style={SignInStyles.container}>
-          <p>
-            <strong>Ingresa aquí tu solicitud.</strong> A través del sistema
-            SIISTAI podrás solicitar toda la información pública del Gobierno
-            del Estado.
-          </p>
+          
           <div style={SignInStyles.logoContainer}>
-            <img src={"./logo_istai_lg.png"} style={{ width: "60%" }} alt="" />
+            <img src={"https://solicitud.sagarhpa.com/img/EscudoSonora.png"} style={{ width: "60%" }} alt="" />
           </div>
-          <p>
-            <i>
-              Si desea consultar las versiones públicas de las resoluciones de
-              los recursos de revisión que han realizado otras personas, a
-              través del SIISTAI,
-              <a href="#" target="_blank" rel="noreferrer">
-                {" "}
-                da clic aquí.
-              </a>
-            </i>{" "}
-          </p>
           <Spin indicator={antIcon} spinning={sessionLoading}>
             <Form
               name="normal_login"
@@ -186,18 +171,10 @@ const Ingresar = () => {
                   </Form.Item>
                 </Col>
               </Row>
-              <div style={{ textAlign: "center" }}>
-                <Typography.Text type="secondary">
-                  ¿No tienes cuenta? <Link to="/registrar">Regístrate</Link>
-                </Typography.Text>
-              </div>
             </Form>
           </Spin>
         </div>
       </Col>
-      <Col>
-        <div style={SignInStyles.container}></div>
-      </Col>
       <br />
     </Row>
   );

+ 144 - 0
src/views/catalogos/estados/EstadoDetalle.jsx

@@ -0,0 +1,144 @@
+import { Form, Input, Button, Spin, Row, Col } from 'antd'
+import { useEffect, useMemo, useState } from 'react'
+import HttpService from '../../../services/httpService'
+import { respuestas } from '../../../utilities'
+import { useNavigate } from 'react-router-dom'
+import { useQuery, useModel } from '../../../hooks'
+import { commonRules } from '../../../constants/rules'
+import DefaultLayout from '../../../components/layouts/DefaultLayout'
+
+
+const endpoints = {
+  estado: "estado",
+};
+
+const EstadoDetalle = () => {
+  const [form] = Form.useForm()
+  const navigate = useNavigate()
+  const [loading, setLoading] = useState(false)
+  const query = useQuery()
+  const id = query.get("id")
+  const [request, setRequest] = useState({})
+
+  // const extraParams = useMemo(() => ({
+  //   idAgenda: id,
+  // }), [id])
+
+  const requestParams = useMemo(() => ({
+    name: endpoints.estado,
+    id,
+    // extraParams
+  }), [id])
+
+
+  const { model, modelLoading } = useModel(request)
+
+  useEffect(() => {
+    if (id) {
+      setRequest(requestParams)
+    }
+    return () => {
+      setRequest({})
+    }
+  }, [id, requestParams])
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({ 
+        ...model,
+      })
+    }
+  }, [form, model])
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+
+      let body = {
+        ...values,
+      };
+
+      if (id) {
+        body.id = id
+      }
+
+      const res = await HttpService.post(`${endpoints.estado}/guardar`, body);
+      respuestas(res);
+      if (res?.status === 200) {
+        navigate('/administracion/catalogos/estados')
+      }
+    } catch (error) {
+      console.log(error);
+      setLoading(false);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  if (modelLoading) {
+    return <Spin
+      size="large"
+      style={{ display: "block", margin: "auto", marginTop: "50px" }}
+    />
+  }
+
+
+  return (
+    <DefaultLayout>
+      <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={24}>
+          <h2>Información del Estado</h2>
+        </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="Nombre"
+            name="nombre"
+            rules={[
+              commonRules.requerido,
+            ]}
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="Abreviatura"
+            name="abreviacion"
+            rules={[
+              commonRules.requerido,
+            ]}
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col span={6}>
+            <Form.Item label="ID Sagarhpa" name="idSagarhpa">
+              <Input type="number"/>
+            </Form.Item>
+          </Col>
+        <Col span={24}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+              loading={loading}
+            >
+              Guardar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+    </DefaultLayout>
+  )
+}
+
+export default EstadoDetalle

+ 118 - 0
src/views/catalogos/estados/Estados.jsx

@@ -0,0 +1,118 @@
+import { useRef, useState } from "react";
+import { Form, Tooltip,  } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
+import { Tabla } from "../../../components";
+import { SimpleTableLayout } from "../../../components/layouts";
+import { ActionsButton } from "../../../components";
+import { isEllipsis, eliminarRegistro } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+
+const endPoint = "estado";
+const endPointEliminar = "estado/eliminar";
+
+const Estados = () => {
+  let tablaRef = useRef(null);
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const [buscarParams, setBuscarParams] = useState({
+    padre: true,
+  });
+
+  const onFinish = (values) => {
+    const { q } = values;
+    const params = {
+      q: q ?? "",
+      padre: true,
+      ordenar: "nombre-asc",
+    };
+    setBuscarParams(params);    
+  };
+
+  const botones = [
+    {
+      onClick: () => navigate(`/administracion/catalogos/estados/agregar`),
+      props: { disabled: false, type: "primary", block: false },
+      text: "Nuevo",
+      icon: <PlusOutlined />,
+    },
+  ];
+
+  const linkText = (value, row, key) => (
+    <Link
+      to={`/administracion/catalogos/estados/editar?id=${row.id}`}
+      style={{ color: "black" }}
+    >
+      {isEllipsis(columns, key) ? (
+        <Tooltip title={value}>{value}</Tooltip>
+      ) : (
+        value
+      )}
+    </Link>
+  );
+
+  const columns = [
+    {
+      title: "Acciones",
+      key: "correo",
+      dataIndex: "correo",
+      width: 100,
+      align: "center",
+      render: (_, item) => (
+        <ActionsButton
+          data={[
+            {
+              label: "Editar",
+              onClick: () =>
+                navigate(`/administracion/catalogos/estados/editar?id=${item?.id}`),
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(item?.nombre, item?.id, endPointEliminar, () =>
+                  tablaRef?.current?.refresh()
+                );
+              },
+              danger: true,
+            },
+          ]}
+        />
+      ),
+    },
+    {
+      title: "Nombre",
+      key: "nombre",
+      dataIndex: "nombre",
+      render: linkText,
+    },
+    {
+      title: "Abreviatura",
+      key: "abreviacion",
+      dataIndex: "abreviacion",
+      render: linkText,
+    },
+  ];
+
+  return (
+    <SimpleTableLayout
+      btnGroup={{
+        btnGroup: botones,
+      }}
+    >
+      <Formulario
+        form={form}
+        onFinish={onFinish} 
+      />
+      <Tabla
+        columns={columns}
+        nameURL={endPoint}
+        extraParams={buscarParams}
+        scroll={{ x: "30vw" }}
+        order={'nombre-asc'}
+        ref={tablaRef}
+      />
+    </SimpleTableLayout>
+  );
+};
+
+export default Estados;

+ 55 - 0
src/views/catalogos/estados/Formulario.jsx

@@ -0,0 +1,55 @@
+import { Form, Input, Button, Row, Col } from 'antd'
+import PropTypes from 'prop-types'
+
+// const selectores = {
+//   consejoElectoral: {
+//     name: "v1/consejo-electoral",
+//   },
+// }
+
+const Formulario = ({
+  form,
+  onFinish,
+}) => {
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      initialValues={{ remember: true }}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={6}>
+
+
+          <Form.Item
+            label="Búsqueda"
+            name="q"
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col span={6}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+            >
+              Buscar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>  
+    </Form>
+  )
+}
+
+export default Formulario
+
+Formulario.propTypes = {
+  form: PropTypes.object.isRequired,
+  onFinish: PropTypes.func.isRequired,
+}

+ 7 - 0
src/views/catalogos/estados/index.js

@@ -0,0 +1,7 @@
+import Estados from './Estados'
+import EstadoDetalle from './EstadoDetalle'
+
+export {
+  Estados,
+  EstadoDetalle
+}

+ 144 - 0
src/views/catalogos/finMovilizaciones/FinMovilizacionDetalle.jsx

@@ -0,0 +1,144 @@
+import { Form, Input, Button, Spin, Row, Col } from 'antd'
+import { useEffect, useMemo, useState } from 'react'
+import HttpService from '../../../services/httpService'
+import { respuestas } from '../../../utilities'
+import { useNavigate } from 'react-router-dom'
+import { useQuery, useModel } from '../../../hooks'
+import { commonRules } from '../../../constants/rules'
+
+
+const endpoints = {
+  finMovilizacion: "fin-movilizacion",
+};
+
+const FinMovilizacionDetalleDetalle = () => {
+  const [form] = Form.useForm()
+  const navigate = useNavigate()
+  const [loading, setLoading] = useState(false)
+  const query = useQuery()
+  const id = query.get("id")
+  const [request, setRequest] = useState({})
+
+  // const extraParams = useMemo(() => ({
+  //   idAgenda: id,
+  // }), [id])
+
+  const requestParams = useMemo(() => ({
+    name: endpoints.finMovilizacion,
+    id,
+    // extraParams
+  }), [id])
+
+
+  const { model, modelLoading } = useModel(request)
+
+  useEffect(() => {
+    if (id) {
+      setRequest(requestParams)
+    }
+    return () => {
+      setRequest({})
+    }
+  }, [id, requestParams])
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({ 
+        ...model,
+      })
+    }
+  }, [form, model])
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+
+      let body = {
+        ...values,
+      };
+
+      if (id) {
+        body.id = id
+      }
+
+      const res = await HttpService.post(`${endpoints.finMovilizacion}/guardar`, body);
+      respuestas(res);
+      if (res?.status === 200) {
+        navigate('/administracion/catalogos/finMovilizaciones')
+      }
+    } catch (error) {
+      console.log(error);
+      setLoading(false);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  if (modelLoading) {
+    return <Spin
+      size="large"
+      style={{ display: "block", margin: "auto", marginTop: "50px" }}
+    />
+  }
+
+  const handleKeyPress = (event) => {
+    const charCode = event.which ? event.which : event.keyCode;
+    if (charCode < 48 || charCode > 57) {
+      event.preventDefault();
+    }
+  };
+
+
+
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={24}>
+          <h2>Información del Fin de Movilizacion</h2>
+        </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="Nombre"
+            name="nombre"
+            rules={[
+              commonRules.requerido,
+            ]}
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="ID Sagarhpa"
+            name="idSagarhpa"
+            rules={[
+              commonRules.requerido,
+            ]}
+          >
+            <Input onKeyPress={handleKeyPress} maxLength={9}/>
+          </Form.Item>
+        </Col>
+        <Col span={24}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+              loading={loading}
+            >
+              Guardar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+  )
+}
+
+export default FinMovilizacionDetalleDetalle

+ 117 - 0
src/views/catalogos/finMovilizaciones/FinMovilizaciones.jsx

@@ -0,0 +1,117 @@
+import { useRef, useState } from "react";
+import { Form, Modal, Tooltip, notification } from "antd";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { Tabla } from "../../../components";
+import { SimpleTableLayout } from "../../../components/layouts";
+import { ActionsButton } from "../../../components";
+import { isEllipsis, eliminarRegistro } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+
+const endPoint = "fin-movilizacion";
+const endPointEliminar = "fin-movilizacion/eliminar";
+
+const FinMovilizaciones = () => {
+  const tablaRef = useRef(null);
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const [buscarParams, setBuscarParams] = useState({
+    padre: true,
+  });
+
+  const onFinish = (values) => {
+    const { q } = values;
+    const params = {
+      q: q ?? "",
+      padre: true,
+    };
+    setBuscarParams(params);    
+  };
+
+  const botones = [
+    {
+      onClick: () => navigate(`/administracion/catalogos/finMovilizaciones/agregar`),
+      props: { disabled: false, type: "primary", block: false },
+      text: "Nuevo",
+      icon: <PlusOutlined />,
+    },
+  ];
+
+  const linkText = (value, row, key) => (
+    <Link
+      to={`/administracion/catalogos/finMovilizaciones/editar?id=${row.id}`}
+      style={{ color: "black" }}
+    >
+      {isEllipsis(columns, key) ? (
+        <Tooltip title={value}>{value}</Tooltip>
+      ) : (
+        value
+      )}
+    </Link>
+  );
+
+
+  const columns = [
+    {
+      title: "Acciones",
+      key: "correo",
+      dataIndex: "correo",
+      width: 100,
+      align: "center",
+      render: (_, item) => (
+        <ActionsButton
+          data={[
+            {
+              label: "Editar",
+              onClick: () =>
+                navigate(`/administracion/catalogos/finMovilizaciones/editar?id=${item?.id}`),
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(`${item.nombre}`, item.id, endPointEliminar, () =>
+                  tablaRef.current.refresh()
+                );
+              },
+              danger: true,
+            },
+          ]}
+        />
+      ),
+    },
+    {
+      title: "Nombre",
+      key: "nombre",
+      dataIndex: "nombre",
+      render: linkText,
+    },
+    {
+      title: "ID Sagarhpa",
+      key: "idSagarhpa",
+      dataIndex: "idSagarhpa",
+      render: linkText,
+    },
+  ];
+
+  return (
+    <SimpleTableLayout
+      btnGroup={{
+        btnGroup: botones,
+      }}
+    >
+      <Formulario
+        form={form}
+        onFinish={onFinish} 
+      />
+      <Tabla
+        columns={columns}
+        nameURL={endPoint}
+        extraParams={buscarParams}
+        scroll={{ x: "30vw" }}
+        ref={tablaRef}
+      />
+    </SimpleTableLayout>
+  );
+};
+
+export default FinMovilizaciones;

+ 55 - 0
src/views/catalogos/finMovilizaciones/Formulario.jsx

@@ -0,0 +1,55 @@
+import { Form, Input, Button, Row, Col } from 'antd'
+import PropTypes from 'prop-types'
+
+// const selectores = {
+//   consejoElectoral: {
+//     name: "v1/consejo-electoral",
+//   },
+// }
+
+const Formulario = ({
+  form,
+  onFinish,
+}) => {
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      initialValues={{ remember: true }}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={6}>
+
+
+          <Form.Item
+            label="Búsqueda"
+            name="q"
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col span={6}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+            >
+              Buscar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>  
+    </Form>
+  )
+}
+
+export default Formulario
+
+Formulario.propTypes = {
+  form: PropTypes.object.isRequired,
+  onFinish: PropTypes.func.isRequired,
+}

+ 7 - 0
src/views/catalogos/finMovilizaciones/index.js

@@ -0,0 +1,7 @@
+import FinMovilizaciones from './FinMovilizaciones'
+import FinMovilizacionDetalle from './FinMovilizacionDetalle'
+
+export {
+  FinMovilizaciones,
+  FinMovilizacionDetalle
+}

+ 55 - 0
src/views/catalogos/municipios/Formulario.jsx

@@ -0,0 +1,55 @@
+import { Form, Input, Button, Row, Col } from 'antd'
+import PropTypes from 'prop-types'
+
+// const selectores = {
+//   consejoElectoral: {
+//     name: "v1/consejo-electoral",
+//   },
+// }
+
+const Formulario = ({
+  form,
+  onFinish,
+}) => {
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      initialValues={{ remember: true }}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={6}>
+
+
+          <Form.Item
+            label="Búsqueda"
+            name="q"
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col span={6}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+            >
+              Buscar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>  
+    </Form>
+  )
+}
+
+export default Formulario
+
+Formulario.propTypes = {
+  form: PropTypes.object.isRequired,
+  onFinish: PropTypes.func.isRequired,
+}

+ 193 - 0
src/views/catalogos/municipios/MunicipioDetalle.jsx

@@ -0,0 +1,193 @@
+import { Form, Input, Button, Row, Col, Select as AntSelect } from "antd";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { respuestas } from "../../../utilities";
+import HttpService from "../../../services/httpService";
+import { useQuery, useModel } from "../../../hooks";
+import { commonRules } from "../../../constants/rules";
+import { DefaultLayout, Select } from "../../../components";
+
+const endpoints = {
+  municipio: "municipio",
+};
+
+const MunicipioDetalle = () => {
+  const [form] = Form.useForm();
+  const navigate = useNavigate();
+  const [loading, setLoading] = useState(false);
+  const query = useQuery();
+  const id = query.get("id");
+  const [estado, setEstado] = useState("");
+  const [timer, setTimer] = useState(null);
+  const [request, setRequest] = useState({});
+  const [niveles, setNiveles] = useState([]);
+
+  const estadoExtraParams = useMemo(
+    () => ({
+      idEstado: id,
+    }),
+    [id]
+  );
+
+  const requestParams = useMemo(
+    () => ({
+      name: endpoints.municipio,
+      expand: "estado,niveles",
+      id,
+    }),
+    [id]
+  );
+
+  const { model, modelLoading } = useModel(request);
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+
+      let body = {
+        ...values,
+      };
+
+      if (id) {
+        body.id = id;
+      }
+
+      const res = await HttpService.post(
+        `${endpoints.municipio}/guardar`,
+        body
+      );
+      respuestas(res);
+      if (res?.status === 200) {
+        navigate("/administracion/catalogos/municipios");
+      }
+    } catch (error) {
+      console.log(error);
+      setLoading(false);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const onSearch = (value) => {
+    clearTimeout(timer);
+    const newTimer = setTimeout(() => {
+      setEstado(value);
+    }, 300);
+    setTimer(newTimer);
+  };
+
+  const getNiveles = useCallback(async () => {
+    try {
+      const res = await HttpService.get("nivel");
+      if (res.status === 200) {
+        setNiveles(
+          res?.resultado?.map((nivel) => ({
+            value: nivel?.id,
+            label: nivel?.nombre,
+          }))
+        );
+      }
+    } catch (e) {
+      console.log(e);
+    }
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      setRequest(requestParams);
+    }
+    return () => {
+      setRequest({});
+    };
+  }, [id, requestParams]);
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({
+        ...model,
+      });
+      let nivelesGuardados = [];
+      if (model?.niveles?.length > 0) {
+        nivelesGuardados = model?.niveles?.map((nivel) => nivel?.id);
+        form.setFieldValue("niveles", nivelesGuardados);
+      }
+    }
+  }, [form, model]);
+
+  useEffect(() => {
+    getNiveles();
+  }, [getNiveles]);
+
+  return (
+    <DefaultLayout>
+      <Form
+        layout="vertical"
+        name="basic"
+        form={form}
+        onFinish={onFinish}
+        onFinishFailed={() => {}}
+      >
+        <Row gutter={[10, 10]}>
+          <Col span={24}>
+            <h2>Información del Municipio</h2>
+          </Col>
+          <Col span={24} md={12}>
+            <Form.Item
+              label="Nombre"
+              name="nombre"
+              rules={[commonRules.requerido]}
+            >
+              <Input size="large" />
+            </Form.Item>
+          </Col>
+          <Col span={24} md={12}>
+            <Form.Item
+              label="Estado"
+              name="idEstado"
+              rules={[commonRules.requerido]}
+            >
+              <Select
+                size="large"
+                modelsParams={{ name: "estado", ordenar: "nombre" }}
+                labelProp="nombre"
+                valueProp="id"
+                append={[model?.estado]}
+                onSearch={onSearch}
+                extraParams={estadoExtraParams}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item label="Nivel" name="niveles">
+              <AntSelect
+                style={{ width: "100%" }}
+                options={niveles}
+                mode="multiple"
+              />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item label="ID Sagarhpa" name="idSagarhpa">
+              <Input type="number"/>
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item>
+              <Button
+                size="large"
+                type="primary"
+                htmlType="submit"
+                loading={loading}
+                style={{ marginTop: "30px" }}
+              >
+                Guardar
+              </Button>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </DefaultLayout>
+  );
+};
+
+export default MunicipioDetalle;

+ 117 - 0
src/views/catalogos/municipios/Municipios.jsx

@@ -0,0 +1,117 @@
+import { useRef, useState } from "react";
+import { Form, Modal, Tooltip, notification } from "antd";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { Tabla } from "../../../components";
+import { SimpleTableLayout } from "../../../components/layouts";
+import { ActionsButton } from "../../../components";
+import { isEllipsis, eliminarRegistro } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+
+const endPoint = "municipio";
+const endPointEliminar = "municipio/eliminar";
+
+const Municipios = () => {
+  let tablaRef = useRef(null);
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const [buscarParams, setBuscarParams] = useState({
+    padre: true,
+  });
+
+  const onFinish = (values) => {
+    const { q } = values;
+    const params = {
+      q: q ?? "",
+      padre: true,
+    };
+    setBuscarParams(params);    
+  };
+
+  const botones = [
+    {
+      onClick: () => navigate(`/administracion/catalogos/municipios/agregar`),
+      props: { disabled: false, type: "primary", block: false },
+      text: "Nuevo",
+      icon: <PlusOutlined />,
+    },
+  ];
+
+  const linkText = (value, row, key) => (
+    <Link
+      to={`/administracion/catalogos/municipios/editar?id=${row.id}`}
+      style={{ color: "black" }}
+    >
+      {isEllipsis(columns, key) ? (
+        <Tooltip title={value}>{value}</Tooltip>
+      ) : (
+        value
+      )}
+    </Link>
+  );
+
+  const columns = [
+    {
+      title: "Acciones",
+      key: "acciones",
+      dataIndex: "acciones",
+      width: 100,
+      align: "center",
+      render: (_, item) => (
+        <ActionsButton
+          data={[
+            {
+              label: "Editar",
+              onClick: () =>
+                navigate(`/administracion/catalogos/municipios/editar?id=${item?.id}`),
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(item?.nombre, item?.id, endPointEliminar, () =>
+                  tablaRef?.current?.refresh()
+                );
+              },
+              danger: true,
+            },
+          ]}
+        />
+      ),
+    },
+    {
+      title: "Nombre",
+      key: "nombre",
+      dataIndex: "nombre",
+      render: linkText,
+    },
+    {
+      title: "Estado",
+      key: "estado",
+      dataIndex: ["estado", "nombre"], 
+      render: (value) => value,
+    },
+  ];
+
+  return (
+    <SimpleTableLayout
+      btnGroup={{
+        btnGroup: botones,
+      }}
+    >
+      <Formulario
+        form={form}
+        onFinish={onFinish} 
+      />
+      <Tabla
+        columns={columns}
+        nameURL={endPoint}
+        expand="estado"
+        extraParams={buscarParams}
+        scroll={{ x: "30vw" }}
+        ref={tablaRef}
+      />
+    </SimpleTableLayout>
+  );
+};
+
+export default Municipios;

+ 7 - 0
src/views/catalogos/municipios/index.js

@@ -0,0 +1,7 @@
+import Municipios from './Municipios'
+import MunicipioDetalle from './MunicipioDetalle'
+
+export {
+  Municipios,
+  MunicipioDetalle
+}

+ 53 - 0
src/views/catalogos/nivel/Detalle.jsx

@@ -0,0 +1,53 @@
+import { useState, useMemo, useEffect } from 'react'
+import { DefaultLayout } from '../../../components/layouts'
+import { useQuery, useModel } from '../../../hooks'
+import { Formulario } from './Formulario'
+
+const Detalle = ({ endPoint, expand, url, orden, idModelo, media }) => {
+  const q = useQuery()
+  const id = q.get('id')
+  const editando = Boolean(id)
+
+  const [request, setRequest] = useState({})
+
+  const requestParams = useMemo(() => ({
+    name: endPoint,
+    id: id,
+    ordenar: orden,
+    expand: expand,
+  }), [id])
+
+  const {
+    model,
+    modelLoading
+  } = useModel(request)
+
+  useEffect(() => {
+    setRequest(requestParams)
+    return () => {
+      setRequest({})
+    }
+  }, [requestParams])
+
+  return (
+    <DefaultLayout
+      viewLoading={{
+        text: 'Cargando ...',
+        spinning: modelLoading
+      }}
+    >
+      <Formulario
+        id={id}
+        url={url}
+        model={model}
+        idModelo={idModelo}
+        expand={expand}
+        endPoint={endPoint}
+        media={media}
+        editando={editando}
+      />
+    </DefaultLayout>
+  )
+}
+
+export default Detalle

+ 97 - 0
src/views/catalogos/nivel/Formulario.jsx

@@ -0,0 +1,97 @@
+import React, { useState } from 'react'
+import { Input, Form, Row, Col, Button } from 'antd'
+import HttpService from '../../../services/httpService'
+import { respuestas } from '../../../utilities'
+import { SaveOutlined } from '@ant-design/icons'
+import { useNavigate } from 'react-router-dom'
+import DefaultLayout from '../../../components/layouts/DefaultLayout'
+import { commonRules } from '../../../constants/rules'
+
+
+export const Formulario = ({ model = null, id, alTerminar, endPoint, url, idModelo, media }) => {
+  const [form] = Form.useForm()
+  const navigate = useNavigate()
+
+  const [guardando, setGuardando] = useState(false)
+
+  const handleFinish = async (values) => {
+
+    try {
+      setGuardando(true)
+
+      let body = {
+        ...values,
+      }
+
+      if (id) {
+        body[idModelo] = id
+      }
+
+      const res = await HttpService.post(`${endPoint}/guardar`, body)
+      if (respuestas(res)) {
+        navigate(url)
+      }
+    } catch (e) {
+      console.log(e)
+    } finally {
+      setGuardando(false)
+    }
+
+  }
+
+  React.useEffect(() => {
+    if (model || model !== null) {
+      form.setFieldsValue({
+        ...model,
+      })
+    } else {
+      form.resetFields()
+    }
+  }, [form, model])
+
+  return (
+    <DefaultLayout>
+      <Form
+        layout="vertical"
+        name="basic"
+        form={form}
+        onFinish={handleFinish}
+        onFinishFailed={() => {}}
+      >
+        <Row gutter={[10, 10]}>
+          <Col span={24}>
+            <h2>Información de Nivel</h2>
+          </Col>
+          <Col span={24} md={12}>
+            <Form.Item
+              label="Clave"
+              name="clave"
+              rules={[commonRules.requerido]}
+            >
+              <Input size="large" />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item label="Nombre" name="nombre">
+              <Input/>
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item>
+              <Button
+                size="large"
+                type="primary"
+                htmlType="submit"
+                loading={guardando}
+                style={{ marginTop: "30px" }}
+              >
+                Guardar
+              </Button>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </DefaultLayout>
+  );
+
+}

+ 79 - 0
src/views/catalogos/nivel/Listado.jsx

@@ -0,0 +1,79 @@
+import React, { useState, useEffect, useMemo } from 'react'
+import { useNavigate } from 'react-router-dom'
+import { PlusOutlined } from '@ant-design/icons'
+import { SimpleTableLayout } from '../../../components/layouts'
+import { Tabla, ActionsButton } from '../../../components'
+import { eliminarRegistro, } from '../../../utilities'
+
+const Listado = ({ endPoint, columnas, idModelo, orden, expand }) => {
+
+  const tablaRef = React.useRef(null)
+
+  const navigate = useNavigate()
+
+  const [reqConvenio, setReqConvenio] = useState({})
+  const [buscar, setBuscar] = useState('')
+
+  const btnGroup = [
+    {
+      onClick: () => navigate('agregar'),
+      props: { disabled: false, type: 'primary', block: false },
+      text: 'Agregar',
+      icon: <PlusOutlined/>,
+    }
+  ]
+
+  const columns = [
+    {
+      width: '5%',
+      title: '',
+      render: (_, item) =>
+        <ActionsButton
+          data={[
+            {
+              label: 'Editar',
+              onClick: () => {
+                setReqConvenio(item)
+                navigate(`editar?id=${item[idModelo]}`)
+              }
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(item?.nombre, item?.id, `${endPoint}/eliminar`, () =>
+                  tablaRef?.current?.refresh()
+                );
+              },
+              danger: true,
+            },
+          ]}
+        />
+    },
+    ...columnas
+  ]
+
+  const handleSearch = (buscar) => {
+    setBuscar(buscar)
+  }
+
+  return (
+    <SimpleTableLayout
+      onSearch={handleSearch}
+      btnGroup={
+        { btnGroup }
+      }
+    >
+      <Tabla
+        nameURL={endPoint}
+        order={orden}
+        extraParams={{buscar: buscar}}
+        columns={columns}
+        ref={tablaRef}
+        idModelo={idModelo}
+        expand={expand}
+      />
+    </SimpleTableLayout>
+  )
+}
+
+export default Listado

+ 49 - 0
src/views/catalogos/nivel/index.jsx

@@ -0,0 +1,49 @@
+import Detalle from './Detalle'
+import Listado from './Listado'
+import { Link } from 'react-router-dom'
+import React from 'react'
+
+const endPoint = 'nivel'
+const url = '/administracion/catalogos/nivel'
+const orden = 'nombre-desc'
+const idModelo = 'id'
+
+const columnas = [
+  {
+    title: 'Clave',
+    index: 'clave',
+    key: 'clave',
+    dataIndex: 'clave',
+    render: (_, item) =>
+      <Link to={`${url}/editar?id=${item[idModelo]}`} style={{ color: 'black' }}>
+        {item?.clave}
+      </Link>
+  },
+  {
+    title: 'Nombre',
+    index: 'nombre',
+    key: 'nombre',
+    dataIndex: 'nombre',
+    render: (_, item) =>
+      <Link to={`${url}/editar?id=${item[idModelo]}`} style={{ color: 'black' }}>
+        {item?.nombre}
+      </Link>
+  }, 
+]
+
+const Nivel = () => (<Listado
+  endPoint={endPoint}
+  url={url}
+  orden={orden}
+  columnas={columnas}
+  idModelo={idModelo}
+/>)
+
+const NivelDetalle = () => (<Detalle
+  endPoint={endPoint}
+  url={url}
+  orden={orden}
+  idModelo={idModelo}
+/>)
+
+export { Nivel, NivelDetalle }

+ 95 - 85
src/views/catalogos/productos/ProductoDetalle.jsx

@@ -1,85 +1,57 @@
-import { Form, Input, Button, Spin, Space, Row, Col } from 'antd'
-import { useEffect, useMemo, useState } from 'react'
-import HttpService from '../../../services/httpService'
-import { respuestas } from '../../../utilities'
-import { useNavigate } from 'react-router-dom'
-import { useQuery, useModel } from '../../../hooks'
-import { commonRules } from '../../../constants/rules'
-import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
-
-// const selectores = {
-//   consejoElectoral: {
-//     name: "v1/consejo-electoral",
-//   },
-//   distrito: {
-//     name: "v1/distrito",
-//   },
-//   estado: {
-//     name: "v1/estado",
-//   },
-//   municipio: {
-//     name: "v1/municipio",
-//   },
-//   participantePolitico: {
-//     name: "v1/participante-politico",
-//   },
-//   seccion: {
-//     name: "v1/seccion",
-//   },
-//   tipoAgenda: {
-//     name: "v1/agenda/tipo-agenda",
-//   },
-//   usuario: {
-//     name: "v1/usuario",
-//   }
-// }
+import { Form, Input, Button, Spin, Space, Row, Col } from "antd";
+import { useEffect, useMemo, useState } from "react";
+import HttpService from "../../../services/httpService";
+import { respuestas } from "../../../utilities";
+import { useNavigate } from "react-router-dom";
+import { useQuery, useModel } from "../../../hooks";
+import { commonRules } from "../../../constants/rules";
+import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
+import DefaultLayout from "../../../components/layouts/DefaultLayout";
 
 const endpoints = {
   producto: "producto",
 };
 
 const ProductoDetalle = () => {
-  const [form] = Form.useForm()
-  const navigate = useNavigate()
-  const [loading, setLoading] = useState(false)
-  const query = useQuery()
-  const id = query.get("id")
-  const [request, setRequest] = useState({})
-
-  // const extraParams = useMemo(() => ({
-  //   idAgenda: id,
-  // }), [id])
-
-  const requestParams = useMemo(() => ({
-    name: endpoints.producto,
-    expand: 'subproductos',
-    id,
-    // extraParams
-  }), [id])
-
-
-  const { model, modelLoading } = useModel(request)
+  const [form] = Form.useForm();
+  const navigate = useNavigate();
+  const [loading, setLoading] = useState(false);
+  const query = useQuery();
+  const id = query.get("id");
+  const [request, setRequest] = useState({});
+
+  const requestParams = useMemo(
+    () => ({
+      name: endpoints.producto,
+      expand: "subproductos",
+      id,
+    }),
+    [id]
+  );
+
+  const { model, modelLoading } = useModel(request);
 
   useEffect(() => {
     if (id) {
-      setRequest(requestParams)
+      setRequest(requestParams);
     }
     return () => {
-      setRequest({})
-    }
-  }, [id, requestParams])
+      setRequest({});
+    };
+  }, [id, requestParams]);
 
   useEffect(() => {
     if (model) {
-      form.setFieldsValue({ //seteo cuando son varios
+      form.setFieldsValue({
+        //seteo cuando son varios
         ...model,
         subproductos: model.subproductos.map((subproducto, index) => ({
           ...subproducto,
-          key: index
-        }))
-      })
+          key: index,
+        })),
+      });
     }
-  }, [form, model])
+  }, [form, model]);
 
   const onFinish = async (values) => {
     try {
@@ -90,13 +62,13 @@ const ProductoDetalle = () => {
       };
 
       if (id) {
-        body.id = id
+        body.id = id;
       }
 
       const res = await HttpService.post(`${endpoints.producto}/guardar`, body);
       respuestas(res);
       if (res?.status === 200) {
-        navigate('/administracion/catalogos/productos')
+        navigate("/administracion/catalogos/productos");
       }
     } catch (error) {
       console.log(error);
@@ -104,38 +76,55 @@ const ProductoDetalle = () => {
     } finally {
       setLoading(false);
     }
-  }
+  };
 
   if (modelLoading) {
-    return <Spin
-      size="large"
-      style={{ display: "block", margin: "auto", marginTop: "50px" }}
-    />
+    return (
+      <Spin
+        size="large"
+        style={{ display: "block", margin: "auto", marginTop: "50px" }}
+      />
+    );
   }
 
+  const handleKeyPress = (event) => {
+    const charCode = event.which ? event.which : event.keyCode;
+    if (charCode < 48 || charCode > 57) {
+      event.preventDefault();
+    }
+  };
+
   return (
-    <Form
+    <DefaultLayout>
+      <Form
       layout="vertical"
       name="basic"
       form={form}
       onFinish={onFinish}
-      onFinishFailed={() => { }}
+      onFinishFailed={() => {}}
     >
       <Row gutter={16}>
         <Col span={24}>
           <h2>Información del Producto</h2>
         </Col>
-        <Col md={8} xs={24}>
+        <Col md={8} xs={12}>
           <Form.Item
             label="Nombre"
             name="nombre"
-            rules={[
-              commonRules.requerido,
-            ]}
+            rules={[commonRules.requerido]}
           >
             <Input />
           </Form.Item>
         </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="ID Sagarhpa"
+            name="idSagarhpa"
+            rules={[commonRules.requerido]}
+          >
+            <Input onKeyPress={handleKeyPress} maxLength={9} />
+          </Form.Item>
+        </Col>
         <Col span={24}>
           <h2>Subproductos:</h2>
         </Col>
@@ -144,17 +133,36 @@ const ProductoDetalle = () => {
             {(fields, { add, remove }) => (
               <>
                 {fields.map(({ key, name, ...restField }) => (
-                  <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
+                  <Row
+                    key={key}
+                    style={{ display: "flex", marginBottom: 12 }}
+                    align="baseline"
+                    gutter={[10, 10]}
+                  >
+                    <Col md={8} xs={24} lg={6}>
+                      <Form.Item
+                        {...restField}
+                        name={[name, "nombre"]}
+                        label="Nombre del Subproducto"
+                        rules={[commonRules.requerido]}
+                      >
+                        <Input placeholder="Nombre del Subproducto" />
+                      </Form.Item>
+                    </Col>
+                    <Col md={8} xs={24} lg={6}>
                       <Form.Item
                         {...restField}
-                        name={[name, 'nombre']}
+                        name={[name, "idSagarhpa"]}
+                        label="ID Subproducto"
                         rules={[commonRules.requerido]}
-                        style={{ width: '350px' }}
                       >
-                        <Input placeholder="Nombre del Subproducto"/>
+                        <Input onKeyPress={handleKeyPress} maxLength={9} />
                       </Form.Item>
+                    </Col>
+                    <Col md={8} xs={24} lg={6} style={{ marginTop: 35 }}>
                       <MinusCircleOutlined onClick={() => remove(name)} />
-                  </Space>
+                    </Col>
+                  </Row>
                 ))}
                 <Form.Item>
                   <Button
@@ -169,6 +177,7 @@ const ProductoDetalle = () => {
             )}
           </Form.List>
         </Col>
+
         <Col span={24}>
           <Form.Item>
             <Button
@@ -183,7 +192,8 @@ const ProductoDetalle = () => {
         </Col>
       </Row>
     </Form>
-  )
-}
+    </DefaultLayout>  
+  );
+};
 
-export default ProductoDetalle
+export default ProductoDetalle;

+ 10 - 46
src/views/catalogos/productos/Productos.jsx

@@ -4,12 +4,12 @@ import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
 import { Tabla } from "../../../components";
 import { SimpleTableLayout } from "../../../components/layouts";
 import { ActionsButton } from "../../../components";
-import { isEllipsis } from "../../../utilities";
+import { isEllipsis, eliminarRegistro } from "../../../utilities";
 import { Link, useNavigate } from "react-router-dom";
 import Formulario from "./Formulario";
-import HttpService from "../../../services/httpService";
 
 const endPoint = "producto";
+const endPointEliminar = "producto/eliminar";
 
 const Productos = () => {
   let tablaRef = useRef(null);
@@ -50,49 +50,6 @@ const Productos = () => {
     </Link>
   );
 
-  const eliminarRegistro = (nombre, id, url, alTerminar) => {
-    if (!id) return;
-    Modal.confirm({
-      title: "Eliminar",
-      content: `¿Está seguro de eliminar "${nombre}"?`,
-      icon: <DeleteOutlined style={{ color: "#ff0000" }} />,
-      okText: "Eliminar",
-      okButtonProps: {
-        type: "primary",
-        danger: true,
-      },
-      cancelText: "Cancelar",
-      onOk: async () => {
-        try {
-          let body = { id: id };
-          if (typeof id === "object") {
-            body = id;
-          }
-          const res = await HttpService.delete(url, body);
-          if (res && res.status === 200) {
-            notification.success({
-              message: "Éxito",
-              description: res?.mensaje,
-            });
-            alTerminar && alTerminar();
-          } else if (res?.status === 400) {
-            notification.error({
-              message: "Atención",
-              description: res?.mensaje,
-            });
-          }
-        } catch (error) {
-          console.log(error);
-          notification.error({
-            message: "Error",
-            description: error,
-          });
-          return "error";
-        }
-      },
-    });
-  };
-
   const columns = [
     {
       title: "Acciones",
@@ -111,7 +68,7 @@ const Productos = () => {
             {
               label: "Eliminar",
               onClick: () => {
-                eliminarRegistro(item?.nombre, item?.id, endPoint+'/eliminar', () =>
+                eliminarRegistro(item?.nombre, item?.id, endPointEliminar, () =>
                   tablaRef?.current?.refresh()
                 );
               },
@@ -127,6 +84,12 @@ const Productos = () => {
       dataIndex: "nombre",
       render: linkText,
     },
+    {
+      title: "ID Sagarhpa",
+      key: "idSagarhpa",
+      dataIndex: "idSagarhpa",
+      render: linkText,
+    },
   ];
 
   return (
@@ -144,6 +107,7 @@ const Productos = () => {
         nameURL={endPoint}
         extraParams={buscarParams}
         scroll={{ x: "30vw" }}
+        ref={tablaRef}
       />
     </SimpleTableLayout>
   );

+ 55 - 0
src/views/catalogos/tipoMovilizaciones/Formulario.jsx

@@ -0,0 +1,55 @@
+import { Form, Input, Button, Row, Col } from 'antd'
+import PropTypes from 'prop-types'
+
+// const selectores = {
+//   consejoElectoral: {
+//     name: "v1/consejo-electoral",
+//   },
+// }
+
+const Formulario = ({
+  form,
+  onFinish,
+}) => {
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      initialValues={{ remember: true }}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={6}>
+
+
+          <Form.Item
+            label="Búsqueda"
+            name="q"
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col span={6}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+            >
+              Buscar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>  
+    </Form>
+  )
+}
+
+export default Formulario
+
+Formulario.propTypes = {
+  form: PropTypes.object.isRequired,
+  onFinish: PropTypes.func.isRequired,
+}

+ 144 - 0
src/views/catalogos/tipoMovilizaciones/TipoMovilizacionDetalle.jsx

@@ -0,0 +1,144 @@
+import { Form, Input, Button, Spin, Row, Col } from 'antd'
+import { useEffect, useMemo, useState } from 'react'
+import HttpService from '../../../services/httpService'
+import { respuestas } from '../../../utilities'
+import { useNavigate } from 'react-router-dom'
+import { useQuery, useModel } from '../../../hooks'
+import { commonRules } from '../../../constants/rules'
+
+
+const endpoints = {
+  tipoMovilizacion: "tipo-movilizacion",
+};
+
+const TipoMovilizacionDetalleDetalle = () => {
+  const [form] = Form.useForm()
+  const navigate = useNavigate()
+  const [loading, setLoading] = useState(false)
+  const query = useQuery()
+  const id = query.get("id")
+  const [request, setRequest] = useState({})
+
+  // const extraParams = useMemo(() => ({
+  //   idAgenda: id,
+  // }), [id])
+
+  const requestParams = useMemo(() => ({
+    name: endpoints.tipoMovilizacion,
+    id,
+    // extraParams
+  }), [id])
+
+
+  const { model, modelLoading } = useModel(request)
+
+  useEffect(() => {
+    if (id) {
+      setRequest(requestParams)
+    }
+    return () => {
+      setRequest({})
+    }
+  }, [id, requestParams])
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({ 
+        ...model,
+      })
+    }
+  }, [form, model])
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+
+      let body = {
+        ...values,
+      };
+
+      if (id) {
+        body.id = id
+      }
+
+      const res = await HttpService.post(`${endpoints.tipoMovilizacion}/guardar`, body);
+      respuestas(res);
+      if (res?.status === 200) {
+        navigate('/administracion/catalogos/tipoMovilizaciones')
+      }
+    } catch (error) {
+      console.log(error);
+      setLoading(false);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  if (modelLoading) {
+    return <Spin
+      size="large"
+      style={{ display: "block", margin: "auto", marginTop: "50px" }}
+    />
+  }
+
+  const handleKeyPress = (event) => {
+    const charCode = event.which ? event.which : event.keyCode;
+    if (charCode < 48 || charCode > 57) {
+      event.preventDefault();
+    }
+  };
+
+
+
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      onFinish={onFinish}
+      onFinishFailed={() => { }}
+    >
+      <Row gutter={16}>
+        <Col span={24}>
+          <h2>Información del Tipo de Movilizacion</h2>
+        </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="Nombre"
+            name="nombre"
+            rules={[
+              commonRules.requerido,
+            ]}
+          >
+            <Input />
+          </Form.Item>
+        </Col>
+        <Col md={8} xs={12}>
+          <Form.Item
+            label="ID Sagarhpa"
+            name="idSagarhpa"
+            rules={[
+              commonRules.requerido,
+            ]}
+          >
+            <Input onKeyPress={handleKeyPress} maxLength={9}/>
+          </Form.Item>
+        </Col>
+        <Col span={24}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+              loading={loading}
+            >
+              Guardar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+  )
+}
+
+export default TipoMovilizacionDetalleDetalle

+ 116 - 0
src/views/catalogos/tipoMovilizaciones/TipoMovilizaciones.jsx

@@ -0,0 +1,116 @@
+import { useRef, useState } from "react";
+import { Form, Modal, Tooltip, notification } from "antd";
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { Tabla } from "../../../components";
+import { SimpleTableLayout } from "../../../components/layouts";
+import { ActionsButton } from "../../../components";
+import { isEllipsis, eliminarRegistro } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+
+const endPoint = "tipo-movilizacion";
+const endPointEliminar = "tipo-movilizacion/eliminar";
+
+const TipoMovilizaciones = () => {
+  let tablaRef = useRef(null);
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const [buscarParams, setBuscarParams] = useState({
+    padre: true,
+  });
+
+  const onFinish = (values) => {
+    const { q } = values;
+    const params = {
+      q: q ?? "",
+      padre: true,
+    };
+    setBuscarParams(params);    
+  };
+
+  const botones = [
+    {
+      onClick: () => navigate(`/administracion/catalogos/tipoMovilizaciones/agregar`),
+      props: { disabled: false, type: "primary", block: false },
+      text: "Nuevo",
+      icon: <PlusOutlined />,
+    },
+  ];
+
+  const linkText = (value, row, key) => (
+    <Link
+      to={`/administracion/catalogos/tipoMovilizaciones/editar?id=${row.id}`}
+      style={{ color: "black" }}
+    >
+      {isEllipsis(columns, key) ? (
+        <Tooltip title={value}>{value}</Tooltip>
+      ) : (
+        value
+      )}
+    </Link>
+  );
+
+  const columns = [
+    {
+      title: "Acciones",
+      key: "correo",
+      dataIndex: "correo",
+      width: 100,
+      align: "center",
+      render: (_, item) => (
+        <ActionsButton
+          data={[
+            {
+              label: "Editar",
+              onClick: () =>
+                navigate(`/administracion/catalogos/tipoMovilizaciones/editar?id=${item?.id}`),
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(item?.nombre, item?.id, endPointEliminar, () =>
+                  tablaRef?.current?.refresh()
+                );
+              },
+              danger: true,
+            },
+          ]}
+        />
+      ),
+    },
+    {
+      title: "Nombre",
+      key: "nombre",
+      dataIndex: "nombre",
+      render: linkText,
+    },
+    {
+      title: "ID Sagarhpa",
+      key: "idSagarhpa",
+      dataIndex: "idSagarhpa",
+      render: linkText,
+    },
+  ];
+
+  return (
+    <SimpleTableLayout
+      btnGroup={{
+        btnGroup: botones,
+      }}
+    >
+      <Formulario
+        form={form}
+        onFinish={onFinish} 
+      />
+      <Tabla
+        columns={columns}
+        nameURL={endPoint}
+        extraParams={buscarParams}
+        scroll={{ x: "30vw" }}
+        ref={tablaRef}
+      />
+    </SimpleTableLayout>
+  );
+};
+
+export default TipoMovilizaciones;

+ 7 - 0
src/views/catalogos/tipoMovilizaciones/index.js

@@ -0,0 +1,7 @@
+import TipoMovilizaciones from './TipoMovilizaciones'
+import TipoMovilizacionDetalle from './TipoMovilizacionDetalle'
+
+export {
+  TipoMovilizaciones,
+  TipoMovilizacionDetalle
+}

+ 323 - 236
src/views/condicionantes/CondicionanteDetalle.jsx

@@ -1,21 +1,28 @@
-import { Form, Input, Button, Spin, Row, Col, Switch, Select as AntdSelect, Checkbox } from 'antd'
-import { useEffect, useMemo, useState } from 'react'
-import HttpService from '../../services/httpService'
-import { respuestas } from '../../utilities'
-import { useNavigate } from 'react-router-dom'
-import { useQuery, useModel } from '../../hooks'
-import { commonRules } from '../../constants/rules'
-import { Select } from '../../components'
+import { Form, Input, Button, Spin, Row, Col, Switch, Select as AntdSelect, Checkbox, notification, Typography, Modal, Divider } from 'antd';
+import { useEffect, useMemo, useState, useCallback } from 'react';
+import HttpService from '../../services/httpService';
+import { respuestas } from '../../utilities';
+import { useNavigate } from 'react-router-dom';
+import { useQuery, useModel } from '../../hooks';
+import { Select } from '../../components';
+import EditorTexto from "../../components/EditorTexto";
+import { DefaultLayout } from '../../components/layouts';
+
+const { Text } = Typography;
 
 const selectores = {
   productos: {
     name: "producto",
     expand: "subproductos",
+    ordenar: "nombre-asc",
   },
-}
+};
 
 const endpoints = {
   condicionante: "condicionante",
+  fines: "fin-movilizacion",
+  tipos: "tipo-movilizacion",
+  estados: "estado?ordenar=nombre-asc&limite=40",
 };
 
 const amplitudes = [
@@ -27,89 +34,116 @@ const amplitudes = [
     value: "Familia (Varios)",
     label: "Familia (Varios)",
   },
-]
+];
 
-const fines = [
-  {
-    value: "Consumo Humano",
-    label: "Consumo Humano",
-  },
-  {
-    value: "Polinización",
-    label: "Polinización",
-  }
-]
-
-const tipos = [
-  {
-    value: "Introducción",
-    label: "Introducción",
-  },
-  {
-    value: "Expotación",
-    label: "Expotación",
-  },
-  {
-    value: "Tránsito",
-    label: "Tránsito",
-  },
-  {
-    value: "Movilización (Salida)",
-    label: "Movilización (Salida)",
-  },
-  {
-    value: "Importación",
-    label: "Importación",
-  }
-]
-
-const ProductoDetalle = () => {
-  const [form] = Form.useForm()
-  const navigate = useNavigate()
-  const [loading, setLoading] = useState(false)
-  const query = useQuery()
-  const id = query.get("id")
-  const [request, setRequest] = useState({})
-  const [activa, setActiva] = useState(false)
-  const [subproductos, setSubproductos] = useState([])
-
-  // const extraParams = useMemo(() => ({
-  //   idAgenda: id,
-  // }), [id])
+const CondicionanteDetalle = () => {
+  const [form] = Form.useForm();
+  const navigate = useNavigate();
+  const [loading, setLoading] = useState(false);
+  const query = useQuery();
+  const id = query.get("id");
+  const [request, setRequest] = useState({});
+  const [activa, setActiva] = useState(false);
+  const [subproductos, setSubproductos] = useState([]);
+  const [fines, setFines] = useState([]);
+  const [tipos, setTipos] = useState([]);
+  const [estados, setEstados] = useState([]);
+  const [selectedSubproductos, setSelectedSubproductos] = useState([]);
+  const [selectedFines, setSelectedFines] = useState([]);
+  const [selectedTipos, setSelectedTipos] = useState([]);
+  const [selectedOrigenes, setSelectedOrigenes] = useState([]);
+  const [selectedDestinos, setSelectedDestinos] = useState([]);
+  const [isModalVisible, setIsModalVisible] = useState(false);
+  const [catalogLoaded, setCatalogLoaded] = useState(false);
 
   const requestParams = useMemo(() => ({
     name: endpoints.condicionante,
-    // expand: 'subproductos',
     id,
-    // extraParams
-  }), [id])
-
+    expand: 'condicionanteFin,condicionanteTipo,condicionanteOrigen,condicionanteDestino'
+  }), [id]);
 
-  const { model, modelLoading } = useModel(request)
+  const { model, modelLoading } = useModel(request);
+  const fetchSubproductos = useCallback(async (productoId) => {
+    try {
+      const response = await HttpService.get(`producto?idPadre=${productoId}`);
+      if (response?.status === 200) {
+        setSubproductos(response.resultado || []);
+      }
+    } catch (error) {
+      console.error("Error fetching subproductos:", error);
+    }
+  }, []);
 
   useEffect(() => {
     if (id) {
-      setRequest(requestParams)
+      setRequest(requestParams);
     }
     return () => {
-      setRequest({})
-    }
-  }, [id, requestParams])
+      setRequest({});
+    };
+  }, [id, requestParams]);
 
   useEffect(() => {
     if (model) {
-      form.setFieldsValue({ //seteo cuando son varios
+      let parsedSubproductos = [];
+      try {
+        parsedSubproductos = JSON.parse(model.subproductos) || [];
+      } catch (e) {
+        console.error("Error parsing subproductos:", e);
+      }
+
+      form.setFieldsValue({
         ...model,
-        subproductos: model.subproductos.map((subproducto, index) => ({
-          ...subproducto,
-          key: index
-        }))
-      })
-      if (model?.activa) {
-        setActiva(true)
+        subproductos: parsedSubproductos,
+        fines: model.condicionanteFin.map(fin => fin.idFin),
+        tipos: model.condicionanteTipo.map(tipo => tipo.idTipo),
+        origenes: model.condicionanteOrigen.map(origen => origen.idOrigen),
+        destinos: model.condicionanteDestino.map(destino => destino.idDestino),
+      });
+      setActiva(model?.activa ?? false);
+      setSelectedSubproductos(parsedSubproductos);
+      setSelectedFines(model.condicionanteFin.map(fin => fin.idFin));
+      setSelectedTipos(model.condicionanteTipo.map(tipo => tipo.idTipo));
+      setSelectedOrigenes(model.condicionanteOrigen.map(origen => origen.idOrigen));
+      setSelectedDestinos(model.condicionanteDestino.map(destino => destino.idDestino));
+      if (model.idProducto) {
+        fetchSubproductos(model.idProducto);
       }
     }
-  }, [form, model])
+  }, [form, model, fetchSubproductos]);
+
+  useEffect(() => {
+    if (!catalogLoaded) {
+      const fetchCatalogData = async () => {
+        try {
+          const [finesResponse, tiposResponse, estadosResponse] = await Promise.all([
+            HttpService.get(`${endpoints.fines}`),
+            HttpService.get(`${endpoints.tipos}`),
+            HttpService.get(`${endpoints.estados}`)
+          ]);
+
+          if (finesResponse?.status === 200) {
+            setFines(finesResponse?.resultado || []);
+          }
+          if (tiposResponse?.status === 200) {
+            setTipos(tiposResponse?.resultado || []);
+          }
+          if (estadosResponse?.status === 200) {
+            setEstados(estadosResponse?.resultado || []);
+          }
+          setCatalogLoaded(true);
+        } catch (error) {
+          console.error(error);
+          notification.error({
+            message: "Error",
+            description: "No se pudieron cargar los datos del catálogo.",
+          });
+        }
+      };
+
+      fetchCatalogData();
+    }
+  }, [catalogLoaded]);
 
   const onFinish = async (values) => {
     try {
@@ -117,186 +151,239 @@ const ProductoDetalle = () => {
 
       let body = {
         ...values,
+        origenes: selectedOrigenes,
+        destinos: selectedDestinos,
+        subproductos: JSON.stringify(values.subproductos),
       };
 
       if (id) {
-        body.id = id
+        body.id = id;
       }
 
-      const res = await HttpService.post(`${endpoints.producto}/guardar`, body);
+      const res = await HttpService.post(`${endpoints.condicionante}/guardar`, body);
       respuestas(res);
       if (res?.status === 200) {
-        navigate('/condicionantes')
+        navigate('/condicionantes');
       }
     } catch (error) {
-      console.log(error);
       setLoading(false);
     } finally {
       setLoading(false);
     }
-  }
+  };
+
+  const showModal = () => {
+    setIsModalVisible(true);
+  };
+
+  const handleOk = () => {
+    setIsModalVisible(false);
+  };
+
+  const handleCancel = () => {
+    setIsModalVisible(false);
+  };
 
   if (modelLoading) {
-    return <Spin
-      size="large"
-      style={{ display: "block", margin: "auto", marginTop: "50px" }}
-    />
+    return <Spin size="large" style={{ display: "block", margin: "auto", marginTop: "50px" }} />;
   }
 
   return (
-    <Form
-      layout="vertical"
-      name="basic"
-      form={form}
-      onFinish={onFinish}
-      onFinishFailed={() => { }}
-    >
-      <Row gutter={16}>
-        <Col span={24}>
-          <h2>Información del Condicionante</h2>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Título"
-            name="titulo"
-            rules={[
-              commonRules.requerido,
-            ]}
-          >
-            <Input />
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Descripción"
-            name="descripcion"
-            rules={[
-              commonRules.requerido,
-            ]}
-          >
-            <Input.TextArea rows={3} />
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Activa"
-            name="activa"
-          >
-            <Switch
-              checkedChildren="Si"
-              unCheckedChildren="No"
-              style={{ backgroundColor: activa ? "#52c41a" : "#f5222d" }}
-              checked={activa}
-              onChange={(checked) => setActiva(checked)}
-            />
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Amplitud"
-            name="amplitud"
-            rules={[
-              commonRules.requerido,
-            ]}
-          >
-            <AntdSelect
-              showSearch
-              placeholder="Seleccione una amplitud"
-              optionFilterProp="children"
-              filterOption={(input, option) =>
-                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-              }
+    <DefaultLayout>
+      <Form layout="vertical" name="basic" form={form} onFinish={onFinish} onFinishFailed={() => { }}>
+        <Row gutter={16}>
+          <Col span={24}>
+            <h2>Información del Condicionante</h2>
+          </Col>
+          <Col md={8} xs={24}>
+            <Form.Item
+              label={<Text strong>Título</Text>}
+              name="titulo"
+              rules={[
+                { required: true, message: 'El título es obligatorio.' }
+              ]}
+            >
+              <Input />
+            </Form.Item>
+          </Col>
+          <Col md={8} xs={24}>
+            <Form.Item
+              label={<Text strong>Amplitud</Text>}
+              name="amplitud"
+              rules={[
+                { required: true, message: 'La amplitud es obligatoria.' }
+              ]}
+            >
+              <AntdSelect
+                showSearch
+                placeholder="Seleccione una amplitud"
+                optionFilterProp="children"
+                filterOption={(input, option) =>
+                  option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }
+              >
+                {amplitudes.map((amplitud) => (
+                  <AntdSelect.Option key={amplitud.value} value={amplitud.value}>
+                    {amplitud.label}
+                  </AntdSelect.Option>
+                ))}
+              </AntdSelect>
+            </Form.Item>
+          </Col>
+          <Col md={8} xs={24}>
+            <Form.Item
+              label={<Text strong>Activa</Text>}
+              name="activa"
+            >
+              <Switch
+                checkedChildren="Si"
+                unCheckedChildren="No"
+                style={{ backgroundColor: activa ? "#52c41a" : "#f5222d" }}
+                checked={activa}
+                onChange={(checked) => setActiva(checked)}
+              />
+            </Form.Item>
+          </Col>
+          <Divider style={{ margin: '8px 0', borderColor: 'transparent' }} />
+          <Col md={12} xs={24}>
+            <Form.Item
+              label={<Text strong>Fines de Movilización</Text>}
+              name="fines"
+              // rules={[
+              //   { required: true, message: 'Seleccione al menos un fin de movilización.' }
+              // ]}
+            >
+              <Checkbox.Group value={selectedFines} onChange={setSelectedFines} style={{ width: '100%' }}>
+                <Row>
+                  {fines.map((fin) => (
+                    <Col span={24} key={fin.id}>
+                      <Checkbox value={fin.id}>{fin.nombre}</Checkbox>
+                    </Col>
+                  ))}
+                </Row>
+              </Checkbox.Group>
+            </Form.Item>
+          </Col>
+          <Col md={12} xs={24}>
+            <Form.Item
+              label={<Text strong>Tipo de Movilización</Text>}
+              name="tipos"
+              rules={[
+                { required: true, message: 'Seleccione al menos un tipo de movilización.' }
+              ]}
+            >
+              <Checkbox.Group value={selectedTipos} onChange={setSelectedTipos} style={{ width: '100%' }}>
+                <Row>
+                  {tipos.map((tipo) => (
+                    <Col span={24} key={tipo.id}>
+                      <Checkbox value={tipo.id}>{tipo.nombre}</Checkbox>
+                    </Col>
+                  ))}
+                </Row>
+              </Checkbox.Group>
+            </Form.Item>
+          </Col>
+          <Divider style={{ margin: '8px 0', borderColor: 'transparent' }} />
+          <Col md={12} xs={24}>
+            <Form.Item
+              label={<Text strong>Especies</Text>}
+              name="idProducto"
+              rules={[{ required: true, message: 'La especie es obligatoria.' }]}
+            >
+              <Select
+                modelsParams={selectores.productos}
+                labelProp="nombre"
+                valueProp="id"
+                append={[model?.producto]}
+                extraParams={{ padre: true }}
+                onChange={async (_, item) => {
+                  setSubproductos(item?.subproductos || []);
+                  setSelectedSubproductos(form.getFieldValue('subproductos'));
+                }}
+              />
+            </Form.Item>
+          </Col>
+          <Col md={12} xs={24}>
+            <Form.Item
+              label={<Text strong>Subproductos</Text>}
+              name="subproductos"
+            >
+              <Checkbox.Group value={selectedSubproductos} onChange={setSelectedSubproductos} style={{ width: '100%' }}>
+                <Row>
+                  {subproductos.map((subproducto) => (
+                    <Col span={24} key={subproducto.id}>
+                      <Checkbox value={subproducto.id}>{subproducto.nombre}</Checkbox>
+                    </Col>
+                  ))}
+                </Row>
+              </Checkbox.Group>
+            </Form.Item>
+          </Col>
+          <Divider style={{ margin: '8px 0', borderColor: 'transparent' }} />
+          <Col md={12} xs={24}>
+            <Form.Item
+              label={<Text strong>Descripción</Text>}
+              name="descripcion"
+              rules={[
+                { required: true, message: 'La descripción es obligatoria.' }
+              ]}
             >
-              {amplitudes.map((amplitud) => (
-                <AntdSelect.Option key={amplitud.value} value={amplitud.value}>
-                  {amplitud.label}
-                </AntdSelect.Option>
-              ))}
-            </AntdSelect>
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Fines de Movilización"
-            name="fines"
-            rules={[
-              commonRules.requerido,
-            ]}
-          >
-            <Checkbox.Group>
-              {fines.map((fin) => (
-                <Checkbox key={fin.value} value={fin.value}>
-                  {fin.label}
-                </Checkbox>
-              ))}
-            </Checkbox.Group>
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Tipo de Movilización"
-            name="tipos"
-            rules={[
-              commonRules.requerido,
-            ]}
-          >
-            <Checkbox.Group>
-              {tipos.map((tipo) => (
-                <Checkbox key={tipo.value} value={tipo.value}>
-                  {tipo.label}
-                </Checkbox>
-              ))}
-            </Checkbox.Group>
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Especies"
-            name="idProducto"
-            rules={[commonRules.requerido]}
-          >
-            <Select
-              modelsParams={selectores.productos}
-              labelProp="nombre"
-              valueProp="idProducto"
-              append={[model?.producto]}
-              extraParams={{ padre: true }}
-              onChange={(_, item) => {
-                setSubproductos(item?.subproductos)
-                console.log(item)
-              }}
-            />
-          </Form.Item>
-        </Col>
-        <Col md={8} xs={24}>
-          <Form.Item
-            label="Subproductos"
-            name="subproductos"
-          >
-            <Checkbox.Group>
-              {subproductos.map((subproducto) => (
-                <Checkbox key={subproducto?.id} value={subproducto?.id}>
-                  {subproducto?.nombre}
-                </Checkbox>
-              ))}
-            </Checkbox.Group>
-          </Form.Item>
-        </Col>
-        <Col span={24}>
-          <Form.Item>
-            <Button
-              type="primary"
-              htmlType="submit"
-              style={{ marginTop: "30px" }}
-              loading={loading}
+              <EditorTexto altura={200} />
+            </Form.Item>
+          </Col>
+          <Col md={12} xs={24}>
+            <Form.Item
+              label={<Text strong>Orígenes y Destinos</Text>}
             >
-              Guardar
-            </Button>
-          </Form.Item>
-        </Col>
-      </Row>
-    </Form>
-  )
+              <Button type="primary" onClick={showModal}>
+                Seleccionar Orígenes y Destinos
+              </Button>
+              <Modal title="Orígenes y Destinos" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
+                <Row>
+                  <Col span={12}>
+                    <h3>Origen</h3>
+                    <Checkbox.Group value={selectedOrigenes} onChange={setSelectedOrigenes}>
+                      <Row>
+                        {estados.map((estado) => (
+                          <Col span={24} key={estado.id}>
+                            <Checkbox value={estado.id}>{estado.nombre}</Checkbox>
+                          </Col>
+                        ))}
+                      </Row>
+                    </Checkbox.Group>
+                  </Col>
+                  <Col span={12}>
+                    <h3>Destino</h3>
+                    <Checkbox.Group value={selectedDestinos} onChange={setSelectedDestinos}>
+                      <Row>
+                        {estados.map((estado) => (
+                          <Col span={24} key={estado.id}>
+                            <Checkbox value={estado.id}>{estado.nombre}</Checkbox>
+                          </Col>
+                        ))}
+                      </Row>
+                    </Checkbox.Group>
+                  </Col>
+                </Row>
+              </Modal>
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item>
+              <Button
+                type="primary"
+                htmlType="submit"
+                style={{ marginTop: "30px" }}
+                loading={loading}
+              >
+                Guardar
+              </Button>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </DefaultLayout>
+  );
 }
 
-export default ProductoDetalle
+export default CondicionanteDetalle;

+ 13 - 78
src/views/condicionantes/Condicionantes.jsx

@@ -1,15 +1,15 @@
-import { useRef, useState } from "react";
-import { Form, Modal, Tooltip, notification } from "antd";
-import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
+import { useRef, useState, useCallback, useMemo } from "react";
+import { Form, Tooltip } from "antd";
+import { PlusOutlined } from "@ant-design/icons";
 import { Tabla } from "../../components";
 import { SimpleTableLayout } from "../../components/layouts";
 import { ActionsButton } from "../../components";
-import { isEllipsis } from "../../utilities";
+import { isEllipsis, eliminarRegistro } from "../../utilities";
 import { Link, useNavigate } from "react-router-dom";
 import Formulario from "./Formulario";
-import HttpService from "../../services/httpService";
 
 const endPoint = "condicionante";
+const endPointEliminar = "condicionante/eliminar";
 
 const Condicionantes = () => {
   let tablaRef = useRef(null);
@@ -17,23 +17,23 @@ const Condicionantes = () => {
   const [form] = Form.useForm();
   const [buscarParams, setBuscarParams] = useState({});
 
-  const onFinish = (values) => {
+  const onFinish = useCallback((values) => {
     const { q } = values;
     const params = {
       q: q ?? "",
       padre: true,
     };
     setBuscarParams(params);    
-  };
+  }, []);
 
-  const botones = [
+  const botones = useMemo(() => [
     {
       onClick: () => navigate(`/condicionantes/agregar`),
       props: { disabled: false, type: "primary", block: false },
       text: "Nuevo",
       icon: <PlusOutlined />,
     },
-  ];
+  ], [navigate]);
 
   const linkText = (value, row, key) => (
     <Link
@@ -48,49 +48,6 @@ const Condicionantes = () => {
     </Link>
   );
 
-  const eliminarRegistro = (nombre, id, url, alTerminar) => {
-    if (!id) return;
-    Modal.confirm({
-      title: "Eliminar",
-      content: `¿Está seguro de eliminar "${nombre}"?`,
-      icon: <DeleteOutlined style={{ color: "#ff0000" }} />,
-      okText: "Eliminar",
-      okButtonProps: {
-        type: "primary",
-        danger: true,
-      },
-      cancelText: "Cancelar",
-      onOk: async () => {
-        try {
-          let body = { id: id };
-          if (typeof id === "object") {
-            body = id;
-          }
-          const res = await HttpService.delete(url, body);
-          if (res && res.status === 200) {
-            notification.success({
-              message: "Éxito",
-              description: res?.mensaje,
-            });
-            alTerminar && alTerminar();
-          } else if (res?.status === 400) {
-            notification.error({
-              message: "Atención",
-              description: res?.mensaje,
-            });
-          }
-        } catch (error) {
-          console.log(error);
-          notification.error({
-            message: "Error",
-            description: error,
-          });
-          return "error";
-        }
-      },
-    });
-  };
-
   const columns = [
     {
       title: "Acciones",
@@ -109,7 +66,7 @@ const Condicionantes = () => {
             {
               label: "Eliminar",
               onClick: () => {
-                eliminarRegistro(item?.titulo, item?.id, endPoint+'/eliminar', () =>
+                eliminarRegistro(item?.titulo, item?.id, endPointEliminar, () =>
                   tablaRef?.current?.refresh()
                 );
               },
@@ -125,30 +82,6 @@ const Condicionantes = () => {
       dataIndex: "titulo",
       render: linkText,
     },
-    {
-      title: "Descripción",
-      key: "descripcion",
-      dataIndex: "descripcion",
-      render: linkText,
-    },
-    {
-      title: "Activa",
-      key: "activa",
-      dataIndex: "activa",
-      render: linkText,
-    },
-    {
-      title: "Editado por",
-      key: "editadoPor",
-      dataIndex: "editadoPor",
-      render: linkText,
-    },
-    {
-      title: "Fecha de Edición",
-      key: "fechaEdicion",
-      dataIndex: "fechaEdicion",
-      render: linkText,
-    }
   ];
 
   return (
@@ -159,13 +92,15 @@ const Condicionantes = () => {
     >
       <Formulario
         form={form}
-        onFinish={onFinish} 
+        onFinish={onFinish}
       />
       <Tabla
         columns={columns}
         nameURL={endPoint}
         extraParams={buscarParams}
         scroll={{ x: "30vw" }}
+        order={'creado-desc'}
+        ref={tablaRef}
       />
     </SimpleTableLayout>
   );