# Backend Finance App - Contrato para IA

Documento para agentes de IA consumirem ou modificarem o backend em `back/`.

## Identidade

```yaml
project: finance-app
backend_path: back/
entrypoint: back/index.php
router: back/App/Routers/Routers.php
language: PHP
autoload:
  namespace: App\
  path: back/App/
database:
  engine: MariaDB/MySQL via PDO
  analyzed_database: financeiro
  local_host_from_windows: 127.0.0.1:3306
  app_host_from_container: value from DB_SERVER, usually mysql
content_type: application/json; charset=utf-8
cors:
  origin: "*"
  methods: GET, POST, PATCH, PUT, DELETE, OPTIONS
```

## Dependencies

```yaml
composer:
  bramus/router: 1.6.1
  vlucas/phpdotenv: 5.6.3
  firebase/php-jwt: 6.11.1
```

## Runtime behavior

```yaml
index_php:
  file: back/index.php
  behavior:
    - require ./vendor/autoload.php
    - set CORS headers
    - respond 200 to OPTIONS
    - set JSON content-type
    - call App\Routers\Routers::execute()

connection:
  file: back/App/Models/Connection.php
  env_keys:
    - DB_SERVER
    - DB_NAME
    - DB_USER
    - DB_PASS
  dsn: mysql:host={DB_SERVER};dbname={DB_NAME};charset=utf8
  pdo_attrs:
    ERRMODE: EXCEPTION
    EMULATE_PREPARES: false

auth:
  login_route: POST /login
  middleware_file: back/App/Middlewares/AuthMiddleware.php
  current_router_enforcement: false
  reason: Routers.php has AuthMiddleware before hooks commented out.
  jwt_secret_env: SECRET_KEY
  jwt_exp_seconds: 2592000
  token_payload:
    erro: false
    iat: unix_time
    exp: unix_time_plus_30_days
    data:
      id: usuarios.id
      nome: usuarios.nome
      email: usuarios.email
```

If auth hooks are enabled later:

```http
Authorization: Bearer <token>
Content-Type: application/json
```

Current controllers do not derive `id_usuario` from JWT. Send `id_usuario` explicitly where required.

## Routes

```yaml
public:
  - method: GET
    path: /
    response: Home
  - method: POST
    path: /
    response: Home
  - method: POST
    path: /login
    controller: UsuariosController.login
  - method: GET
    path: /payments-forms
    controller: FormasPagamentosController.find

root:
  usuarios:
    - POST /root/usuarios/buscar
    - POST /root/usuarios/criar
    - POST /root/usuarios/listar
    - PUT /root/usuarios/atualizar/{id}

private:
  categorias:
    - POST /private/categorias/buscar
    - POST /private/categorias/criar
    - POST /private/categorias/listar
    - PUT /private/categorias/atualizar/{id}
  cards:
    - POST /private/cards/criar
    - POST /private/cards/listar
    - PUT /private/cards/atualizar/{id}
  tarefas:
    - POST /private/tarefas/criar
    - POST /private/tarefas/listar
    - PUT /private/tarefas/atualizar/{id}
  contas:
    - POST /private/contas/buscar
    - POST /private/contas/criar
    - POST /private/contas/listar
    - PUT /private/contas/atualizar/{id}
  contas_bancarias:
    - POST /private/contas-bancarias/buscar
    - POST /private/contas-bancarias/criar
    - POST /private/contas-bancarias/listar
    - PUT /private/contas-bancarias/atualizar/{id}
  resumos:
    - POST /private/resumos/geral
    - POST /private/resumos/anual
  notas:
    - POST /private/notas/criar
    - POST /private/notas/listar
    - PUT /private/notas/atualizar/{id}
```

No `DELETE` route is published.

No `/tabela` DataTable route is published, although `searchDataTable()` exists in controllers.

## Standard controller contract

```yaml
controller_base:
  file: back/App/Controllers/ControllerBase.php
  abstract_methods:
    - findOnly(array $data)
    - find(array $data)
    - update(array $data)
    - create(array $data)

findOnly:
  input:
    filter: object optional
    limit: integer optional
    offset: integer optional
    order: object optional
    date_ranger: object optional but ignored by current models
    includes: object optional
  behavior:
    - merge filter with {"deletado": "N"}
    - call model.find()
    - process includes if present
  returns: array of records

find:
  input: same as findOnly
  behavior:
    - total = model.totalCount(filter + deletado=N)
    - data = findOnly(input)
  output:
    total: integer
    data: array

create:
  behavior:
    - validate required DB columns from model.structureTable
    - validate unique DB columns
    - validate relation min_count where configured
    - call model.insert(data)
  output: created record

update:
  behavior:
    - load current model by route id
    - update only sent fields that exist in DB table
    - for senha, hash with password_hash PASSWORD_BCRYPT
    - optional relation mutation blocks: create/update/delete
  output: updated record
```

## Standard model contract

```yaml
base_model:
  file: back/App/Models/BaseModel.php
  common_methods:
    - buildFilterConditions(filters, conditions, bindings)
    - getStructureTable(PDO)
    - verifyUniqueKey(key, value)
    - dataTable(params, callback)
  abstract_methods:
    - totalCount(filters = [])
    - findById(id)
    - find(filters = [], limit = null, offset = null, order = [])
    - current()
    - insert(data)
    - update(data)
    - delete()
    - getTableName()

insert:
  skips:
    - primary_key columns
    - TIMESTAMP columns
    - fields absent from input
    - empty string values
  side_effects:
    - hashes senha if present
    - inserts relationConfig children when relation.ignore != true
    - reloads record using findById(newId)

update:
  skips:
    - id
    - fields not present in table structure
  where: id = current attributes.id

delete:
  type: physical
  sql: DELETE FROM table WHERE id = :id
  route_published: false
```

## Database schema

Analyzed tables:

```yaml
tables:
  usuarios:
    rows: 2
    columns:
      id: int primary auto_increment
      nome: varchar(100) not_null
      email: varchar(100) not_null unique
      senha: varchar(200) not_null default_empty
      deletado: char(1) default N
      dthr_registro: timestamp default current_timestamp
      dthr_atualizacao: timestamp default current_timestamp on_update

  categorias:
    rows: 12
    columns:
      id: int primary auto_increment
      id_usuario: int not_null fk usuarios.id
      descricao: varchar(255) not_null
      deletado: char(1) default N
      dthr_criacao: timestamp default current_timestamp
      dthr_registro: timestamp default current_timestamp on_update

  contas:
    rows: 182
    columns:
      id: int primary auto_increment
      id_usuario: int not_null fk usuarios.id
      id_forma_pagamento: int nullable fk formas_pagamentos.id
      id_conta_bancaria: int nullable fk contas_bancarias.id
      id_categoria: int nullable fk categorias.id
      token_unico: varchar(100) not_null default_empty
      titulo: varchar(100) not_null
      descricao: text nullable
      valor: decimal(15,2) not_null
      valor_pago: decimal(15,2) nullable
      tipo: char(1) not_null
      status: char(2) not_null default PN
      vencimento: date not_null
      parcelas: int nullable
      num_parcela: int nullable
      conta_agrupada: char(1) nullable default N
      id_conta_resultante: int nullable
      id_original: int nullable
      frequencia: int nullable
      valor_total_parcelas: decimal(15,2) nullable
      modo_pagamento: char(1) nullable
      data_pagamento: date nullable
      deletado: char(1) nullable default N
      dthr_registro: timestamp default current_timestamp
      dthr_atualizacao: timestamp default current_timestamp on_update
    distinct_values:
      tipo: [D, R]
      status: [PA, PE]
      deletado: [N, S]
      conta_agrupada: [N]

  contas_bancarias:
    rows: 1
    columns:
      id: int primary auto_increment
      id_usuario: int not_null fk usuarios.id
      descricao: varchar(100) not_null
      observacoes: text not_null
      saldo: double(15,2) not_null default 0.00
      principal: char(1) not_null default N
      deletado: char(1) not_null default N
      dthr_registro: timestamp default current_timestamp
      dthr_atualizacao: timestamp default current_timestamp on_update

  formas_pagamentos:
    rows: 5
    columns:
      id: int primary auto_increment
      descricao: varchar(100) not_null
      tipo: char(1) not_null default V
      deletado: char(1) not_null default N
    rows_known:
      - {id: 1, descricao: DINHEIRO, tipo: V}
      - {id: 2, descricao: CARTAO DE DEBITO, tipo: V}
      - {id: 3, descricao: CARTAO DE CREDITO, tipo: C}
      - {id: 4, descricao: PIX, tipo: V}
      - {id: 6, descricao: DEBITO EM CONTA, tipo: V}

  kanban_cards:
    rows: 4
    columns:
      id: int primary auto_increment
      id_usuario: int not_null fk usuarios.id
      titulo: varchar(100) not_null
      ordem: int not_null
      deletado: char(1) default N
      dthr_registro: datetime default current_timestamp
      dthr_atualizacao: datetime default current_timestamp

  tarefas:
    rows: 3
    columns:
      id: int primary auto_increment
      id_usuario: int not_null fk usuarios.id
      id_card: int not_null fk kanban_cards.id
      titulo: varchar(100) not_null
      descricao: longtext nullable
      deletado: char(1) default N
      dthr_registro: datetime default current_timestamp
      dthr_atualizacao: datetime default current_timestamp on_update

  notas:
    rows: 1
    columns:
      id: int primary auto_increment
      id_usuario: int not_null fk usuarios.id
      titulo: varchar(255) not_null default_empty
      texto: longtext not_null
      arquivado: char(1) default N
      deletado: char(1) default N
      dthr_registro: timestamp default current_timestamp
      dthr_atualizacao: timestamp default current_timestamp on_update

unexposed_tables:
  lista_compra:
    has_model: false
    has_controller: false
    has_route: false
  lista_compras_itens:
    has_model: false
    has_controller: false
    has_route: false
```

## Relation/include map

Relations observed from `relationConfig` at runtime:

```yaml
usuarios: []

categorias:
  usuarios:
    kind: fk_detected_ignore
    foreign_key: id_usuario
    target: usuarios.id
    controller: UsuariosController
    return_shape_top_level: array

contas:
  usuarios:
    kind: fk_detected_ignore
    foreign_key: id_usuario
    target: usuarios.id
    controller: UsuariosController
  contas_bancarias:
    kind: fk_detected_ignore
    foreign_key: id_conta_bancaria
    target: contas_bancarias.id
    controller: ContasBancariasController
  categorias:
    kind: fk_detected_ignore
    foreign_key: id_categoria
    target: categorias.id
    controller: CategoriasController
  formas_pagamentos:
    db_fk_exists: true
    include_available: false
    reason: BaseModel.structureDatabase maps singular formas_pagamento/table formas_pagamento, but DB/model use formas_pagamentos.

contas_bancarias:
  usuarios:
    kind: fk_detected_ignore
    foreign_key: id_usuario
    target: usuarios.id
    controller: UsuariosController

formas_pagamentos: []

kanban_cards:
  tarefas:
    kind: explicit_has_many
    foreign_key: id_card
    target_model: TarefasModel
    min_count: 0
    return_shape: array
  usuarios:
    kind: fk_detected_ignore
    foreign_key: id_usuario
    target: usuarios.id
    controller: UsuariosController

tarefas:
  usuarios:
    kind: fk_detected_ignore
    foreign_key: id_usuario
    target: usuarios.id
    controller: UsuariosController
  kanban_cards:
    kind: fk_detected_ignore
    foreign_key: id_card
    target: kanban_cards.id
    controller: KanbanCardsController

notas:
  usuarios:
    kind: fk_detected_ignore
    foreign_key: id_usuario
    target: usuarios.id
    controller: UsuariosController
```

Include request shape:

```json
{
  "includes": {
    "relation_name": true,
    "relation_with_options": {
      "filter": {},
      "limit": 10,
      "offset": 0,
      "order": {
        "cols": ["id"],
        "direction": "DESC"
      },
      "includes": {}
    }
  }
}
```

Include examples:

```json
{
  "filter": {
    "id_usuario": 1
  },
  "includes": {
    "categorias": true,
    "contas_bancarias": true,
    "usuarios": true
  }
}
```

```json
{
  "filter": {
    "id_usuario": 1
  },
  "includes": {
    "tarefas": {
      "filter": {
        "deletado": "N"
      },
      "order": {
        "cols": ["id"],
        "direction": "ASC"
      }
    }
  }
}
```

## Filter grammar

Source: `BaseModel::buildFilterConditions()`.

```yaml
request_shape:
  filter: object
  limit: integer optional
  offset: integer optional
  order:
    cols: array<string>
    direction: ASC|DESC

automatic_controller_filter:
  deletado: N
```

Equality:

```json
{
  "filter": {
    "id_usuario": 1,
    "status": "PE"
  }
}
```

LIKE:

```json
{
  "filter": {
    "titulo": {
      "LIKE": "aluguel"
    }
  }
}
```

Operator:

```json
{
  "filter": {
    "valor": {
      "OPERATOR": ">=",
      "VALUE": 100
    }
  }
}
```

Between:

```json
{
  "filter": {
    "vencimento": {
      "BETWEEN": ["2026-07-01", "2026-07-31"]
    }
  }
}
```

IN:

```json
{
  "filter": {
    "status": {
      "IN": ["PE", "PA"]
    }
  }
}
```

NOT IN:

```json
{
  "filter": {
    "id_categoria": {
      "NOT IN": [1, 2]
    }
  }
}
```

Null:

```json
{
  "filter": {
    "data_pagamento": {
      "IS NULL": true
    }
  }
}
```

Not null:

```json
{
  "filter": {
    "id_conta_bancaria": {
      "IS NOT NULL": true
    }
  }
}
```

Logical OR:

```json
{
  "filter": {
    "id_usuario": 1,
    "OR": {
      "titulo": {
        "LIKE": "internet"
      },
      "descricao": {
        "LIKE": "fibra"
      }
    }
  }
}
```

Nested AND/OR:

```json
{
  "filter": {
    "id_usuario": 1,
    "OR": {
      "status": "PE",
      "AND": {
        "tipo": "D",
        "valor": {
          "OPERATOR": ">=",
          "VALUE": 500
        }
      }
    }
  }
}
```

Same-field OR:

```json
{
  "filter": {
    "tipo": {
      "OR": ["D", "R"]
    }
  }
}
```

Complete find request:

```json
{
  "filter": {
    "id_usuario": 1,
    "tipo": "D",
    "status": {
      "IN": ["PE", "PA"]
    },
    "vencimento": {
      "BETWEEN": ["2026-07-01", "2026-07-31"]
    },
    "OR": {
      "titulo": {
        "LIKE": "cartao"
      },
      "descricao": {
        "LIKE": "compra"
      }
    }
  },
  "limit": 25,
  "offset": 0,
  "order": {
    "cols": ["vencimento", "titulo"],
    "direction": "ASC"
  }
}
```

Security notes for agents:

```yaml
unsafe_inputs:
  - filter field names are interpolated into SQL
  - order cols are interpolated into SQL
  - OPERATOR is interpolated into SQL
recommendation:
  - use only known DB column names
  - use only whitelisted operators: ["=", "<>", ">", ">=", "<", "<="]
```

## Resource contracts and examples

### Login

```http
POST /login
Content-Type: application/json
```

```json
{
  "email": "usuario@email.com",
  "senha": "123456"
}
```

Success:

```json
{
  "id": "1",
  "nome": "Usuario",
  "email": "usuario@email.com",
  "token": "jwt..."
}
```

### Usuarios

Create:

```http
POST /root/usuarios/criar
Content-Type: application/json
```

```json
{
  "nome": "Maria Silva",
  "email": "maria@example.com",
  "senha": "123456"
}
```

List:

```http
POST /root/usuarios/listar
Content-Type: application/json
```

```json
{
  "filter": {
    "nome": {
      "LIKE": "Maria"
    }
  },
  "order": {
    "cols": ["nome"],
    "direction": "ASC"
  }
}
```

Update:

```http
PUT /root/usuarios/atualizar/1
Content-Type: application/json
```

```json
{
  "nome": "Maria Souza"
}
```

### Categorias

Create:

```http
POST /private/categorias/criar
Content-Type: application/json
```

```json
{
  "id_usuario": 1,
  "descricao": "Moradia"
}
```

List:

```http
POST /private/categorias/listar
Content-Type: application/json
```

```json
{
  "filter": {
    "id_usuario": 1,
    "descricao": {
      "LIKE": "Mor"
    }
  },
  "order": {
    "cols": ["descricao"],
    "direction": "ASC"
  },
  "includes": {
    "usuarios": true
  }
}
```

Search caveat:

```yaml
route: POST /private/categorias/buscar
status: published_but_buggy
actual_model_behavior:
  - reads $_REQUEST["id_conta"]
  - searches usuarios table by nome/email/login
  - filters usuarios.id_conta, but usuarios table has no id_conta column in analyzed schema
recommended_use: use /private/categorias/listar with LIKE on descricao
```

### Contas bancarias

Create:

```http
POST /private/contas-bancarias/criar
Content-Type: application/json
```

```json
{
  "id_usuario": 1,
  "descricao": "Carteira principal",
  "observacoes": "Conta usada no dia a dia",
  "saldo": 1500.00,
  "principal": "S"
}
```

List:

```json
{
  "filter": {
    "id_usuario": 1,
    "principal": "S"
  },
  "includes": {
    "usuarios": true
  }
}
```

Update:

```http
PUT /private/contas-bancarias/atualizar/1
Content-Type: application/json
```

```json
{
  "saldo": 1750.00
}
```

Search caveat:

```yaml
route: POST /private/contas-bancarias/buscar
status: published_but_buggy
actual_model_behavior:
  - reads $_REQUEST["id_conta"]
  - searches usuarios table by nome/email/login
  - filters usuarios.id_conta, but usuarios table has no id_conta column in analyzed schema
recommended_use: use /private/contas-bancarias/listar with filters
```

### Formas de pagamento

```http
GET /payments-forms
```

Expected shape:

```json
{
  "total": 5,
  "data": [
    {
      "id": "1",
      "descricao": "DINHEIRO",
      "tipo": "V",
      "deletado": "N"
    }
  ]
}
```

No create/update/delete route is published for this resource.

### Contas

Core domain values:

```yaml
tipo:
  D: despesa
  R: receita
status:
  PE: pendente
  PA: pago
```

Create single bill:

```http
POST /private/contas/criar
Content-Type: application/json
```

```json
{
  "id_usuario": 1,
  "id_categoria": 2,
  "titulo": "Aluguel",
  "descricao": "Aluguel de julho",
  "valor": 1200.00,
  "tipo": "D",
  "status": "PE",
  "vencimento": "2026-07-10"
}
```

Controller side effect:

```yaml
ContasController.create:
  always_sets:
    token_unico: date("YmdHis") + microtime(true)
  branch_order:
    - if frequencia numeric: recurring branch
    - else if parcelas numeric: installment branch
    - else: single insert
```

Create recurring bills:

```json
{
  "id_usuario": 1,
  "id_categoria": 2,
  "titulo": "Assinatura",
  "descricao": "Assinatura mensal",
  "valor": 59.90,
  "tipo": "D",
  "status": "PE",
  "vencimento": "2026-07-05",
  "frequencia": 6
}
```

Recurring behavior:

```yaml
for_i: 0 to frequencia - 1
fields_set_per_copy:
  num_parcela: i + 1
  vencimento: base_vencimento + i months
  valor: unchanged
  token_unico: same token for all generated rows
response:
  main_object: first generated bill
  contas_geradas: all generated bills
```

Create installment bills:

```json
{
  "id_usuario": 1,
  "id_categoria": 3,
  "titulo": "Notebook",
  "descricao": "Compra parcelada",
  "valor": 3000.00,
  "tipo": "D",
  "status": "PE",
  "vencimento": "2026-07-15",
  "parcelas": 3
}
```

Installment behavior:

```yaml
valorConta: input.valor
valorParcela: valorConta / parcelas
for_i: 0 to parcelas - 1
fields_set_per_copy:
  valor_total_parcelas: valorConta
  valor: valorParcela
  num_parcela: i + 1
  vencimento: base_vencimento + i months
  token_unico: same token for all generated rows
response:
  main_object: first generated bill
  contas_geradas: all generated bills
do_not_send_with_frequencia: true
```

List bills due in period:

```http
POST /private/contas/listar
Content-Type: application/json
```

```json
{
  "filter": {
    "id_usuario": 1,
    "vencimento": {
      "BETWEEN": ["2026-07-01", "2026-07-31"]
    }
  },
  "order": {
    "cols": ["vencimento"],
    "direction": "ASC"
  },
  "includes": {
    "categorias": true,
    "contas_bancarias": true,
    "usuarios": true
  }
}
```

Pay bill:

```http
PUT /private/contas/atualizar/123
Content-Type: application/json
```

```json
{
  "status": "PA",
  "valor_pago": 1200.00,
  "id_forma_pagamento": 1,
  "data_pagamento": "2026-07-10",
  "id_conta_bancaria": 1
}
```

Payment validation:

```yaml
trigger: current.status != data.status and data.status == PA
required:
  valor_pago: present and > 0
  id_forma_pagamento: present
default_if_missing:
  data_pagamento: date("Y-m-d")
```

Payment side effects:

```yaml
if result.valor_pago < current.valor:
  create_conta_resultante:
    id_usuario: current.id_usuario
    token_unico: current.token_unico
    titulo: "Resto do pagamento da conta: " + current.descricao
    tipo: current.tipo
    valor: current.valor - result.valor_pago
    vencimento: +1 month from server date
    id_original: current.id
    status: PE
  update_original:
    id_conta_resultante: conta_resultante.id

if data.id_conta_bancaria present:
  load bank account by:
    id: data.id_conta_bancaria
    id_usuario: current.id_usuario
    deletado: N
  update saldo:
    if result.tipo == D: saldo = saldo - result.valor_pago
    else: saldo = saldo + result.valor_pago
```

Search caveat:

```yaml
route: POST /private/contas/buscar
status: published_but_buggy
actual_model_behavior:
  - reads $_REQUEST["id_conta"]
  - searches usuarios table by nome/email/login
  - filters usuarios.id_conta, but usuarios table has no id_conta column in analyzed schema
recommended_use: use /private/contas/listar with filters
```

### Cards

Create:

```http
POST /private/cards/criar
Content-Type: application/json
```

```json
{
  "id_usuario": 1,
  "titulo": "A fazer",
  "ordem": 1
}
```

Create with tarefas:

```json
{
  "id_usuario": 1,
  "titulo": "A fazer",
  "ordem": 1,
  "tarefas": [
    {
      "id_usuario": 1,
      "titulo": "Conferir contas do mes",
      "descricao": "Validar despesas pendentes"
    }
  ]
}
```

Generated child behavior:

```yaml
parent_model: KanbanCardsModel
relation: tarefas
injected_into_child:
  id_card: new kanban_cards.id
```

List with tarefas:

```json
{
  "filter": {
    "id_usuario": 1
  },
  "includes": {
    "tarefas": true
  },
  "order": {
    "cols": ["ordem"],
    "direction": "ASC"
  }
}
```

### Tarefas

Create:

```http
POST /private/tarefas/criar
Content-Type: application/json
```

```json
{
  "id_usuario": 1,
  "id_card": 1,
  "titulo": "Pagar fornecedor",
  "descricao": "Vence hoje"
}
```

List:

```json
{
  "filter": {
    "id_usuario": 1,
    "id_card": 1
  },
  "includes": {
    "kanban_cards": true,
    "usuarios": true
  }
}
```

### Notas

Create:

```http
POST /private/notas/criar
Content-Type: application/json
```

```json
{
  "id_usuario": 1,
  "titulo": "Ideias",
  "texto": "Anotar melhorias do dashboard",
  "arquivado": "N"
}
```

List active notes:

```json
{
  "filter": {
    "id_usuario": 1,
    "arquivado": "N"
  },
  "order": {
    "cols": ["dthr_registro"],
    "direction": "DESC"
  },
  "includes": {
    "usuarios": true
  }
}
```

Archive:

```http
PUT /private/notas/atualizar/1
Content-Type: application/json
```

```json
{
  "arquivado": "S"
}
```

## Resumos

Resumo geral:

```http
POST /private/resumos/geral
Content-Type: application/json
```

```json
{
  "inicio": "2026-07-01",
  "fim": "2026-07-31",
  "usuario": 1
}
```

Validation:

```yaml
required: [inicio, fim, usuario]
query_logic:
  table: contas
  period_field: vencimento
  period_start: inicio + " 00:00:00"
  period_end: fim + " 23:59:59"
  forced_filter:
    id_usuario: usuario
    deletado: N
  aggregates:
    totalPagar: SUM valor where tipo D
    totalFaltaPagar: SUM valor where tipo D and status PE
    totalPago: SUM valor where tipo D and status PA
    totalReceber: SUM valor where tipo R
    totalFaltaReceber: SUM valor where tipo R and status PE
    totalRecebido: SUM valor where tipo R and status PA
    saldo: totalReceber - totalPagar
```

Resumo anual:

```http
POST /private/resumos/anual
Content-Type: application/json
```

```json
{
  "ano": 2026,
  "usuario": 1
}
```

Behavior:

```yaml
required: [ano, usuario]
returns: 12 array items
months:
  - janeiro
  - fevereiro
  - marco
  - abril
  - maio
  - junho
  - julho
  - agosto
  - setembro
  - outubro
  - novembro
  - dezembro
per_month:
  totalPagar: SUM valor where tipo D in month
  totalReceber: SUM valor where tipo R in month
  saldo: totalReceber - totalPagar
```

## DataTable code path

```yaml
status: implemented_but_unrouted
model_method: BaseModel.dataTable(params, callback)
controller_method: searchDataTable(data)
published_routes: []
```

Potential request if route is added:

```json
{
  "draw": 1,
  "start": 0,
  "length": 10,
  "search": {
    "value": "global"
  },
  "order": [
    {
      "column": 0,
      "dir": "asc"
    }
  ],
  "columns": [
    {
      "data": "titulo",
      "name": "titulo",
      "searchable": true,
      "orderable": true,
      "search": {
        "value": ""
      }
    }
  ]
}
```

Known DataTable caveats:

```yaml
related_column_join_bug:
  code_prefixes_related_table_with: axpem_
  current_db_uses_prefix: false
formatData_copied_from_usuarios:
  risk: expects id, nome, email, status in several models
```

## Known defects and caveats

```yaml
auth:
  issue: /private and /root are not actually protected
  file: back/App/Routers/Routers.php
  reason: AuthMiddleware hooks commented

search_routes:
  affected:
    - /root/usuarios/buscar
    - /private/categorias/buscar
    - /private/contas/buscar
    - /private/contas-bancarias/buscar
  issue:
    - methods read $_REQUEST["id_conta"]
    - methods filter usuarios.id_conta
    - usuarios.id_conta does not exist in analyzed schema
  workaround: use /listar with filter LIKE

notas_search:
  route_published: false
  model_issue_if_used: NotasModel.search references notas.conteudo but DB field is texto

formas_pagamentos_include:
  issue: contas FK exists, but BaseModel structureDatabase does not map plural table
  workaround: fetch /payments-forms separately or fix map

date_ranger:
  read_by_controllers: true
  implemented_by_current_find_models: false

delete:
  model_delete_type: physical
  route_published: false
  recommended_soft_delete: update deletado to S

lista_compra:
  db_tables_exist: true
  api_exposed: false

security:
  field_names_interpolated_into_sql: true
  operator_interpolated_into_sql: true
  use_trusted_column_names_only: true
```

## Agent usage rules

```yaml
when_consuming_api:
  - prefer /listar over every /buscar route in current code
  - always send id_usuario for user-owned resources
  - add filter.id_usuario to prevent cross-user data mixing
  - use deletado=S update for soft delete behavior
  - do not rely on Authorization being enforced until router hooks are enabled
  - do not request include formas_pagamentos from contas in current code
  - do not send frequencia and parcelas together
  - when paying a conta, send status=PA, valor_pago, id_forma_pagamento, and optionally id_conta_bancaria

when_modifying_backend:
  - update Routers.php when adding public API surface
  - fix AuthMiddleware hooks before assuming private routes are private
  - fix BaseModel.structureDatabase formas_pagamentos mapping before documenting include
  - review copied search methods before exposing buscar endpoints
  - prefer allowlists for filter/order fields before accepting untrusted clients
  - keep schema-driven validation behavior in mind: DB not-null columns define required payload fields
```
