# Metodología de Trabajo

## Scrum

La metodología de trabajo que adoptamos es Scrum. Si aún no estás familiarizado con la metodología vas a encontrar mucha documentación online. No obstante, te recomendamos profundices en los fundamentos de la metodología, y para eso te proponemos el libro "Scrum: El arte de hacer el doble de trabajo en la mitad de tiempo" de Jeff Sutherland.

{% embed url="<https://books.google.com.py/books/about/Scrum.html?id=XMqLDwAAQBAJ&printsec=frontcover&source=kp_read_button&redir_esc=y#v=onepage&q&f=false>" %}

## Sprints

Nuestros sprints duran una semana, y tienen una carga de 50 pts. por desarrollador. El 30% de este cupo se reserva durante la planificación para el desarrollo fuera de roadmap o casos de soporte que involucre desarrollo.

## Reuniones de Planificación

Las reuniones de restrospectiva y planificación de Scrum se realizan los días lunes por la mañana. Participan de esta reunión todos los desarrolladores y un representante del equipo de operaciones en rol de *Product Owner*.

En estas reuniones determinamos los *stories* fuera de roadmaps que serán incluídos en el sprint.

## Daily Standups

De lunes a jueves se realizan los *daily standups* de Scrum. Es una reunión que normalmente no dura más de 5 minutos y requiere de la participación de todos los desarrolladores, un representante del equipo de operaciones en rol de *Product Owner* y el *Scrum Master*.

## Pipelines

Pipelines es un módulo de BIMS para gestión de tareas. En él se pueden crear tickets, determinar sus costos, asignar responsables, documentar su ejecución y automatizar la comunicación con las partes involucradas.

{% embed url="<https://youtu.be/ADDbuDv3C2I>" %}

Todos los *sprints* y *stories* se configuran en un Pipeline de BIMS creado para el efecto:

{% embed url="<https://tiva.bims.app/issues/index/5>" %}

Este Pipeline tiene 4 estados:

1. **Pendiente:** Aquí están los tickets que aún no procesaste.
2. **Procesando:** Pasá a este estado los tickets que estás procesando.
3. **Testing:** Una vez hayas concluído el procesamiento de un ticket, pasalo a este estado.
4. **Resuelto:** Los tickets se pasan a este estado cuando el *Product Owner* los haya validado. No te corresponde a vos o a otro desarrollador pasar tickets a este estado.


# Modalidad de Trabajo

## Remote Office Durante la Cuarentena

El trabajo antes del inicio de la cuarentena sanitaria se desarrollaba de forma presencial. Desde el inicio de la crisis sanitaria hemos optado por trabajar en modo de *remote office*. Si bien, la fase de cuarentena actual ya nos permite volver a adoptar el trabajo presencial, hemos decidido mantener la modalidad remota para no comprometer la salud del equipo y de sus familias, y viendo también el buen resultado del trabajo remoto.

Cuando sea seguro, nuestra idea es volver al trabajo presencial con determinadas libertades para el trabajo remoto.

## La Oficina

No obstante, la oficina sigue hoy abierta y toda su infraestructura disponible para el que quiera utilizarla.

## Check-In / Check-Out

Es recomendable que aún en la modalidad remota, estructuremos nuestros horarios y nos permitamos desconectarnos en hora al final de las jornadas. Informá a todo el quipo en el grupo de whatsapp de Soporte cuando comenzás y dejás de estar disponible.


# Fuentes y Versiones

## Control de Versiones

Para el control de versiones de desarrollo de BIMS empleamos **GIT** y los repositorios están en Github.&#x20;


# Tu Entorno de Desarrollo

## Configurá un Entorno de Desarrollo

Hacé checkout del código de BIMS en tu estación de trabajo y creá ahí tu ambiente de desarrollo.

Independientemente del sistema operativo que utilices en tu estación de trabajo, instalá lo siguiente:

{% tabs %}
{% tab title="Servidor de Base de Datos" %}

### PostgreSQL

#### Versiones

9.1 \~ 9.6
{% endtab %}

{% tab title="Servidor Web" %}

### Apache

#### Versiones

Apache 2

#### Notas de Instalación

Luego de instalar Apache, asegurate de reemplaazar en el archivo de configuración `httpd.conf` o `apache2.conf` todas las entradas de:

```
Allow Override None
```

por

```
Allow Override All
```

{% endtab %}

{% tab title="Intérpetes" %}

### PHP

#### Versiones

5.6

#### Notas de Instalación

Asegurate de instalar también los módulos

```
php-pgsql
php-xml
php-gd
```

{% endtab %}

{% tab title="Fuentes" %}

### BIMS

#### Notas de Instalación

Hacé checkout del código fuente de BIMS. Copiá el código fuente de BIMS a un directorio público del Apache.

Creá y configurá el archivo configuración de acceso a la Base de Datos de CakePHP:

```
app/config/database.php
```

{% hint style="info" %}
Este archivo no está incluído en el repositorio SVN pues varía para cada instalación de BIMS. Asegurate igualmente de no incluirlo a tu working copy de SVN ni hacer commit de este archivo.
{% endhint %}

Hay una ejemplo de configuración en el archivo:

```
app/config/database-sample.php
```

{% endtab %}
{% endtabs %}

## IDE

Usá el IDE de tu preferencia para programar.


# Cómo Subir tus Cambios

## Consideraciones Previas

#### Actualizá tu Working Copy

Antes de iniciar un sprint, creá un branch de desarrollo para vos a partir del branch "beta". Normalizamos los nombres de branches de desarrollo así: sprint-\<numero-de-sprint>-\<desarrollador>

#### Creá tus Scripts SQL

Si realizaste cambios en el esquema de la base de datos, copiá los scripts correspondientes en un archivo de manera que donde se actualice tu versión se puedan aplicar, además del código fuente, los cambios en el esquema de la base de datos.

Ubicá el archivo en el directorio `/sql/sprints` en la raiz del working copy y nombralo respetando la siguiente normalización: sprint\_`<sprint>`\_`<developer>`\_`<numero-commit>`.sql. Donde:

`sprint`: Es el número de Sprint de SCRUM actual con tres dígitos.

`developer`: Es tu nombre, en un palabra y en minúsculas.

`numero-commit`: Es el número de SQL que subís en el mismo sprint. Si durante un mismo sprint, por algún motivo, hacés más de un commit con cambios en el esquema de base datos, establecé aquí el número de archivo que subís por sprint, comenzando en 1.

**Ejemplo**: sprint\_072\_enrique\_1.sql.

## Cuándo Subir tus Cambios

Siempre que hayas cerrado tickets de desarrollo y establizado la versión de tu working copy, subí tus cambios al cierre de cada sprint y hacé un push request al branch "beta".


# Diseño de Base de Datos

## Normalizaciones

### Nombres de Tablas

Definí todos los nombres de tablas en plural y en inglés. Ej.: `sales`, `products`, `contacts`.

### Claves Primarias

El campo de clave primaria de todas las tablas siempre debe llamarse `id` y ser del tipo `bigserial`, que es básicamente un BIGINT ligado a un sequence.

Aunque un diseño normalizado correctamente demande una clave primaria compuesta, como el caso de las tablas de relación mucho a mucho, mantené la recla del PK `id`. Esto viabiliza la automatización de muchas funciones de modelado del framework que usamos constantemente.

### Nombres de Claves Foráneas

Las claves foráneas de llamarse siempre como el nombre de la tabla referenciada en singular y con el sufijo `_id`. Ejemplos: `sale_id`, `product_id`, `contact_id`.


# La Interfaz de Usuario

## El Esquema Básico de los ABMs

El esquema básico de los ABMs en BIMS está compuesto de los siguientes elementos:

1. Una Lista.
2. Una Vista.
3. Un Formulario de Creación.
4. Un Formulario de Edición.

{% hint style="info" %}
Dependiendo de los casos de uso y el formato de los elementos, algunos ABMs pueden no respetar este esquema.
{% endhint %}

### Lista

La lista muestra los elementos creados, usualmente en una tabla.

Se define en el método `index()` del controlador.

Su vista (MVC) está definida en el archivo `app/views/nombre_controlador/index.ctp`.

### Vista

Muestra los detalles de un elemento.

Se define en el método `view(id)` del controlador.

Su vista (MVC) está definida en el archivo `app/views/nombre_controlador/view.ctp`.

### Formulario de Creación

Formulario para crear un nuevo elemento.

Se define en el método `add()` del controlador.

Su vista (MVC) está definida en el archivo `app/views/nombre_controlador/add.ctp`.

Usualmente en el archivo `add.ctp` referenciamos al contenido del archivo `edit.ctp` para unificar los formularios de creación y de edición.

**Ejemplo:**

```
<?php
	echo $this->element("../products/edit");
?>
```

### Formulario de Edición

Formulario para editar nuevo elemento existente.

Se define en el método `edit()` del controlador.

Su vista (MVC) está definida en el archivo `app/views/nombre_controlador/edit.ctp`.

## Los Helpers

Los helpers son clases que empleamos para la generación y el formateo en las vistas.

### HTML5

Este helper es un API para la construcción de los elementos comunes de la interfaz de usuario. Hemos creado este helper para agilizar la composición de la interfaz de usuario y para normalizar su estructuración y etiquetado, a modo de estilizar las vistas con un CSS común y centralizar los cambios que deban hacerse en el mockup HTML sobre elementos comunes.

### MT

Este helper formatea tipos de datos y crea elementos comunes.

#### MT -> Format

La función `$mt->format($texto, $tipo, $opciones)` es utilizada para dar formato a textos.

**Retorno**

Texto.

**Atributos:**

1. `$texto`: Es el texto de entrada.
2. `$tipo`: Establece el tipo de formato a aplicar.
3. `$opciones`: Define un array de opciones de configuración para el `$tipo` seleccionado.

**Tipos Definidos**

| Tipo     | Descripción       | Opciones                                                                                                                                                                                                                                                                |
| -------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| money    | Valor monetario   | <p><code>decimals</code> (integer) - Decimales</p><p><code>symbol</code> (string) - Símbolo de la Moneda</p><p><code>thousand\_separator</code> (char) - Caracter de separación de miles</p><p><code>decimal\_separator</code> (char) - Caracter de punto flotante </p> |
| number   | Número            | <p><code>decimals</code> (integer) - Decimales</p><p><code>thousand\_separator</code> (char) - Caracter de separación de miles</p><p><code>decimal\_separator</code> (char) - Caracter de punto flotante </p>                                                           |
| time     | Duración          |                                                                                                                                                                                                                                                                         |
| date     | Fecha             |                                                                                                                                                                                                                                                                         |
| datetime | Fecha y Hora      |                                                                                                                                                                                                                                                                         |
| invoice  | Número de Factura | <p><code>agency\_billing\_code</code> (varchar) - Prefijo de Código de Establecimiento</p><p><code>posale\_billing\_code</code> (varchar) - Prefijo de Código de Punto de Expedición</p>                                                                                |
| user     | Usuario           | `$user['User']`                                                                                                                                                                                                                                                         |
| contact  | Contacto          | `$contact['Contact']`                                                                                                                                                                                                                                                   |

**Ejemplos:**

```
echo $mt->format($amount, 'money', $currency['Currency']);
echo $mt->format(
    $sale['Sale']['invoice_number'],
    'invoice',
    $sale['Sale']
);
```

#### MT -> FBLink

Abre el link en una ventana modal.

**Ejemplo**:

```
echo $mt->fblink(
    $product['Product']['name'],
    array(
        'controller' => 'products',
        'action' => 'view',
        $product['Product']['id']
    )
);
```


# Desarrolladores Externos

Desarrolladores fuera del equipo de BIMS pueden sumar funcionalidades o integrar otras plataformas y aplicaciones a BIMS a través de la API de BIMS y también añadiendo archivos de controladores, modelos y vistas directamente a BIMS.

## El API de BIMS

BIMS cuenta con una API Restful a través de la cuál pueden ejecutarse muchas de las acciones que pueden ejecutarse desde la interfaz de usuario.

La documentación completa del API está disponible en el link de abajo.

{% embed url="<https://bims1.docs.apiary.io/>" %}

## Sumar Código a BIMS

También es posible sumar código a BIMS desarrollando modelos, vistas y controladores que pueden ser añadidos al framework de BIMS.

### Cómo Obtener a los Archivos de BIMS

Puede descargar los archivos del framework de BIMS con el código ofuscado del repositorio SVN para desarrolladores externos.

```
svn co http://getbims.com/svn-dist/bims
```

{% hint style="info" %}
Solicite al equipo de BIMS las credenciales de acceso al repositorio SVN.
{% endhint %}

Para que su intérprete PHP reconozca el código, deberá instalar el loader de Ioncube como un módulo de PHP.&#x20;

{% embed url="<https://www.ioncube.com/loaders.php>" %}

Y deberá colocar en la raiz de su working copy el archivo de licencia del código de BIMS.

{% hint style="info" %}
Solicite al equipo de BIMS el archivo de licencia del código de BIMS.
{% endhint %}

### Cómo Obtener el Esquema de Base de Datos

En su working copy encontrará el archivo /database/bims.sql que contiene el dump del esquema de base de datos de BIMS.

### Cómo Añadir Código a BIMS

Añada modelos, vistas y controladores esquematizados para el framework CakePHP 1.3 en los directorios:

```
app/models/
app/views/
app/controllers/
```

Haga *commit* de sus cambios y notifique al equipo de BIMS.&#x20;

El equipo de BIMS revisará sus cambios y los aprobará o rechazará. En caso de ser aprobados los cambios, los mismos serán integrados a la siguiente revisión de BIMS. Los cortes de cada revisión se realizan los días lunes de cada semana. En caso de que sus cambios sean rechazados, se le informará el motivo y las medidas que deben tomarse para el el código sea aprobado.

Los motivos para el rechazo de un *commit* son estos:

1. Su código pone en riesgo la integridad o la confidencialidad de los datos en BIMS.
2. Su código conflictúa con otras funciones de BIMS.
3. Su código es ineficiente o puede de alguna manera degradar el rendimiento general del sistema o de los servicios.
4. Su código expone publicidad de cualquier tipo.


# Notificaciones a Usuarios

Las Notificaciones de Usuario permiten alertar a Usuarios sobre Eventos relevantes para ellos.

## Presentación

Las notificaciones se presentan como una Alerta de BIMS y también en la sección de notificaciones en el encabezado.

![](/files/-MWfKPEpWlKX6aK6clkv)

## Interfaces

Existen dos interfaces para postear notificaciones: una para su uso desde el front-end y otra para su uso desde el back-end.

### Front-End

Para enviar una notificación desde el front-end utilizá la función pushNotification(JSON options). Esta función registra la notificación en la tabla *notifications* a través de la API, y envía un push para que el Usuario recipiente actualice sus notificaciones.

Los argumentos de la función pushNotification(JSON options) son los siguientes.

| Argumento | Tipo de Dato | Descripción                                                                                                                                                         | Requerido | Default   |
| --------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------- |
| title     | String       | Título de la Notificación                                                                                                                                           | Optativo  |           |
| message   | String       | Cuerpo de la Notificación                                                                                                                                           | Requerido |           |
| recipient | String       | Id del Usuario Destinatario                                                                                                                                         | Requerido |           |
| email     | Boolean      | Si TRUE entonces la notificación se envía también via e-mail al destinatario                                                                                        | Optativo  | FALSE     |
| link      | String       | Enlace de la notificación. Ruta relativa al host de BIMS.                                                                                                           | Optativo  |           |
| type      | String       | Tipo / Origen de la notificación. Referencia usualmente al feature de BIMS desde donde se originó. Consultá la lista de opciones en la tabla *notification\_types*. | Optativo  | "mailing" |

#### Ejemplo de Uso

```
<script>

	pushNotification({
		title: 					'Ticket Asignado',
		message: 				'@victor te asignó un nuevo ticket en el Pipeline "Soporte Técnico"',
		recipient: 			12,
		link:						'issues/index/1/2124',
		email: 					false,
		type:						'pipelines'
	});
	
</script>
```

### Back-End

Para enviar una notificación desde el back-end invocá desde un controlador el método $this->pushNotification(ARRAY options). Esta función registra la notificación en la tabla *notifications*, y envía un push para que el Usuario recipiente actualice sus notificaciones desde el front-end.

Los argumentos de la función pushNotification(ARRAY options) son los siguientes.

| Argumento | Tipo de Dato | Descripción                                                                                                                                                         | Requerido | Default   |
| --------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | --------- |
| title     | String       | Título de la Notificación                                                                                                                                           | Optativo  |           |
| message   | String       | Cuerpo de la Notificación                                                                                                                                           | Requerido |           |
| recipient | String       | Id del Usuario Destinatario                                                                                                                                         | Requerido |           |
| email     | Boolean      | Si TRUE entonces la notificación se envía también via e-mail al destinatario                                                                                        | Optativo  | FALSE     |
| link      | String       | Enlace de la notificación. Ruta relativa al host de BIMS.                                                                                                           | Optativo  |           |
| type      | String       | Tipo / Origen de la notificación. Referencia usualmente al feature de BIMS desde donde se originó. Consultá la lista de opciones en la tabla *notification\_types*. | Optativo  | "mailing" |

#### Ejemplo de Uso

```
$this->pushNotification(array(
    "title"			    => 		'Ticket Asignado',
    "message"		    => 		'@victor te asignó un nuevo ticket en el Pipeline "Soporte Técnico"',
    "recipient"		  => 		12,
    "type"          =>    'pipelines'
));
```


# API de BIMS

BIMS cuenta con un API de tipo Restful mediante el cual puede integrarse todo tipo de plataformas foráneas.

Consultá la documentación completa del API, con ejemplos de implementación incluídos en varios lenguajes de programación, aquí:&#x20;

{% embed url="<https://bims1.docs.apiary.io/>" %}

Adicionalmente, estamos construyendo una documentación en un formato más accesible aquí:

<https://ayuda.bims.app/api>


# ABM Simple

Paso a paso para la creación de un ABM simple para BIMS.

## Introducción

Este tutorial le guiará paso a paso en la creación de un ABM (Alta, Baja y Modificación) de una entidad en BIMS.

En este ejercicio implementaremos la configuración de un maestro de Libros, asociados a un maestro de Autores.

## Modo de Desarrollo

Para comenzar a desarrollar configuremos el framework en modo de desarrollo. En este modo se evita el caché de las estructuras de las tablas para los modelos, lo que nos permitirá hacer cambios en las estructuras que los modelos aprenderán con cada ejecución, también se evita el caché de localización y nos permitirá ver todos los errores en pantalla. Por otro lado, la ejecución se realentiza bastante, principalmente en ejecuciones sobre MS Windows. Así que no nos preocupemos si la ejecución de BIMS se vuelve lenta en nuestras estaciones.

Editemos el archivo app/config/core.php y reemplacemos la linea:

```
Configure::write('debug', 0);
```

por:

```
Configure::write('debug', 1);
```

Antes de hacer commit de nuestros cambios, debemos asegurarnos siempre de retornar el valor de "debug" a su estado original: "1".

## Flujo Normalizado Básico de ABM

Las pantallas para la mayoría de los elementos configurables en BIMS mantienen este flujo estándar.

![Flujo Normalizado Básico de ABM](/files/-MFlJjHuLp4zgo2IWkWt)

## Base de Datos

Recuerde que, por convención y en función de la automatización de muchos procesos, establecemos los nombres de las tablas en inglés y en plural. Así, a la tabla del maestro de Libros la llamaremos "books" y a la de autores "authors".

![](/files/-MFkTh1l_d5BcSnFsXZV)

Como también está convenido, los nombres de los campos que harán de clave primeria serán "id", y los nombres de las claves foráneas se contruirán con el nombre de la tabla referenciada, pero en singular y con el sufijo "\_id".

```
CREATE TABLE "authors"
(
    id bigserial NOT NULL,
    name character varying NOT NULL,
    user_id bigint NOT NULL,
    created timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    modified timestamp without time zone,
    PRIMARY KEY (id),
    UNIQUE (name),
    FOREIGN KEY (user_id)
        REFERENCES "users" (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE RESTRICT
);

CREATE TABLE "books"
(
    id bigserial NOT NULL,
    name character varying NOT NULL,
    release_date date,
    author_id bigint,
    user_id bigint NOT NULL,
    created timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    modified timestamp with time zone,
    PRIMARY KEY (id),
    FOREIGN KEY (author_id)
        REFERENCES "authors" (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE RESTRICT,
    FOREIGN KEY (user_id)
        REFERENCES "users" (id) MATCH SIMPLE
        ON UPDATE CASCADE
        ON DELETE RESTRICT
);
```

Solo con fines didácticos, ingresemos ya algunos datos.

```
INSERT INTO "authors" (
    "name",
    "user_id"
)
VALUES (
    'Augusto Roa Bastos',
    ( SELECT MIN(id) FROM users )
);

INSERT INTO "books" (
    "name",
    "release_date",
    "author_id",
    "user_id"
)
VALUES (
    'Yo, El Supremo',
    '1974-08-01',
    ( SELECT MIN(id) FROM authors ),
    ( SELECT MIN(id) FROM users )
);

INSERT INTO "books" (
    "name",
    "release_date",
    "author_id",
    "user_id"
)
VALUES (
    'Hijo de Hombre',
    '1974-05-20',
    ( SELECT MIN(id) FROM authors ),
    ( SELECT MIN(id) FROM users )
);
```

## Creación de Modelos, Vistas y Controladores con el Bake Shell

### Ejecución del Bake

CakePHP ofrece un shell para automatizar la creación de Modelos, Controladores y Vistas a partir del diseño de las tablas. Hemos adaptado muchas funciones del baking de Cake para que los elementos se ajusten a las convenciones de BIMS, no obstante, luego de su generación deberemos realizar de forma manual algunas adaptaciones mínimas. Como sea, el trabajo ahorramos mucho trabajo empleando esta herramienta.

Una vez creadas las tablas "authors" y "books" en nuestra base de datos, ejecutamos el shell "bake" del core de CakePHP.

Ejecutamos lo siguiente desde el&#x20;

```
# cd cake/console/
# ./cake -app ../../app bake
```

Se desplegará el siguiente menú:

```
Welcome to CakePHP v1.3.14 Console
---------------------------------------------------------------
App : app
Path: /Users/victor/Sites/bims2/cake/console/../../app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
```

### Generación de los Modelos

Primeramente creamos los modelos. Para eso presionamos la tecla "m", tal como se indica en pantalla. Se listarán los nombres de modelos para todas las tablas de la base de datos.

```
Possible Models based on your current database:
1. Accentry
2. AccountsDefault
.
.
.
728. Author
729. Book
Enter a number from the list above,
type in the name of another model, or 'q' to exit  
[q] > Author
```

Los nombres de los modelos son los de las tablas pero en singular y capitalizados. Ejemplo:

| Nombre de la Tabla | Nombre del Modelo |
| ------------------ | ----------------- |
| accentries         | Accentry          |
| accounts\_defaults | AccountsDefault   |
| authors            | Author            |
| books              | Book              |

Seleccionamos primero "Author" ingresando el número que le corresponde en la lista (728 en este ejemplo) o directamente ingresando "Author".

El Bake nos consultará si deseamos definir los criterios de validación de los campos de la tabla. Aceptemos la propuesta con "y".

```
Would you like to supply validation criteria 
for the fields in your model? (y/n) 
[y] > y
```

El shell en base a la configuración tipo de dato y constraints sobre los campos, sugerirá las reglas de validación adecuadas, pero podremos modificarlas tanto en esta instancia como luego editando directamente el modelo. Aceptemos todas las reglas sugeridas por el Bake.

Estos criterios de validación se implementarán como reglas en la clase del modelo en el array dentro de un atributo llamado "validate", y estas reglas se validarán siempre antes de guardar un registro a través del modelo.

La siguiente fase del Bake es la definición de relaciones con otros modelos.

```
Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n) 
[y] > y
```

Aceptaremos con "y" y seguiremos el wizard para modelar estas relaciones. El Bake reconocerá automáticamente y sugerirá las relaciones en base a la construción de la BD, siempre que hayamos respetado las convenciones en la nomenclatura de las tablas y los campos de clave primaria y foránea.

```
Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n) 
[y] > y
One moment while the associations are detected.
---------------------------------------------------------------
Please confirm the following associations:
---------------------------------------------------------------
Author belongsTo User? (y/n) 
[y] > y
Author hasMany Book? (y/n) 
[y] > 
Would you like to define some additional model associations? (y/n) 
[n] > n
```

La tabla "authors" tiene una sola clave foránea: "user\_id" -> "users.id". Esta relación es del tipo "**belongsTo**". Por otro lado, la tabla "books" referencia a "authors" a través de la clave foránea "books"."author\_id" -> "authors"."id". Dese la perspectiva del modelo Book, esta relación es del tipo "**belongsTo**", mientras que desde la perspectiva de "Author" esta relación es del tipo "**hasMany**". Se aprovechará el modelado de estas relaciones tanto al consultar como al guardar registros. Ya llegaremos a eso.&#x20;

Para finalizar, el shell presentará un resumen del modelo y esperará a nuestra confirmación para crearlo. Confirmaremos ingresando: "y".

```
---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Author
DB Table:   "authors"
Validation: Array
(
    [name] => Array
        (
            [notempty] => notempty
        )

    [user_id] => Array
        (
            [numeric] => numeric
        )

)

Associations:
	Author belongsTo User
	Author hasMany Book
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y
```

Se creará el archivo **app/models/author.php**. Que se verá así:

```
<?php
	class Author extends AppModel {
		var $name = 'Author';
		var $displayField = 'name';
		var $validate = array(
			'name' => array(
				'notempty' => array(
					'rule' => array('notempty'),
					//'message' => 'Your custom message here',
					//'allowEmpty' => false,
					//'required' => false,
					//'last' => false, // Stop validation after this rule
					//'on' => 'create', // Limit validation to 'create' or 'update' operations
				),
			),
			'user_id' => array(
				'numeric' => array(
					'rule' => array('numeric'),
					//'message' => 'Your custom message here',
					//'allowEmpty' => false,
					//'required' => false,
					//'last' => false, // Stop validation after this rule
					//'on' => 'create', // Limit validation to 'create' or 'update' operations
				),
			),
		);
		//The Associations below have been created with all possible keys, those that are not needed can be removed
	
		var $belongsTo = array(
			'User' => array(
				'className' => 'User',
				'foreignKey' => 'user_id',
				'conditions' => '',
				'fields' => '',
				'order' => ''
			)
		);
	
		var $hasMany = array(
			'Book' => array(
				'className' => 'Book',
				'foreignKey' => 'author_id',
				'dependent' => false,
				'conditions' => '',
				'fields' => '',
				'order' => '',
				'limit' => '',
				'offset' => '',
				'exclusive' => '',
				'finderQuery' => '',
				'counterQuery' => ''
			)
		);
	
	}
?>
```

Seguidamente ejecutamos este mismo ejercicio para crear el modelo "**Book**" para la tabla "**books**".

### Generación de los Controladores

Una vez creados ambos modelos: "**Author**" y "**Book**", seguimos con la creación de los controladores correspondientes, que llevarán los nombres: "**AuthorsController**" y "**BooksController**" respectivamente. Para esto, en el menú principal del shell ingresamos: "c".

```
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> c
---------------------------------------------------------------
```

Así como con los modelos, se listarán todas las tablas de la base de datos con la nomenclatura estructurada para los controladores.

```
Possible Controllers based on your current database:
1. Accentries
2. AccountsDefaults
.
.
.
728. Authors
729. Books
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 
```

Para la construcción de los nombres de los controladores se toma el nombre de la tabla pero capitalizado. Ejemplos:

| Nombre de la Tabla | Nombre del Controlador |
| ------------------ | ---------------------- |
| accentries         | Accentries             |
| accounts\_defaults | AccountsDefaults       |
| authors            | Authors                |
| books              | Books                  |

Seleccionaremos primero el controlador "**Authors**", ingresando el número que le corresponde en la lista (en esta caso: 728) o escribiendo directamente "Authors" y las demás opciones según el ejemplo de abajo:

```
Possible Controllers based on your current database:
1. Accentries
2. AccountsDefaults
.
.
.
728. Authors
729. Books
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > Authors

---------------------------------------------------------------
Baking AuthorsController
---------------------------------------------------------------
Would you like to build your controller interactively? (y/n) 
[y] > y
Would you like to use dynamic scaffolding? (y/n) 
[n] > n
Would you like to create some basic class methods 
(index(), add(), view(), edit())? (y/n) 
[n] > y
Would you like to create the basic class methods for admin routing? (y/n) 
[n] > n
Would you like this controller to use other helpers
besides HtmlHelper and FormHelper? (y/n) 
[n] > n
Would you like this controller to use any components? (y/n) 
[n] > n
Would you like to use Session flash messages? (y/n) 
[y] > y

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:
	Authors
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y
```

Se creará el archivo app/controllers/authors\_controller.php con la definición del controlador. Debe tener la siguiente estructura:

```
<?php
class AuthorsController extends AppController {

	var $name = 'Authors';

	function index() {
		$this->Author->recursive = 0;
		$this->set('authors', $this->paginate());
	}

	function view($id = null) {
		if (!$id) {
			$this->setFlash('Invalid author', 'error');
			$this->redirect(array('action' => 'index'));
		}
		$data = $this->Author->read(null, $id);
		$this->set('author', $data);
		
		// Personalizaciones para BIMS de TIVA
		$this->navbar = array(
			'Authors' => array('action'=>'index')
		);
		$this->sidebar->name = 'author';
		$this->sidebar->data = $data;
	}

	function add() {
		if (!empty($this->data)) {
			$this->Author->create();
			if ($this->Author->save($this->data)) {
				$this->setFlash('The author has been saved');
				$this->redirect(array('action' => 'view', $this->Author->id));
			} else {
				$this->setFlash('The author could not be saved. Please, try again.', 'error');
			}
		}
		$users = $this->Author->User->find('list');
		$this->set(compact('users'));

		// Personalizaciones para BIMS de TIVA
		$this->navbar = array(
			'Authors' => array('action'=>'index')
		);
	}

	function edit($id = null) {
		if (!$id && empty($this->data)) {
			$this->setFlash('Invalid author');
			$this->redirect(array('action' => 'index'));
		}
		if (!empty($this->data)) {
			if ($this->Author->save($this->data)) {
				$this->setFlash('The author has been saved');
				$this->redirect(array('action' => 'view', $id));
			} else {
				$this->setFlash('The author could not be saved. Please, try again.', 'error');
			}
		}
		if (empty($this->data)) {
			$this->data = $this->Author->read(null, $id);
		}
		$users = $this->Author->User->find('list');
		$this->set(compact('users'));
	
		// Personalizaciones para BIMS de TIVA
		$this->navbar = array(
			'Authors' => array('action'=>'index'),
			$this->data['Author']['name'] => array('action'=>'index', $id)
		);
		$this->sidebar->name = 'author';
	}

	function delete($id = null) {
		if (!$id) {
			$this->setFlash('Invalid id for author', 'error');
			$this->redirect(array('action'=>'index'));
		}
		if ($this->Author->delete($id)) {
			$this->setFlash('Author deleted', 'error');
			$this->redirect(array('action'=>'index'));
		}
		$this->setFlash('Author was not deleted');
		$this->redirect(array('action' => 'index'));
	}
}

```

Ejecutamos el mismo proceso para el controlador "**Books**".

### Generación de las Vistas

Una vez generados los modelos y los controladores, seguiremos con las vistas. En el menú principal del shell ingresamos: "v".

```
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> v
```

Se listarán todas las tablas de la base de datos con la nomenclatura estructurada para los controladores.

```
Possible Controllers based on your current database:
1. Accentries
2. AccountsDefaults
.
.
.
728. Authors
729. Books
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 
```

Seleccionamos "**Authors**" ingresando el número que le corresponde en la lista o la palabra "Authors" y seguimos el proceso como en el ejemplo siguiente:

```
Would you like bake to build your views interactively?
Warning: Choosing no will overwrite Authors views if it exist. (y/n) 
[n] > n

Creating file /Users/victor/Sites/bims2/cake/console/../../app/views/authors/index.ctp
Wrote `/Users/victor/Sites/bims2/cake/console/../../app/views/authors/index.ctp`

Creating file /Users/victor/Sites/bims2/cake/console/../../app/views/authors/view.ctp
Wrote `/Users/victor/Sites/bims2/cake/console/../../app/views/authors/view.ctp`

Creating file /Users/victor/Sites/bims2/cake/console/../../app/views/authors/add.ctp
Wrote `/Users/victor/Sites/bims2/cake/console/../../app/views/authors/add.ctp`

Creating file /Users/victor/Sites/bims2/cake/console/../../app/views/authors/edit.ctp
Wrote `/Users/victor/Sites/bims2/cake/console/../../app/views/authors/edit.ctp`
---------------------------------------------------------------

View Scaffolding Complete.

```

Se crearán los siguientes archivos, cada uno para uno de los métodos del controlador correspondiente:

* **app/views/authors/index.ctp**, donde se listan los autores;
* **app/views/authors/view\.ctp**, la interna o "ficha" del autor;
* **app/views/authors/add.ctp**, el formulario de alta de un autor;
* **app/views/authors/edit.ctp**, el formulario de edición de un autor.

Todos los archivos de vistas llevan la extensión .ctp y su código el HTML / CSS / PHP / JS.

En este punto, ya podremos ver estas vistas en la web en las siguientes URLs:&#x20;

* http\://\<bims>/books (index)
* http\://\<bims>/add (add)
* http\://\<bims>/view/\<id> (view)
* http\://\<bims>/edit/\<id> (edit)

![Vista Original de la Lista de Libros](/files/-MFl0vgQNPN5P4Jh3kS3)

Notaremos que el diseño de la GUI aún no luce como las pantallas de BIMS. Así que seguidamente haremos las adaptaciones necesarias sobre las vistas.

## Adaptaciones sobre las Vistas

A continuación veremos las adaptaciones que deberán realizarse sobre las vistas para que nuestras pantallas adopten los linamientos de BIMS. Emplearemos algunos *helpers* para el efecto:

| Helper          | Descripción                                                                                                                                                                       | Archivo                      |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| **Html5**Helper | Creamos este helper con el objeto de agilizar la construcción de las interfaces de usuario y más importante: normalizarlas. Se emplea principalmente para la creación de layouts. | app/views/elements/html5.php |
| **Mt**Helper    | Este helper contiene métodos para el formateo de datos y la generación de algunos componentes de la interfaz de usuario.                                                          | app/views/elements/mt.php    |

### Índice (Index)

Con ayuda del Bake Shell se generó este archivo para el índice de libros: **app/views/books/index.ctp**.

```
<div class="books index">
	<h1><?php __('Books');?></h1>
	<table cellpadding="0" cellspacing="0">
	<tr>
			<th class="pk"><?php echo $this->Paginator->sort('id');?></th>
			<th><?php echo $this->Paginator->sort('name');?></th>
			<th><?php echo $this->Paginator->sort('release_date');?></th>
			<th><?php echo $this->Paginator->sort('author_id');?></th>
			<th><?php echo $this->Paginator->sort('user_id');?></th>
			<th><?php echo $this->Paginator->sort('created');?></th>
			<th><?php echo $this->Paginator->sort('modified');?></th>
	</tr>
	<?php
	$i = 0;
	foreach ($books as $book):
		$class = null;
		if ($i++ % 2 == 0) {
			$class = ' class="altrow"';
		}
	?>
	<tr<?php echo $class;?>>
		<td class="pk"><?php echo $book['Book']['id']; ?>&nbsp;</td>
		<td><?php echo $html->link($book['Book']['name'], array('action'=>'view', $book['Book']['id'])); ?>&nbsp;</td>
		<td><?php echo $book['Book']['release_date']; ?>&nbsp;</td>
		<td>
			<?php echo $mt->fblink($book['Author']['name'], array('controller' => 'authors', 'action' => 'view', $book['Author']['id'])); ?>
		</td>
		<td>
			<?php echo $mt->fblink($book['User']['name'], array('controller' => 'users', 'action' => 'view', $book['User']['id'])); ?>
		</td>
		<td><?php echo $book['Book']['created']; ?>&nbsp;</td>
		<td><?php echo $book['Book']['modified']; ?>&nbsp;</td>
	</tr>
<?php endforeach; ?>
	</table>
	<p>
	<?php
	echo $this->Paginator->counter(array(
	'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
	));
	?>	</p>

	<div class="paging">
		<?php echo $this->Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?>
	 | 	<?php echo $this->Paginator->numbers();?>
 |
		<?php echo $this->Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?>
	</div>
</div>
```

Reemplacemos la estructura original por esta:

```
<?php
	$html5->paginator = $paginator;
	$html5->listBefore(array(
		'title' => __('Books', true),
		'options' => array(
			'export' => array(
				'mode' => 'excel'
			),
			'print' => array(
				'auto' => 1
			),
			'filter' => 'books/_filter_index'
		)
	));
?>
<table class="table table-bordered table-striped table-hover table-custom-border ">
	<thead>
		<tr>
			<th class="pk"><?php echo $this->Paginator->sort('id');?></th>
			<th><?php echo $this->Paginator->sort('name');?></th>
			<th><?php echo $this->Paginator->sort('release_date');?></th>
			<th><?php echo $this->Paginator->sort('author_id');?></th>
		</tr>
	</thead>
	<tbody>
	<?php
		foreach ($books as $book):
	?>
		<tr>
			<td class="pk">
				<?php echo $book['Book']['id']; ?>
			</td>
			<td>
				<?php echo $html->link($book['Book']['name'], array('action'=>'view', $book['Book']['id'])); ?>
			</td>
			<td>
				<?php echo $book['Book']['release_date']; ?>
			</td>
			<td>
				<?php echo $mt->fblink($book['Author']['name'], array('controller' => 'authors', 'action' => 'view', $book['Author']['id'])); ?>
			</td>
		</tr>
	<?php
		endforeach;
	?>
</table>
<?php
	$html5->listAfter();
?>
```

La lista de Libros se verá ahora así:

![](/files/-MFl2Txk48DJNjztZbv9)

Seguidamente creamos el menú del sidebar. De forma predeterminada, los sidebars de las vistas de índice (index) y adición (add) se buscarán en app/views/elements/sidebars/\<nombre-de-tabla>.ctp. Así que crearemos el archivo **app/views/elements/sidebars/books.ctp** con este contenido:

```
<?php
	echo $mt->sidebutton("Add", array('controller'=>'books', 'action'=>'add'), $sidebar->selected);
?>
<ul class="nav nav-stacked">
<?php
	echo $mt->sidemenu("Listar Libros", array('controller'=>'books', 'action'=>'index'), $sidebar->selected);
?>
</ul>
```

Y lucirá asi:

![](/files/-MFl3RYxaWgROAiiJnqQ)

### Adición (add) y Edición (edit)

Con ayuda del Bake Shell se generó este archivo para el índice de libros: **app/views/books/add.ctp** y **app/views/books/edit.ctp**.

Editaremos el contenido de **app/views/books/add.ctp** para que simplemente cargue el contenido de **app/views/books/edit.ctp**, de manera a normalizar un código para ambos usos y preocuparnos solo por mantener un archivo.

Reemplazaremos el contenido de **app/views/books/edit.ctp** por el que sigue:

```
<?php
	$html5->tmpForm(array(
		'model' => 'Book',
		'title' => empty($this->data['Book']['id']) ? 'Add Book' : 'Edit Book',
		'displayFilters' => false,
		'sections' => array(
			array(
				'title' => 'Main Information',
				'description' => __d('desc', 'Main Information', true),
				'fields' => array(
					'name',
					'release_date' => array(
						'label' => 'Release Date',
						'type' => 'date'
					),
					'author_id'
				)
			),
		),
		'submit' => __('Save', true)
	));
?>
```

La pantalla deberá verse así:

![Formulario de Creación de Libros](/files/-MFlA2ouII2CHfQF0UcM)

### Vista (view)

Con ayuda del Bake Shell se generó este archivo para la vista de libros: **app/views/books/view\.ctp**.

Reemplazaremos su contenido actual por este, que emplea el helper Html5:

```
<?php
	$html5->tmpView(array(
		'head' => array(
			'title' => $book['Book']['name']
		),
		'spotlight' => array(
			'title' => $book['Book']['name'],
			'fields' => array(
				'Author'						=> $book['Author']['name'],
				'Release Date'					=> $book['Book']['release_date'],
			),
		),
		'body' => array(
			'sections' => array(
				'Main Information' => array(
					'Name'						=> $book['Author']['name'],
					'Release Date'				=> $book['Book']['release_date']
				),
				'Audit Information' => array(
					'User' => $mt->fblink($book['User']['name'], array('controller'=>'users', 'action'=>'view', $book['Book']['user_id'])),
					'Created' => $book['Book']['created'],
					'Modified' => $book['Book']['modified']
				)
			)
		)
	));
?>
```

Y también creamos el archivo de para el menú del sidebar: **app/views/elements/sidebar/book.ctp** con este contenido:

```
<ul class="nav nav-stacked">
<?php
	echo $mt->sidemenu("View", array('controller'=>'books', 'action'=>'view', $sidebar->data['Book']['id']), $sidebar->selected);
	echo $mt->sidemenu("Edit", array('controller'=>'books', 'action'=>'edit', $sidebar->data['Book']['id']), $sidebar->selected);
	echo $mt->sidemenu("Delete this element", array('controller'=>'books', 'action'=>'delete', $sidebar->data['Book']['id']), $sidebar->selected, null, array('confirm'=>__('Do you really want to delete this element?', true)));
?>
</ul>
```

La pantalla se verá así:

![](/files/-MFlCfJrHRn7JO3Js2_l)

## Localización

El texto estático nuevo que hemos incorporado a la GUI esta expresado en inglés, que es la lengua básica para la interfaz de usuario de BIMS. Así que resta configurar las traducciones al español de estos términos y mensajes en el archivo: **app/locale/esp/LC\_MESSAGES/default.po**. Ejemplo:

```
msgid "Books"
msgstr "Libros"

msgid "Release Date"
msgstr "Fecha de Lanzamiento"

msgid "Authors"
msgstr "Autores"
```

Los cambios que realicemos en este archivo se verán reflejados en la interfaz de usuario de manera inmediata si el framework se encuentra en modo de desarrollo. Si el framework se encuentra en modo de producción, para que los cambios tengan efecto se deberá eliminar el caché, asi:

```
rm -f app/tmp/cache/persistent/*
```

## ¡Voilá!

¡Felicitaciones! Ya tenemos nuestro primer ABM funcional en BIMS.&#x20;

En otros posts encontraremos mayor información sobre los helpers utilizados aquí.


# Mobile POS

Delineamientos de Desarrollo

## Instancias de BIMS

BIMS puede distribuirse bajo dos modelos de negocio:

1. Software as a Service
2. On Premise

Las cuentas de BIMS bajo el modelo de Software as a Service emplean la misma URL: <https://bims.app>.

Las instancias de BIMS de cuentas bajo el modelo de licenciamiento perpetuo pueden pueden implementarse en otros hosts. Ejemplo: <https://beta.bims.app>.

Es por ese motivo que la aplicación móvil debe permitirle al usuario configurar el host ($HOST) de su instancia de BIMS y la opción predeterminada debe ser la de SaaS: <https://bims.app>.

Para las cuentas SaaS, se debe indicar en el login también el código de empresa ($TENANT\_CODE). Este código es alfanumérico. Ej.: "prueba24".

Se recomienda que las casillas de $HOST y de $TENANT\_CODE, al no haber necesidad de cambiarlas en la app en un escenario normal, se configuren en una pantalla distinta del login.

## Login

Update: <https://ayuda.bims.app/api#mecanismos-de-autenticacion>

Para hacer login en BIMS se requiere de un nombre de usuario ($USERNAME) una contraseña ($PASSWORD) y en el caso de tratarse de una cuenta de SaaS: el $TENTANT\_CODE.

## Endpoint para Inicio de Sesión

<mark style="color:green;">`POST`</mark> `https://$HOST/api/users/login`

#### Request Body

| Name                                       | Type   | Description               |
| ------------------------------------------ | ------ | ------------------------- |
| user<mark style="color:red;">\*</mark>     | String | Nombre de Usuario         |
| password<mark style="color:red;">\*</mark> | String | Hash MD5 de la Contraseña |
| tenant                                     | String | Código de Tenant          |

{% tabs %}
{% tab title="200: OK Login Exitoso" %}

```
{
    status: "ok",
    code: "200",
    data: {
        User: {
            id: 1,
            login: "demo",
            name: "Cuenta de Demo",
            ...
        },
        Group: {
            id: 1,
            name: "Administradores",
            full: true
            ...
        },
        Session {
            id: "vsknhtsefpt9pmr4pusgtgvdm3",
            expire: "2023-11-01 10:00:00",
            current_timestamp: "2023-11-01 08:00:00",
            ...
        }
    }
}
```

{% endtab %}

{% tab title="200: OK Login Fallido" %}

```
{
    "status": "error",
    "code": "401",
    "message": "Login Incorrecto",
    "last_update": "2023-10-31 17:22:25"
}
```

{% endtab %}
{% endtabs %}

### Ejemplo de Login en cURL

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
          \"user\": \"usuario2\",
          \"password\": \"2fb6c8d2f3842a5ceaa9bf320e649ff0\",
          \"tenant\": \"test123\"
     }" \
'https://bims.app/api/users/login?parse_perms=1';
```

## Manejo de Sesiones

### El Id de Sesión

En la respuesta del API a un login existoso, se retorna el Id de la sesión ($SESSION\_ID) en el atributo: data.Session.id.

La aplicación debe guardar el valor $SESSION\_ID y pasar a las siguientes consultas que haga al API  en el argumento de la URL: "sid".

### Ejemplo de uso en cURL

Suponiendo que el valor de $SESSION\_ID = 7cr7cn9m6k598mv7gdvag2vrq7.

```
curl "https://bims.app/api/products?sid=7cr7cn9m6k598mv7gdvag2vrq7&company=1&limit=2"
```

### Vigencia de la Sesión

Las sesiones son temporales. En la respuesta al login exitoso, el campo data.Session.expire se indica la fecha de expiración de la sesión, mientras que en el campo data.Session.current\_timestamp se indica la marca de tiempo actual como referencia.

Se recomienda actualizar períodicamente el $SESSION\_ID con un nuevo login en background y no exigirle al usuario un nuevo login hasta que el mismo cierre su sesión.

## Permisos

Para que un usuario pueda usar el POS Mobile debe tener permisos para registrar ventas. Agregá a la URL del login el parámetro "?parse\_perms=1" para obtener en la respuesta del login la lista de permisos del usuario en el atributo: data.Group.perms.

Si el valor del atributo data.Group.full es true, entonces el usuario tiene permisos irrestrictos y no es necesario validar nada más. Pero si data.Group.full es false, entonces se debe controlar que el código "salesAdd" esté presente en el array data.Group.perms.

Si el usuario no tiene permisos para registrar ventas, entonces se debe alertar en pantalla y rechazar el login.

Más sobre la gestión de permisos en BIMS aquí: <https://ayuda.bims.app/productos/que-son-productos/permisos>

***

## Multiempresa

### Comportamiento esperado

El sistema es multiempresa, lo que significa que cada instancia de BIMS puede gestionar las operaciones e información de más de una empresa ($COMPANY).

Cada usuario de BIMS puede estar a sociado a una empresa, cuando el valor en User.company\_id indica el id de la empresa ($COMPANY\_ID); o bien, el usuario puede tener acceso a TODAS las empresas, cuando el valor del campo User.company\_id es NULL.

Si el usuario que inició sesión tiene acceso a múltiples empresas, entonces se debe autoseleccionar tras el login la última empresa utilizada, o bien, la primer empresa de la lista de empresas. Y se le debe dar la opción de seleccionar con cuál empresa operar desde una pantalla de configuración.

Se ofrece de muestra la implementación en el PWA actual.

{% file src="/files/b5nnpNHLWyKQu5NE6gWK" %}
Ejemplo de selección de empresa en PWA de BIMS
{% endfile %}

***

## Productos

### Estructura de Datos

| Nombre      | Tipo de Dato | Descripción                           |
| ----------- | ------------ | ------------------------------------- |
| id          | BIGINT       | Número Identificador                  |
| name        | VARCHAR      | Nombre del Producto                   |
| ptype\_id   | BIGINT       | Categoría de Productos                |
| sell\_price | NUMERIC      | Precio Principal                      |
| company\_id | BIGINT       | Id de la Empresa                      |
| image       | VARCHAR      | URL de la Imagen del Producto         |
| sellable    | BOOLEAN      | Bandera de habilitación para la venta |
| enabled     | BOOLEAN      | Bander                                |

Hay muchos otros campos en la tabla de productos, pero estos son los relevantes para una implementación sencilla del Mobile POS.

### Consulta de Productos

Para consultar productos utilizá el endpoint (api/products) cuya documentación completa está disponible aquí: <https://bims1.docs.apiary.io/#reference/0/products/list-products>

Ejemplo:

```
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&webpos=1&company=1"
```

Siempre indicá el $COMPANY\_ID en el argumento "company" en la URL.

También indica siempre el argumento "webpos=1" en la URL para que el backend seleccione los campos necesarios para una implementación de un POS, tal cual lo hace con la versión web del POS.

### Paginación de la Consulta de Productos

La lista de productos puede llegar a ser inmensa, algunas veces en término de millones de ítems. Por este motivo es necesario siempre paginar la consulta de productos, y esto se logra haciendo uso de los argumentos de URL: "offset" y "limit". Su uilización es tal cuál se utilizarían en una consulta SQL.

Por ejemplo: si deseáramos consultar la lista completa de productos de 100 en 100, las consultas serían las siguientes:

```
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&webpos=1&company=1&limit=100&offset=0";
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&webpos=1&company=1&limit=100&offset=100";
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&webpos=1&company=1&limit=100&offset=200";
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&webpos=1&company=1&limit=100&offset=300";
```

Y así sucesivamente.

Cuándo parar? Una opción rápida es parar cuando el array en el atributo "data" de la respuesta esté vacío. Pero una mejor implementación sería conocer la cantidad total de elementos en la muestra, y así, en función del limit que usemos, determinar cuántas consultas se requerirán. Esto también permitiría darle al usuario una idea del progreso en la sincronización de productos. La cantidad total de elementos se indica en la respuesta a la primera página, cuando offset=0 (o no está definido), y se indica en el atributo "count". Ejemplo:

```
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&company=1&limit=100&offset=0"

{
    "code": "200",
    "status": "ok",
    "count": "1134",
    "last_update": "2023-10-31 18:29:22",
    "data": [
        {
            "Product": {
                "id": "1",
                "name": "Desarrollo de pagina web basico",
                "company_id": "1",
                ...
```

Evitá hacer llamados concurrentes.

### Obtener solo productos actualizados

Dado el potencial volumen de la lista de productos, es recomendable consultar en cada ciclo solo los productos que fueron creados o modificados desde la última consulta. Esto se puede lograr haciendo uso del argumento "last\_update".&#x20;

En cada respuesta del API se incorpora un atributo llamado "last\_update", que es una marca de tiempo del servidor del momento en que se generó la respuesta. Guardá este valor cada vez que comiences a sincronizar productos, y en el siguiente ciclo de sincronización de productos, pasá este mismo valor como argumento en la URL en un atributo con el mismo nombre: "last\_update".

Si está presente el atributo "last\_update", BIMS retornará solo los registros que fueron creados o modificados luego de esa marca de tiempo.

Ejemplo:

```
curl "https://bims.app/api/products?sid=pidc5562uluse71b9u0kltj444&company=1&limit=100&offset=0&last_update=2023-10-31 18:29:22"

{
    "code": "200",
    "status": "ok",
    "count": "1134",
    "last_update": "2023-11-01 18:00:01",
    "data": [
        {
            "Product": {
                "id": "1",
                "name": "Desarrollo de pagina web basico",
                "company_id": "1",
                ...
```

### Sincronización de Productos

El POS Mobile apunta a un uso intensivo y debe ser muy ágil. Es por esto que no es recomendable que la búsqueda y exposición de produtos en la app se realice online, en su lugar, debe hacerse contra una base de datos local. Esta base de datos debe mantenerse sincronizada mediante los mecanismos descritos anteriormente.

Igualmente, las imágenes de los productos deben cachearse.

La sincronización de productos debe hacerse por empresa, lo que implicará que debas guardar el last\_update del resultado de consultas de productos de cada empresa que se haya realizado.

## Clientes

### Estructura de Datos

| Nombre         | Tipo de Dato | Descripción                      |
| -------------- | ------------ | -------------------------------- |
| id             | BIGINT       | Número Identificador Único       |
| name           | VARCHAR      | Nombre del Cliente               |
| document\_id   | VARCHAR      | Número de Documento              |
| document\_type | VARCHAR      | Tipo de Documento                |
| notaxes        | BOOLEAN      | Bandera de Exensión de Impuestos |

### Consulta de Clientes

Para consultar productos utilizá el endpoint (api/contacts) cuya documentación completa está disponible aquí: <https://bims1.docs.apiary.io/#reference/0/contacts/list-contacts>

Ejemplo:

Ejemplo:

```
curl "https://bims.app/api/contacts?sid=pidc5562uluse71b9u0kltj444&webpos=1&company=1"
```

Siempre indicá el $COMPANY\_ID en el argumento "company" en la URL.

También indica siempre el argumento "webpos=1" en la URL para que el backend seleccione los campos necesarios para una implementación de un POS, tal cual lo hace con la versión web del POS.

### Paginación de la Consulta de Clientes

La lista de clientes puede llegar a ser inmensa, algunas veces en término de millones de ítems. Por este motivo es necesario siempre paginar la consulta de clientes, y esto se logra haciendo uso de los argumentos de URL: "offset" y "limit". Su uilización es tal cuál se utilizarían en una consulta SQL.

Cuándo parar? Una opción rápida es parar cuando el array en el atributo "data" de la respuesta esté vacío. Pero una mejor implementación sería conocer la cantidad total de elementos en la muestra, y así, en función del limit que usemos, determinar cuántas consultas se requerirán. Esto también permitiría darle al usuario una idea del progreso en la sincronización de productos. La cantidad total de elementos se indica en la respuesta a la primera página, cuando offset=0 (o no está definido), y se indica en el atributo "count".

### Obtener solo Clientes Actualizados

Dado el potencial volumen de la lista de clientes, es recomendable consultar en cada ciclo solo los clientes que fueron creados o modificados desde la última consulta. Esto se puede lograr haciendo uso del argumento "last\_update".&#x20;

En cada respuesta del API se incorpora un atributo llamado "last\_update", que es una marca de tiempo del servidor del momento en que se generó la respuesta. Guardá este valor cada vez que comiences a sincronizar productos, y en el siguiente ciclo de sincronización de productos, pasá este mismo valor como argumento en la URL en un atributo con el mismo nombre: "last\_update".

Si está presente el atributo "last\_update", BIMS retornará solo los registros que fueron creados o modificados luego de esa marca de tiempo.

## Formas de Pago

### Definición

Una venta puede ser abonada con una o la combinación de varias formas de pago. Estas formas de pago se configuran en BIMS y su configuración reúne la lógica del tratamiento de los fondos recibidos con esa forma de pago.

Ejemplos de Formas de Pago: Efectivo, Cheques, Transferencias Bancarias.

### Estructura de Datos

<table><thead><tr><th width="171.33333333333331">Nombre</th><th>Tipo de Dato</th><th>Descripción</th></tr></thead><tbody><tr><td>id</td><td>BIGINT</td><td>Identificador Numérico Único</td></tr><tr><td>name</td><td>VARCHAR</td><td>Nombre de la Forma de Pago</td></tr></tbody></table>

Existen muchos otros atributos en la estructura de las Formas de Pago, pero para la construción del PMV bastan estos.

### Consulta de Formas de Pago

Para consultar las formas de pago configuradas en BIMS, hacé uso del endpoint: "api/payment\_methods", cuya documentación completa está disponible aquí: <https://bims1.docs.apiary.io/#reference/0/payment-methods/list-payment-methods>

La lista no será larga, así que no será necesario paginarla, pero sí se recomienda el uso del last\_update.

## Sucursales y Puntos de Venta

Una empresa en BIMS puede tener asociados uno o más establecimientos. Y cada establecimiento, a su vez, puede tener asociados uno o más puntos de venta (cajas). El POS Mobile siempre estará operando en una caja determinada, por lo que será necesario que el usuario, en el contexto de la configuración de la app, la pueda seleccionar.

Posteriormente, cuando se registre una venta, se deberá indicar en el objeto de la venta los ids de la sucursal y del punto de venta.

### Estructura de Datos

<table><thead><tr><th width="171.33333333333331">Nombre</th><th>Tipo de Dato</th><th>Descripción</th></tr></thead><tbody><tr><td>id</td><td>BIGINT</td><td>Identificador Numérico Único</td></tr><tr><td>name</td><td>VARCHAR</td><td>Nombre de la Forma de Pago</td></tr></tbody></table>

Para ambas entidades nos interesa de momento solo estos dos atributos.

### Consulta de Sucursales

Ejemplo:

```
curl "https://bims.app/api/agencies?sid=pidc5562uluse71b9u0kltj444&company=1"
```

### Consulta de Puntos de Venta

Ejemplo:

```
curl "https://bims.app/api/posales?sid=pidc5562uluse71b9u0kltj444&company=1"
```

## Ventas

En este punto estamos listos para configurar una venta sencilla.

El endpoint para el registro de ventas está documentado aquí: <https://bims1.docs.apiary.io/#reference/0/sales/create-a-new-sale>

### Comportamiento Esperado

El usuario deberá poder buscar / seleccionar los productos de una grilla, luego deberá poder buscar / seleccionar clientes de una lista.

Seguidamente el usuario deberá determinar la condición de venta, entre las opciones "Contado" y "Crédito".

Si se ha indicado la condición de venta: "Contado", se deberá determinar una o más formas de pago. Los subtotales por forma de pago deben igualar el valor total de la venta.

Si se ha indicado la condición de venta: "Crédito", se deberá configurar una fecha de vencimiento.

En la versión definitiva del Mobile POS, el regitro de la venta deberá hacerse offline, y sincronizarse a posteriori a la nube de BIMS. Sin embargo, esto requerirá la implementación de un amplio set de controles que no se recomiendan para un PMV. Así que en esta primer versión haremos el registro de la venta y su facturación en linea. En el atributo data.Sale.invoice\_number indicá: "auto". Con esto se dejará a BIMS la tarea de asignar un número de factura que se retornará en la respuesta del API.

Con la respuesta del API se deberá confeccionar una factura para su impresión en ticket. Aunque BIMS admite la configuración de múltiples plantillas de impresión de facturas, para el Mobile POS admitiremos solo un diseño estándar. Ejemplo:

<figure><img src="/files/2xIkaLz9PDU2I3jdOJ54" alt=""><figcaption></figcaption></figure>

***

## Registros de Pedidos

### Definición de Pedidos

¡Felicitaciones! A este punto, el POS ya tiene capacidad de registrar ventas 🥳. Ahora trabajemos en otra operación que debe soportarse: Pedidos. Los Pedidos son operaciones que, a posteriori, se convertirán en ventas. La entidad en el sistema es la misma que la de las ventas: "sales", solo cambian algunos atributos en la composición del objeto:

1. El atributo Sale.status lleva el valor "pending".
2. No se establecen formas de pago \[SalesPaymentMethod] ni planes de pago \[SalesPnote].

### Endpoint del API en el Backend

Se utiliza el mismo endpoint que para el registro de ventas, cuya documentación completa está disponible aquí: <https://bims1.docs.apiary.io/#reference/0/sales/create-a-new-sale>

Ejemplo:

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
    \"Sale\": {
        \"contact_id\": 351,
        \"company_id\": 1,
        \"agency_id\": 1,
        \"posale_id\": 1,
        \"currency_id\": 3,
        \"status\": \"pending\",
	\"preorder\": true,
        \"billed\": true,
        \"credit\": false,
	\"amount\": 220000
    },
    \"SalesProduct\": [
        {
            \"product_id\": 3433,
            \"quantity\": 1,
            \"price\": 220000
        }
    ]
}" \
'https://beta.bims.app/api/sales/?sid=kroi8vnrp20ojhhnjsgam7u823'

{
    "code": "200",
    "status": "ok",
    "data": {
        "Sale": {
            "id": "5842",
            ...
            "issue_date": "2024-03-11",
            ...
            "amount": "220000",
            "paid": "0",
            "currency_id": "3",
            "company_id": "1",
            "void": false,
            "created": "2024-03-11 12:21:42",
            "modified": "2024-03-11 12:21:42",
            "agency_id": "1",
            "contact_id": "351",
            ...
            "status": "pending",
            "billed": true,
            "credit": false,
            ...
            "preorder_number": "1399",
            "preorder_status": "confirmed",
            ...
        },
        "Contact": {
            "id": "351",
            "name": "JOSU\u00c9 V\u00c1SQUEZ",
            "document_id": "57070067"
        },
        "SalesProduct": [
            {
                "id": "14667",
                "sale_id": "5842",
                "product_id": "3433",
                "quantity": "1",
                "price": "220000",
                "cost": "176000",
                "created": "2024-03-11 12:21:43",
                "tax_id": "8",
                "tax_rate": "10",
                "tax_amount": "20000",
                ...
                "Product": {
                    "id": "3433",
                    "name": "producto prueba 6",
                    ...
                },
            }
        ],
    }
}
```

### Interfaz de Usuario

Se utilizará el mismo flujo actual para el registro de operaciones de Ventas. En la pantalla del Carrito, hoy se presenta un solo botón con el título "Checkout". Este botón lleva a una pantalla donde el usuario determina la forma de pago y cierra y factura la venta. Renombrar el botón "Checkout" por "Facturar" e incoporar un segundo botón con el título "Crear Pedido". El botón "Crear Pedido" registrará directamente el Pedido en el backend y al recibir una confirmación de la transacción del backend, expondrá la pantalla de feedback al usuario con las opciones de imprimir el Pedido o regresar al home.

<figure><img src="/files/Rkq3nPQ9OGZOKpXelJIw" alt=""><figcaption><p>Imagen de Referencia de Cambios Propuestos en la UI del Carrito</p></figcaption></figure>

### Lista de Pedidos

De la misma forma en que hoy se listan las Ventas registradas desde el POS, se debe listar los Pedidos en una opción diferente.

<figure><img src="/files/ccN6vkJrwbIqEnp4B1P8" alt=""><figcaption><p>Pantallas Actuales de Lista y Detalle de Ventas</p></figcaption></figure>

Donde en las pantallas lista y vista de Ventas se muestra el número de factura \[Sale.invoice\_number], en el caso de las pantallas de Pedidos se mostrará el número de Pedido \[Sale.preorder\_number] y además el nombre del cliente Contact.name.

Debe poderse buscar Pedidos a partir del número de pedido o del nombre del Cliente haciendo uso de la casilla de búsqueda en la parte superior de la pantalla.

En la pantalla de vista (detalle) del Pedido, se debe incluir las opciones de reabrir el Pedido en modo de edición. Esta opción configurará la orden actual con los datos del Pedido, incluyendo su id \[Sale.id]. En la pantalla del Carrito, el título del botón "Crear Pedido" se cambiará a "Guardar Pedido". Su función será la misma, solo se añadirá el valor Sale.id al JSON enviado al backend, con el objeto de modificar el registro en lugar de crear uno nuevo.

Y la opción de facturación seguirá su curso normal con los siguientes cambios:

1. Se añadira el valor Sale.id al JSON enviado al endpoint del API del backend \[api/sales]. De esta manera, en lugar de crearse una nueva operación, se editará la preexistente.
2. Se establecerá el valor de Sale.status a "approved".

### Impresión de Pedidos

La impresión de pedidos debe tener la siguiente estructura:

<figure><img src="/files/QfTXNK1SSF3G4eQcMx5G" alt=""><figcaption><p>Estructura de Pedido Impreso</p></figcaption></figure>

***

## Fixes Requeridos

### Aplicación de Descuentos en Pedidos

En la pantalla de cierre del Pedido está presente un campo de descuentos. El valor ahí ingresado debe aplicarse.

<figure><img src="/files/O8pEAgHyS7rWBC9zFtTV" alt=""><figcaption></figcaption></figure>

En el POST al API el descuento se ingresa en el campo SalesProduct.discount\_amount, y se resta del precio unitario en SalesProduct.price. Entonces, por ejemplo, dado el caso de un ítem con las siguientes características:

* Id de Producto: 1
* Precio Unitario: 60.000
* Cantidad: 2
* Descuento: 10.000

Los objetos dentro del array SalesProduct deberán construirse según el siguiente modelo:

```
SalesProduct: [
    {
        product_id: 1,
        price: 55000,
        quantity: 2,
        discount_amount: 10000
    }
]
```

El valor del precio unitario se ajusta según:

```
SalesProduct.price = SalesProduct.price - SalesProduct.discount_amount / SalesProduct.quantity
```

### Corrección de Facturas con Descuentos

La factura para impresión generada por la app para una venta con descuentos, aplica dos veces el valor de descuento para el cálculo del total.

En el caso de abajo, la venta tiene un precio unitario de 120.000, y se aplica un descuento de 100.000. Sin embargo, se expone como precio unitario 100.000 y se le vuelve a aplicar el descuento dejando el valor final en 90.000.

Además, a la linea del descuento en la factura le falta la columna de "Descripción", donde debe llevar la palabra "Descuento".

<figure><img src="/files/QqYjkJIBkwDhP8wYxJWP" alt=""><figcaption></figcaption></figure>

Aplicar el mismo criterio para la impresión de Pedidos.

### Liquidación de Impuestos en la Factura

En la factura generada actualmente se muestra la siguiente sección:

<figure><img src="/files/COSEclYkmCv2I2qzwnPc" alt=""><figcaption><p>Liquidación de Impuestos en la Factura</p></figcaption></figure>

Añadir como título de la sección: "Liquidación de IVA". Anteponer la palabra "IVA" a las tasas de cada impuesto. Ejemplo: "IVA 10%", "IVA 5%".  Suprimir la linea "EXENTO" y en su lugar incluir la suma de los valores de los impuestos con la etiqueta: "Total IVA:".

### Campo "Observaciones" en Pedidos

Al registrar una venta en la app, en la pantalla de Checkout está presente el campo "Observaciones" (Sale.notes). Sin embargo, el mismo campo no se presenta al registrar un Pedido debido a que no se ingresa a la pantalla de Checkout. Pero las notas son una información importante para los Pedidos por lo que se requiere incoporar a estos, y para eso, deberá moverse el campo a la pantalla anterior al Checkout, la del Carrito.

<figure><img src="/files/Nv6p5gRgqLLg86QZClO6" alt=""><figcaption><p>Carrito</p></figcaption></figure>

<figure><img src="/files/pCrlRwWLCLYydIMYOmfk" alt=""><figcaption><p>Checkout</p></figcaption></figure>

El valor del campo se debe guardar en Sale.notes.

### Cambios en Pantalla de Detalle de Pedidos y Ventas

Indicar para cada ítem de la venta los siguiente datos: Nombre del Producto, Precio Unitario, Cantidad, Subtotal (Precio Unitario \* Cantidad).

1. Exponer las notas del Pedido / Venta (Sale.notes).
2. Ejecutar cambios estéticos según muestra a continuación o sugerir otra opción.

<figure><img src="/files/wcoc23v70pofUWkBn4wP" alt=""><figcaption><p>Modelo de Diseño de Pantallas de Detalles de Pedidos y Ventas</p></figcaption></figure>

### Fecha de Vencimiento de la Factura

Cuando en la pantalla de Checkout se indica como Condición de pago: "Crédito", se expone un campo de tipo fecha para el ingreso de la fecha de vencimiento.

<figure><img src="/files/3nGYpT7rF8HWr7mVbpS3" alt=""><figcaption></figcaption></figure>

Sin embargo, por error se envía al backend siempre la fecha predeterminada. Es necesario enviar al backend la fecha configurada por el usuario en el array de objetos SalesPnote, en el atributo expiration\_date del único objeto contenido, según el ejemplo a continuación:

```json
"SalesPnote":[
    {
        "expiration_date":"2024-05-02",
        "amount":2000
    }
]
```

### Dígito Verificador del RUC en Factura Impresa

El dígito verificador de un número de documento es el resultado de la ejecución de una función matemática en base al número de documento como argumento. El dígito verificador (o de validación) es el que se indica a la derecha del guión en un RUC. Ejemplo: 8007495&#x34;**-5**, donde el dígito verificador del RUC "80074954" es "5".

En BIMS, por lo general, el número de documento (RUC) del cliente se registra sin el dígito verificador. Esto con el objeto de ir calculándolo en la interfaz de usuario mientras el usuario lo va escribiendo, de manera que sirva como ayuda visual para la validación de ingreso del usuario. En muy raros casos, el documento registrado ya incluye el dígito verificador, con el guión incluído.

En la factura que se genera para impresión desde la aplicación, si el número de documento del Cliente no incluye el dígito verificador, y es un valor numérico, se debe calcular el dígito verificador y añadirlo al RUC en la impresión con un guión que separe el RUC de su dígito verificador.

Abajo se indica la función para generar el dígito verificador en JavaScript.

```javascript
function dv_py(p_numero, p_basemax = 11) {
	p_numero = ''+p_numero;
	var v_total = 0;
	var v_resto;
	var k;
	var v_numero_aux;
	var v_numero_al = '';
	var v_caracter;
	var v_digit;

	for (i=0; i < p_numero.length; i++) {
		v_caracter = p_numero.substring(i, i+1);
		if ( v_caracter.charCodeAt(0) < 48 && v_caracter.charCodeAt(0) > 57) {
		        v_numero_al = v_numero_al + v_caracter.charCodeAt(0);
		}
		else {
			v_numero_al = v_numero_al + v_caracter;
		}
	}
	k = 2;
	v_total = 0;

	for (i = v_numero_al.length - 1; i >= 0; i--) {
		if (k > p_basemax) {
			k = 2;
		}
		v_numero_aux = v_numero_al.substring(i, i+1);
		v_total = eval(v_total) + eval(v_numero_aux) * k;
		k++;
	}

	v_resto = v_total % 11;

	if (v_resto > 1) {
		v_digit = 11 - v_resto;
	}
	else {
		v_digit = 0;
	}

	return v_digit;
}
```

### Facturación de Valores Exentos

Una factura puede ser exenta de IVA por dos motivos:

1. El producto vendido es exento de IVA;
2. El cliente es exento de IVA.

En ambos escenarios la factura generada es incorrecta.

#### Cuando el Producto Vendido es Exento de IVA

Cuando el Producto vendido es exento de IVA (Product.tax\_id = null), el valor del producto no sufre ninguna alteración, simplemente no se gravan impuestos. La aplicación opera correctamente y no es necesaria ninguna adaptación.

#### Cuando el cliente es Exento de IVA

Cuando es el ciente exento de IVA (Contact.notaxes = true) y el Producto es gravado (Product.tax\_id != null) entonces el valor del IVA se resta del precio de venta del Producto.

En el ejemplo de abajo, se factura a un Cliente Exento un Producto con un precio de venta gravado de Gs. 10.000. El valor del IVA es Gs. 909, por lo que el precio final de venta queda en Gs. 9.091 (10.000 - 909). Sin embargo, en la sección de liquidación de IVA, se calcula un IVA sobre ese valor: Gs. 826. Si el cliente es exento, la factura no lleva IVA. Cuando el cliente es exento, la liquidación de IVA debe tener valor: 0. Esto debe corregirse.

<figure><img src="/files/obchxZvjtQcjCRMzILtM" alt=""><figcaption><p>Factura a Cliente Exento con Liquidaciónd de IVA incorrecta.</p></figcaption></figure>

&#x20;

### Cantidades de Productos No Enteras

La pantalla de Selección de Productos para la venta no admite actualmente el ingresos de cantidades con valores no enteros. Es necesario soportar valores no enteros para las cantidades de los Productos. Como alternativa, se propone un pop-up con un campo de cantidad al hacer tap sobre el valor de la cantidad. Y conservar los comandos "+" y "-" con su funcionamiento actual que suman o restan una unidad a la cantidad actual.

<figure><img src="/files/KvRASltCvKdIG49lcDiw" alt=""><figcaption><p>Selección de Productos para la Venta</p></figcaption></figure>

## Soporte Offline

### Objeto

Hasta este punto, la app permite registrar Pedidos y Ventas que envía al backend de forma síncrona. En otras palabras, cuando se ejecuta el checkout de una Venta o se hace clic en el botón de creación del Pedido se envía los datos de la operación al backend y se espera su respuesta antes de emitir el comprobante.  Ahora es necesario enviar la venta al backend de forma asíncrona con el objeto de:

1. Agilizar la operación, evitando que el usuario deba esperar la respuesta del backend para cerrar la operación, emitir el comprobante y seguir operando.
2. Poder operar offline.

### Modo de Operación

Para esto, es necesario que las ventas se guarden primero en una BD local, y en background se vayan sincronizando con el backend siempre que haya conexión. El estado de sincronización debe indicarse en la lista de ventas y en la lista de pedidos. Se debe informar en la lista los errores de sincronización que pudieran ser retornados por el backend en caso de no poder guardar una transacción.

La causa más frecuente en errores de sincronización de ventas con este modus operandi es el conflicto en la numeración de facturas o de números de pedidos, debida por lo general a la utilización simultánea del mismo punto de expedición por más de una terminal operando offline.

Por este motivo, este modo de operación debe ser optativo y se debe poder configurar en la pantalla de Configuración de la App con el título "Soporte de Operación Offline" y las opciones: "Activado" y "Desactivado".

### Emisión de Comprobantes

Tanto para emitir una factura de Venta, como un comprobante de Pedido, se expone información que se recibe actualmente del backend en la respuesta de la transacción. Estos campos son:

A. Para las Facturas de Ventas: Número de Factura (Sale.invoice\_number).

B. Para los Pedidos: Número de Pedido (Sale.preorder\_number).

Para poder operar con soporte offline, estos valores deben consultarse al backend a través de un endpoint provisto para el caso períodicamente, siempre que exista conexión. Luego se deben ir autoincrementando con cada operación.

En caso que otra terminal se encuentre utilizando el mismo punto de expedición (la misma "Caja" lógica), se recibirá un push informando el nuevo número de comprobante emitido para actualizar localmente el número de comprobante a utilizar en la siguiente emisión. Igualmente, deberá enviar un push cada vez que se genere una venta. La conexión al servidor push se documentará completamente.

### Cómo Obtener los Últimos Números de Comprobantes Emitidos

Para determinar qué número de comprobante emitir, es necesario saber cuál fue el último número de comprobante emitido con el timbrado del Punto de Venta en uso. Para consultar este dato, utilizá el endpoint api/posales indicando los argumentos en la URL:  id=\<id del Punto de Venta> y set\_current\_invoice\_number=1. Ejemplo:

```
curl 'https://beta.bims.app/api/posales?id=3&set_current_invoice_number=1&sid=...'
{
    "code": "200",
    "status": "ok",
    "data": [
        {
            "Posale": {
                "id": "3",
                "name": "Caja 002",
                "bill_code": "001",
                "latest_invoice_number": "1821",
                "latest_alt_invoice_number": "74"
            },
            "Agency": {
                "id": "1",
                "name": "Casa Matriz",
                "bill_code": "001",
                "address": "Mariscal López 377",
                "city": "Asunción",
                "phone": "021440104"
            },
            "Stamping": {
                "id": "6",
                "code": "411121312",
                "issue_date": "2022-06-17",
                "expiration_date": "2099-12-31",
                "invoice_from": "1",
                "invoice_to": "9999999999"
            }
        }
    ],
    "last_update": "2024-04-23 11:25:22"
}
```

Y esta es la descripción de los datos que se exponen en el JSON de la respuesta.

<table><thead><tr><th width="298">Campo</th><th>Descripción</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>Posale.id</td><td>Id del Punto de Venta</td><td>INT</td></tr><tr><td>Posale.name</td><td>Nombre del Punto de Venta</td><td>VARCHAR</td></tr><tr><td>Posale.bill_code</td><td>Código Fiscal del Punto de Expedición</td><td>VARCHAR</td></tr><tr><td>Posale.latest_invoice_number</td><td>Último Número de Comprobante Fiscal (Factura) Emitido</td><td>INT</td></tr><tr><td>Posale.latest_alt_invoice_number</td><td>Último Número de Comprobante No Fiscal Emitido</td><td>INT</td></tr><tr><td>Agency.id</td><td>Id del Establecimiento</td><td>INT</td></tr><tr><td>Agency.name</td><td>Nombre del Establecimiento</td><td>VARCHAR</td></tr><tr><td>Agency.bill_code</td><td>Código Fiscal del Establecimiento</td><td>VARCHAR</td></tr><tr><td>Stamping.id</td><td>Id del Timbrado</td><td>INT</td></tr><tr><td>Stamping.code</td><td>Código de Timbrado</td><td>VARCHAR</td></tr><tr><td>Stamping.issue_date</td><td>Fecha de Emisión del Timbrado</td><td>DATE</td></tr><tr><td>Stamping.expiration_date</td><td>Fecha de Vencimiento del Timbrado</td><td>DATE</td></tr><tr><td>Stamping.invoice_from</td><td>Menor Número de Comprobante Admitido</td><td>INT</td></tr><tr><td>Stamping.invoice_to</td><td>Mayor Número de Comprobante Admitido</td><td>INT</td></tr></tbody></table>

### Cómo Construir el Número de Factura

El número de factura que se expone en una factura tiene tres partes, separadas entre ellas con un guión y cada parte con una longitud específica, con ceros a la izquierda de relleno según se indica a continuación.

<table><thead><tr><th width="98">Parte</th><th width="399">Dato</th><th>Longitud</th><th>Ejemplo</th></tr></thead><tbody><tr><td>1</td><td>Código Fiscal del Establecimiento</td><td>3</td><td>001</td></tr><tr><td>2</td><td>Código Fiscal del Punto de Expedición</td><td>3</td><td>001</td></tr><tr><td>3</td><td>Número de Factura</td><td>7</td><td>0001822</td></tr></tbody></table>

Ejemplo: "001-001-0001822".

### Controles

El número de factura a emitir debe estar en el rango de Stamping.invoce\_from a Stamping.invoice\_to. Si ya se ha alcanzado el valor de Stamping.invoice\_to, no debe admitirse la facturación.

### Dirección de la Empresa en la Factura

Si la Agency.address está definida, entonces esta debe ser la dirección que se exponga en la factura para la Empresa. Si Agency.address no está definida, usar Company.address.

## Soporte de Precios Abiertos

Es posible configurar un producto con precio abierto, lo cual implica que el valor configurado como precio para el producto es un precio sugerido que podrá ser modificado por el usuario al momento de la venta.

El atributo Product.price\_open (bool) determina si el precio del producto está abierto. La app debe permitir modificar el precio de un producto al momento de la venta cuando y solo cuando su atributo Product.price\_open = TRUE.

&#x20;

<figure><img src="/files/zFp9Ewq7uhP4lDOevLWO" alt=""><figcaption><p>Implementación Recomendad en UX</p></figcaption></figure>

La recomendación de implementación en la interfaz de usuario es que al hacer tap sobre un item del Carrito, se abra una ventana de tipo popup con los datos del item: Imagen, Nombre, Precio y Cantidad. Y si el precio es abierto, que se permita ahí modificar el precio unitario.

## Categorías  de Productos y Catálogos Habilitados para el Punto de Ventas

Los PRODUCTOS en BIMS están agrupados asociados de manera independiente a CATEGORÍAS DE PRODUCTOS (Product.ptype\_id -> Ptype.id) y a CATÁLOGOS (Product.catalog\_id -> Catalog.id).

Y opcionamente, el usuario administrador de BIMS podrá establecer que solo determinadas CATEGORÍAS DE PRODUCTOS y/o CATÁLOGOS DE PRODUCTOS estén disponibles en un PUNTO DE VENTAS.

La lista de PUNTOS DE VENTA se obtiene con el endpoint "posales". A este punto ya se ha implementado para la selección del PUNTO DE VENTA según la EMPRESA en la APP. En cada item del array retornado por el endpoint "posales" vas a encontrar arrays con los títulos "Catalog" y "Ptype", que listan CATÁLOGOS y CATEGORÍAS DE PRODUCTOS habilitados para el PUNTO DE VENTA respectivamente.

````
{
    "Posale": {
        "id": "1",
        "name": "Caja 1 - TM",
        ...
    },
    "Company": {
        "id": "1",
        "credit_interest": "0"
    },
    "Agency": {
        "id": "1",
        "name": "Sucursal Centro",
        ...
    },
    "Stamping": {
        "id": "2",
        "code": "1",
        ...
    },
    "Catalog": [
        {
            "id": "7",
            "name": "BARCINO"
        },
        {
            "id": "6",
            "name": "CEBU"
        },
        {
            "id": "4",
            "name": "Cat\u00e1logo A"
        }
    ],
    "Ptype": [
        {
            "id": "545",
            "name": "Activos Fijos"
        },
        {
            "id": "111",
            "name": "Animadas"
        },
        {
            "id": "112",
            "name": "Aventura"
        }
    ],
    "EnabledUser": [
        {
            "id": "273",
            "name": "Juan",
            "login": "juan"
        },
        {
            "id": "2",
            "name": "api",
            "login": "api"
        },
        {
            "id": "282",
            "name": "Monica",
            "login": "monica"
        }
    ]
}
```
````

Si el array "Catalog" está vacío, entonces se asume que todos los CATÁLOGOS de PRODUCTOS están habilitados para el PUNTO DE VENTA. Igualmente, si el array de "Ptype" está vacío, se asume que todas las CATEGORÍAS DE PRODUCTOS están habilitadas para el PUNTO DE VENTA.

En la pantalla de configuración de la APP, bajo el PUNTO DE VENTA, se deberá listar todos los CATÁLOGOS y CATEGORÍAS DE PRODUCTOS habilitadas para el PUNTO DE VENTA seleccionado. Si todas las CATEGORÍAS o CATÁLOGOS estuviesen habilitados, no es necesario listarlos, sino solamente indicar: "Todos".

La lista de PRODUCTOS y de CATEGORÍAS DE PRODUCTOS debe filtrarse según los CATÁLOGOS y CATEGORÍAS DE PRODUCTOS habilitadas.

## Usuarios Habilitados para el Punto de Venta

También, un PUNTO DE VENTA podría no estar habilitado para todos los USUARIOS, en tal caso, el atributo Posale.allusers (bool) tendrá el valor true y se listarán los USUARIOS habilitados en el objeto EnabledUser, asociado al PUNTO DE VENTA en el JSON del endpoint "posales".

Se deberá restringir el uso de PUNTOS DE VENTA para el USUARIO según esta configuración. Si el PUNTO DE VENTA no estuviera habilitado para todos los USUARIOS, y el USUARIO activo no estuviera incluído en la lista de EnabledUser, entonces el PUNTO DE VENTA no deberá poder ser seleccionado en la APP.

## Setup desde la app

Hasta este punto, la app estuvo enfocada como un add-on de la plataforma de BIMS, llevando casi todo el setup necesario para las funciones de la app en la plataforma. Ahora hagamos que el usuario pueda utilizar la app pueda hacer toda la configuración necesaria desde la propia aplicación móvil. Esto implica la configuración de esta colección mínima de elementos:

1. Productos y Categorías de Productos
2. Timbrados de Facturación
3. Empresa, Sucursales y Puntos de Venta
4. Formas de Pago

### Consideraciones generales

1. El PK de todos los maestros que se configurarán aquí es el campo "id".&#x20;
2. Cuando se desee crear un nuevo registro, debe omitirse el campo "id".
3. Cuando se desee editar un registro preexistente, debe incluirse el campo "id".

### Configuración de Categorías de Productos

#### Descripción

Las Categorías de Productos son uno de los esquemas de clasificación de Productos en BIMS, el principal. Es una estructura jerárquica, sin embargo, en esta app admitiremos solo una configuración escalar.

#### Estructura de Datos

| Campo       | Tipo de Dato | Descripción                  | Valor Predeterminado |
| ----------- | ------------ | ---------------------------- | -------------------- |
| id          | BIGINT       | Identificador numérico único | SERIAL               |
| name        | VARCHAR      | Nombre                       |                      |
| company\_id | BIGINT       | Identificador de la EMPRESA  |                      |

#### Permisos Requeridos

ptypesView: Ver Categorías de Productos

ptypesAdd: Crear Categorías de Productos

ptypesEdit: Modificar Categorías de Productos

ptypesDelete: Eliminar Categoreias de Productos

#### Ejemplos

{% tabs %}
{% tab title="Crear Categoría de Productos" %}

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
    \"Ptype\": {
        \"name\": \"Frutas\",
        \"company_id\": 1
    }
}" \
'https://beta.bims.app/api/ptypes/?sid=kroi8vnrp20ojhhnjsgam7u823'

{
    "code": "200",
    "status": "ok",
    "data": {
        "Ptype": {
            "name": "Frutas",
            "company_id": 1,
            "id": "330"
        }
    },
    "last_update": "2025-05-05 12:46:09"
}
```

{% endtab %}

{% tab title="Editar Categoría de Productos" %}

```json
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
    \"Ptype\": {
        \"name\": \"Verduras\",
        \"company_id\": 1,
        \"id\": 330
    }
}" \
'https://beta.bims.app/api/ptypes/?sid=d3t8ckv4suqm1oe1udg6o7fjs5'

{
    "code": "200",
    "status": "ok",
    "data": {
        "Ptype": {
            "name": "Verduras",
            "company_id": 1,
            "id": 330
        }
    },
    "last_update": "2025-05-05 12:47:25"
}
```

{% endtab %}

{% tab title="Listar Categorías de Productos" %}

```
curl https://beta.bims.app/api/ptypes
```

{% endtab %}
{% endtabs %}

### Configuración de Productos

#### Descripción

Los PRODUCTOS representan los ítems que estarán disponibles para la venta desde la app.

#### Estructura de Datos

| Campo       | Tipo de Dato | Descripción                                          | Valor Predeterminado |
| ----------- | ------------ | ---------------------------------------------------- | -------------------- |
| id          | BIGINT       | Identificador numérico único                         | SERIAL               |
| name        | VARCHAR      | Nombre                                               |                      |
| company\_id | BIGINT       | Identificador de la EMPRESA                          |                      |
| ptype\_id   | BIGINT       | Identificador de la CATEGORÍA DE PRODUCTOS           |                      |
| sell\_price | FLOAT        | Precio de venta                                      | 0                    |
| price\_open | BOOLEAN      | Bandera de precio modificable al momento de la venta | FALSE                |
| tax\_id     | BIGINT       | Identificador del impuesto aplicado a la venta       | NULL                 |

***

## Variantes de Productos

Las variantes de PRODUCTOS permiten manejar diferentes versiones de un PRODUCTO disponibles para la venta. Por ejemplo, un modelo de zapatillas en diferentes tallas.

<figure><img src="/files/Fi38TKsmhl0xwhhyYCIJ" alt=""><figcaption></figcaption></figure>

Cada talla de esta zapatilla será un PRODUCTO en BIMS. Y para relacionarlos entre sí y con un PRODUCTO maestro que pueda exponerse como en el ejemplo, se implementó una relación del tipo padre / hijo entre PRODUCTOS a través del campo Product.master\_product\_id -> Product.id.

Se configura cada variante como un PRODUCTO separado, de manera a poder llevar una gestión de inventario independiente, un costeo independiente, una gestión de compras independientes, así como la propia venta de forma independiente e incluso con precios distintos.

Así como la talla, hay otros atributos que pueden diferenciar a las variantes, incluso la diferencia puede estar dada por la combinación de varias variantes (Ej.: talla y color). Estos atributos se configuran como "Campos Personalizados" en las CATEGORÍAS DE PRODUCTOS.

Los campos personalizados de CATEGORÍAS DE PRODUCTOS permiten establecer atributos especiales de PRODUCTOS de un tipo específico. Por ejemplo, para libros: el autor y el año de edición, para prendas: la talla y el color. Los campos personalizables pueden tener estos tres tipos de datos:

* Número
* Texto
* Lista de Opciones Cerradas

<figure><img src="/files/biuzqFIJBLQHZkGlYVMz" alt=""><figcaption></figcaption></figure>

Y en el caso de uso de variantes, los campos personalizados constituyen el eje de las diferencias de cada variante.

Los PRODUCTOS que tengan variantes tendrán un valor mayor a cero en el campo Product.total\_variants. Estos productos no estarán disponibles para la venta, en su lugar, servirán de agrupadores de las variantes en las interfaces de usuario de ventas.

Sus variantes, serán aquellos PRODUCTOS cuyo valor Product.master\_product\_id sea el Id del PRODUCTO maestro.

Ver: [Product](https://bims.gitbook.io/api/estructuras-de-datos/product)

Los valores de los campos personalizados de cada Product estarán en la relación [PtypesCustomField](https://bims.gitbook.io/api/estructuras-de-datos/ptypescustomfield).

{% content-ref url="/spaces/QSJPi7nTrNTUlIg04Gju/pages/b4su1B0mCvSMAA8tQq05" %}
[PtypesCustomField](https://bims.gitbook.io/api/estructuras-de-datos/ptypescustomfield)
{% endcontent-ref %}

Ver la documentación de consulta de productos via API:&#x20;

{% content-ref url="/spaces/QSJPi7nTrNTUlIg04Gju/pages/A59M7PemjF6wplAC5Pnp" %}
[Productos](https://bims.gitbook.io/api/endpoints/productos)
{% endcontent-ref %}

La lista de productos es potencialmente grande, por lo que se recomienda:

1. [El uso de paginado en las consultas](https://bims.gitbook.io/api/paginado-de-consultas).
2. La consulta de solo los últimos cambios desde la consulta previa, haciendo uso del argumento [last\_update](https://bims.gitbook.io/api/consulta-de-ultimos-cambios).

***

## Soporte Multi-Moneda

BIMS es una plataforma multi-moneda. Permite a los usuarios configurar múltiples monedas y operar con cualquier de ellas.

Una de las monedas configuradas actúa como la principal y el resto de las monedas tienen cotizaciones definidas para la venta y la compra en función de la moneda principal. Para cada moneda además se define el nivel de precisión (cantidad de decimales). Ver: [Currency](https://bims.gitbook.io/api/estructuras-de-datos/currency).

Además, al ser BIMS también una plataforma multi-empresa, la definición de moneda principal y las cotizaciones de las monedas puede variar según empresa. Ver: [CompaniesCurrencies](https://bims.gitbook.io/api/estructuras-de-datos/companiescurrencies).

En esta primera implementación, dejemos que todo el setup de monedas se siga manejando en el backend, y enfoquémonos en darle al POS móvil la capacidad de facturar en distintas monedas. Para esto será necesario:

1. Sincronizar las monedas desde el backend. Ver: [Monedas](https://bims.gitbook.io/api/endpoints/monedas).
2. Implementar un selector de moneda en pantalla.
3. Expresar los precios de los productos, subtotales y totales en la moneda seleccionada, esto implica: aplicar el tipo de cambio correspondiente y respetar la cantidad de decimales configurada para la moneda seleccionada.
4. Confeccionar la factura de venta en la moneda seleccionada, indicando el nombre de la moneda, y los valores expresados en la moneda seleccionada.

### Sincronizar Monedas desde el backend

Para consultar la configuración de monedas en BIMS, usá el endpoint `/currencies`. Aquí tenés su documentación: [Monedas](https://bims.gitbook.io/api/endpoints/monedas).

Es conveniente que realices esta consulta y actualices la configuración períódicamente en ciclos de diez a treinta minutos. Los cambios en la configuración de las monedas se realizan usualmente a una razón de no más de una vez por día. En una siguiente versión implementaremos un push.

### Implementar un selector de moneda en pantalla

### Expresar valores en la moneda seleccionada

#### Precios de Productos

Los precios de los productos pueden estar expresados en una moneda distinta de la principal. La moneda en la que está expresado el precio del producto se establece en el atributo `Product.sell_price_currency_id`.

Para expresar el precio del producto (ProductCurrency) en la moneda seleccionada (SaleCurrency),  usá la siguiente fórmula:

`x = Product.sell_price * ProductCurrency.purchase_price / SaleCurrency.purchase_price`

### Sincronizar la venta

Al sincronizar la venta con la nube, incluí los siguientes campos al JSON:

```
Sale: {
    ...
    currency_id: <Id de la Moneda Seleccionada>,
    currency_price: <Cotización de la Moneda Seleccionada>
    ...
}
```


# App de Servicios para Consorcios

Aplicación móvil dirigida a miembros de un consorcio, propietarios y/o inquilinos, con servicios para la reserva de amenities y presentación de liquidaciones de expensas.

Instancias de BIMS

BIMS puede distribuirse bajo dos modelos de negocio:

1. Software as a Service
2. Licenciamiento Perpétuo

Las cuentas de BIMS bajo el modelo de Software as a Service emplean la misma URL: <https://bims.app>.

Las instancias de BIMS de cuentas bajo el modelo de licenciamiento perpetuo pueden pueden implementarse en otros hosts. Ejemplo: <https://beta.bims.app>.

Es por ese motivo que la aplicación móvil debe permitirle al usuario configurar el host ($HOST) de su instancia de BIMS y la opción predeterminada debe ser la de SaaS: <https://bims.app>.

Para las cuentas SaaS, se debe indicar en el login también el código de empresa ($TENANT\_CODE). Este código es alfanumérico. Ej.: "prueba24".

Se recomienda que las casillas de $HOST y de $TENANT\_CODE, al no haber necesidad de cambiarlas en la app en un escenario normal, se configuren en una pantalla distinta del login.

## Entidades

### Propiedades

Las Propiedades \[Estate] representan unidades inmobiliarias como casas o departamentos.

### Consorcios

Los Consorcios \[EstatesGroup] o Codominios son en esencia un conjunto de Propiedades. Representan edificios, barrios cerrados o cualquier estructura organizada de Propiedades.

### Amenities

Los Amenities \[Amenity] son espacios u otros recursos comunes en un Consorcio. Ejemplo: Quinchos, Piscinas, Canchas, Lavarropas.

### Usuarios

Los Usuarios \[User] son individuos que harán uso de la app. Los Usuarios están relacionados a una o más Propiedades en uno de dos roles por cada Propiedad: Propietario o Inquilino. Los Usuarios se crean en BIMS como usuarios  del sistema con permisos específicos, a través del módulo de Bienes Raíces desde la ficha de una Propiedad.

A diferencia de los Usuarios regulares, los Usuarios para esta aplicación tienen definido el atriibuto User.account\_type = 're'.

## Login

Para hacer login en BIMS se requiere de un nombre de usuario ($USERNAME) una contraseña ($PASSWORD) y en el caso de tratarse de una cuenta de SaaS: el $TENTANT\_CODE.

## Endpoint para Inicio de Sesión

<mark style="color:green;">`POST`</mark> `https://$HOST/api/users/login`

#### Request Body

| Name                                       | Type   | Description               |
| ------------------------------------------ | ------ | ------------------------- |
| user<mark style="color:red;">\*</mark>     | String | Nombre de Usuario         |
| password<mark style="color:red;">\*</mark> | String | Hash MD5 de la Contraseña |
| tenant                                     | String | Código de Tenant          |
| account\_type                              | String | Tipo de Cuenta = "re"     |

{% tabs %}
{% tab title="200: OK Login Exitoso" %}

```
{
    status: "ok",
    code: "200",
    data: {
        User: {
            id: 1,
            login: "demo",
            name: "Cuenta de Demo",
            ...
        },
        Group: {
            id: 1,
            name: "Administradores",
            full: true
            ...
        },
        Session {
            id: "vsknhtsefpt9pmr4pusgtgvdm3",
            expire: "2023-11-01 10:00:00",
            current_timestamp: "2023-11-01 08:00:00",
            ...
        }
    }
}
```

{% endtab %}

{% tab title="200: OK Login Fallido" %}

```
{
    "status": "error",
    "code": "401",
    "message": "Login Incorrecto",
    "last_update": "2023-10-31 17:22:25"
}
```

{% endtab %}
{% endtabs %}

### Ejemplo de Login en cURL

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
          \"user\": \"usuario2\",
          \"password\": \"2fb6c8d2f3842a5ceaa9bf320e649ff0\",
          \"tenant\": \"test123\",
          \"account_type\": \"re\",
     }" \
'https://bims.app/api/users/login?parse_perms=1';
```

### Login según Tipo de Instancia de BIMS

Si se accede a una cuenta de tipo SaaS (1), el host será siempre <https://bims.app> y para identificar la instancia de BIMS se deberá indicar el código de empresa en el campo "tenant" en el login.

Si se accede a una cuenta de tipo Licenciamiento Perpetuo (2), el host será distinto de <https://bims.app>, y el mismo deberá ser indicado por el usuario.

En un escenario normal, el usuario definirá el código de empresa (para una cuenta SaaS) o la URL del host propio (para una cuenta de licanciamiento perpetuo) una sola vez; por eso se recomienda:

1. Unificar el campo URL de Host y Código de Empresa en un mismo campo en el formulario.
2. Mostrar este campo solo en el primer login, y luego ocultarlo y mostrarlo en un modo de visualización avanzada.

Ejemplo:

<figure><img src="/files/MHyy3e5xT0wdJvPdF8qv" alt=""><figcaption></figcaption></figure>

## Manejo de Sesiones

### El Id de Sesión

En la respuesta del API a un login existoso, se retorna el Id de la sesión ($SESSION\_ID) en el atributo: data.Session.id.

La aplicación debe guardar el valor $SESSION\_ID y pasar a las siguientes consultas que haga al API  en el argumento de la URL: "sid".

### Ejemplo de uso en cURL

Suponiendo que el valor de $SESSION\_ID = 7cr7cn9m6k598mv7gdvag2vrq7.

```
curl "https://bims.app/api/products?sid=7cr7cn9m6k598mv7gdvag2vrq7&company=1&limit=2"
```

### Vigencia de la Sesión

Las sesiones son temporales. En la respuesta al login exitoso, el campo data.Session.expire se indica la fecha de expiración de la sesión, mientras que en el campo data.Session.current\_timestamp se indica la marca de tiempo actual como referencia.

Se recomienda actualizar períodicamente el $SESSION\_ID con un nuevo login en background y no exigirle al usuario un nuevo login hasta que el mismo cierre su sesión.

### Rol del Usuario

Como se ha indicado antes, el Usuario puede estar asociado a una o más Propiedades con uno de los sigiuientes dos roles por Propiedad: Propietario o Inquilino. Estas relaciones, con sus respectivos roles, se indican en la respuesta del login en el atributo data.UsersEstate. Ejemplo:

```
{
    "status": "ok",
    "code": "200",
    "data": {
        "User": {
            "id": "316",
            "name": "Tony Stark",
            ...
        },
        "UsersEstate": [
            {
                "id": "17",
                "user_id": "316",
                "estate_id": "2",
                "role": "tenant",
                "Estate": {
                    "id": "2",
                    "name": "Departamento A1",
                    "estates_group_id": "1"
                }
            }
        ],
        ...
    }
}
```

### Edición de Perfil Propio

El usuario debe tener la posibilidad de cambiar su contraseña, su nombre de usuario, su dirección de e-mail o su número telefónico. Para tal efecto, utilizar el endpoint "api/users/me". Ejemplo:

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
		\"password\": \"nuevopass\"
}" \
'https://beta.bims.app/api/users/me?sid=qfq2rqi5fnepd56qpoinven6m1';
```

En el ejemplo de arriba estamos actualizando la contraseña del usuario actual (el correspondiente al sid indicado) a "nuevopass". Si el resultado es existoso, BIMS retornará los datos actualizados del usuario. Ejemplo:

```
{
    "code": "200",
    "status": "ok",
    "data": {
        "User": {
            "id": "38",
            "name": "Fernando",
            "login": "fernando-propietario",
            "password": "cc5e214c2c6daf2af2055712905e1c99"
            ...
```

Si ha ocurrido un error, BIMS retornará un status:  'error'. Ejemplo:

```
{
    "code": "405",
    "status": "error",
    "message": "No hay argumentos de entrada",
    "last_update": "2024-03-17 18:57:13"
}
```

## Consorcios

El endpoint del API para listar los Consorcios es "estates\_groups". Ejemplo de consulta:

```
curl 'https://beta.bims.app/api/estates_groups/index?sid=2fb6c8d2f3842a5ceaa9bf320e649ff0&company=1';
```

Indicar en las consultas siempre el valor sid (recibido en la respuesta del login en data.Session.id) y company\_id (indicado en la respuesta del login en data.User.company\_id).

## Amenities

Para consultar la lista de Amenities de un Consorcio, utilice el endpoint "amenities" e indique el Id del Consorcio en la URL con el argumento: states\_group\_id. Ejemplo:

```
curl 'https://beta.bims.app/api/amenities/index?estates_group_id=1&sid=2fb6c8d2f3842a5ceaa9bf320e649ff0&company=1';
```

## Reservas de Amenities

### Estructura de Datos

La tabla de reserva de amenities es "amenity\_bookings" y cuenta con los siguientes campos:

| Campo          | Descripción                                          | Tipo de Dato |
| -------------- | ---------------------------------------------------- | ------------ |
| id             | Id de la Reserva                                     | BIGINT       |
| amenity\_id    | Id del Amenity                                       | BIGINT       |
| user\_id       | Id del Usuario titular de la reserva                 | BIGINT       |
| start\_date    | Fecha y Hora de Inicio de la Reserva                 | TIMESTAMP    |
| end\_date      | Fecha y Hora de Fin de la Reserva                    | TIMESTAMP    |
| status         | Estado de la Reserva (pending, confirmed, declined)  | VARCHAR      |
| auth\_user\_id | Id del Usuario que Autorizó / Rechazó la Reserva     | BIGINT       |
| authorized     | Fecha y Hora de Autorización / Rechazo de la Reserva | TIMESTAMP    |
| created        | Fecha y Hora de Creación de la Reserva               | TIMESTAMP    |
| modified       | Fecha y Hora de Última Modificación de la Reserva    | TIMESTAMP    |
| notes          | Observaciones                                        | TEXT         |

### Consulta de Reservas

Para consultar las Reservas de Amenities existentes, ejecute un GET al endpoint "amenity\_bookings". Puede indicar los siguientes argumentos en la URL:

<table><thead><tr><th width="150">Argumento</th><th>Descripción</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>amenity_id</td><td>Id del Amenity</td><td>Entero Largo</td></tr><tr><td>from</td><td>Inicio del Rango de Fechas de la Consulta (Default: ahora).</td><td>Timestamp (YYYY-mm-dd HH:ii)</td></tr><tr><td>to</td><td>Fin del Rango de Fechas de la Consulta</td><td>Timestamp (YYYY-mm-dd HH:ii)</td></tr></tbody></table>

Ejemplo:

```
curl 'https://beta.bims.app/api/amenities/index?amenity_id=5&sid=2fb6c8d2f3842a5ceaa9bf320e649ff0&company=1';
```

### Reservas de Amenities

#### Estructuras de Datos

**AmenityBooking**: Reserva

<table><thead><tr><th width="150">Campo</th><th>Descripción</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>amenity_id</td><td>Id del Amenity</td><td>Entero Largo</td></tr><tr><td>start_date</td><td>Fecha y Hora de Inicio de la Reserva</td><td>Timestamp (YYYY-mm-dd HH:ii)</td></tr><tr><td>end_date</td><td>Fecha y Hora de Fin de la Reserva</td><td>Timestamp (YYYY-mm-dd HH:ii)</td></tr><tr><td>notes</td><td>Observaciones</td><td>TEXT</td></tr></tbody></table>

**AmenityBookingGuest**: Invitados de la Reserva

<table><thead><tr><th width="174">Campo</th><th>Descripción</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>guest_document</td><td>Número de Documento</td><td>Varchar</td></tr><tr><td>guest_name</td><td>Nombre</td><td>Varchar</td></tr><tr><td>guest_email</td><td>Dirección de E-Mail</td><td>Varchar</td></tr></tbody></table>

**AmenityBookingProduct**: Productos solicitados al momento de la Reserva

<table><thead><tr><th width="174">Campo</th><th>Descripción</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>product_id</td><td>Id del Producto</td><td>Entero Largo</td></tr><tr><td>quantity</td><td>Cantidad</td><td>Float</td></tr><tr><td>price</td><td>Precio Unitario</td><td>Float</td></tr></tbody></table>

#### Registro de Reservas de Amenities

Para solicitar la reserva de un Amenity, utiice el endpoint "amenity\_bookings". Ejemplo:

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
          \"AmenityBooking\": {
               \"amenity_id\": \"5\",
               \"start_date\": \"2024-04-05 18:00\",
               \"end_date\": \"2024-04-06 02:00\",
               \"notes\": \"Para fiesta de cumpleaños infantil\"
          },
          \"AmenityBookingGuest\": [
               {
                    \"guest_document\": \"521321\",
                    \"guest_name\": \"Julio Fleitas\",
                    \"guest_email\": \"jf@gmail.com\"
               },
              {
                    \"guest_document\": \"3152545\",
                    \"guest_name\": \"Luis Fleitas\",
                    \"guest_email\": \"lf@gmail.com\"
              } 
          ],
          \"AmenityBookingProduct\": [
               {
                    \"product_id\": \"12\",
                    \"quantity\": \"2\",
                    \"price\": \"56000\"
               },
               {
                    \"product_id\": \"782\",
                    \"quantity\": \"5\",
                    \"price\": \"5000\"
               }
          ]
     }" \
'http://localhost:8080/bims2/api/amenity_bookings?sid=ssriq8vfjluel5fpjtn0b6mqm7';
```

## Facturas Emitidas

### Descripción

Se emiten a los Inquilinos y/o Propietarios facturas en conceptos de Expensas (a partir de una Liquidación de Expensas). El usuario debe poder consultar las facturas que le han sido emitidas.

### Estructuras de Datos

Esta es la descripción de los datos recibidos del backend al consultar las facturas emitidas.

{% tabs %}
{% tab title="Venta (Sale)" %}

| Campo                 | Descripción                         | Tipo de Dato |
| --------------------- | ----------------------------------- | ------------ |
| id                    | Id de la Venta                      | BIGINT       |
| issue\_date           | Fecha de Emisieon                   | DATE         |
| invoice\_number       | Número de Factura sin Prefijos      | VARCHAR      |
| full\_invoice\_number | Número de Factura Completo          | VARCHAR      |
| amount                | Importe de la Venta                 | NUMERIC      |
| notes                 | Observaciones                       | TEXT         |
| local\_amount         | Importe de la Venta en Moneda lOcal | NUMERIC      |
| pdf\_url              | URL a la Factura en PDF             | VARCHAR      |
| {% endtab %}          |                                     |              |

{% tab title="Moneda (Currency)" %}

| Campo        | Descripción          | Tipo de Dato |
| ------------ | -------------------- | ------------ |
| symbol       | Símbolo de la Moneda | VARCHAR      |
| {% endtab %} |                      |              |

{% tab title="Propiedad (Estate)" %}

| Campo        | Descripción            | Tipo de Dato |
| ------------ | ---------------------- | ------------ |
| id           | Id de la Propiedad     | VARCHAR      |
| name         | Nombre de la Propiedad | VARCHAR      |
| {% endtab %} |                        |              |

{% tab title="Consorcio (EstatesGroup)" %}

| Campo        | Descripción          | Tipo de Dato |
| ------------ | -------------------- | ------------ |
| id           | Id del Consorcio     | BIGINT       |
| name         | Nombre del Consorcio | VARCHAR      |
| {% endtab %} |                      |              |

{% tab title="Cliente  (Contact)" %}

| Campo        | Descripción                     | Tipo de Dato |
| ------------ | ------------------------------- | ------------ |
| id           | Id del Cliente                  | BIGINT       |
| name         | Nombre del Cliente              | VARCHAR      |
| document\_id | Número de Documento del Cliente | VARCHAR      |
| {% endtab %} |                                 |              |

{% tab title="Liquidaciones de Expensas (ReExpensesSet)" %}

| Campo         | Descripción                 | Tipo de Dato |
| ------------- | --------------------------- | ------------ |
| id            | Id de la Liquidación        | BIGINT       |
| issue\_date   | Fecha de Emisión            | DATE         |
| period\_from  | Fecha de Inicio del Período | DATE         |
| period\_to    | Fecha de Cierre del Período | DATE         |
| {% endtab %}  |                             |              |
| {% endtabs %} |                             |              |

### Consulta de Facturas Emitidas

El endpoint para la consulta es api/estates\_groups/invoices. El backend retornará las facturas emitidas a todas las propiedades asociadas al usuario, exclusivamente.

Indicar siempre el id del consorcio actual en la consulta en al argumento de url:  estates\_group\_id.

Ejemplo:

```
curl 'http://localhost:8080/bims2/api/estates_groups/invoices?sid=p6t2iudoed74flqnkig152en44&estates_group_id=1'
{
    "code": "200",
    "status": "ok",
    "data": [
        {
            "Sale": {
                "id": "13004",
                "issue_date": "2023-10-20",
                "invoice_number": "1969",
                "full_invoice_number": "001-001-0001969",
                "amount": "1100000",
                "notes": "Factura generada autom\u00e1ticamente a partir de la Liquidaci\u00f3n de Expensas #13 para la propiedad .",
                "local_amount": "1100000",
                "pdf_url": "http:\/\/localhost:8080\/bims2sales\/pdf\/13004\/845ee69d25fca3a280e037edfefa215b"
            },
            "Currency": {
                "symbol": "Gs"
            },
            "Estate": {
                "id": "2",
                "name": "Departamento A1"
            },
            "EstatesGroup": {
                "id": "1",
                "name": "Edificio San Mart\u00edn"
            },
            "Contact": {
                "id": "74",
                "name": "Tony Stark",
                "document_id": "1233818"
            },
            "ReExpensesSet": {
                "id": "13",
                "issue_date": "2023-10-20",
                "period_from": "2023-09-01",
                "period_to": "2023-09-30"
            }
        }
    ],
    "last_update": "2024-04-22 14:36:50"
}
```

## Liquidaciones de Expensas

Se espera que la app exponga las Liquidaciones de Expensas generadas en BIMS para el Consorcio.  Las Liquidaciones de Expensas señalan todos los gastos comunes y su distribución entre las propiedades del consorcio. Solo a modo de referencia, estas liquidaciones en pantalla tienen el siguiente formato:

<figure><img src="/files/bneqOq7n31eVyZVl9Squ" alt=""><figcaption><p>Resumen de una Liquidación de Expensas en BIMS</p></figcaption></figure>

En la app, las liquidaciones de expensas deben listarse indicando para cada cada registro de la lista los siguientes datos de cabecera:

| Título                | Descripción                                                                                                                                                                                                                                 | Campo                                                                                                                           |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Id                    | Id de la Liquidación                                                                                                                                                                                                                        | ReExpensesSet.id                                                                                                                |
| Fecha de Emisión      | Fecha de Emisión de la Liquidación                                                                                                                                                                                                          | ReExpensesSet.issue\_date                                                                                                       |
| Período               | Período Cubierto por la Liquidación, compuesto por una Fecha de Inicio y Fecha de Cierre                                                                                                                                                    | ReExpensesSet.period\_from / ReExpensesSet.period\_to                                                                           |
| Gastos Totales        | Valor Total de Liquidación, compuesto por la suma de 2 campos.                                                                                                                                                                              | ReExpensesSet.total\_proportional + ReExpensesSet.total\_equitative                                                             |
| Reserva               | Valor destinado a fondo de reserva                                                                                                                                                                                                          | ReExpensesSet.total\_reserve                                                                                                    |
| Valor Correspondiente | Es el valor de la Liquidación que le corresponde abonar a las Propiedades del consorcio del usuario activo. Las Propiedades del Usuario activo están definidas en el atributo UsersEstate de los datos del usuario retornados con el login. | SUM(ReExpensesEstate.amount) tal que ReExpensesEstate.estate\_id sea uno de los UserEstate.estate\_id en los datos del Usuario. |

Finalmente, para obtener los datos de las Liquidaciones de Expensas, utilizá el endpoint "re\_expenses\_sets". Ejemplo:

```
curl 'https://beta.bims.app/api/re_expenses_sets?sid=4qcgsgctj1eapgsidlnutms1d6&estates_group_id=1'
```

Siempre indicá el Consorcio actual en el argumento estates\_group\_id.

No es necesario desarrollar un detalle de las liquidaciones, en su lugar crear un webview a la URL indicada en ReExpense.public\_url.

## Check-In de Invitados

Cuando desde la app se registra la reserva de un amenity, se genera un código QR. Este código QR lleva a un enlace a BIMS, desde cuya versión móvil se debe poder registrar el checkin de los invitados. La pantalla debe mostrar los datos generales de la reserva y la lista de invitados con posibilidad de seleccionar los usuarios que ingresan.

<figure><img src="/files/0feN0mz0fmxIMpjuWGZp" alt=""><figcaption><p>Pantalla de Check-In de Invitados: Imagen de Referencia</p></figcaption></figure>

La URL de los QR estarán construídas de la siguiente manera: \<BIMS-HOST>?ab\_checkin=\<ID-RESERVA>. Ejemplo: <https://bims.app?ab\\_checkin=12>.

La app deberá consultar los datos de la reserva. Para ello empleara el endpoint "amenity\_bookings", indicando el id de ls reserva (ab\_checkin) en el argumento "id" en la URL. Ejemplo:

<pre><code><strong>curl 'https://beta.bims.app/api/amenity_bookings/index?id=51&#x26;sid=2fb6c8d2f3842a5ceaa9bf320e649ff0&#x26;company=1';
</strong></code></pre>

La URL del ejemplo es válida, para pruebas solo cambiá el valor de "sid" por un session id válido.

Para que se habilite esta opción en la versión móvil de BIMS, el usuario debe tener el siguinte permiso: "reAmenityBookingGuestCheckin" o bien, su tipo de usuario debe tener permisos completos (user.Group.full = true).

Los datos en la respuesta para construir la pantalla son los siguientes.

<table><thead><tr><th width="226">Descripción</th><th width="390">Campo</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>Nombre del Consorcio</td><td>data.Amenity.EstatesGroup</td><td>VARCHAR</td></tr><tr><td>Nombre del Amenity</td><td>data.Amenity.name</td><td>VARCHAR</td></tr><tr><td>Fecha de Inicio</td><td>data.AmenityBooking.start_date</td><td>TIMESTAMP</td></tr><tr><td>Fecha de Fin</td><td>data.AmenityBooking.end_date</td><td>TIMESTAMP</td></tr><tr><td>Solicitante</td><td>data.User.name</td><td>VARCHAR</td></tr><tr><td>Unidad</td><td>data.User.UsersEstate[0].Estate.name</td><td>VARCHAR</td></tr><tr><td>Nombre del Invitado</td><td>data.AmenityBookingGuest[n].guest_name</td><td>VARCHAR</td></tr><tr><td>Documento del Invitado</td><td>data.AmenityBookingGuest[n].guest_document</td><td>VARCHAR</td></tr><tr><td>Id del Invitado</td><td>data.AmenityBookingGuest[n].id</td><td>BIGINT</td></tr><tr><td>Id de la Reserva</td><td>data.AmenityBookings.id</td><td>BIGINT</td></tr><tr><td>Invitado Checkeado</td><td>data.AmenitiBookingGuest[n].checkedin</td><td>TIMESTAMP</td></tr></tbody></table>

Cuando el usuario haga clic sobre la casilla de un invitado, se debe hacer checkin del usuario. Para esto utilizá el endpoint amenity\_bookings/checkin, e indicá el id de la reserva y el id del invitado.&#x20;

Ejemplo:

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
          \"amenity_booking_id\": 51,
          \"amenity_booking_guest_id\": 85
     }" \
'https://beta.bims.app/api/amenity_bookings/checkin?sid=v2la11i3l697ppsi2v9k2famg0';

{
    "code": "200",
    "status": "ok"
}
```

Marcá la casilla del usuario como chequeada.

Si el atributo "status" en la respuesta indica "ok", entonces el checkin se ha realizado con éxito.

Si el atributo "status" en la respuesta indica "error", entonces la operación no se ha completado y debe revertirse el estado del la casilla del invitado.

Ejemplo de transacción fallida:

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
          \"amenity_booking_id\": 51
     }" \
'https://beta.bims.app/api/amenity_bookings/checkin?sid=v2la11i3l697ppsi2v9k2famg0';

{
    "code": "405",
    "status": "error",
    "message": "No hay argumentos de entrada"
}
```

El usuario debe poder deshacer un checkin haciendo clic nuevamente sobre un invitado previamente checkeado. Se debe ofrecer un mensaje de confirmación. Para deschequear un invitado en el backend utilizá el endpoint amenity\_bookings/checkin y sumá el argumento "uncheck" con el valor 1. Ejemplo:

```
curl --include \
     --request POST \
     --header "Content-Type: application/json" \
     --data-binary "{
          \"amenity_booking_id\": 51,
          \"amenity_booking_guest_id\": 85,
          \"uncheck\": 1
     }" \
'https://beta.bims.app/api/amenity_bookings/checkin?sid=v2la11i3l697ppsi2v9k2famg0';

{
    "code": "200",
    "status": "ok"
}
```

## Bloqueos de Amenities

Desde BIMS es posible la activación de bloqueos de uno o más Amenities, para todas o algunas Propiedades por distintos motivos y por un período de tiempo de terminado.

El endpoint para la consulta de bloqueos de Amenities es: /amlocks.

Ejemplo:

```
curl 'https://beta.bims.app/api/amlocks/index?sid=1fdqbchqok1k1uhhk77acikct2
```

Se listará automáticamente solo los bloqueos activos y futuros para el usuario de la sesión activa indicada en el argumento "sid".

Se esperará una respuesta como esta:

```
{
    "code": "200",
    "status": "ok",
    "data": [
        {
            "Amlock": {
                "id": "1",
                "lock_from": "2024-07-16 00:00:00",
                "lock_to": "2024-07-20 12:00:00",
                "notes": "Prueba",
                "user_id": "3",
                "created": "2024-07-15 12:14:34",
                "modified": "2024-07-15 12:15:04",
                "estates_group_id": "3"
            },
            "Amenity": [
                {
                    "id": "2",
                    "name": "Rooftop",
                    "estates_group_id": "3",
                    "AmlocksAmenity": {
                        "id": "2",
                        "amlock_id": "1",
                        "amenity_id": "2",
                        "created": "2024-07-15 12:15:04.386662",
                        "modified": null
                    }
                }
            ]
        }
    ],
    "last_update": "2024-07-15 12:54:28"
}
```

Adicionalmente los bloqueos pueden ser filtrados según los siguientes atributos:

| Atributo    | Descripción                          | Tipo de Dato                                  |
| ----------- | ------------------------------------ | --------------------------------------------- |
| amenity\_id | Id de Amenity incluído en el bloqueo | Entero                                        |
| from        | Fecha de Inicio del Bloqueo          | Fecha y Hora en formato "YYYY-mm-dd ii:ss:ss" |
| to          | Fecha de Fin del Bloqueo             | Fecha y Hora en formato "YYYY-mm-dd ii:ss:ss" |

Se recomienda consultar directamente los bloqueos de un amenity específico al ingresar a la ficha del amenity.

Se debe restringir la reserva en espacios de tiempo bloqueados de un amenity. Mostrar estos espacios con el mismo sombreado utilizado para los espacios reservados.


# Mobile App para Repartidores

Aplicación con servicios para repartidores integrada al módulo de delivery de BIMS.

## Jornadas de Repartidores

Las Jornadas de Repartidores son registros que engloban la operación de un Repartidor en una Jornada de Trabajo. Cada Jornada de Repartidor inicia y finaliza con un arqueo de caja.

Cada Repartidor tiene asignada de forma exclusiva una Cuenta de Fondos en BIMS. Las Cuentas de Fondos en BIMS representan repositorios físicos o virtuales de dinero.

Al inicio de la operación, el repartidor podría recibir de la empresa una suma de dinero en efectivo que usará para el cashback ("vuelto"). En BIMS un usuario transfiere estos valores desde una Cuenta de Fondos a la Cuenta de Fondos del Repartidor.&#x20;

Al iniciar su Jornada, el Repartidor declara los valores que ha recibido y estos se establecen como el saldo inicial para las transacciones que vendrán y que afectarán la caja virtual que administra y que deberá rendir al final de su Jornada. El valor que declara el Repartidor puede diferir del valor indicado por el usuario de BIMS. Esta diferencia se alerta en BIMS.&#x20;

Si existiera una diferencia entre el saldo computado en la Cuenta de Fondos del Repartidor al momento de la Apertura de la Jornada, versus el saldo declarado por el Repartidor en la apertura de la Jornada, se crea automáticamente en BIMS un movimiento de ajuste de saldo para ajustar el saldo de la Cuenta de Fondos del Repartidor al valor declarado por el Repartidor, dado que ese es el valor considerado como real y con declaración de responsabilidad del Repartidor.

### Estructura de Datos

Las Jornada de Repartidores se almacenan en las siguientes tablas con la siguiente composición.

#### journals\_deliveries: JournalsDelivery

<table><thead><tr><th width="256">Campo</th><th>Descripción</th><th>Tipo de Dato</th></tr></thead><tbody><tr><td>id</td><td>Id de la Jornada de Repartidor</td><td>BIGINT</td></tr><tr><td>open_user_id</td><td>Id del Usuario de Apertura</td><td>BIGINT</td></tr><tr><td>close_user_id</td><td>Id del Usuario de Cierre</td><td>BIGINT</td></tr><tr><td>opening_computed_balance</td><td>Saldo Computado en la Apertura</td><td>NUMERIC</td></tr><tr><td>credit</td><td>Saldo Declarado en la Apertura</td><td>NUMERIC</td></tr><tr><td>computed_balance</td><td>Saldo Computado al Cierre</td><td>NUMERIC</td></tr><tr><td>closing_balance</td><td>Saldo Declarado al Cierre</td><td>NUMERIC</td></tr><tr><td>open_tm</td><td>Fecha y Hora de Apertura</td><td>TIMESTAMP</td></tr><tr><td>close_tm</td><td>Fecha y Hora de Cierre</td><td>TIMESTAMP</td></tr><tr><td>approval_status</td><td>Estado de Aprobación: [pending, approved, rejected]</td><td>VARCHAR</td></tr><tr><td>approval_tm</td><td>Fecha y Hora de Cambio de Estado de Aprobación</td><td>TIMESTAMP</td></tr><tr><td>fund_account_id</td><td>Id de Cuenta de Fondos</td><td>BIGINT</td></tr></tbody></table>

### Resumen de Jornada

La pantalla de Resumen de la Jornada debe exponer los valores de apertura y cierre de una Jornada de Repartidor.

#### Endpoint

Consultá los datos de una Jornada de Repartidor con un GET al endpoint "journals\_deliveries" indicando el id de la Jornada de Repartidor como un argumento en la URL. Ejemplo:

```
curl 'http://localhost:8080/bims2/api/journals_deliveries?sid=li3cpmbmlq1jtjobi28aus8qn1&id=8'

{
    "code": "200",
    "status": "ok",
    "data": [
        {
            "JournalsDelivery": {
                "id": "8",
                "open_user_id": "286",
                "close_user_id": "286",
                "enabled": true,
                "created": "2023-08-21 10:51:39",
                "modified": "2023-08-21 10:53:20",
                "credit": "0",
                "driver_id": "286",
                "computed_balance": "0",
                "close_tm": "2023-08-21 10:53:00",
                "closing_balance": "2000",
                "open_tm": "2023-08-21 10:48:00",
                "fund_account_id": "125",
                "closing_badjust_id": "129",
                "approval_status": "pending",
                "approval_tm": null,
                "approval_user_id": null,
                "opening_computed_balance": "0",
                "fund_transfer_id": null,
                "income": "60000",
                "outcome": "20000"
            },
            "OpenUser": {
                "id": "286",
                "name": "Amancio Martinez"
            },
            "CloseUser": {
                "id": "286",
                "name": "Amancio Martinez"
            },
            "Driver": {
                "id": "286",
                "name": "Amancio Martinez"
            }
        }
    ],
    "last_update": "2024-04-15 12:14:30"
}

```

#### Datos

**Sección 1: Apertura**

<table><thead><tr><th width="181">Título del Campo</th><th>Descripción</th><th>Origen</th></tr></thead><tbody><tr><td>Fecha</td><td>Fecha y Hor de Apertura de la Jornada del Repartidor</td><td>JournalsDelivery.open_tm</td></tr><tr><td>Usuario</td><td>Nombre del Usuario que Abrió la Jornada (Siempre será el mismo Usuario activo)</td><td>OpenUser.name</td></tr></tbody></table>

Sección 2: Jornada

<table><thead><tr><th width="181">Título del Campo</th><th>Descripción</th><th>Origen</th></tr></thead><tbody><tr><td>Saldo Inicial</td><td>Saldo Declarado en la Apertura</td><td>JournalsDelivery.credit</td></tr><tr><td>Ingresos</td><td>Total de movimientos con signo positivo en el extracto de la Cuenta de Fondos del Repartidor en el período de la Jornada posteriores al eventual Ajuste de Saldos de apertura.</td><td>JournalsDelivery.income</td></tr><tr><td>Egresos</td><td>Total de movimientos con signo negativo en el extracto de la Cuenta de Fondos del Repartidor en el período de la Jornada posteriores al eventual Ajuste de Saldos de apertura.</td><td>JournalsDelivery.outcome</td></tr><tr><td>Saldo Computado</td><td>Saldo computado por BIMS al cierre de la Jornada</td><td>Ingresos (menos) Egresos</td></tr><tr><td>Saldo Declarado</td><td>Saldo declarado por el Repartidor al cierre de la Jornada</td><td>JournalsDelivery.closing_balance</td></tr><tr><td>Diferencia</td><td>Diferencia entre Saldos Computado y Declarado</td><td>Saldo Declarado - Saldo Computado</td></tr></tbody></table>

#### Mockup de UI

<figure><img src="/files/rtkIAumG7NsvwOJrypRJ" alt=""><figcaption></figcaption></figure>


# Web Hooks

Los web hooks fueron creados para ejecutar acciones sobre entidades y operaciones del sistema desde la web y sin contar con elementos en la GUI para el efecto.

| Argumento       | Ámbito                                              | Acción                                                                                                       | Ejemplo                                 |
| --------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------- |
| ?update\_cost=1 | Fichas del Producto                                 | Reestablece el costo del producto según el precio de la última compra incluyendo los gastos anexos.          | bims.app/products/view/1?update\_cost=1 |
| ?acc=1          | Ficha de Operaciones que Generan Asientos Contables | Regenera los asientos contables de la operación según la configuración actual de las cuentas y asociaciones. | bims.app/sales/view/1?acc=1             |
| ?ad=1           | Ficha de Notas de Crédito                           | Recalcula los descuentos sobre las ventas aplicadas.                                                         | bims.app/sale\_credit\_notes/view/?ad=1 |
| ?cashback=1     | Ficha de Venta                                      | Recalcula el cambio.                                                                                         | bims.app/sales/view/1?cashback=1        |
| ?com=1          | Ficha de Venta                                      | Recalcula las comisinones de venta                                                                           | bims.app/sales/view/1?com=1             |
| ?pc=1           | Ficha de Compra                                     | Recalcula los costos de los productos asociados a la compra.                                                 | bims.app/purchases/view/1?pc=1          |


# Shells

Los shells de BIMS permiten ejecutar auditorías de bajo nivel y operaciones en lote.

## Regenerar Asientos Contables

### Asientos por Ventas

```
cd app/cake/console;
./cake -app ../../app tims accSales from=2020-01-01 to=2020-12-31
```

### Recalcular Libro de IVA de Ventas

```
cd app/cake/console;
./cake -app ../../app tims sumTaxesSales from=2020-01-01 to=2020-12-31
```

### Asientos por Compras

```
cd app/cake/console;
./cake -app ../../app tims accPurchases from=2020-01-01 to=2020-12-31
```

### Asientos por Cobros

```
cd app/cake/console;
./cake -app ../../app tims accCollections from=2020-01-01 to=2020-12-31
```

### Asientos por Pagos

```
cd app/cake/console;
./cake -app ../../app tims accPayments from=2020-01-01 to=2020-12-31
```

### Asientos por Notas de Crédito de Ventas

```
cd app/cake/console;
./cake -app ../../app tims accSaleCreditNotes from=2020-01-01 to=2020-12-31
```

### Asientos por Notas de Crédito de Compras

```
cd app/cake/console;
./cake -app ../../app tims accPurchaseCreditNotes from=2020-01-01 to=2020-12-31
```

### Asientos por Notas de Crédito de Compras

```
cd app/cake/console;
./cake -app ../../app tims accPurchaseCreditNotes from=2020-01-01 to=2020-12-31
```

### Asientos por Préstamos

```
cd app/cake/console;
./cake -app ../../app tims accLoans from=2020-01-01 to=2020-12-31
```

### Asientos por Transferencias de Fondos

```
cd app/cake/console;
./cake -app ../../app tims accFundTransfers from=2020-01-01 to=2020-12-31
```

### Asientos por Ajustes de Inventario

```
cd app/cake/console;
./cake -app ../../app tims accInvads from=2020-01-01 to=2020-12-31
```

### Asientos por Canjes

```
cd app/cake/console;
./cake -app ../../app tims accSwaps from=2020-01-01 to=2020-12-31
```

## Stock

### Recalcular Ajustes de Inventario

```
cd app/cake/console;
./cake -app ../../app tims updateInvads from=2020-01-01 to=2020-12-31 [warehouse=1]
```

## Replicación de Ventas de TR de BIMS 2 a BIMS 1

### Análisis Comparativo de Datos entre BIMS 1 y BIMS 2

```
/root/bims2/cake/console/cake -app /root/bims2/app apisync validate from=2020-08-24 to=2020-08-24 [agency=8]
```

### Forzar Replicación de un Período

```
/root/bims2/cake/console/cake -app /root/bims2/app apisync validate from=2020-08-24 to=2020-08-24 [agency=8] fix=1
```

### Sincronizar Cuentas de Fondos desde BIMS 1 a BIMS 2

```
php -q /var/www/html/app/webroot/bims2_std/cake/console/cake.php -working / -app /var/www/html/app/webroot/bims2_std/app todorico sync_fund_accounts
```

<br>


# Full Stack Developer

## **Skills Requeridos**

Full Stack Developer con experiencia en las siguiente pila:

HTML + JS + PHP (Esquema MVC) y PostgreSQL.

## **La Empresa y el Producto**

TIVA fue fundada en el año 2010, está basada en Asunción. Nuestra misión es desarrollar permanentemente soluciones reales e innovadoras.

## **El Producto**

Nuestra empresa desarrolla un producto llamado BIMS; una solución de gestión e inteligencia de negocios en la nube distribuida bajo el modelo de licenciamiento y SaaS.Van algunos enlaces con más información sobre el producto:  [https://getbims.com](https://getbims.com/), <http://getbims.com/docs/TIVA%20BIMS.pdf>, <https://www.youtube.com/watch?v=-WW07iNWD8c><br>

## **La Tecnología**

BIMS está desarrollado en PHP con un diseño MVC, y utiliza PostgreSQL 9.x como base de datos. El Front-End es HTML5/CSS 3 con Bootstrap y JS con JQuery.&#x20;

## **La Metodología de Trabajo**

Adoptamos el remote office desde la cuarentena sanitaria y dado los buenos resultados hemos decidido sostener la dinámica de trabajo de forma indefinida, no obstante ponemos a disposición del equipo infraestructura local en caso que gusten hacer oficina.La metodología de trabajo se describe brevemente aquí: <https://developers.bims.xyz/> y con más detalle aquí: <https://medium.com/@cartesv/un-marco-de-trabajo-remoto-40ef068782ff><br>

## **Nuestra Expectativa**

Nuestro roadmap es es muy ambicioso, y los deadlines muy ajustados. Nuestra expectativa sobre la persona que integremos al equipo, es que ya cuente con el know how y la experiencia necesarios sobre la tecnología y metodología de trabajo que aplicamos a fin de que pueda enfocarse los primeros 30 días en conocer a fondo el producto – que es muy extenso – su código y los pormenores de nuestra metodología de trabajo. Esperamos que sea una persona creativa que pueda proponer ideas y soluciones, y entusiasta para construirlas.

\
Por favor confirmá tu interés y si tus habilidades se ajustan a las expectativas del cargo, indicanos tu expectativa salarial y agendá entrevista remota aquí: <https://calendly.com/cartesv/30min>


# React Native Front-End Developer

## **Skills Requeridos**

Experiencia en desarrollo en React Native y conexiones a API Restful. Experiencia de tres años con la tecnología y proyectos publicados que se puedan evaluar.

## **La Empresa**

TIVA fue fundada en el año 2010, está basada en Asunción. Nuestra misión es desarrollar permanentemente soluciones reales e innovadoras.

## **El Producto**

Nuestra empresa desarrolla un producto llamado BIMS; una solución de gestión e inteligencia de negocios en la nube distribuida bajo el modelo de licenciamiento y SaaS.Van algunos enlaces con más información sobre el producto:  [https://getbims.com](https://getbims.com/), <http://getbims.com/docs/TIVA%20BIMS.pdf>, <https://www.youtube.com/watch?v=-WW07iNWD8c><br>

## **La Tecnología**

BIMS te ofrece un API Restful robusto y funcional para la integración de la app que desarrollarás. Su documentación está disponible aquí: <https://bims1.docs.apiary.io/>

## **La Metodología de Trabajo**

Adoptamos el remote office desde la cuarentena sanitaria y dado los buenos resultados hemos decidido sostener la dinámica de trabajo de forma indefinida, no obstante ponemos a disposición del equipo infraestructura local en caso que gusten hacer oficina.La metodología de trabajo se describe brevemente aquí: <https://developers.bims.xyz/> y con más detalle aquí: <https://medium.com/@cartesv/un-marco-de-trabajo-remoto-40ef068782ff><br>

## **Nuestra Expectativa**

Nuestro roadmap es es muy ambicioso, y los deadlines muy ajustados. Nuestra expectativa sobre la persona que integremos al equipo, es que ya cuente con el know how y la experiencia necesarios sobre la tecnología y metodología de trabajo que aplicamos a fin de que pueda enfocarse los primeros 30 días en conocer a fondo el producto – que es muy extenso – su código y los pormenores de nuestra metodología de trabajo. Esperamos que sea una persona creativa que pueda proponer ideas y soluciones, y entusiasta para construirlas.

\
Por favor confirmá tu interés y si tus habilidades se ajustan a las expectativas del cargo, indicanos tu expectativa salarial y agendá entrevista remota aquí: <https://calendly.com/cartesv/30min>


