22 Commits 1f0ea49c91 ... 726623197a

Auteur SHA1 Message Date
  Jose Cienfuegos 726623197a Se agrego localhost en .env.development.local il y a 11 mois
  Jose Cienfuegos 744dccc989 Se quitaron columnas en el grid de condicionantes il y a 11 mois
  Jose Cienfuegos 700d697ea2 Se agrego imagen en login de inicio il y a 11 mois
  Jose Cienfuegos 02a0e9fc9b Se cambio los estilos del dashboard y de los CRUDS il y a 1 an
  Jose Cienfuegos 6c4163d463 Se agrego campo idSagarhpa en modulos de municipio y estado il y a 1 an
  Jose Cienfuegos c87b51023f Merge branch 'desarrollo' of https://git.miralo.xyz/SAGARHPA/Sagarhpa_2024_web into jcrdev il y a 1 an
  Jose Cienfuegos 93532d3edb Merge branch 'desarrollo' of https://git.miralo.xyz/SAGARHPA/Sagarhpa_2024_web into jcrdev il y a 1 an
  Jose Cienfuegos b84ca935cf Se cambio el orden del grid de la tabla nivel il y a 1 an
  Jose Cienfuegos 025b692ee3 Se agrego columna para di del subproducto il y a 1 an
  Jose Cienfuegos 877c074493 Se agrego modulo nivel il y a 1 an
  OscarGil03 cb3c3ef394 corrección estilos wysywyg il y a 1 an
  OscarGil03 03267d89a4 Wysywyg condicionantes il y a 1 an
  OscarGil03 752afd3d1f Edición municipios il y a 1 an
  OscarGil03 4256e9ca73 ordenar il y a 1 an
  OscarGil03 f2d0a2ab93 Actualización de tabla al eliminar un registro il y a 1 an
  OscarGil03 eea4198cc9 Edicion condicionantes il y a 1 an
  OscarGil03 507cb960f7 Merge branch 'desarrollo' into OGdev il y a 1 an
  OscarGil03 db169fb0b1 subproductos il y a 1 an
  OscarGil03 788f7261f1 Merge branch 'desarrollo' into OGdev il y a 1 an
  OscarGil03 c77458630e Condicionante il y a 1 an
  OscarGil03 95e224ddc7 Catalogos il y a 1 an
  OscarGil03 30bd952065 campo idSagarhpa il y a 1 an
34 fichiers modifiés avec 2238 ajouts et 492 suppressions
  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>
   );