# Phase 0 — Emergency Fixes Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Fix 8 critical production bugs blocking auth flows, causing wrong API calls, and leaving security holes in the Monetarie admin panel.

**Architecture:** Each task is a self-contained fix to a specific file. No new modules or APIs needed — these are all corrections to existing code. Banking app has 2 views using Vuetify (not installed), 3 views with broken `baseURL`, and 1 view with double-submit. Admin has missing assembly route, wrong SISBAJUD prefix, and circular loan navigation.

**Tech Stack:** Vue 3, PrimeVue 4, TypeScript, Pinia, TanStack Query, vue-router 4

---

### Task 1: Rewrite ActivateAccountView.vue (Vuetify → PrimeVue)

**Files:**
- Modify: `apps/banking/src/views/auth/ActivateAccountView.vue` (entire template)

**Context:** This view uses Vuetify components (v-container, v-card, v-text-field, v-btn, v-alert, v-progress-circular, v-icon) but Vuetify is NOT installed. The view renders nothing — completely broken. Script logic is fine, only template needs replacement.

**Step 1: Replace entire file with PrimeVue version**

```vue
<template>
  <div class="activate-account-container">
    <div class="activate-card">
      <Card>
        <!-- Loading state -->
        <template #content>
          <div v-if="loading" class="text-center py-6">
            <ProgressSpinner style="width: 48px; height: 48px" />
            <p class="mt-4">Verificando token de ativação...</p>
          </div>

          <!-- Token valid — set password -->
          <div v-else-if="tokenValid">
            <h2 class="text-center mb-4">Ative sua Conta</h2>
            <p class="text-center mb-4 text-color-secondary">
              Defina sua senha para acessar o internet banking.
            </p>

            <div class="flex flex-column gap-3">
              <div class="flex flex-column gap-1">
                <label for="password">Nova Senha</label>
                <Password
                  id="password"
                  v-model="password"
                  :feedback="true"
                  toggleMask
                  class="w-full"
                  inputClass="w-full"
                  placeholder="Mínimo 8 caracteres"
                />
              </div>

              <div class="flex flex-column gap-1">
                <label for="confirmPassword">Confirmar Senha</label>
                <Password
                  id="confirmPassword"
                  v-model="confirmPassword"
                  :feedback="false"
                  toggleMask
                  class="w-full"
                  inputClass="w-full"
                />
                <small v-if="confirmPassword && password !== confirmPassword" class="text-red-500">
                  As senhas não coincidem
                </small>
              </div>

              <Button
                label="Ativar Conta"
                :loading="submitting"
                :disabled="!canSubmit"
                class="w-full mt-2"
                @click="activateAccount"
              />
            </div>
          </div>

          <!-- Token invalid or expired -->
          <div v-else-if="tokenError" class="text-center">
            <i class="pi pi-times-circle text-red-500 mb-4" style="font-size: 3rem"></i>
            <h3>Token inválido ou expirado</h3>
            <p class="text-color-secondary mt-2">
              Solicite um novo link de ativação ao administrador da cooperativa.
            </p>
            <Button
              label="Voltar ao Login"
              outlined
              class="w-full mt-4"
              @click="$router.push('/auth/login')"
            />
          </div>

          <!-- Success -->
          <div v-else-if="activated" class="text-center">
            <i class="pi pi-check-circle text-green-500 mb-4" style="font-size: 3rem"></i>
            <h3>Conta ativada com sucesso!</h3>
            <p class="text-color-secondary mt-2">Você já pode acessar o internet banking.</p>
            <Button
              label="Fazer Login"
              class="w-full mt-4"
              @click="$router.push('/auth/login')"
            />
          </div>

          <Message v-if="error" severity="error" class="mt-4">{{ error }}</Message>
        </template>
      </Card>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import api from '@/lib/api'
import Card from 'primevue/card'
import Button from 'primevue/button'
import Password from 'primevue/password'
import ProgressSpinner from 'primevue/progressspinner'
import Message from 'primevue/message'

const route = useRoute()
const router = useRouter()

const loading = ref(true)
const submitting = ref(false)
const tokenValid = ref(false)
const tokenError = ref(false)
const activated = ref(false)
const error = ref('')
const password = ref('')
const confirmPassword = ref('')

const canSubmit = computed(() =>
  password.value.length >= 8 && password.value === confirmPassword.value
)

onMounted(async () => {
  const token = route.query.token as string
  if (!token) {
    tokenError.value = true
    loading.value = false
    return
  }

  try {
    await api.get(`/api/auth/verify-reset-token?token=${token}`)
    tokenValid.value = true
  } catch {
    tokenError.value = true
  } finally {
    loading.value = false
  }
})

async function activateAccount() {
  submitting.value = true
  error.value = ''
  const token = route.query.token as string

  try {
    await api.post('/api/auth/reset-password', {
      token,
      password: password.value,
    })
    activated.value = true
    tokenValid.value = false
  } catch (e: any) {
    error.value = e.response?.data?.error || 'Erro ao ativar conta'
  } finally {
    submitting.value = false
  }
}
</script>

<style scoped>
.activate-account-container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 1rem;
}
.activate-card {
  width: 100%;
  max-width: 420px;
}
</style>
```

**Step 2: Verify the view renders**

Run: `cd apps/banking && npx vue-tsc --noEmit --pretty 2>&1 | grep -i ActivateAccount`
Expected: No TypeScript errors for this file.

**Step 3: Commit**

```bash
git add apps/banking/src/views/auth/ActivateAccountView.vue
git commit -m "fix: rewrite ActivateAccountView from Vuetify to PrimeVue"
```

---

### Task 2: Rewrite OTPSetupView.vue (Vuetify → PrimeVue)

**Files:**
- Modify: `apps/banking/src/views/auth/OTPSetupView.vue` (entire template + imports)

**Context:** Same issue as Task 1 — entirely Vuetify components. Multi-step wizard: choose method → TOTP/SMS setup → verify → success. Script logic is fine, only template and imports need replacement.

**Step 1: Replace entire file with PrimeVue version**

```vue
<template>
  <div class="otp-setup-container">
    <div class="otp-card">
      <Card>
        <template #title>
          <div class="text-center">Configuração de Autenticação em Dois Fatores</div>
        </template>
        <template #content>
          <!-- Step 1: Choose method -->
          <div v-if="step === 'choose'">
            <p class="text-center mb-4 text-color-secondary">
              Proteja sua conta com verificação em duas etapas
            </p>

            <div class="flex flex-column gap-3">
              <Button
                label="Aplicativo Autenticador (TOTP)"
                icon="pi pi-mobile"
                class="w-full"
                @click="setupTotp"
              />
              <Button
                label="SMS"
                icon="pi pi-comments"
                outlined
                class="w-full"
                @click="setupSms"
              />
            </div>
          </div>

          <!-- Step 2: TOTP Setup -->
          <div v-if="step === 'totp'">
            <p class="text-center mb-4">
              Escaneie o QR code abaixo com seu aplicativo autenticador
              (Google Authenticator, Authy, etc.)
            </p>

            <div class="flex justify-content-center mb-4">
              <div class="border-1 border-round surface-border p-3">
                <img v-if="qrCodeUrl" :src="qrCodeUrl" alt="QR Code" width="200" height="200" />
                <ProgressSpinner v-else style="width: 48px; height: 48px" />
              </div>
            </div>

            <div v-if="backupCodes.length > 0" class="mb-4">
              <Message severity="warn" :closable="false">
                <p class="font-bold">Códigos de Recuperação</p>
                <p class="text-sm">Guarde estes códigos em local seguro. Cada um pode ser usado uma vez.</p>
                <div class="flex flex-wrap gap-2 mt-2">
                  <Tag v-for="code in backupCodes" :key="code" :value="code" severity="secondary" />
                </div>
              </Message>
            </div>

            <div class="flex flex-column gap-3">
              <div class="flex flex-column gap-1">
                <label for="totpCode">Código de Verificação</label>
                <InputText
                  id="totpCode"
                  v-model="verificationCode"
                  placeholder="000000"
                  maxlength="6"
                  class="w-full"
                  @keyup.enter="verifyTotp"
                />
              </div>

              <Button
                label="Verificar e Ativar"
                :loading="loading"
                class="w-full"
                @click="verifyTotp"
              />
            </div>
          </div>

          <!-- Step 2: SMS Setup -->
          <div v-if="step === 'sms'">
            <div class="flex flex-column gap-3">
              <div class="flex flex-column gap-1">
                <label for="phone">Número de Celular</label>
                <InputText
                  id="phone"
                  v-model="phoneNumber"
                  placeholder="+55 11 99999-0000"
                  class="w-full"
                />
              </div>

              <div v-if="smsSent" class="flex flex-column gap-1">
                <label for="smsCode">Código SMS</label>
                <InputText
                  id="smsCode"
                  v-model="verificationCode"
                  placeholder="000000"
                  maxlength="6"
                  class="w-full"
                  @keyup.enter="verifySms"
                />
              </div>

              <Button
                v-if="!smsSent"
                label="Enviar Código"
                :loading="loading"
                class="w-full"
                @click="sendSmsCode"
              />
              <Button
                v-else
                label="Verificar e Ativar"
                :loading="loading"
                class="w-full"
                @click="verifySms"
              />
            </div>
          </div>

          <!-- Step 3: Success -->
          <div v-if="step === 'success'" class="text-center">
            <i class="pi pi-check-circle text-green-500 mb-4" style="font-size: 3rem"></i>
            <h3>Autenticação em dois fatores ativada!</h3>
            <p class="text-color-secondary">Sua conta está mais segura agora.</p>
            <Button
              label="Voltar ao Dashboard"
              class="w-full mt-4"
              @click="$router.push('/dashboard')"
            />
          </div>

          <Message v-if="error" severity="error" class="mt-4">{{ error }}</Message>

          <Button
            v-if="step !== 'choose' && step !== 'success'"
            label="Voltar"
            text
            class="mt-3"
            @click="step = 'choose'"
          />
        </template>
      </Card>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import api from '@/lib/api'
import Card from 'primevue/card'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import ProgressSpinner from 'primevue/progressspinner'
import Message from 'primevue/message'
import Tag from 'primevue/tag'

const step = ref<'choose' | 'totp' | 'sms' | 'success'>('choose')
const loading = ref(false)
const error = ref('')
const verificationCode = ref('')
const phoneNumber = ref('')
const qrCodeUrl = ref('')
const backupCodes = ref<string[]>([])
const smsSent = ref(false)

async function setupTotp() {
  loading.value = true
  error.value = ''
  try {
    const { data } = await api.post('/api/auth/otp/setup-totp')
    qrCodeUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(data.provisioning_uri)}`
    backupCodes.value = data.backup_codes || []
    step.value = 'totp'
  } catch (e: any) {
    error.value = e.response?.data?.error || 'Erro ao configurar TOTP'
  } finally {
    loading.value = false
  }
}

async function setupSms() {
  step.value = 'sms'
}

async function sendSmsCode() {
  loading.value = true
  error.value = ''
  try {
    await api.post('/api/auth/otp/setup-sms', { phone_number: phoneNumber.value })
    smsSent.value = true
  } catch (e: any) {
    error.value = e.response?.data?.error || 'Erro ao enviar SMS'
  } finally {
    loading.value = false
  }
}

async function verifyTotp() {
  loading.value = true
  error.value = ''
  try {
    await api.post('/api/auth/otp/verify', { code: verificationCode.value })
    await api.post('/api/auth/otp/enable')
    step.value = 'success'
  } catch (e: any) {
    error.value = e.response?.data?.error || 'Código inválido'
  } finally {
    loading.value = false
  }
}

async function verifySms() {
  loading.value = true
  error.value = ''
  try {
    await api.post('/api/auth/otp/verify-sms', { code: verificationCode.value })
    await api.post('/api/auth/otp/enable')
    step.value = 'success'
  } catch (e: any) {
    error.value = e.response?.data?.error || 'Código inválido'
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
.otp-setup-container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 1rem;
}
.otp-card {
  width: 100%;
  max-width: 420px;
}
</style>
```

**Step 2: Verify no TypeScript errors**

Run: `cd apps/banking && npx vue-tsc --noEmit --pretty 2>&1 | grep -i OTPSetup`
Expected: No errors.

**Step 3: Commit**

```bash
git add apps/banking/src/views/auth/OTPSetupView.vue
git commit -m "fix: rewrite OTPSetupView from Vuetify to PrimeVue"
```

---

### Task 3: Remove `baseURL: ''` from Banking API calls

**Files:**
- Modify: `apps/banking/src/views/payments/PaymentsView.vue:45`
- Modify: `apps/banking/src/views/payments/PaymentsConfirmView.vue:64,67`
- Modify: `apps/banking/src/views/DashboardAnalyticsView.vue:38`

**Context:** These views pass `{ baseURL: '' }` to axios, which overrides the configured `VITE_API_URL`. In development this accidentally works (proxy catches it), but in production it sends requests to the wrong host.

**Step 1: Fix PaymentsView.vue**

At line 45, change:
```typescript
const response = await api.post('/api/boletos', { barcode: barcodeValue }, { baseURL: '' })
```
To:
```typescript
const response = await api.post('/api/boletos', { barcode: barcodeValue })
```

**Step 2: Fix PaymentsConfirmView.vue**

At line 64, change:
```typescript
await api.post(`/api/boletos/${boletoId}/pay`, {}, { baseURL: '' })
```
To:
```typescript
await api.post(`/api/boletos/${boletoId}/pay`, {})
```

At line 67, change:
```typescript
await api.post('/api/boletos/pay', { barcode: paymentData.value.barcode }, { baseURL: '' })
```
To:
```typescript
await api.post('/api/boletos/pay', { barcode: paymentData.value.barcode })
```

**Step 3: Fix DashboardAnalyticsView.vue**

At line 38, change:
```typescript
const response = await api.get('/api/analytics/dashboard', { baseURL: '' })
```
To:
```typescript
const response = await api.get('/api/analytics/dashboard')
```

**Step 4: Verify no other occurrences**

Run: `grep -rn "baseURL.*''" apps/banking/src/`
Expected: Zero results.

**Step 5: Commit**

```bash
git add apps/banking/src/views/payments/PaymentsView.vue apps/banking/src/views/payments/PaymentsConfirmView.vue apps/banking/src/views/DashboardAnalyticsView.vue
git commit -m "fix: remove baseURL override that bypasses configured API endpoint"
```

---

### Task 4: Fix LoginView double-submit vulnerability

**Files:**
- Modify: `apps/banking/src/views/LoginView.vue`

**Context:** The login button's `:disabled` only checks `!isValid` (form validity), not `authStore.isLoading`. Users can click multiple times during slow connections, sending duplicate login requests.

**Step 1: Find the submit button in the template**

Search for the `<Button type="submit"` tag. It should have `:disabled="!isValid"`.

Change the disabled binding to:
```vue
:disabled="!isValid || authStore.isLoading"
```

And add a loading indicator:
```vue
:loading="authStore.isLoading"
```

**Step 2: Commit**

```bash
git add apps/banking/src/views/LoginView.vue
git commit -m "fix: prevent double-submit on login by disabling button during request"
```

---

### Task 5: Fix SISBAJUD endpoint prefix inconsistency

**Files:**
- Modify: `apps/admin/src/views/regulatory/SisbajudDetailView.vue:55,82,119`

**Context:** The backend mounts SISBAJUD routes under `/regulatory/sisbajud/...` (router.ex line 719-722). The list view (`SisbajudListView.vue`) correctly calls `/admin/regulatory/sisbajud/orders`. But the detail view calls `/admin/judicial/orders/...` which is WRONG — those routes don't exist.

**Step 1: Fix all 3 endpoints in SisbajudDetailView.vue**

At line 55, change:
```typescript
const { data } = await api.get(`/admin/judicial/orders/${orderId}`)
```
To:
```typescript
const { data } = await api.get(`/admin/regulatory/sisbajud/orders/${orderId}`)
```

At line 82, change:
```typescript
const { data } = await api.get(`/admin/judicial/orders/${orderId}/blocks`)
```
To:
```typescript
const { data } = await api.get(`/admin/regulatory/sisbajud/orders/${orderId}/blocks`)
```

At line 119, change:
```typescript
await api.post(`/admin/judicial/orders/${orderId}/process`)
```
To:
```typescript
await api.post(`/admin/regulatory/sisbajud/orders/${orderId}/process`)
```

**Step 2: Verify consistency**

Run: `grep -rn "judicial" apps/admin/src/`
Expected: Zero results (all SISBAJUD endpoints now use `/regulatory/sisbajud/`).

**Step 3: Commit**

```bash
git add apps/admin/src/views/regulatory/SisbajudDetailView.vue
git commit -m "fix: correct SISBAJUD endpoint prefix from /judicial/ to /regulatory/sisbajud/"
```

---

### Task 6: Fix LoansView "Novo Emprestimo" circular navigation

**Files:**
- Modify: `apps/admin/src/views/credit/LoansView.vue:108`

**Context:** The "Novo Emprestimo" button navigates to `{ name: 'LoanList' }` — the same view it's on. There is no `LoanCreate` route. For now, the simplest correct fix is to disable the button with a tooltip explaining it's not yet implemented.

**Step 1: Replace the button at line 104-109**

Change:
```vue
<Button
  :label="t('credit.loans.newApplication')"
  icon="pi pi-plus"
  severity="primary"
  @click="router.push({ name: 'LoanList' })"
/>
```
To:
```vue
<Button
  :label="t('credit.loans.newApplication')"
  icon="pi pi-plus"
  severity="primary"
  disabled
  v-tooltip.top="'Funcionalidade em desenvolvimento'"
/>
```

**Step 2: Commit**

```bash
git add apps/admin/src/views/credit/LoansView.vue
git commit -m "fix: disable loan create button instead of circular navigation"
```

---

### Task 7: Register AssemblyView route in admin router

**Files:**
- Modify: `apps/admin/src/router/index.ts` (add route after surplus routes, ~line 528)
- Modify: `apps/admin/src/layouts/AdminLayout.vue` (add sidebar menu item)

**Context:** `AssemblyView.vue` exists at `views/cooperative/assemblies/AssemblyView.vue` with full CRUD (list, detail, create, open, close, resolutions, voting). But no route is registered and no sidebar link exists.

**Step 1: Add route in router/index.ts**

Find the surplus routes block (around line 511-528). After the last surplus route's closing brace, add:

```typescript
      // Assemblies
      {
        path: 'cooperative/assemblies',
        name: 'Assemblies',
        component: () => import('@/views/cooperative/assemblies/AssemblyView.vue'),
        meta: { title: 'Assembleias' },
      },
```

**Step 2: Add sidebar menu item in AdminLayout.vue**

Find the Cooperativa section in the navItems array. After the Sobras/Surplus item, add:

```typescript
      { label: 'Assembleias', icon: 'pi pi-users', to: '/dashboard/cooperative/assemblies' },
```

**Step 3: Verify the route loads**

Run: `grep -n "Assemblies\|assemblies" apps/admin/src/router/index.ts`
Expected: Shows the new route entry.

**Step 4: Commit**

```bash
git add apps/admin/src/router/index.ts apps/admin/src/layouts/AdminLayout.vue
git commit -m "feat: register AssemblyView route and add sidebar menu item"
```

---

### Task 8: Deploy Phase 0 fixes to Monetarie ProxMox

**Files:**
- No code changes — deployment only

**Context:** All 7 previous tasks must be committed. Then build and deploy both Banking and Admin frontends to Monetarie Swarm.

**Step 1: Build and deploy Admin frontend**

```bash
# Create tarball on Mac
cd /Users/luizpenha/monetarie/core
tar czf /tmp/core-admin-phase0.tar.gz apps/admin/src apps/admin/package.json apps/admin/index.html apps/admin/tsconfig.json apps/admin/tsconfig.node.json apps/admin/vite.config.ts apps/admin/env.d.ts apps/admin/public apps/admin/.env.production packages/ package.json pnpm-workspace.yaml pnpm-lock.yaml infrastructure/docker/nginx-frontend.conf infrastructure/docker/Dockerfile.frontend

# SCP to swarm
scp -O /tmp/core-admin-phase0.tar.gz planner@172.69.88.10:/home/planner/

# On swarm: extract, build, push, update
ssh planner@172.69.88.10 "mkdir -p /home/planner/build-phase0-admin && cd /home/planner/build-phase0-admin && tar xzf /home/planner/core-admin-phase0.tar.gz && mkdir -p docs-site && docker build -f infrastructure/docker/Dockerfile.frontend --build-arg APP_NAME=admin --build-arg APP_PATH=apps/admin -t 172.69.88.10:5000/cc/core-admin-ui:v20260308-phase0 . && docker push 172.69.88.10:5000/cc/core-admin-ui:v20260308-phase0 && docker service update --image 172.69.88.10:5000/cc/core-admin-ui:v20260308-phase0 cc-core_core-admin-ui"
```

**Step 2: Build and deploy Banking frontend**

```bash
# Create tarball
tar czf /tmp/core-banking-phase0.tar.gz apps/banking/src apps/banking/package.json apps/banking/index.html apps/banking/tsconfig.json apps/banking/tsconfig.node.json apps/banking/vite.config.ts apps/banking/env.d.ts apps/banking/public apps/banking/.env.production packages/ package.json pnpm-workspace.yaml pnpm-lock.yaml infrastructure/docker/nginx-frontend.conf infrastructure/docker/Dockerfile.frontend

# SCP + build + deploy
scp -O /tmp/core-banking-phase0.tar.gz planner@172.69.88.10:/home/planner/
ssh planner@172.69.88.10 "mkdir -p /home/planner/build-phase0-banking && cd /home/planner/build-phase0-banking && tar xzf /home/planner/core-banking-phase0.tar.gz && mkdir -p docs-site && docker build -f infrastructure/docker/Dockerfile.frontend --build-arg APP_NAME=banking --build-arg APP_PATH=apps/banking -t 172.69.88.10:5000/cc/core-banking-ui:v20260308-phase0 . && docker push 172.69.88.10:5000/cc/core-banking-ui:v20260308-phase0 && docker service update --image 172.69.88.10:5000/cc/core-banking-ui:v20260308-phase0 cc-core_banking-ui"
```

**Step 3: Verify both services converge**

```bash
ssh planner@172.69.88.10 "docker service ps cc-core_core-admin-ui --filter desired-state=running --format '{{.CurrentState}} {{.Image}}'"
ssh planner@172.69.88.10 "docker service ps cc-core_banking-ui --filter desired-state=running --format '{{.CurrentState}} {{.Image}}'"
```
Expected: Both running with `v20260308-phase0` tag.

**Step 4: Smoke test**

- Admin: Login → navigate to Assembleias menu item → verify view loads
- Admin: Navigate to SISBAJUD list → click any order → verify detail loads (no 404)
- Admin: Navigate to Emprestimos → verify "Novo Emprestimo" button is disabled
- Banking: Navigate to /auth/activate?token=test → verify PrimeVue card renders (token error is OK)
