If I try to save by explicitly creating a new ActivityInvoiceMap (tried this as it does not seem to be doing it "magically")
excerpt from InvoicesController.php:
                    $activity_invoice_map = new ActivityInvoiceMap();
                    $activity_detail = Activities::findFirstByactivity_id($invoice_rows[$n]['activity_id']);
                    $activity_detail->activity_state = 'Billed';
                    if (!$activity_detail->save()) {
                        foreach ($activity_detail->getMessages() as $message) {
                            $this->flash->error($message);
                        }
                    }
and further down:
            $invoice = new Invoices();
            $activity_invoice_map = new ActivityInvoiceMap();
            $invoice->client_id = $details['client_id'];
            $invoice->invoice_number = $details['invoice_number'];
            $invoice->invoice_description = 'Invoice '.$details['invoice_number'].' - '.date('M j Y');
            $invoice->invoice_created = date('Y-m-d H:i:s');
            $invoice->invoice_submitted = date('Y-m-d H:i:s');
            $invoice->invoice_edited = date('Y-m-d H:i:s');
            $invoice->invoice_status = 'Submitted';
            $invoice->invoice_due = date('Y-m-d H:i:s', strtotime((date('Y-m-d H:i:s').'+ 5 days')) );
            $invoice->invoice_amount = preg_replace("/([^0-9\\.])/i", "", ($details['invoice_detail_total_billable']));
            if (!$invoice->create()) {
                foreach ($invoice->getMessages() as $message) {
                    $this->flash->error($message);
                }
If does not like that - it says it can't find the model.  I tried using save(), update(), create() as per docs to no effect.
The schema was designed to be beyond obvious, e.g. in invoices there's invoice_id and in activities there's activity_id, in the map table it's referred to as exactly the same, activity_id and invoice_id. It's an old schema. Here are the models, a lot of this is just standard generation from Phalcon Tools.
ActivityInvoiceMap.php
<?php
namespace App\Models;
class ActivityInvoiceMap extends \Phalcon\Mvc\Model
{
    /**
     *
     * @var integer
     */
    public $activity_invoice_map;
    /**
     *
     * @var integer
     */
    public $activity_id;
    /**
     *
     * @var integer
     */
    public $invoice_id;
    /**
     * Initialize method for model.
     */
    public function initialize()
    {
        $this->belongsTo('activity_id', 'Activities', 'activity_id', array('alias' => 'Activities'));
        $this->belongsTo('invoice_id', 'Invoices', 'invoice_id', array('alias' => 'Invoices'));
    }
    /**
     * Returns table name mapped in the model.
     *
     * @return string
     */
    public function getSource()
    {
        return 'activity_invoice_map';
    }
    /**
     * Allows to query a set of records that match the specified conditions
     *
     * @param mixed $parameters
     * @return ActivityInvoiceMap[]
     */
    public static function find($parameters = null)
    {
        return parent::find($parameters);
    }
    /**
     * Allows to query the first record that match the specified conditions
     *
     * @param mixed $parameters
     * @return ActivityInvoiceMap
     */
    public static function findFirst($parameters = null)
    {
        return parent::findFirst($parameters);
    }
}
Invoices.php
<?php
namespace App\Models;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset\Simple as Resultset;
use Phalcon\Mvc\Model\Manager as ModelsManager;
use App\Models\Activities;
use App\Models\ActivityInvoiceMap;
use App\Models\Clients;
use App\Models\Projects;
class Invoices extends \Phalcon\Mvc\Model
{
    /**
     *
     * @var integer
     */
    public $invoice_id;
    /**
     *
     * @var integer
     */
    public $client_id;
    /**
     *
     * @var string
     */
    public $invoice_number;
    /**
     *
     * @var string
     */
    public $invoice_description;
    /**
     *
     * @var string
     */
    public $invoice_created;
    /**
     *
     * @var string
     */
    public $invoice_edited;
    /**
     *
     * @var string
     */
    public $invoice_submitted;
    /**
     *
     * @var string
     */
    public $invoice_status;
    /**
     *
     * @var string
     */
    public $invoice_paid;
    /**
     *
     * @var string
     */
    public $invoice_due;
    /**
     *
     * @var double
     */
    public $invoice_amount;
    /**
     * Initialize method for model.
     */
    public function initialize()
    {
        $this->hasMany('invoice_id', 'ActivityInvoiceMap', 'invoice_id', array('alias' => 'ActivityInvoiceMap'));
        $this->belongsTo('client_id', 'Clients', 'client_id', array('alias' => 'Clients'));
    }
    /**
     * Returns table name mapped in the model.
     *
     * @return string
     */
    public function getSource()
    {
        return 'invoices';
    }
    /**
     * Allows to query a set of records that match the specified conditions
     *
     * @param mixed $parameters
     * @return Invoices[]
     */
    public static function find($parameters = null)
    {
        return parent::find($parameters);
    }
    /**
     * Allows to query the first record that match the specified conditions
     *
     * @param mixed $parameters
     * @return Invoices
     */
    public static function findFirst($parameters = null)
    {
        return parent::findFirst($parameters);
    }
    public static function getHourlyBilling($client_id, $params = null)
    {
        if (is_numeric($client_id)) {
            $sql = 'select client_companyname, client_contact_address, client_contact_email, client_id, activity_id, project_id, project_name, project_currency, activity_description, activity_hourly, activity_start,
                    activity_end, time, round((time * activity_hourly),2) as billable from (
                    select c.client_companyname as client_companyname, c.client_contact_email as client_contact_email, c.client_id as client_id, activity_id, project_id, p.project_name as project_name,
                    p.project_currency as project_currency, activity_description, activity_hourly,
                    concat_ws(\'<br />\',`client_contact`,`client_billing_street`,`client_billing_city`,`client_billing_state`,`client_billing_postcode`) as client_contact_address,
                    convert_tz(activity_start,\'UTC\',\'America/New_York\') as activity_start, convert_tz(activity_end,\'UTC\',\'America/New_York\') as activity_end,
                    (hour(timediff(`activity_end`,`activity_start`))+round((ceil(minute(timediff(`activity_end`,`activity_start`)))*0.016666667),2)) as time
                    from activities a
                    join projects p using (project_id)
                    join clients c using (client_id)
                    where activity_state = "Unbilled" and activity_type not like "Fixed" and activity_type not like "Subcontractor" and activity_start is not null and activity_end is not null
                   and c.client_id = '.$client_id.' ) sq1 where time > 0 order by sq1.activity_start';
            $invoice = new Invoices();
            $unbilled = (new Resultset(null, $invoice, $invoice->getReadConnection()->query($sql,$params)))->toArray();
            return $unbilled;
        } else {
            return false;
        }
    }
    public static function getFixedCostBilling($client_id, $params = null)
    {
        if (is_numeric($client_id))
        {
            $invoice = new Invoices();
            $sql = 'select c.client_companyname, c.client_id, c.client_contact_email, activity_id, project_id, p.project_name, p.project_currency, activity_description, activity_fixed_amount as billable,
                    concat_ws(\'<br />\',`client_contact`,`client_billing_street`,`client_billing_city`,`client_billing_state`,`client_billing_postcode`) as client_contact_address
                    from activities a
                    join projects p using (project_id)
                    join clients c using (client_id)
                    where activity_state = "Unbilled" and activity_type like "Fixed" and activity_type not like "Subcontractor" and activity_fixed_amount != 0 and c.client_id='.$client_id.' order by activity_start';
            $unbilled = (new Resultset(null, $invoice, $invoice->getReadConnection()->query($sql,$params)))->toArray();
            return $unbilled;
            }
            else
            {
                return false;
            }
    }
    public static function getLast()
    {
        $invoice = new Invoices();
        $sql = 'select max(invoice_id) as max from invoices';
        $params = new \StdClass();
        $lastId = (new Resultset(null, $invoice, $invoice->getReadConnection()->query($sql,$params)))->toArray();
        return $lastId;
    }
}