yadid rosell 5 miesięcy temu
rodzic
commit
c3999ad80f

+ 9 - 8
src/constants/index.ts

@@ -1,18 +1,19 @@
-import imagenNoDisponible from '@/assets/imagen-no-disponible.png'
-
-export const IS_DEV = import.meta.env.VITE_IS_DEV === 'true';
-export const SHOW_DEVTOOLS = import.meta.env.VITE_SHOW_DEVTOOLS === 'true';
+// export const API_URL = "https://registro.api.congresoson.gob.mx/";
+export const API_URL = "http://localhost:8080/";
+export const IS_DEV = import.meta.env.VITE_IS_DEV === "true";
+export const SHOW_DEVTOOLS = import.meta.env.VITE_SHOW_DEVTOOLS === "true";
 export const VERSION = import.meta.env.VITE_VERSION;
 export const PROJECT_NAME = import.meta.env.VITE_PROJECT_NAME;
 
-export const IMAGEN_NO_DISPONIBLE = imagenNoDisponible;
-
 export const REGEX = {
   NUMEROS_ENTEROS: /^[0-9\b]+$/,
   NUMEROS_DECIMALES: /^\d*\.?\d*$/,
   CURRENCY_NUMBER_FORMAT: /^\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})?$/,
   DIVISA: /^[0-9.,]+$/,
-  CORREO: /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
+  CORREO:
+    /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
   TELEFONO: /^[0-9]{10,10}$/,
-  BASE_64_IMG: /^data:image\/(png|jpeg|jpg|gif);base64,[A-Za-z0-9+/]+={0,2}$/
+  BASE_64_IMG: /^data:image\/(png|jpeg|jpg|gif);base64,[A-Za-z0-9+/]+={0,2}$/,
+  CURP: /^[A-Z]{4}[0-9]{6}[HM][A-Z]{5}[0-9A-Z]{2}$/,
+  CLAVE_ELECTOR: /[A-Z]{6}[0-9]{8}[A-Z]{1}[0-9]{3}/
 };

+ 149 - 0
src/hooks/useFile.tsx

@@ -0,0 +1,149 @@
+import { useState, useRef } from "react"
+import { Archivo } from "../models/Archivo.model"
+import { auth } from "../services";
+
+
+interface UseFileType {
+  archivo: Archivo | undefined,
+  error: string | undefined,
+  progreso: number | undefined,
+  agregar: (archivo: Archivo) => void,
+  subir: () => Promise<Archivo>,
+  limpiar: () => void,
+  descargar: () => void,
+}
+
+export function useFile(): UseFileType {
+
+  const [archivo, setArchivo] = useState<Archivo | undefined>();
+  const [error, setError] = useState<string | undefined>();
+  const [progreso, setProgreso] = useState<number | undefined>();
+
+  const archivoRef = useRef<Archivo | undefined>(undefined);
+
+  const actualizar = (actualizacion: Partial<Archivo>) => {
+    if (archivoRef.current) {
+      const archivoActualizado = new Archivo({
+        ...archivoRef.current,
+        ...actualizacion,
+      });
+      archivoRef.current = archivoActualizado;
+      setArchivo(archivoActualizado);
+    }
+  };
+
+  const agregar = (archivo: Archivo) => {
+    archivoRef.current = archivo;
+    setArchivo(archivo);
+  };
+
+  const limpiar = () => {
+    archivoRef.current = undefined;
+    setArchivo(undefined);
+    setProgreso(undefined);
+    setError(undefined);
+  };
+
+  const subir = async (): Promise<Archivo> => {
+    if (!archivoRef.current || !archivoRef.current.referencia) {
+      throw new Error("El archivo no está definido o falta la referencia");
+    }
+
+    if (archivoRef.current.enNube !== null) {
+      throw new Error("El archivo ya se ha subido");
+    }
+
+    actualizar({ estatus: Archivo.ESTATUS.SUBIENDO, progreso: 0 });
+
+    try {
+      // Solicitar la URL al servidor
+      const res = await auth.get("aspirante/archivo/generar-url.json", {
+        nombre: archivoRef.current.nombre,
+        tipo: archivoRef.current.mimetype,
+        peso: archivoRef.current.peso,
+      });
+
+      if (res.status >= 400) {
+        throw new Error(`Error al generar URL: ${res.detalle}`);
+      }
+
+      const media = res.detalle as Archivo;
+
+      actualizar({ ...media });
+
+      // Subir el archivo a la URL obtenida
+      return await new Promise<Archivo>((resolve, reject) => {
+        const xhr = new XMLHttpRequest();
+        xhr.open("PUT", media.urlNube!, true);
+
+        xhr.upload.onprogress = (event) => {
+          if (event.lengthComputable) {
+            const porcentaje = parseInt(`${(event.loaded / event.total) * 100}`);
+            actualizar({ progreso: porcentaje });
+            setProgreso(porcentaje);
+          }
+        };
+
+        xhr.onload = async () => {
+          if (xhr.status >= 200 && xhr.status < 300) {
+
+            let resArchivo = await auth.post("aspirante/archivo/confirmar-subida.json", { id: media.id });
+            let detalle = resArchivo.detalle as Archivo;
+
+            actualizar({ ...detalle, estatus: Archivo.ESTATUS.COMPLETADO, progreso: 100 });
+            resolve(archivoRef.current as Archivo);
+
+          } else {
+            const mensajeError = `Error en la subida: ${xhr.statusText}`;
+            actualizar({ estatus: Archivo.ESTATUS.ERROR });
+            setError(mensajeError);
+            reject(new Error(mensajeError));
+          }
+        };
+
+        xhr.onerror = () => {
+          const mensajeError = "Error de red o conexión interrumpida";
+          actualizar({ estatus: Archivo.ESTATUS.ERROR });
+          setError(mensajeError);
+          reject(new Error(mensajeError));
+        };
+
+        xhr.send(archivoRef.current!.referencia);
+      });
+    } catch (err) {
+      const mensajeError = err instanceof Error ? err.message : "Error desconocido";
+      setError(mensajeError);
+      actualizar({ estatus: Archivo.ESTATUS.ERROR });
+      throw err;
+    }
+  };
+
+  const descargar = async () => {
+    if(!archivoRef.current?.id) {
+      return;
+    }
+
+    let blob = await auth.getBlob("aspirante/archivo/descarga.json", { id: archivoRef.current.id });
+    const urlBlob = URL.createObjectURL(new Blob([blob]));
+
+    const enlace = document.createElement('a');
+    enlace.href = urlBlob;
+    enlace.setAttribute("download", archivoRef.current.nombre);
+
+    document.body.appendChild(enlace);
+    enlace.click();
+    document.body.removeChild(enlace);
+
+    URL.revokeObjectURL(urlBlob);
+  }
+
+  return ({
+    archivo,
+    error,
+    progreso,
+    agregar,
+    subir,
+    limpiar,
+    descargar
+  });
+}

+ 148 - 0
src/hooks/useHttp.tsx

@@ -0,0 +1,148 @@
+
+import { App } from "antd"
+import React, { type ReactNode } from "react"
+import { useContext, createContext } from "react"
+import type { DefaultResponse } from "../types/responses"
+import { type IHttpService, type IRequestParams } from "../services"
+
+interface HttpContextType {
+  request: IHttpService
+  queryParams: Record<string, string>
+  getModel: (params: { url: string, id?: number | string, params?: IRequestParams }) => Promise<DefaultResponse<any>>
+  postModel: <T = any>(url: string, body: T, onSuccess?: (data: any) => void, notifySuccess?: boolean) => Promise<DefaultResponse<T>>
+  getModels: (url: string, params?: IRequestParams, updateQueryParams?: boolean) => Promise<DefaultResponse<any>>
+  deleteModel: <T = any>(url: string, body: T, onSuccess?: (data: any) => void, notifySuccess?: boolean) => Promise<DefaultResponse<any>>
+  isLoading: boolean
+}
+
+const HttpContext = createContext<HttpContextType>({
+  request: {} as IHttpService,
+  queryParams: {},
+  getModel: async () => { throw new Error("getModel not implemented") },
+  postModel: async () => { throw new Error("postModel not implemented") },
+  getModels: async () => { throw new Error("getModels not implemented") },
+  deleteModel: async () => { throw new Error("deleteModel not implemented") },
+  isLoading: false,
+});
+
+export function HttpProvider({ children, request }: { children: ReactNode, request: IHttpService }) {
+  const [isLoading, setIsLoading] = React.useState(false);
+  const [queryParams, setQueryParams] = React.useState({});
+  // const { notification } = App.useApp();
+
+  
+
+  async function getModel<T = any>({ url, id, params }: { url: string, id?: number | string, params?: IRequestParams }): Promise<DefaultResponse<any>> {
+    setIsLoading(true);
+    try {
+      let _params = { ...params }
+      if (id) {
+        _params.id = id;
+      }
+      const response = await request.get(url, _params, false);
+      return response as DefaultResponse<T>;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  async function getModels(url: string, params: IRequestParams = {}, updateQueryParams: boolean = false): Promise<DefaultResponse<any>> {
+    setIsLoading(true);
+    try {
+      const response = await request.get(url, params, updateQueryParams);
+      return response as DefaultResponse<any>;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  async function postModel<T = any>(url: string, body: T, onSuccess?: (data: any) => void, notifySuccess: boolean = true): Promise<DefaultResponse<T>> {
+    setIsLoading(true);
+    try {
+      const response = await request.post(url, body);
+      console.log(response)
+      if (response.status !== 200) {
+        // notification.error({
+        //   message: "Error",
+        //   description: response?.mensaje
+        // });
+        return response as DefaultResponse<any>;
+      }
+
+      if (notifySuccess && !response.isError) {
+        // notification.success({
+        //   message: response?.mensaje
+        // });
+      }
+      if (onSuccess && !response.isError && response.status === 200) {
+        onSuccess(response);
+      }
+      return response as DefaultResponse<T>;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  async function deleteModel<T = any>(url: string, body: T, onSuccess?: (data: any) => void, notifySuccess: boolean = true): Promise<DefaultResponse<any>> {
+    setIsLoading(true);
+    try {
+      const response = await request.delete<T>(url, body);
+
+      if (response.status !== 200) {
+        // notification.error({
+        //   message: "Error",
+        //   description: response?.mensaje
+        // });
+        return response as DefaultResponse<any>;
+      }
+
+      if (notifySuccess) {
+        // notification.success({
+        //   message: response?.mensaje
+        // });
+      }
+
+      if (onSuccess && response.status === 200) {
+        onSuccess(response);
+      }
+
+      return response as DefaultResponse<any>;
+    } finally {
+      setIsLoading(false);
+    }
+  }
+
+  React.useEffect(() => {
+    const search = window.location.search;
+    const params = new URLSearchParams(search);
+    const query: Record<string, string> = {};
+    for (const [key, value] of params) {
+      query[key] = value;
+    }
+    setQueryParams(query);
+  }, []);
+
+  return (
+    <HttpContext.Provider value={{
+      queryParams,
+      request,
+      getModel,
+      getModels,
+      postModel,
+      deleteModel,
+      isLoading
+    }}>
+      {children}
+    </HttpContext.Provider>
+  );
+}
+
+export function useHttp() {
+  const context = useContext(HttpContext);
+
+  if (!context) {
+    throw new Error('useHttp must be used within a HttpProvider');
+  }
+
+  return context;
+}

+ 59 - 0
src/hooks/useModalState.tsx

@@ -0,0 +1,59 @@
+import React, { useMemo } from "react";
+
+export interface IModalState {
+  initialState?: boolean,
+  header?: React.ReactNode,
+  footer?: React.ReactNode,
+  onOk?: () => void,
+  onCancel?: () => void,
+  content?: React.ReactNode,
+  width?: number | string
+}
+
+export type ModalStateType = {
+  isOpen: boolean,
+  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>,
+  header: React.ReactNode,
+  footer: React.ReactNode,
+  onOk: () => void,
+  onCancel: () => void,
+  content: React.ReactNode,
+  width?: number | string
+};
+
+export function useModalState({ initialState = false, header = null, footer = null, onOk = () => { }, onCancel = () => { }, content = null, width }: IModalState = {}) {
+  const [isOpen, setIsOpen] = React.useState(initialState);
+
+  const contentCopy = useMemo(() => {
+    if (content) {
+      return React.cloneElement(content as React.ReactElement, {
+        afterOnFinish: () => {
+          onOk && onOk();
+          setIsOpen(false);
+        },
+        autoNavigate: false
+      });
+    }
+  }, [content, onOk]);
+
+  function open() {
+    setIsOpen(true);
+  }
+
+  function close() {
+    setIsOpen(false);
+  }
+
+  return {
+    isOpen,
+    setIsOpen,
+    header,
+    footer,
+    onOk,
+    onCancel,
+    content: contentCopy,
+    width,
+    open,
+    close
+  };
+}

+ 55 - 0
src/hooks/useSortColumns.tsx

@@ -0,0 +1,55 @@
+import { DashOutlined, MenuOutlined, SettingOutlined } from "@ant-design/icons";
+import React from "react"
+
+export function useSortColumns({
+  columnsData = [] as any[],
+  order = "",
+}) {
+  const [sortValue, setSortValue] = React.useState(order)
+  const [sortedColumns, setSortedColumns] = React.useState<any[]>([]);
+
+  const onHeaderCell = React.useCallback((column: { key: string, orden?: string, dataIndex: string }) => {
+    return {
+      onClick: () => {
+        const _sort = sortValue.indexOf("asc") > 0 ? "desc" : "asc"
+        setSortValue(`${column?.orden ? column.orden : column.dataIndex}-${_sort}`)
+      },
+    }
+  }, [sortValue])
+
+  React.useEffect(() => {
+    const columnsDefaultProps = {
+      sorter: { multiple: 2 },
+      sortOrder: sortValue.indexOf("asc") >= 0 ? "ascend" : "descend",
+      onHeaderCell: onHeaderCell,
+      showSorterTooltip: false
+    };
+
+    const _columns = columnsData?.map((column) => {
+      column.sortOrder = null;
+      if (column?.orden === false) {
+        return column;
+      }
+      if (column?.orden) {
+        if (sortValue.indexOf(column.orden) >= 0) {
+          column.sortOrder = sortValue.indexOf("asc") >= 0 ? "ascend" : "descend";
+        }
+      } else if (sortValue.indexOf(column.dataIndex) >= 0) {
+        column.sortOrder = sortValue.indexOf("asc") >= 0 ? "ascend" : "descend";
+      }
+
+      if(column.key === "acciones") {
+        return { ...column, title: "",};
+      }
+      return { ...columnsDefaultProps, ...column };
+    });
+
+    setSortedColumns(_columns);
+  }, [onHeaderCell, columnsData, sortValue]);
+
+  return React.useMemo(() =>
+  ({
+    sortValue,
+    sortedColumns,
+  }), [sortedColumns, sortValue]);
+}

+ 81 - 0
src/services/authService.ts

@@ -0,0 +1,81 @@
+import { API_URL } from "../constants";
+import httpStatusCodes from "../constants/http-status-codes";
+import type { Sesion } from "../models/Sesion.model";
+import type { DefaultResponse } from "../types/responses";
+import { HttpService } from "./httpService";
+import { authStore } from "../stores";
+
+export interface IAuthService {
+  login: <T>(correo: string, clave: string) => Promise<DefaultResponse<T>>;
+  logout: () => any;
+  usuario?: Sesion | null;
+  isActive?: boolean;
+}
+
+export class AuthService extends HttpService implements IAuthService {
+
+  constructor(api_url: string) {
+    super(api_url);
+  }
+
+  DEFAULT_HEADERS = () => {
+    let headers: Record<string, string> = {
+      "Content-Type": "application/json",
+    };
+    if (this.isActive) {
+      headers.Authorization = `Bearer ${this.usuario?.token}`;
+    }
+    return headers;
+  };
+
+  FILE_HEADERS = () => {
+    let headers: Record<string, string> = {
+      "Content-Type": "application/json",
+    };
+    if (this.isActive) {
+      headers.Authorization = `Bearer ${this.usuario?.token}`;
+    }
+    return headers;
+  };
+
+  async login<Sesion>(
+    correo?: string,
+    clave?: string
+  ): Promise<DefaultResponse<Sesion>> {
+    const body = {
+      correo,
+      clave,
+    };
+
+    const res = await this.post("aspirante/iniciar-sesion.json", body);
+
+    if (res.status === httpStatusCodes.OK) {
+      authStore.set(res.detalle as Partial<Sesion>);
+      window.location.href = "/";
+    }
+
+    throw new Error("Login");
+  }
+
+  logout() {
+    authStore.set(undefined);
+    window.location.href = "/";
+  }
+
+  get isActive(): boolean {
+    let active = false;
+    if (authStore.get()) {
+      active = true;
+    }
+    return active;
+  }
+
+  get usuario(): Sesion | undefined {
+    if (typeof window === "undefined") {
+      return;
+    }
+    return authStore.get();
+  }
+}
+
+export const auth = new AuthService(API_URL);

+ 22 - 16
src/services/httpService.ts

@@ -1,4 +1,5 @@
-import type { TPaginacion, DefaultResponse } from "../types/responses";
+import { API_URL } from "../constants";
+import type { DefaultResponse, TPaginacion } from "../types/responses";
 
 export interface IRequestParams {
   expand?: string;
@@ -17,8 +18,6 @@ export interface IRequest {
   body: any;
 }
 
-const API_URL = "https://pos.api.turquessacoffee.com/";
-
 export interface IHttpService {
   get: <T>(
     endpoint: string,
@@ -44,17 +43,15 @@ export class HttpService implements IHttpService {
     this.API_URL = API_URL;
   }
 
-  static DEFAULT_HEADERS = () => {
+  DEFAULT_HEADERS(): Record<string, string> {
     return {
       "Content-Type": "application/json",
-      // Authorization: `Bearer ${localStorage.getItem("token")}`,
     };
   };
 
-  static FILE_HEADERS = () => {
+  FILE_HEADERS(): Record<string, string> {
     return {
       "Content-Type": "multipart/form-data",
-      // Authorization: `Bearer ${localStorage.getItem("token")}`,
     };
   };
 
@@ -134,10 +131,12 @@ export class HttpService implements IHttpService {
       }
     }
 
+    const headers = this.DEFAULT_HEADERS();
     const _response = await fetch(url, {
       method: "GET",
-      headers: HttpService.DEFAULT_HEADERS(),
+      headers: headers,
     });
+
     const response = (await _response.json()) as DefaultResponse<T>;
 
     return {
@@ -149,10 +148,12 @@ export class HttpService implements IHttpService {
   };
 
   getBlob = async (endpoint: string, data: any) => {
-    const _response = await fetch(`${this.API_URL}${endpoint}`, {
+    const headers = this.DEFAULT_HEADERS();
+    const queryParams = new URLSearchParams(data).toString();
+    const url = `${this.API_URL}${endpoint}?${queryParams}`;
+    const _response = await fetch(url, {
       method: "GET",
-      headers: HttpService.DEFAULT_HEADERS(),
-      body: JSON.stringify(data),
+      headers: headers,
     });
     const response = await _response.blob();
     return response;
@@ -163,9 +164,10 @@ export class HttpService implements IHttpService {
     data: any,
     fileName: string = "fileName"
   ) => {
+    const headers = this.DEFAULT_HEADERS();
     const _response = await fetch(`${this.API_URL}${endpoint}`, {
       method: "GET",
-      headers: HttpService.DEFAULT_HEADERS(),
+      headers: headers,
       body: JSON.stringify(data),
     });
     const blob = await _response.blob();
@@ -181,9 +183,10 @@ export class HttpService implements IHttpService {
   };
 
   post = async <T>(endpoint: string, body: any) => {
+    const headers = this.DEFAULT_HEADERS();
     const _response = await fetch(`${this.API_URL}${endpoint}`, {
       method: "POST",
-      headers: HttpService.DEFAULT_HEADERS(),
+      headers: headers,
       body: JSON.stringify(body),
     });
     const status = _response.status;
@@ -197,9 +200,10 @@ export class HttpService implements IHttpService {
   };
 
   postFormData = async (endpoint: string, data: any) => {
+    const headers = this.FILE_HEADERS();
     const _response = await fetch(`${this.API_URL}${endpoint}`, {
       method: "POST",
-      headers: HttpService.FILE_HEADERS(),
+      headers: headers,
       body: data,
     });
 
@@ -213,10 +217,11 @@ export class HttpService implements IHttpService {
   };
 
   delete = async <T = any>(endpoint: string, body: T) => {
+    const headers = this.DEFAULT_HEADERS();
     const response = await fetch(`${this.API_URL}${endpoint}`, {
       body: JSON.stringify(body),
       method: "DELETE",
-      headers: HttpService.DEFAULT_HEADERS(),
+      headers: headers,
     });
 
     const status = response.status;
@@ -229,9 +234,10 @@ export class HttpService implements IHttpService {
   };
 
   put = async (endpoint: string, body: any) => {
+    const headers = this.DEFAULT_HEADERS();
     const response = await fetch(`${this.API_URL}${endpoint}`, {
       method: "PUT",
-      headers: HttpService.DEFAULT_HEADERS(),
+      headers: headers,
       body: JSON.stringify(body),
     });
     return response.json();

+ 2 - 1
src/services/index.js

@@ -1 +1,2 @@
-export * from "./httpService";
+export * from "./httpService";
+export * from "./authService";

+ 65 - 0
src/types/ModeloBase.model.ts

@@ -0,0 +1,65 @@
+export interface IModeloBase {
+  id?: string;
+
+  creado?: Date;
+  modificado?: Date;
+
+  ENDPOINTS: {
+    DEFAULT: string, 
+    [key: string]: string
+  };  
+
+  EXPAND: {
+    DEFAULT: string,
+    [key: string]: string
+  };
+
+  COLUMNS: ModelColumnsType[]
+}
+
+export interface ModelColumnsType {
+  key: string;
+  title: string;
+  type?: 'boolean' | 'object' | 'date' | 'currency'
+  default?: string;
+  options?: Map<string, string>;
+}
+export class ModeloBase {
+  id?: string;
+
+  creado?: Date;
+  modificado?: Date;
+
+  //#region STATICS DEFAULT PARA CRUDS
+  static BASE_ROUTE = "/";
+
+  static ENDPOINTS = {
+    DEFAULT: '/v1/default.json',
+  };
+
+  static EXPAND = {
+    DEFAULT: '',
+  }
+ 
+  static fromJson(data: Partial<ModeloBase>) {
+    return new ModeloBase(data);
+  }
+
+  static fromJsonList(data: Partial<ModeloBase>[]) {
+    return data.map((_data) => new ModeloBase(_data));
+  }
+
+  static COLUMNS: ModelColumnsType[] = [
+    {
+      key: 'id',
+      title: 'Id',
+    }
+  ]
+  //#endregion
+  protected constructor(
+    data: Partial<ModeloBase> = {},
+  ) {
+    Object.assign(this, data);
+  }
+
+}

+ 6 - 0
src/types/propTypes.interface.ts

@@ -0,0 +1,6 @@
+export type ListStateType<T = string | number> = {
+  index: number,
+  key: T
+  isEditing: boolean
+  value?: T
+}

+ 241 - 0
src/utilities/httpService.ts

@@ -0,0 +1,241 @@
+import { API_URL } from "../constants";
+import type { DefaultResponse, TPaginacion } from "../types/responses";
+
+export interface IRequestParams {
+  expand?: string;
+  ordenar?: string | "id-desc" | "id-asc";
+  limite?: number;
+  pagina?: number;
+  buscar?: string;
+
+  [key: string]: any;
+}
+
+export interface IRequest {
+  req: string;
+  endpoint: string;
+  params: any;
+  body: any;
+}
+
+export interface IHttpService {
+  get: <T>(
+    endpoint: string,
+    params?: any,
+    updateQueryParams?: boolean
+  ) => Promise<DefaultResponse<T>>;
+  getBlob: (endpoint: string, data: any) => Promise<Blob>;
+  downloadBlob: (
+    endpoint: string,
+    data: any,
+    fileName: string
+  ) => Promise<void>;
+  post: <T>(endpoint: string, body: any) => Promise<DefaultResponse<T>>;
+  postFormData: (endpoint: string, data: any) => Promise<DefaultResponse<any>>;
+  delete: <T>(endpoint: string, body: T) => Promise<DefaultResponse<any>>;
+  put: (endpoint: string, body: any) => Promise<any>;
+}
+
+export class HttpService implements IHttpService {
+  API_URL: string;
+
+  constructor(API_URL: string) {
+    this.API_URL = API_URL;
+  }
+
+  static DEFAULT_HEADERS = () => {
+    return {
+      "Content-Type": "application/json",
+      Authorization: `Bearer ${localStorage.getItem("token")}`,
+    };
+  };
+
+  static FILE_HEADERS = () => {
+    return {
+      "Content-Type": "multipart/form-data",
+      Authorization: `Bearer ${localStorage.getItem("token")}`,
+    };
+  };
+
+  static DEFAULT_REQUEST_PARAMS = {
+    limite: 10,
+    pagina: 1,
+    ordenar: "id-desc",
+  };
+
+  static DEFAULT_PAGINACION: TPaginacion = {
+    total: 0,
+    pagina: 1,
+    limite: 10,
+  };
+
+  static paramsToQuery = (params: any) => {
+    return Object.keys(params)
+      .map(
+        (key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key])
+      )
+      .join("&");
+  };
+
+  static EMPTY_REQUEST = (): IRequest => ({
+    req: "",
+    endpoint: "",
+    params: null,
+    body: null,
+  });
+
+  static GET_REQUEST = (endpoint: string, params: any = {}): IRequest => ({
+    req: "GET",
+    endpoint,
+    params,
+    body: null,
+  });
+
+  static POST_REQUEST = <T = any>(
+    endpoint: string,
+    body: T,
+    params: any = {}
+  ): IRequest => ({
+    req: "POST",
+    endpoint,
+    params,
+    body,
+  });
+
+  static DELETE_REQUEST = (endpoint: string, params: any = {}) =>
+    ({
+      req: "DELETE",
+      endpoint: `${endpoint}/eliminar`,
+      body: {
+        ...params,
+      },
+    } as IRequest);
+
+  get = async <T>(
+    endpoint: string,
+    params: any = HttpService.DEFAULT_REQUEST_PARAMS,
+    updateQueryParams: boolean = false
+  ) => {
+    const stringParams = params ? HttpService.paramsToQuery(params) : "";
+    const queryParams = `?${new URLSearchParams(stringParams).toString()}`;
+
+    let url = `${this.API_URL}${endpoint}`;
+    if (queryParams) {
+      url = `${this.API_URL}${endpoint}${queryParams}`;
+
+      if (updateQueryParams && window.location.search !== queryParams) {
+        //Actualizar los queryparams de la url actual
+        window.history.pushState(
+          {},
+          "",
+          window.location.pathname + `${queryParams}`
+        );
+      }
+    }
+
+    const _response = await fetch(url, {
+      method: "GET",
+      headers: HttpService.DEFAULT_HEADERS(),
+    });
+
+    const response = (await _response.json()) as DefaultResponse<T>;
+
+    return {
+      ...response,
+      isError: response?.status !== 200 ? true : false,
+      status: response?.status,
+      resultado: response?.resultado || response,
+    } as DefaultResponse<T>;
+  };
+
+  getBlob = async (endpoint: string, data: any) => {
+    const _response = await fetch(`${this.API_URL}${endpoint}`, {
+      method: "GET",
+      headers: HttpService.DEFAULT_HEADERS(),
+      body: JSON.stringify(data),
+    });
+    const response = await _response.blob();
+    return response;
+  };
+
+  downloadBlob = async (
+    endpoint: string,
+    data: any,
+    fileName: string = "fileName"
+  ) => {
+    const _response = await fetch(`${this.API_URL}${endpoint}`, {
+      method: "GET",
+      headers: HttpService.DEFAULT_HEADERS(),
+      body: JSON.stringify(data),
+    });
+    const blob = await _response.blob();
+
+    const urlBlob = URL.createObjectURL(blob);
+    const link = document.createElement("a");
+    link.href = urlBlob;
+    link.setAttribute("download", fileName);
+    document.body.appendChild(link);
+    link.click();
+    URL.revokeObjectURL(urlBlob);
+    link.remove();
+  };
+
+  post = async <T>(endpoint: string, body: any) => {
+    const _response = await fetch(`${this.API_URL}${endpoint}`, {
+      method: "POST",
+      headers: HttpService.DEFAULT_HEADERS(),
+      body: JSON.stringify(body),
+    });
+    const status = _response.status;
+    const response = (await _response.json()) as DefaultResponse<T>;
+
+    return {
+      ...response,
+      isError: status !== 200 ? true : false,
+      status: status,
+    } as DefaultResponse<T>;
+  };
+
+  postFormData = async (endpoint: string, data: any) => {
+    const _response = await fetch(`${this.API_URL}${endpoint}`, {
+      method: "POST",
+      headers: HttpService.FILE_HEADERS(),
+      body: data,
+    });
+
+    const response = await _response.json;
+
+    return {
+      ...response,
+      isError: _response?.status !== 200 ? true : false,
+      status: _response?.status,
+    } as DefaultResponse<any>;
+  };
+
+  delete = async <T = any>(endpoint: string, body: T) => {
+    const response = await fetch(`${this.API_URL}${endpoint}`, {
+      body: JSON.stringify(body),
+      method: "DELETE",
+      headers: HttpService.DEFAULT_HEADERS(),
+    });
+
+    const status = response.status;
+    const responseJson = await response.json();
+    return {
+      ...responseJson,
+      isError: status !== 200 ? true : false,
+      status: status,
+    } as DefaultResponse<any>;
+  };
+
+  put = async (endpoint: string, body: any) => {
+    const response = await fetch(`${this.API_URL}${endpoint}`, {
+      method: "PUT",
+      headers: HttpService.DEFAULT_HEADERS(),
+      body: JSON.stringify(body),
+    });
+    return response.json();
+  };
+}
+
+export const http = new HttpService(API_URL);