Explorar el Código

Merge remote-tracking branch 'origin/desarrollo' into luisdev

ElPoteito hace 11 meses
padre
commit
00e7efadc1
Se han modificado 27 ficheros con 2598 adiciones y 3 borrados
  1. 2 2
      src/components/layouts/DashboardLayout.jsx
  2. 51 0
      src/constants/rules.js
  3. 141 1
      src/routers/routes.jsx
  4. 136 0
      src/views/catalogos/estados/EstadoDetalle.jsx
  5. 158 0
      src/views/catalogos/estados/Estados.jsx
  6. 55 0
      src/views/catalogos/estados/Formulario.jsx
  7. 7 0
      src/views/catalogos/estados/index.js
  8. 144 0
      src/views/catalogos/finMovilizaciones/FinMovilizacionDetalle.jsx
  9. 158 0
      src/views/catalogos/finMovilizaciones/FinMovilizaciones.jsx
  10. 55 0
      src/views/catalogos/finMovilizaciones/Formulario.jsx
  11. 7 0
      src/views/catalogos/finMovilizaciones/index.js
  12. 55 0
      src/views/catalogos/municipios/Formulario.jsx
  13. 134 0
      src/views/catalogos/municipios/MunicipioDetalle.jsx
  14. 159 0
      src/views/catalogos/municipios/Municipios.jsx
  15. 7 0
      src/views/catalogos/municipios/index.js
  16. 55 0
      src/views/catalogos/productos/Formulario.jsx
  17. 207 0
      src/views/catalogos/productos/ProductoDetalle.jsx
  18. 158 0
      src/views/catalogos/productos/Productos.jsx
  19. 7 0
      src/views/catalogos/productos/index.js
  20. 55 0
      src/views/catalogos/tipoMovilizaciones/Formulario.jsx
  21. 144 0
      src/views/catalogos/tipoMovilizaciones/TipoMovilizacionDetalle.jsx
  22. 158 0
      src/views/catalogos/tipoMovilizaciones/TipoMovilizaciones.jsx
  23. 7 0
      src/views/catalogos/tipoMovilizaciones/index.js
  24. 302 0
      src/views/condicionantes/CondicionanteDetalle.jsx
  25. 174 0
      src/views/condicionantes/Condicionantes.jsx
  26. 55 0
      src/views/condicionantes/Formulario.jsx
  27. 7 0
      src/views/condicionantes/index.js

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

@@ -119,7 +119,7 @@ const DashboardLayout = ({ children }) => {
 
   const sidebarMapper = (route) => {
     let puedeVer = user?.permisos.find((permiso) => permiso === route?.ver)
-    if (puedeVer) {
+    // if (puedeVer) {
       if (route.sidebar === 'single') {
         return {
           key: route.path,
@@ -140,7 +140,7 @@ const DashboardLayout = ({ children }) => {
           children: route.routes.map(innerMap).map(sidebarMapper),
         }
       }
-    }
+    // }
     return null
   }
 

+ 51 - 0
src/constants/rules.js

@@ -0,0 +1,51 @@
+// regex para validar distintos formatos
+const regex = {
+  telefono: /^[0-9\b]+$/,
+  rfc: /^[A-Z0-9\b]+$/,
+  letras: /^[a-zA-Z\s]*$/,
+  alfanumerico: /^[a-zA-Z0-9\s]*$/,
+  url: /^(http|https):\/\/[^ "]+$/,
+  numero: /^[0-9]*$/,
+  decimal: /^\d+(\.\d{1,2})?$/,
+  fecha: /^\d{4}-\d{2}-\d{2}$/,
+  hora: /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/,
+  curp: /^[A-Z]{4}\d{6}[HM][A-Z]{5}[A-Z0-9]{2}$/,
+};
+
+// reglas comunes para validaciones de formularios en Ant Design
+const commonRules = {
+  requerido: { required: true, message: 'Este campo es obligatorio.' },
+  correo: { type: 'email', message: 'El correo electrónico no es válido' },
+  telefono: [
+    { min: 10, max: 10, message: 'El número de teléfono debe tener 10 dígitos' },
+    { pattern: regex.telefono, message: 'El número de teléfono debe contener solo números' }
+  ],
+  numero: { type: 'number', message: 'El valor debe ser un número' },
+  password: { min: 6, message: 'La contraseña debe tener al menos 6 caracteres' },
+  rfc: [
+    { min: 12, max: 13, message: 'El RFC debe tener entre 12 y 13 caracteres' },
+    { pattern: regex.rfc, message: 'El RFC debe contener solo números y letras mayúsculas' }
+  ],
+  letras: { pattern: regex.letras, message: 'Este campo solo puede contener letras' },
+  url: { type: 'url', message: 'La URL no es válida' },
+  longitud: (min, max) => ({ min, max, message: `Este campo debe tener entre ${min} y ${max} caracteres` }),
+  longitudMin: min => ({ min, message: `Este campo debe tener al menos ${min} caracteres` }),
+  longitudMax: max => ({ max, message: `Este campo debe tener máximo ${max} caracteres` }),
+  rango: (min, max) => ({ type: 'number', min, max, message: `El valor debe estar entre ${min} y ${max}` }),
+  rangoMin: min => ({ type: 'number', min, message: `El valor debe ser mayor o igual a ${min}` }),
+  rangoMax: max => ({ type: 'number', max, message: `El valor debe ser menor o igual a ${max}` }),
+  formato: (regex, message) => ({ pattern: regex, message }),
+  comparar: (field, message) => ({
+    validator: (rule, value, callback) => {
+      if (value && value !== field) {
+        callback(message);
+      } else {
+        callback();
+      }
+    }
+  })
+}
+
+export {
+  commonRules
+};

+ 141 - 1
src/routers/routes.jsx

@@ -11,6 +11,8 @@ import {
   DatabaseOutlined,
   LockOutlined,
   UsergroupAddOutlined,
+  ApartmentOutlined,
+  UnorderedListOutlined 
 } from "@ant-design/icons";
 //Íconos de Ant Design
 
@@ -22,6 +24,12 @@ import { Inicio } from '../views/inicio'
 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";
 /* CATÁLOGOS */
 import { Perfil } from "../views/perfil";
 import { Modulos } from "../views/admin/permisos/modulos";
@@ -57,7 +65,7 @@ const dashboardRoutes = [
     name: "Inicio",
     icon: <HomeOutlined />,
     sidebar: "single",
-    ver: "MENU-SOLICITUD",
+    ver: "MENU-INICIO",
     element: Inicio,
   },
   {
@@ -169,10 +177,142 @@ const dashboardRoutes = [
         sidebar: "collapse",
         ver: "MENU-ADMIN",
         routes: [
+          {
+            layout: "dashboard",
+            path: "/productos",
+            name: "Productos",
+            icon: <ApartmentOutlined />,
+            sidebar: "single",
+            ver: "MENU-ADMIN",
+            routes: [
+              {
+                path: "/",
+                element: Productos,
+              },
+              {
+                path: "/agregar",
+                element: ProductoDetalle,
+              },
+              {
+                path: "/editar",
+                element: ProductoDetalle,
+              },
+            ],
+          },
+          {
+            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: "/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,
+              },
+            ],
+          },
         ],
       },
     ],
   },
+  {
+    layout: "dashboard",
+    path: "/condicionantes",
+    name: "Condicionantes",
+    icon: <UnorderedListOutlined />,
+    sidebar: "single",
+    ver: "MENU-CODICIONANTES",
+    routes: [
+      {
+        path: "/",
+        element: Condicionantes,
+      },
+      {
+        path: "/agregar",
+        element: CondicionanteDetalle,
+      },
+      {
+        path: "/editar",
+        element: CondicionanteDetalle,
+      },
+    ],
+  },
   ...sharedRoutes,
 ];
 

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

@@ -0,0 +1,136 @@
+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 = {
+  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 (
+    <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={24}>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              style={{ marginTop: "30px" }}
+              loading={loading}
+            >
+              Guardar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+  )
+}
+
+export default EstadoDetalle

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

@@ -0,0 +1,158 @@
+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 } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+import HttpService from "../../../services/httpService";
+
+const endPoint = "estado";
+
+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,
+    };
+    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 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",
+      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, endPoint+'/eliminar', () =>
+                  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" }}
+      />
+    </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={10}/>
+          </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

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

@@ -0,0 +1,158 @@
+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 } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+import HttpService from "../../../services/httpService";
+
+const endPoint = "fin-movilizacion";
+
+const FinMovilizaciones = () => {
+  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/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 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",
+      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, endPoint+'/eliminar', () =>
+                  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" }}
+      />
+    </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,
+}

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

@@ -0,0 +1,134 @@
+import { Form, Input, Button, Spin, Row, Col } from 'antd';
+import { useEffect, useMemo, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import HttpService from '../../../services/httpService';
+import { useQuery, useModel } from '../../../hooks';
+import { commonRules } from '../../../constants/rules';
+import { 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 extraParams = useMemo(() => ({
+    idMunicipio: id,
+    idEstado: id,
+  }), [id]);
+
+  const estadoExtraParams = useMemo(() => {
+    let params = {};
+    if (estado !== "") {
+      params.nombre = estado;
+    }
+    return params;
+  }, [estado]);
+
+  const requestParams = useMemo(() => ({
+    name: endpoints.municipio,
+    id: id,
+    extraParams: extraParams,
+    expand: 'estado',
+  }), [id, extraParams]);
+
+  const { model, modelLoading } = useModel(requestParams);
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({
+        ...model,
+        idEstado: model.estado?.id,
+      });
+    }
+  }, [form, model]);
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+      const body = { ...values, id };
+      const res = await HttpService.post(`${endpoints.municipio}/guardar`, body);
+      if (res?.status === 200) {
+        navigate('/administracion/catalogos/municipios');
+      }
+    } catch (error) {
+      console.log(error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  if (modelLoading) {
+    return <Spin size="large" style={{ display: "block", margin: "auto", marginTop: "50px" }} />;
+  }
+
+  const onSearch = (value) => {
+    clearTimeout(timer);
+    const newTimer = setTimeout(() => {
+      setEstado(value);
+    }, 300);
+    setTimer(newTimer);
+  };
+
+  return (
+    <Form
+      layout="vertical"
+      name="basic"
+      form={form}
+      onFinish={onFinish}
+      onFinishFailed={() => {}}
+    >
+      <Row gutter={16}>
+        <Col span={24}>
+          <h2>Información del Municipio</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="Estado"
+            name="idEstado"
+            rules={[commonRules.requerido]}
+          >
+            <Select
+              modelsParams={{ name: 'estado', ordenar: 'nombre' }}
+              labelProp="nombre"
+              valueProp="id"
+              append={[model?.estado]}
+              onSearch={onSearch}
+              extraParams={estadoExtraParams}
+            />
+          </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 MunicipioDetalle;

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

@@ -0,0 +1,159 @@
+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 } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+import HttpService from "../../../services/httpService";
+
+const endPoint = "municipio";
+
+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 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",
+      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, endPoint+'/eliminar', () =>
+                  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" }}
+      />
+    </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
+}

+ 55 - 0
src/views/catalogos/productos/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,
+}

+ 207 - 0
src/views/catalogos/productos/ProductoDetalle.jsx

@@ -0,0 +1,207 @@
+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",
+//   }
+// }
+
+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)
+
+  useEffect(() => {
+    if (id) {
+      setRequest(requestParams)
+    }
+    return () => {
+      setRequest({})
+    }
+  }, [id, requestParams])
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({ //seteo cuando son varios
+        ...model,
+        subproductos: model.subproductos.map((subproducto, index) => ({
+          ...subproducto,
+          key: index
+        }))
+      })
+    }
+  }, [form, model])
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+
+      let body = {
+        ...values,
+      };
+
+      if (id) {
+        body.id = id
+      }
+
+      const res = await HttpService.post(`${endpoints.producto}/guardar`, body);
+      respuestas(res);
+      if (res?.status === 200) {
+        navigate('/administracion/catalogos/productos')
+      }
+    } 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 Producto</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={10}/>
+          </Form.Item>
+        </Col>
+        <Col span={24}>
+          <h2>Subproductos:</h2>
+        </Col>
+        <Col span={24}>
+          <Form.List name="subproductos">
+            {(fields, { add, remove }) => (
+              <>
+                {fields.map(({ key, name, ...restField }) => (
+                  <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
+                    <Form.Item
+                      {...restField}
+                      name={[name, 'nombre']}
+                      rules={[commonRules.requerido]}
+                      style={{ width: '350px' }}
+                    >
+                      <Input placeholder="Nombre del Subproducto" />
+                    </Form.Item>
+                    <MinusCircleOutlined onClick={() => remove(name)} />
+                  </Space>
+                ))}
+                <Form.Item>
+                  <Button
+                    type="dashed"
+                    onClick={() => add()}
+                    icon={<PlusOutlined />}
+                  >
+                    Agregar Subproducto
+                  </Button>
+                </Form.Item>
+              </>
+            )}
+          </Form.List>
+        </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 ProductoDetalle

+ 158 - 0
src/views/catalogos/productos/Productos.jsx

@@ -0,0 +1,158 @@
+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 } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+import HttpService from "../../../services/httpService";
+
+const endPoint = "producto";
+
+const Productos = () => {
+  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/productos/agregar`),
+      props: { disabled: false, type: "primary", block: false },
+      text: "Nuevo",
+      icon: <PlusOutlined />,
+    },
+  ];
+
+  const linkText = (value, row, key) => (
+    <Link
+      to={`/administracion/catalogos/productos/editar?id=${row.id}`}
+      style={{ color: "black" }}
+    >
+      {isEllipsis(columns, key) ? (
+        <Tooltip title={value}>{value}</Tooltip>
+      ) : (
+        value
+      )}
+    </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",
+      key: "correo",
+      dataIndex: "correo",
+      width: 100,
+      align: "center",
+      render: (_, item) => (
+        <ActionsButton
+          data={[
+            {
+              label: "Editar",
+              onClick: () =>
+                navigate(`/administracion/catalogos/productos/editar?id=${item?.id}`),
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(item?.nombre, item?.id, endPoint+'/eliminar', () =>
+                  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" }}
+      />
+    </SimpleTableLayout>
+  );
+};
+
+export default Productos;

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

@@ -0,0 +1,7 @@
+import Productos from './Productos'
+import ProductoDetalle from './ProductoDetalle'
+
+export {
+  Productos,
+  ProductoDetalle
+}

+ 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={10}/>
+          </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

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

@@ -0,0 +1,158 @@
+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 } from "../../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+import HttpService from "../../../services/httpService";
+
+const endPoint = "tipo-movilizacion";
+
+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 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",
+      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, endPoint+'/eliminar', () =>
+                  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" }}
+      />
+    </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
+}

+ 302 - 0
src/views/condicionantes/CondicionanteDetalle.jsx

@@ -0,0 +1,302 @@
+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'
+
+const selectores = {
+  productos: {
+    name: "producto",
+    expand: "subproductos",
+  },
+}
+
+const endpoints = {
+  condicionante: "condicionante",
+};
+
+const amplitudes = [
+  {
+    value: "Familia",
+    label: "Familia",
+  },
+  {
+    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 requestParams = useMemo(() => ({
+    name: endpoints.condicionante,
+    // expand: 'subproductos',
+    id,
+    // extraParams
+  }), [id])
+
+
+  const { model, modelLoading } = useModel(request)
+
+  useEffect(() => {
+    if (id) {
+      setRequest(requestParams)
+    }
+    return () => {
+      setRequest({})
+    }
+  }, [id, requestParams])
+
+  useEffect(() => {
+    if (model) {
+      form.setFieldsValue({ //seteo cuando son varios
+        ...model,
+        subproductos: model.subproductos.map((subproducto, index) => ({
+          ...subproducto,
+          key: index
+        }))
+      })
+      if (model?.activa) {
+        setActiva(true)
+      }
+    }
+  }, [form, model])
+
+  const onFinish = async (values) => {
+    try {
+      setLoading(true);
+
+      let body = {
+        ...values,
+      };
+
+      if (id) {
+        body.id = id
+      }
+
+      const res = await HttpService.post(`${endpoints.producto}/guardar`, body);
+      respuestas(res);
+      if (res?.status === 200) {
+        navigate('/condicionantes')
+      }
+    } catch (error) {
+      console.log(error);
+      setLoading(false);
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  if (modelLoading) {
+    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
+              }
+            >
+              {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}
+            >
+              Guardar
+            </Button>
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+  )
+}
+
+export default ProductoDetalle

+ 174 - 0
src/views/condicionantes/Condicionantes.jsx

@@ -0,0 +1,174 @@
+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 } from "../../utilities";
+import { Link, useNavigate } from "react-router-dom";
+import Formulario from "./Formulario";
+import HttpService from "../../services/httpService";
+
+const endPoint = "condicionante";
+
+const Condicionantes = () => {
+  let tablaRef = useRef(null);
+  const navigate = useNavigate();
+  const [form] = Form.useForm();
+  const [buscarParams, setBuscarParams] = useState({});
+
+  const onFinish = (values) => {
+    const { q } = values;
+    const params = {
+      q: q ?? "",
+      padre: true,
+    };
+    setBuscarParams(params);    
+  };
+
+  const botones = [
+    {
+      onClick: () => navigate(`/condicionantes/agregar`),
+      props: { disabled: false, type: "primary", block: false },
+      text: "Nuevo",
+      icon: <PlusOutlined />,
+    },
+  ];
+
+  const linkText = (value, row, key) => (
+    <Link
+      to={`/condicionantes/editar?id=${row.id}`}
+      style={{ color: "black" }}
+    >
+      {isEllipsis(columns, key) ? (
+        <Tooltip title={value}>{value}</Tooltip>
+      ) : (
+        value
+      )}
+    </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",
+      key: "id",
+      dataIndex: "id",
+      width: 100,
+      align: "center",
+      render: (_, item) => (
+        <ActionsButton
+          data={[
+            {
+              label: "Editar",
+              onClick: () =>
+                navigate(`/condicionantes/editar?id=${item?.id}`),
+            },
+            {
+              label: "Eliminar",
+              onClick: () => {
+                eliminarRegistro(item?.titulo, item?.id, endPoint+'/eliminar', () =>
+                  tablaRef?.current?.refresh()
+                );
+              },
+              danger: true,
+            },
+          ]}
+        />
+      ),
+    },
+    {
+      title: "Título",
+      key: "titulo",
+      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 (
+    <SimpleTableLayout
+      btnGroup={{
+        btnGroup: botones,
+      }}
+    >
+      <Formulario
+        form={form}
+        onFinish={onFinish} 
+      />
+      <Tabla
+        columns={columns}
+        nameURL={endPoint}
+        extraParams={buscarParams}
+        scroll={{ x: "30vw" }}
+      />
+    </SimpleTableLayout>
+  );
+};
+
+export default Condicionantes;

+ 55 - 0
src/views/condicionantes/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/condicionantes/index.js

@@ -0,0 +1,7 @@
+import Condicionantes from './Condicionantes'
+import CondicionanteDetalle from './CondicionanteDetalle'
+
+export {
+  Condicionantes,
+  CondicionanteDetalle
+}