diff --git a/quasar.config.js b/quasar.config.js
index 84e934d1..a9d50c62 100644
--- a/quasar.config.js
+++ b/quasar.config.js
@@ -23,7 +23,7 @@ module.exports = configure(function (ctx) {
         // app boot file (/src/boot)
         // --> boot files are part of "main.js"
         // https://v2.quasar.dev/quasar-cli-webpack/boot-files
-        boot: ['i18n', 'axios', 'vnDate', 'error-handler', 'app'],
+        boot: ['i18n', 'axios', 'vnDate', 'app'],
 
         // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
         css: ['app.scss', 'width.scss', 'responsive.scss'],
diff --git a/src/boot/error-handler.js b/src/boot/error-handler.js
deleted file mode 100644
index 1e11c952..00000000
--- a/src/boot/error-handler.js
+++ /dev/null
@@ -1,66 +0,0 @@
-export default async ({ app }) => {
-    /*
-  window.addEventListener('error',
-    e => onWindowError(e));
-  window.addEventListener('unhandledrejection',
-    e => onWindowRejection(e));
-
-  ,onWindowError(event) {
-    errorHandler(event.error);
-  }
-  ,onWindowRejection(event) {
-    errorHandler(event.reason);
-  }
-*/
-    app.config.errorHandler = (err, vm, info) => {
-        errorHandler(err, vm)
-    }
-
-    function errorHandler (err, vm) {
-        let message
-        let tMessage
-        let res = err.response
-
-        // XXX: Compatibility with old JSON service
-        if (err.name === 'JsonException') {
-            res = {
-                status: err.statusCode,
-                data: { error: { message: err.message } }
-            }
-        }
-
-        if (res) {
-            const status = res.status
-
-            if (status >= 400 && status < 500) {
-                switch (status) {
-                    case 401:
-                        tMessage = 'loginFailed'
-                        break
-                    case 403:
-                        tMessage = 'authenticationRequired'
-                        vm.$router.push('/login')
-                        break
-                    case 404:
-                        tMessage = 'notFound'
-                        break
-                    default:
-                        message = res.data.error.message
-                }
-            } else if (status >= 500) {
-                tMessage = 'internalServerError'
-            }
-        } else {
-            tMessage = 'somethingWentWrong'
-            console.error(err)
-        }
-
-        if (tMessage) {
-            message = vm.$t(tMessage)
-        }
-        vm.$q.notify({
-            message,
-            type: 'negative'
-        })
-    }
-}
diff --git a/src/components/common/FormModel.vue b/src/components/common/FormModel.vue
new file mode 100644
index 00000000..7f0bfa2e
--- /dev/null
+++ b/src/components/common/FormModel.vue
@@ -0,0 +1,232 @@
+<script setup>
+import { ref, inject, onMounted, computed, Teleport, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+import { useAppStore } from 'stores/app';
+import { storeToRefs } from 'pinia';
+import useNotify from 'src/composables/useNotify.js';
+
+const props = defineProps({
+    title: {
+        type: String,
+        default: ''
+    },
+    table: {
+        type: String,
+        default: ''
+    },
+    schema: {
+        type: String,
+        default: ''
+    },
+    // Objeto con los datos iniciales del form, si este objeto es definido, no se ejecuta la query fetch
+    formInitialData: {
+        type: Object,
+        default: () => {}
+    },
+    autoLoad: {
+        type: Boolean,
+        default: true
+    },
+    defaultActions: {
+        type: Boolean,
+        default: true
+    },
+    showBottomActions: {
+        type: Boolean,
+        default: false
+    },
+    saveFn: {
+        type: Function,
+        default: null
+    },
+    separationBetweenInputs: {
+        type: String,
+        default: 'xs'
+    },
+    url: {
+        type: String,
+        default: ''
+    },
+    urlUpdate: {
+        type: String,
+        default: null
+    },
+    urlCreate: {
+        type: String,
+        default: null
+    },
+    observeFormChanges: {
+        type: Boolean,
+        default: true,
+        description:
+            'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)'
+    },
+    mapper: {
+        type: Function,
+        default: null
+    },
+    filter: {
+        type: Object,
+        default: null
+    }
+});
+
+const emit = defineEmits(['onDataSaved', 'onDataFetched']);
+const api = inject('api');
+const { t } = useI18n();
+const { notify } = useNotify();
+const appStore = useAppStore();
+const { isHeaderMounted } = storeToRefs(appStore);
+
+const isLoading = ref(false);
+const formData = ref({});
+const addressFormRef = ref(null);
+const hasChanges = ref(!props.observeFormChanges);
+const isResetting = ref(false);
+const originalData = ref(null);
+
+const separationBetweenInputs = computed(() => {
+    return `q-gutter-y-${props.separationBetweenInputs}`;
+});
+
+const onSubmitSuccess = () => {
+    emit('onDataSaved');
+    notify(t('dataSaved'), 'positive');
+};
+
+onMounted(async () => {
+    if (!props.formInitialData) {
+        if (props.autoLoad && props.url) await fetch();
+        originalData.value = { ...formData.value };
+    } else {
+        formData.value = { ...props.formInitialData };
+        originalData.value = { ...props.formInitialData };
+    }
+
+    if (props.observeFormChanges) {
+        watch(
+            () => formData.value,
+            (newVal, oldVal) => {
+                if (!oldVal) return;
+                hasChanges.value =
+                    !isResetting.value &&
+                    JSON.stringify(newVal) !==
+                        JSON.stringify(originalData.value);
+                isResetting.value = false;
+            },
+            { deep: true }
+        );
+    }
+});
+async function fetch() {
+    try {
+        let { data } = await api.get(props.url, {
+            params: { filter: JSON.stringify(props.filter) }
+        });
+        if (Array.isArray(data)) data = data[0] ?? {};
+        formData.value = { ...data };
+        emit('onDataFetched', formData.value);
+    } catch (e) {
+        throw e;
+    }
+}
+
+async function submit() {
+    console.log('submit: ');
+    if (props.observeFormChanges && !hasChanges.value)
+        return notify('globals.noChanges', 'negative');
+
+    isLoading.value = true;
+    try {
+        const body = props.mapper
+            ? props.mapper(formData.value, originalData.value)
+            : formData.value;
+        const method = props.urlCreate ? 'post' : 'patch';
+        const url = props.urlCreate || props.urlUpdate || props.url;
+
+        let response;
+
+        if (props.saveFn) await props.saveFn(body);
+        else await api[method](url, body);
+
+        onSubmitSuccess();
+        hasChanges.value = false;
+    } finally {
+        isLoading.value = false;
+    }
+}
+
+defineExpose({
+    formData,
+    submit
+});
+</script>
+
+<template>
+    <QCard class="form-container" v-bind="$attrs">
+        <QForm
+            v-if="!isLoading"
+            ref="addressFormRef"
+            class="form"
+            :class="separationBetweenInputs"
+        >
+            <span v-if="title" class="text-h6 text-bold">
+                {{ title }}
+            </span>
+            <slot name="form" :data="formData" />
+            <slot name="extraForm" :data="formData" />
+            <component
+                v-if="isHeaderMounted"
+                :is="showBottomActions ? 'div' : Teleport"
+                to="#actions"
+                class="flex row justify-end q-gutter-x-sm"
+                :class="{ 'q-mt-md': showBottomActions }"
+            >
+                <QBtn
+                    v-if="defaultActions && showBottomActions"
+                    :label="t('cancel')"
+                    :icon="showBottomActions ? undefined : 'check'"
+                    rounded
+                    no-caps
+                    flat
+                    v-close-popup
+                >
+                    <QTooltip>{{ t('cancel') }}</QTooltip>
+                </QBtn>
+                <QBtn
+                    v-if="defaultActions"
+                    :label="t('save')"
+                    :icon="showBottomActions ? undefined : 'check'"
+                    rounded
+                    no-caps
+                    flat
+                    :disabled="!showBottomActions && !hasChanges"
+                    @click="submit()"
+                    data-cy="formModelDefaultSaveButton"
+                >
+                    <QTooltip>{{ t('save') }}</QTooltip>
+                </QBtn>
+                <slot name="actions" :data="formData" />
+            </component>
+        </QForm>
+        <QSpinner v-else color="primary" size="3em" :thickness="2" />
+    </QCard>
+</template>
+
+<style lang="scss" scoped>
+.form-container {
+    width: 100%;
+    height: max-content;
+    padding: 32px;
+    max-width: 544px;
+    display: flex;
+    justify-content: center;
+}
+
+.form {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+}
+</style>
diff --git a/src/pages/Account/AddressDetails.vue b/src/pages/Account/AddressDetails.vue
index 9e4210d7..31a372d7 100644
--- a/src/pages/Account/AddressDetails.vue
+++ b/src/pages/Account/AddressDetails.vue
@@ -5,32 +5,26 @@ import { useRouter, useRoute } from 'vue-router';
 
 import VnInput from 'src/components/common/VnInput.vue';
 import VnSelect from 'src/components/common/VnSelect.vue';
-import VnForm from 'src/components/common/VnForm.vue';
+import FormModel from 'src/components/common/FormModel.vue';
 
+import { useUserStore } from 'stores/user';
 import { useAppStore } from 'stores/app';
 import { storeToRefs } from 'pinia';
 
 const router = useRouter();
 const route = useRoute();
 const { t } = useI18n();
-const jApi = inject('jApi');
+const api = inject('api');
 const appStore = useAppStore();
+const userStore = useUserStore();
 const { isHeaderMounted } = storeToRefs(appStore);
 
 const vnFormRef = ref(null);
 const countriesOptions = ref([]);
 const provincesOptions = ref([]);
-const pks = { id: route.params.id };
 const isEditMode = route.params.id !== '0';
-const fetchAddressDataSql = {
-    query: `
-        SELECT a.id, a.street, a.nickname, a.city, a.postalCode, a.provinceFk, p.countryFk
-        FROM myAddress a
-        LEFT JOIN vn.province p ON p.id = a.provinceFk
-        WHERE a.id = #address
-    `,
-    params: { address: route.params.id }
-};
+const editAddressData = ref(null);
+const showForm = ref(false);
 
 watch(
     () => vnFormRef?.value?.formData?.countryFk,
@@ -40,23 +34,49 @@ watch(
 const goBack = () => router.push({ name: 'addressesList' });
 
 const getCountries = async () => {
-    countriesOptions.value = await jApi.query(
-        `SELECT id, name FROM vn.country
-        ORDER BY name`
-    );
+    const filter = { fields: ['id', 'name'], order: 'name' };
+    const { data } = await api.get('Countries', {
+        params: { filter: JSON.stringify(filter) }
+    });
+    countriesOptions.value = data;
 };
 
 const getProvinces = async countryFk => {
     if (!countryFk) return;
-    provincesOptions.value = await jApi.query(
-        `SELECT id, name FROM vn.province
-        WHERE countryFk = #id
-        ORDER BY name`,
-        { id: countryFk }
-    );
+
+    const filter = {
+        where: { countryFk },
+        fields: ['id', 'name'],
+        order: 'name'
+    };
+    const { data } = await api.get('Provinces', {
+        params: { filter: JSON.stringify(filter) }
+    });
+    provincesOptions.value = data;
 };
 
-onMounted(() => getCountries());
+const getAddressDetails = async () => {
+    const { data } = await api.get(`Addresses/${route.params.id}`);
+    if (!data) return;
+
+    const { nickname, street, city, postalCode, province, provinceFk } = data;
+    editAddressData.value = {
+        nickname,
+        street,
+        city,
+        postalCode,
+        countryFk: province?.countryFk,
+        provinceFk
+    };
+};
+
+onMounted(async () => {
+    if (isEditMode) {
+        await getAddressDetails();
+    }
+    getCountries();
+    showForm.value = true;
+});
 </script>
 
 <template>
@@ -74,19 +94,21 @@ onMounted(() => getCountries());
                 </QTooltip>
             </QBtn>
         </Teleport>
-        <VnForm
+        <FormModel
+            v-if="showForm"
             ref="vnFormRef"
-            :fetch-form-data-sql="fetchAddressDataSql"
-            :columns-to-ignore-update="['countryFk']"
-            :create-model-default="{
-                field: 'clientFk',
-                value: 'account.myUser_getId()'
-            }"
-            :pks="pks"
-            :is-edit-mode="isEditMode"
+            :url-create="
+                !isEditMode
+                    ? `Clients/${userStore?.user?.id}/createAddress`
+                    : ''
+            "
+            :url-update="
+                isEditMode
+                    ? `Clients/${userStore?.user?.id}/updateAddress/${route?.params?.id}`
+                    : ''
+            "
+            :form-initial-data="editAddressData"
             :title="t(isEditMode ? 'editAddress' : 'addAddress')"
-            table="myAddress"
-            schema="hedera"
             @on-data-saved="goBack()"
         >
             <template #form="{ data }">
@@ -125,7 +147,7 @@ onMounted(() => getCountries());
                     data-cy="addressFormProvince"
                 />
             </template>
-        </VnForm>
+        </FormModel>
     </QPage>
 </template>
 
diff --git a/src/pages/Account/AddressList.vue b/src/pages/Account/AddressList.vue
index a603715c..43fe2b0c 100644
--- a/src/pages/Account/AddressList.vue
+++ b/src/pages/Account/AddressList.vue
@@ -10,13 +10,16 @@ import useNotify from 'src/composables/useNotify.js';
 import { useVnConfirm } from 'src/composables/useVnConfirm.js';
 import { useAppStore } from 'stores/app';
 import { storeToRefs } from 'pinia';
+import { useUserStore } from 'stores/user';
 
 const router = useRouter();
 const jApi = inject('jApi');
+const api = inject('api');
 const { notify } = useNotify();
 const { t } = useI18n();
 const { openConfirmationModal } = useVnConfirm();
 const appStore = useAppStore();
+const userStore = useUserStore();
 const { isHeaderMounted } = storeToRefs(appStore);
 
 const addresses = ref([]);
@@ -65,16 +68,13 @@ const changeDefaultAddress = async () => {
     notify(t('defaultAddressModified'), 'positive');
 };
 
-const removeAddress = async id => {
+async function removeAddress(address) {
     try {
-        await jApi.execQuery(
-            `START TRANSACTION;
-            UPDATE hedera.myAddress SET isActive = FALSE
-            WHERE ((id = #id));
-            SELECT isActive FROM hedera.myAddress WHERE ((id = #id));
-            COMMIT`,
+        await api.patch(
+            `/Clients/${userStore?.user?.id}/updateAddress/${address.id}`,
             {
-                id
+                ...address,
+                isActive: false
             }
         );
         getActiveAddresses();
@@ -82,7 +82,7 @@ const removeAddress = async id => {
     } catch (error) {
         console.error('Error removing address:', error);
     }
-};
+}
 
 onMounted(async () => {
     getDefaultAddress();
@@ -145,7 +145,7 @@ onMounted(async () => {
                             openConfirmationModal(
                                 null,
                                 t('confirmDeleteAddress'),
-                                () => removeAddress(address.id)
+                                () => removeAddress(address)
                             )
                         "
                     >
diff --git a/src/test/cypress/integration/config/AddresList.spec.js b/src/test/cypress/integration/config/AddresList.spec.js
index d8a39271..6b5ad682 100644
--- a/src/test/cypress/integration/config/AddresList.spec.js
+++ b/src/test/cypress/integration/config/AddresList.spec.js
@@ -46,15 +46,28 @@ describe('PendingOrders', () => {
             .should('contain', data.postcode);
     };
 
+    it('should fail if we enter a wrong postcode', () => {
+        cy.dataCy('newAddressBtn').should('exist');
+        cy.dataCy('newAddressBtn').click();
+        cy.dataCy('formModelDefaultSaveButton').should('exist');
+        cy.dataCy('formModelDefaultSaveButton').should('be.disabled');
+        const addressFormData = getRandomAddressFormData();
+        fillFormWithData(addressFormData);
+        cy.dataCy('formModelDefaultSaveButton').should('not.be.disabled');
+        cy.dataCy('formModelDefaultSaveButton').click();
+        cy.checkNotify('negative');
+    });
+
     it('should create a new address', () => {
         cy.dataCy('newAddressBtn').should('exist');
         cy.dataCy('newAddressBtn').click();
-        cy.dataCy('formDefaultSaveButton').should('exist');
-        cy.dataCy('formDefaultSaveButton').should('be.disabled');
+        cy.dataCy('formModelDefaultSaveButton').should('exist');
+        cy.dataCy('formModelDefaultSaveButton').should('be.disabled');
         const addressFormData = getRandomAddressFormData();
+        addressFormData.postcode = '46460'; // Usamos un postcode válido
         fillFormWithData(addressFormData);
-        cy.dataCy('formDefaultSaveButton').should('not.be.disabled');
-        cy.dataCy('formDefaultSaveButton').click();
+        cy.dataCy('formModelDefaultSaveButton').should('not.be.disabled');
+        cy.dataCy('formModelDefaultSaveButton').click();
         cy.checkNotify('positive', 'Datos guardados');
         verifyAddressCardData(addressFormData);
     });
@@ -71,9 +84,10 @@ describe('PendingOrders', () => {
         });
         // Fill form with new data
         const addressFormData = getRandomAddressFormData();
+        addressFormData.postcode = '46460'; // Usamos un postcode válido
         fillFormWithData(addressFormData);
-        cy.dataCy('formDefaultSaveButton').should('not.be.disabled');
-        cy.dataCy('formDefaultSaveButton').click();
+        cy.dataCy('formModelDefaultSaveButton').should('not.be.disabled');
+        cy.dataCy('formModelDefaultSaveButton').click();
         cy.checkNotify('positive', 'Datos guardados');
         verifyAddressCardData(addressFormData);
     });
diff --git a/src/test/cypress/integration/UserFlows.spec.js b/src/test/cypress/integration/flows/UserFlows.spec.js
similarity index 100%
rename from src/test/cypress/integration/UserFlows.spec.js
rename to src/test/cypress/integration/flows/UserFlows.spec.js
diff --git a/src/test/cypress/support/commands.js b/src/test/cypress/support/commands.js
index 2d4de848..cb22f86a 100644
--- a/src/test/cypress/support/commands.js
+++ b/src/test/cypress/support/commands.js
@@ -90,5 +90,6 @@ Cypress.Commands.add('setConfirmDialog', () => {
 });
 
 Cypress.Commands.add('checkNotify', (status, content) => {
-    cy.dataCy(`${status}Notify`).should('contain', content);
+    if (content) cy.dataCy(`${status}Notify`).should('contain', content);
+    else cy.dataCy(`${status}Notify`).should('exist');
 });