Compare commits

..

1 commit

Author SHA1 Message Date
nat
95ba0733ab WIP 2022-02-22 10:47:06 -03:00
119 changed files with 1805 additions and 32168 deletions

View file

@ -1,9 +0,0 @@
[Dolphin]
HeaderColumnWidths=372,72,103
SortRole=modificationtime
Timestamp=2022,6,1,16,36,48
Version=4
ViewMode=1
[Settings]
HiddenFilesShown=true

Binary file not shown.

View file

@ -6,11 +6,9 @@ APP_URL=http://localhost
LOG_CHANNEL=stack LOG_CHANNEL=stack
USERID=
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=db DB_HOST=db
DB_PORT_EXPOSED=3306 DB_PORT=3306
DB_DATABASE=pedi2 DB_DATABASE=pedi2
DB_USERNAME=pedi2 DB_USERNAME=pedi2
DB_PASSWORD=pedi2 DB_PASSWORD=pedi2
@ -50,4 +48,3 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
WEB_CLIENT_EMAIL=informaticamps@buzon.uy WEB_CLIENT_EMAIL=informaticamps@buzon.uy
WEB_CLIENT_NAME=web WEB_CLIENT_NAME=web
WEB_CLIENT_PASS=pass WEB_CLIENT_PASS=pass
NGINX_PORT=8000

6
.gitignore vendored
View file

@ -10,9 +10,3 @@ Homestead.json
Homestead.yaml Homestead.yaml
npm-debug.log npm-debug.log
yarn-error.log yarn-error.log
.idea
/resources/csv/exports/*.csv
/resources/csv/canastas/*.csv
/public/css/
/public/js/
/public/mix-manifest.json

View file

@ -12,8 +12,7 @@ RUN apt-get update && apt-get install -y \
libonig-dev \ libonig-dev \
libxml2-dev \ libxml2-dev \
zip \ zip \
unzip \ unzip
npm
# Clear cache # Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/* RUN apt-get clean && rm -rf /var/lib/apt/lists/*

View file

@ -1,6 +1,6 @@
# Pedi2 # Pedi2
Aplicación de pedidos del Mercado Popular de Subsistencia. Aplicación de compras del Mercado Popular de Subsistencia.
Pedi2 está hecha en Laravel 7 y utiliza laravel7-docker de dyarleniber. Pedi2 está hecha en Laravel 7 y utiliza laravel7-docker de dyarleniber.
@ -17,39 +17,41 @@ Se utilizan los siguientes servicios, separadamente:
## Instalación ## Instalación
1. Una vez descargado el proyecto, hacé una copia del archivo `.env.example` que se encuentra en la raíz del proyecto y nombrala `.env`. Seteá los valores correctos - específicamente, para las variables, `APP_URL`, `DB_USERNAME` y `DB_PASSWORD`. Prestá atención a que `DB_HOST` sea el nombre del servicio que corre MySQL (por defecto `DB_HOST=db`). 1. Una vez descargado el proyecto, hacé una copia del archivo `.env.example` que se encuentra en la raíz del proyecto y nombrala `.env`. Seteá los valores correctos - específicamente, para las variables, `APP_URL`, `DB_USERNAME` y `DB_PASSWORD`. Prestá atención a que `DB_HOST` sea el nombre del servicio que corre MySQL (por defecto `DB_HOST=db`).
2. Levantá los contenedores, construyendo la imagen de la app primero 2. Construí la imagen de la app
```bash ```bash
docker-compose up -d --build docker-compose build app
```
3. Cuando termine, levantá los contenedores:
```bash
docker-compose up -d
``` ```
El ambiente ahora está andando, pero necesitamos ejecutar un par de comandos para terminar la instalación de Laravel. Podemos usar `docker-compose exec [nombre-del-servicio]` previo a un comando para ejecutarlo dentro del contenedor. El ambiente ahora está andando, pero necesitamos ejecutar un par de comandos para terminar la instalación de Laravel. Podemos usar `docker-compose exec [nombre-del-servicio]` previo a un comando para ejecutarlo dentro del contenedor.
3. Terminá de instalar las dependencias de la app, según fueron definidas en `composer.json`. 4. Terminá de instalar las dependencias de la app, según fueron definidas en `composer.json`.
```bash ```bash
docker-compose exec app composer install docker-compose exec app composer update
``` ```
4. Generá una clave de aplicación. Esta clave se usa para encriptar datos sensibles. 5. Generá una clave de aplicación. Esta clave se usa para encriptar datos sensibles.
```bash ```bash
docker-compose exec app php artisan key:generate docker-compose exec app php artisan key:generate
``` ```
5. Corré las migraciones y seeders de Laravel 6. Corré las migraciones y seeders de Laravel
```bash ```bash
docker-compose exec app php artisan migrate:fresh --seed docker-compose exec app php artisan migrate --seed
``` ```
6. Copia el token que se imprime al correr los seeders. Lo necesitamos para autenticar las llamadas que hagamos desde nuestro cliente web 7. Copia el token que se imprime al correr los seeders. Lo necesitamos para autenticar las llamadas que hagamos desde nuestro cliente web
7. Instala las dependencias de npm
```bash
docker-compose exec app npm install
```
Ahora la aplicación está corriendo y la podés ver en el puerto 8000 de tu dominio o IP. En caso de que estés en tu máquina local, la vas a ver accediendo a `http://localhost:8000` desde tu navegador. Ahora la aplicación está corriendo y la podés ver en el puerto 8000 de tu dominio o IP. En caso de que estés en tu máquina local, la vas a ver accediendo a `http://localhost:8000` desde tu navegador.
@ -59,11 +61,6 @@ Podés usar el comando `logs` para ver los logs generados por tus servicios:
docker-compose logs nginx docker-compose logs nginx
``` ```
8. Ejecuta npm para compilar el js y css
```bash
docker-compose exec app npm run prod
```
## Services description ## Services description
### Dockerfile ### Dockerfile

View file

@ -1,11 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CanastaLog extends Model
{
protected $fillable = ["path", "descripcion"];
protected $table = "carga_de_canastas";
}

View file

@ -1,72 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class AgregarEsBonoAPedidosAprobados extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'command:AgregarEsBonoAPedidosAprobados';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Agrega "producto_bono" a la view PedidosAprobados';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
DB::statement("
ALTER VIEW pedidos_aprobados(
grupo_de_compra_id,
grupo_de_compra_nombre,
grupo_de_compra_region,
producto_id,
producto_nombre,
producto_precio,
cantidad_pedida,
total_por_producto,
producto_es_bono
) AS
SELECT
g.id as grupo_de_compra_id,
g.nombre as grupo_de_compra_nombre,
g.region as grupo_de_compra_region,
pr.id AS producto_id,
pr.nombre as producto_nombre,
pr.precio as producto_precio,
SUM(ps.cantidad) as cantidad_pedida,
pr.precio*SUM(ps.cantidad) as total_por_producto,
pr.bono
FROM grupos_de_compra g
JOIN subpedidos s ON (s.grupo_de_compra_id = g.id AND s.aprobado=1)
JOIN producto_subpedido ps ON (ps.subpedido_id = s.id)
JOIN productos pr ON (pr.id = ps.producto_id)
GROUP BY
g.id, g.nombre, pr.id, pr.nombre
;");
return 0;
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class CrearPedidosAprobadosViewCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'view:CrearPedidosAprobadosView';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Crea view "pedidos_aprobados"';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
DB::statement("
CREATE OR REPLACE VIEW pedidos_aprobados
AS
SELECT
g.id as grupo_de_compra_id,
g.nombre as grupo_de_compra_nombre,
g.region as grupo_de_compra_region,
pr.id AS producto_id,
pr.nombre as producto_nombre,
pr.precio as producto_precio,
SUM(ps.cantidad) as cantidad_pedida,
pr.precio*SUM(ps.cantidad) as total_por_producto
FROM grupos_de_compra g
JOIN subpedidos s ON (s.grupo_de_compra_id = g.id AND s.aprobado=1)
JOIN producto_subpedido ps ON (ps.subpedido_id = s.id)
JOIN productos pr ON (pr.id = ps.producto_id)
GROUP BY
g.id, g.nombre, pr.id, pr.nombre
;");
return 0;
}
}

View file

@ -5,15 +5,12 @@ namespace App\Filtros;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
use TypeError;
class Filtro extends Model class Filtro extends Model
{ {
protected Request $request; protected $request;
protected $builder; protected $builder;
protected array $MENSAJES_ERROR = [ protected $MENSAJES_ERROR = [
'ARGUMENTO' => 'Argumento inválido para el parámetro %s. Revise la documentación.' 'ARGUMENTO' => 'Argumento inválido para el parámetro %s. Revise la documentación.'
]; ];
@ -25,10 +22,10 @@ class Filtro extends Model
/** /**
* Apply all existing filters, if available. * Apply all existing filters, if available.
* *
* @param Builder $builder * @param \Illuminate\Database\Eloquent\Builder $builder
* @return Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
public function aplicar(Builder $builder): Builder public function aplicar(Builder $builder)
{ {
$this->builder = $builder; $this->builder = $builder;
@ -54,7 +51,7 @@ class Filtro extends Model
//Llamar métodos con argumentos //Llamar métodos con argumentos
try { try {
$this->$metodo($valor); $this->$metodo($valor);
} catch (Throwable $th) { } catch (\Throwable $th) {
if (is_a($th,'TypeError') ) { throw new HttpException(400, sprintf($this->MENSAJES_ERROR['ARGUMENTO'],$filtro)); } if (is_a($th,'TypeError') ) { throw new HttpException(400, sprintf($this->MENSAJES_ERROR['ARGUMENTO'],$filtro)); }
throw $th; throw $th;
} }
@ -66,7 +63,7 @@ class Filtro extends Model
//Buscar un término en el nombre //Buscar un término en el nombre
public function nombre(String $valor) public function nombre(String $valor)
{ {
$this->builder->where('nombre', "LIKE", "%" . $valor . "%")->orderByRaw("IF(nombre = '$valor',2,IF(nombre LIKE '$valor%',1,0)) DESC"); $this->builder->where('nombre', "LIKE", "%" . $valor . "%")->orderByRaw("IF(nombre = '{$valor}',2,IF(nombre LIKE '{$valor}%',1,0)) DESC");
} }
public function alfabetico(String $order = 'asc') public function alfabetico(String $order = 'asc')

View file

@ -1,6 +1,7 @@
<?php <?php
namespace App\Filtros; namespace App\Filtros;
use Illuminate\Database\Eloquent\Builder;
class FiltroDeProducto extends Filtro { class FiltroDeProducto extends Filtro {

View file

@ -2,7 +2,7 @@
namespace App\Filtros; namespace App\Filtros;
use TypeError; use Illuminate\Database\Eloquent\Model;
class FiltroDeSubpedido extends Filtro class FiltroDeSubpedido extends Filtro
{ {

View file

@ -2,279 +2,17 @@
namespace App; namespace App;
use App\Helpers\CsvHelper;
use App\Helpers\PdfHelper;
use App\Helpers\TransporteHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class GrupoDeCompra extends Model class GrupoDeCompra extends Model
{ {
public $timestamps = false; public $timestamps = false;
protected $fillable = ["nombre", "region", "telefono", "correo", "referente_finanzas", "cantidad_de_nucleos", "fila", "devoluciones_habilitadas"]; protected $fillable = [ "nombre","region","telefono","correo","referente_finanzas","cantidad_de_nucleos"];
protected $table = 'grupos_de_compra'; protected $table = 'grupos_de_compra';
protected $hidden = ['password']; protected $hidden = ['password'];
public function subpedidos(): HasMany public function subpedidos() {
{ return $this->hasMany('App\Subpedido');
return $this->hasMany(Subpedido::class); }
}
public function toggleDevoluciones(): bool
{
$this->devoluciones_habilitadas = !$this->devoluciones_habilitadas;
$this->save();
return $this->devoluciones_habilitadas;
}
public function pedidosAprobados()
{
return $this->subpedidos->where('aprobado', 1);
}
public function totalARecaudar()
{
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->total();
}
return $total;
}
public function totalBarrial()
{
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->totalBarrial();
}
return $total;
}
public function totalDevoluciones()
{
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->devoluciones_total;
}
return $total;
}
public function totalATransferir()
{
return $this->totalCentralesQueNoPaganTransporte()
+ $this->totalCentralesQuePaganTransporte()
+ $this->totalTransporte();
}
public function totalCentralesQueNoPaganTransporte()
{
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->totalCentralesQueNoPaganTransporte();
}
return $total;
}
public function totalCentralesQuePaganTransporte()
{
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->totalCentralesQuePaganTransporte();
}
return $total;
}
public function totalTransporte()
{
return TransporteHelper::totalTransporte($this->totalCentralesQuePaganTransporte());
}
/**
* @return int
* Calcula la cantidad de bonos de transporte del barrio
*/
public function cantidadTransporte(): int
{
return TransporteHelper::cantidadTransporte($this->totalCentralesQuePaganTransporte());
}
public function exportarPedidosAPdf()
{
$subpedidos = $this->pedidosAprobados();
PdfHelper::exportarPedidos($this->nombre . '.pdf', $subpedidos);
}
function pedidoParaPdf(): array
{
$productos = $this->productosPedidos(true, true);
$pedido = [];
$pedido['productos'] = [];
$pedido['nombre'] = $this->nombre;
foreach ($productos as $producto) {
$productoParaPdf = [];
$productoParaPdf['pivot'] = [];
$productoParaPdf['nombre'] = $producto->producto_nombre;
$productoParaPdf['pivot']['cantidad'] = $producto->cantidad_pedida;
$productoParaPdf['pivot']['notas'] = false;
$productoParaPdf['bono'] = $producto->producto_es_bono;
$pedido['productos'][] = $productoParaPdf;
}
return $pedido;
}
public function generarHTML()
{
$view = view("pdfgen.pedido_tabla", ["pedido" => $this->pedidoParaPdf()]);
return $view->render();
}
public static function exportarPedidosBarrialesAPdf()
{
$barrios = GrupoDeCompra::barriosMenosPrueba()->get();
PdfHelper::exportarPedidos('pedidos_por_barrio.pdf', $barrios);
}
static function filaVacia(string $product, int $columns): array
{
$fila = [$product];
for ($i = 1; $i <= $columns; $i++) {
$fila[$i] = "0";
}
return $fila;
}
//Asume que los productos están gruadados en orden de fila
public static function obtenerTemplateDeFilasVacias(int $columns): array
{
$productosFilaID = Producto::productosFilaID();
$productosIDNombre = Producto::productosIDNombre();
$num_fila = 1;
$template = [];
foreach ($productosFilaID as $fila => $id) {
for ($i = $num_fila; $i < $fila; $i++) {
$template[$i] = GrupoDeCompra::filaVacia("", $columns);
}
$template[$fila] = GrupoDeCompra::filaVacia($productosIDNombre[$id], $columns);
$num_fila = $fila + 1;
}
$template[TransporteHelper::filaTransporte()] = GrupoDeCompra::filaVacia("Bonos de transporte", $columns);
return $template;
}
public function exportarPedidoEnCSV()
{
$records = $this->generarColumnaCantidades();
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '.csv', $records);
}
public function generarColumnaCantidades(): array
{
$productos_en_pedido = $this->productosPedidos();
//si no hay pedidos aprobados, salir
if ($productos_en_pedido->count() == 0) {
Log::debug("El grupo de compra " . $this->nombre . " no tiene pedidos aprobados.");
return [];
}
$records = $this->obtenerTemplateDeFilasVacias(1);
$productos_id_fila = Producto::productosIDFila();
foreach ($productos_en_pedido as $id => $producto_pedido) {
$fila = $productos_id_fila[$id];
$records[$fila][1] = $producto_pedido->cantidad_pedida;
}
$records[TransporteHelper::filaTransporte()][1] = $this->cantidadTransporte();
return $records;
}
public function exportarPedidoConNucleosEnCSV()
{
$productos_en_pedido = $this->productosPedidos();
// si no hay pedidos aprobados, salir
if ($productos_en_pedido->count() == 0) {
Log::debug("El grupo de compra " . $this->nombre . " no tiene pedidos aprobados.");
return;
}
$pedidos = $this->pedidosAprobados();
// Generar tabla vacía con una columna por núcleo
$records = $this->obtenerTemplateDeFilasVacias($pedidos->count());
$productos_id_fila = Producto::productosIDFila();
foreach ($productos_en_pedido as $id => $producto_pedido) {
$fila = $productos_id_fila[$id];
$i = 1;
// Poner cantidad de cada producto para cada núcleo
foreach ($pedidos as $pedido) {
list($records, $i, $_) = $this->agregarCantidad($pedido, $id, $records, $fila, $i);
}
}
// Insertar lista de núcleos en la primera fila
$nucleos = [""];
$i = 1;
foreach ($pedidos as $pedido) {
$nucleos[$i] = $pedido->nombre;
$i++;
}
array_splice($records, 0, 0, array($nucleos));
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-completo.csv', $records);
}
public function agregarCantidad($pedido, $id, array $records, $fila, int $i): array
{
$producto = $pedido->productos()->find($id);
$cantidad = $producto == NULL ? 0 : $producto->pivot->cantidad;
$records[$fila][$i] = $cantidad;
$i++;
return array($records, $i, $cantidad);
}
public static function barriosMenosPrueba(): Builder
{
return self::where('nombre', '<>', 'PRUEBA')
->orderBy('region')
->orderBy('nombre');
}
public static function transportePorBarrio(): array
{
$result = [];
$barrios = GrupoDeCompra::barriosMenosPrueba()->get();
foreach ($barrios as $barrio) {
$result[] = $barrio->cantidadTransporte();
}
return $result;
}
/**
* @return Collection
*/
public function productosPedidos($excluirBarriales = false, $excluirBonos = false): Collection
{
$query = DB::table('pedidos_aprobados')
->where('grupo_de_compra_id', $this->id);
if ($excluirBarriales)
$query = $query->where('producto_nombre','NOT LIKE','%barrial%');
if ($excluirBonos)
$query = $query->where('producto_es_bono',false);
return $query
->get()
->keyBy('producto_id');
}
} }

View file

@ -1,146 +0,0 @@
<?php
namespace App\Helpers;
use App\Producto;
use App\Proveedor;
use App\CanastaLog;
use DatabaseSeeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class CanastaHelper
{
const TIPO = "Tipo";
const TOTAL = "TOTAL";
const ARCHIVO_SUBIDO = 'Archivo subido';
const CANASTA_CARGADA = 'Canasta cargada';
const TIPOS_BONO = ["B", "F", "BE"];
public static function guardarCanasta($data, $path): string {
$nombre = $data->getClientOriginalName();
$data->move(resource_path($path), $nombre);
self::log($path . $nombre, self::ARCHIVO_SUBIDO);
return $nombre;
}
public static function cargarCanasta($archivo) {
self::limpiarTablas();
$registros = CsvHelper::getRecords($archivo);
$toInsert = [];
$categoria = '';
foreach($registros as $i => $registro) {
// finalizar
if ($registro[self::TIPO] == self::TOTAL)
break;
// saltear filas que no tienen tipo
if (self::noTieneTipo($registro)) {
var_dump("no hay tipo en la fila " . $i);
continue;
}
// saltear bono de transporte
if ($registro[self::TIPO] == "T"){
continue;
}
// obtener categoria si no hay producto
if ($registro['Producto'] == '') {
// no es la pregunta de la copa?
if (!Str::contains($registro[self::TIPO],"¿"))
$categoria = $registro[self::TIPO];
continue;
}
// completar producto
$toInsert[] = [
'fila' => $i,
'categoria' => $categoria,
'nombre' => trim(str_replace('*', '',$registro['Producto'])),
'precio' => $registro['Precio'],
'proveedor_id' => self::obtenerProveedor($registro['Producto']),
'bono' => in_array($registro[self::TIPO], self::TIPOS_BONO),
'requiere_notas'=> $registro[self::TIPO] =="PTC",
];
}
foreach (array_chunk($toInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) {
DB::table('productos')->insert($chunk);
}
self::agregarBonoBarrial();
self::log($archivo, self::CANASTA_CARGADA);
}
private static function obtenerProveedor($nombre) {
$result = null;
if (Str::contains($nombre,"*")){
$result = Proveedor::firstOrCreate([
'nombre' => 'Proveedor de economía solidaria',
'economia_solidaria' => 1,
'nacional' => 1
])->id;
}
return $result;
}
/**
* @param $path
* @param $descripcion
* @return void
*/
private static function log($path, $descripcion): void
{
$log = new CanastaLog([
'path' => $path,
'descripcion' => $descripcion,
]);
$log->save();
}
private static function limpiarTablas()
{
DB::delete('delete from producto_subpedido');
DB::delete('delete from productos');
DB::delete('delete from subpedidos');
}
private static function agregarBonoBarrial()
{
$categoria = Producto::all()
->pluck('categoria')
->unique()
->flatten()
->first(function ($c) {
return Str::contains($c, 'BONO');
});
DB::table('productos')->insert([
'fila' => 420,
'nombre' => "Bono barrial",
'precio' => 20,
'categoria' => $categoria,
'bono' => 1,
'proveedor_id' => null,
'requiere_notas'=> false,
]);
}
/**
* @param $registro
* @return bool
*/
public static function noTieneTipo($registro): bool
{
return !Arr::has($registro, self::TIPO) || trim($registro[self::TIPO]) == '';
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Log;
use Iterator;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\InvalidArgument;
use League\Csv\Reader;
use League\Csv\Writer;
class CsvHelper
{
public static function getRecords($filePath): Iterator {
$csv = Reader::createFromPath(resource_path($filePath));
try {
$csv->setDelimiter("|");
$csv->setEnclosure("'");
$csv->setHeaderOffset(0);
return $csv->getRecords();
} catch (InvalidArgument|Exception $e) {
Log::error($e->getMessage());
return null;
}
}
public static function generarCsv($filePath, $contenido, $headers = null): void
{
try {
$writer = Writer::createFromPath(resource_path($filePath), 'w');
if ($headers) {
$writer->insertOne($headers);
}
$writer->insertAll($contenido);
} catch (CannotInsertRecord $e) {
Log::error($e->getMessage(), $e->getTrace());
}
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Helpers;
use Mpdf\Mpdf;
use Mpdf\MpdfException;
class PdfHelper
{
/**
* Requiere que el segundo argumento tenga definida la función generarHTML()
* para crear la tabla con los datos del pedido que se inserta en el pdf.
*
* @return void
* @throws MpdfException
*/
public static function exportarPedidos($filepath, $pedidos)
{
$mpdf = new Mpdf();
foreach ($pedidos as $pedido) {
$html = $pedido->generarHTML();
$mpdf->WriteHTML($html);
$mpdf->AddPage();
}
$mpdf->Output($filepath, 'D');
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Helpers;
use App\CanastaLog;
use Illuminate\Support\Facades\Log;
class TransporteHelper
{
const COSTO_TRANSPORTE = 15;
const MONTO_TRANSPORTE = 500;
public static function cantidadTransporte($monto)
{
return ceil($monto / self::MONTO_TRANSPORTE);
}
public static function totalTransporte($monto)
{
return self::cantidadTransporte($monto) * self::COSTO_TRANSPORTE;
}
public static function filaTransporte()
{
$ultimaCanasta = CanastaLog::where('descripcion', CanastaHelper::CANASTA_CARGADA)
->orderBy('created_at', 'desc')
->pluck('path')
->first();
$registros = CsvHelper::getRecords($ultimaCanasta);
foreach ($registros as $key => $registro)
if ($registro[CanastaHelper::TIPO] == 'T') return $key;
Log::error('No hay fila de tipo T en la planilla: ' . $ultimaCanasta);
return null;
}
}

View file

@ -2,35 +2,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\GrupoDeCompra; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class AdminController extends Controller class AdminController extends Controller
{ {
public function show() //
{
return view('auth/admin_login');
}
public function index() {
return view('auth/admin_subpedidos');
}
public function exportarPedidosAPdf(GrupoDeCompra $gdc) {
$gdc->exportarPedidosAPdf();
}
public function exportarPedidoACSV(GrupoDeCompra $gdc): BinaryFileResponse
{
$gdc->exportarPedidoEnCSV();
$file = resource_path('csv/exports/'.$gdc->nombre.'.csv');
return response()->download($file);
}
public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc): BinaryFileResponse
{
$gdc->exportarPedidoConNucleosEnCSV();
$file = resource_path('csv/exports/'.$gdc->nombre.'-completo.csv');
return response()->download($file);
}
} }

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\GrupoDeCompra;
use App\Http\Controllers\Controller;
use App\Http\Resources\GrupoDeCompraResource;
class GrupoDeCompraController extends Controller
{
public function index()
{
return GrupoDeCompraResource::collection(GrupoDeCompra::all());
}
public function show(GrupoDeCompra $grupoDeCompra)
{
return new GrupoDeCompraResource($grupoDeCompra);
}
}

View file

@ -10,11 +10,24 @@ use App\Producto;
class ProductoController extends Controller class ProductoController extends Controller
{ {
/**
* Mostrar una lista de productos.
*
* @param App\Filtros\FiltroDeProducto $filtros
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(FiltroDeProducto $filtros, Request $request) public function index(FiltroDeProducto $filtros, Request $request)
{ {
return ProductoResource::collection(Producto::filtrar($filtros)->paginate(Producto::getPaginar($request))); return ProductoResource::collection(Producto::filtrar($filtros)->paginate(Producto::getPaginar($request)));
} }
/**
* Display the specified resource.
*
* @param \App\Producto $producto
* @return \Illuminate\Http\Response
*/
public function show(Producto $producto) public function show(Producto $producto)
{ {
return new ProductoResource($producto); return new ProductoResource($producto);

View file

@ -3,28 +3,35 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Producto;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Filtros\FiltroDeSubpedido; use App\Filtros\FiltroDeSubpedido;
use App\Subpedido; use App\Subpedido;
use App\GrupoDeCompra; use App\GrupoDeCompra;
use App\Http\Resources\SubpedidoResource; use App\Producto;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
class SubpedidoController extends Controller class SubpedidoController extends Controller
{ {
/**
* Mostrar una lista de productos.
*
* @param App\Filtros\FiltroDeSubpedido $filtros
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(FiltroDeSubpedido $filtros, Request $request) public function index(FiltroDeSubpedido $filtros, Request $request)
{ {
return Subpedido::filtrar($filtros)->get(); return Subpedido::filtrar($filtros)->get();
} }
public function indexResources(FiltroDeSubpedido $filtros, Request $request) /**
{ * Guardar un nuevo registro en el almacenamiento.
return SubpedidoResource::collection(Subpedido::filtrar($filtros)->get()); *
} * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request) public function store(Request $request)
{ {
$validado = $this->validateSubpedido(); $validado = $this->validateSubpedido();
@ -38,8 +45,29 @@ class SubpedidoController extends Controller
return $s; return $s;
} }
protected function validateSubpedido(): array /**
* Agregar un producto a un subpedido.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function updateProducto(Subpedido $subpedido, Producto $producto, Request $request)
{ {
$validado = $this->validateActualizacionDeProducto();
$cantidad = $validado['cantidad'];
if ($cantidad){
//si la cantidad es 1 o más se agrega el producto o actualiza la cantidad
$subpedido->productos()->syncWithoutDetaching([$producto->id => ['cantidad' => $cantidad]]);
} else {
//si la cantidad es 0, se elimina el producto del subpedido
$subpedido->productos()->detach($producto->id);
}
return response('Producto ' . $producto->nombre . ' actualizado en subpedido de ' . $subpedido->nombre . ' (cantidad ' . $cantidad . ')', 200);
}
protected function validateSubpedido(){
return request()->validate([ return request()->validate([
'nombre' => 'required|max:255', 'nombre' => 'required|max:255',
'grupo_de_compra_id' => [ 'grupo_de_compra_id' => [
@ -49,49 +77,9 @@ class SubpedidoController extends Controller
]); ]);
} }
public function show(Subpedido $subpedido) protected function validateActualizacionDeProducto(){
{ return request()->validate([
return new SubpedidoResource($subpedido); 'cantidad' => 'required|min:0'
}
// recibe request, saca producto y cantidad, valida, y pasa a syncProducto en Subpedido
public function syncProductos(Subpedido $subpedido) {
if ($subpedido->aprobado)
return new SubpedidoResource($subpedido);
$valid = request()->validate([
'cantidad' => ['integer','required','min:0'],
'notas' => 'nullable',
'producto_id' => [
'required',
Rule::in(Producto::all()->pluck('id')),
]
]); ]);
$producto = Producto::find($valid['producto_id']);
$notas = $valid['notas'];
$cantidad = $valid['cantidad'];
$subpedido->syncProducto($producto, $cantidad, $notas ?? "");
return new SubpedidoResource($subpedido);
}
public function toggleAprobacion(Subpedido $subpedido) {
$valid = request()->validate([
'aprobacion' => 'required | boolean'
]);
$subpedido->toggleAprobacion($valid['aprobacion']);
return new SubpedidoResource($subpedido);
}
public function syncDevoluciones(Subpedido $subpedido) {
if ($subpedido->aprobado) return new SubpedidoResource($subpedido);
$valid = request()->validate([
'total' => 'required|min:0',
'notas' => 'min:0'
]);
$subpedido->syncDevoluciones($valid['total'], $valid['notas'] ?? "");
return new SubpedidoResource($subpedido);
} }
} }

View file

@ -5,7 +5,6 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class LoginController extends Controller class LoginController extends Controller
@ -29,18 +28,6 @@ class LoginController extends Controller
*/ */
protected $redirectTo = RouteServiceProvider::HOME; protected $redirectTo = RouteServiceProvider::HOME;
protected function authenticated(Request $request, $user)
{
if ($user->is_compras) {
return redirect('compras/pedidos');
} else if ($user->is_admin) {
session(['admin_gdc' => $user->grupo_de_compra_id]);
return redirect('admin/pedidos');
} else {
return redirect('/');
}
}
/** /**
* Create a new controller instance. * Create a new controller instance.
* *

View file

@ -1,62 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Helpers\CanastaHelper;
use App\Producto;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ComprasController
{
const CANASTAS_PATH = 'csv/canastas/';
public function indexPedidos() {
return view('compras_pedidos');
}
public function descargarPedidos(): BinaryFileResponse
{
Producto::planillaTotales();
$file = resource_path('csv/exports/pedidos-por-barrio.csv');
return response()->download($file);
}
public function descargarNotas(): BinaryFileResponse
{
Producto::planillaNotas();
$file = resource_path('csv/exports/notas-por-barrio.csv');
return response()->download($file);
}
public function pdf() {
GrupoDeCompra::exportarPedidosBarrialesAPdf();
}
public function show()
{
return view('auth/compras_login');
}
public function cargarCanasta(Request $request): JsonResponse
{
$request->validate([
'data' => 'required|file|mimes:csv,txt|max:2048',
]);
$nombre = CanastaHelper::guardarCanasta($request->file('data'), self::CANASTAS_PATH);
CanastaHelper::cargarCanasta(self::CANASTAS_PATH . $nombre);
return response()->json([
'message' => 'Canasta cargada exitosamente',
]);
}
public function descargarCanastaEjemplo(): BinaryFileResponse
{
$file = resource_path('csv/productos.csv');
return response()->download($file);
}
}

View file

@ -2,6 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProductoController extends Controller class ProductoController extends Controller
{ {
/** /**
@ -14,6 +16,11 @@ class ProductoController extends Controller
$this->middleware(['auth','subpedido']); $this->middleware(['auth','subpedido']);
} }
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index() public function index()
{ {
return view('productos'); return view('productos');

View file

@ -56,8 +56,6 @@ class Kernel extends HttpKernel
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class, 'auth' => \App\Http\Middleware\Authenticate::class,
'admin' => \App\Http\Middleware\Admin::class,
'compras' => \App\Http\Middleware\Compras::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View file

@ -1,20 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class Admin
{
public function handle(Request $request, Closure $next)
{
$user = Auth::user();
if ($user->is_admin) {
return $next($request);
} else {
return response('Necesitás ser admin para hacer esto', 403);
}
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class Compras
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if (!Auth::check())
return redirect()->route('compras_login.show');
if (Auth::user()->is_compras) {
return $next($request);
} else {
return response('Necesitás ser de comisión compras para hacer esto', 403);
}
}
}

View file

@ -3,18 +3,17 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request;
class Subpedido class Subpedido
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @param Request $request * @param \Illuminate\Http\Request $request
* @param Closure $next * @param \Closure $next
* @return mixed * @return mixed
*/ */
public function handle(Request $request, Closure $next) public function handle($request, Closure $next)
{ {
if (!session('subpedido_nombre') || !session('subpedido_id')) { if (!session('subpedido_nombre') || !session('subpedido_id')) {
return redirect()->route('subpedidos.create'); return redirect()->route('subpedidos.create');

View file

@ -1,31 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class GrupoDeCompraResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'nombre' => $this->nombre,
'devoluciones_habilitadas' => $this->devoluciones_habilitadas,
'pedidos' => SubpedidoResource::collection($this->subpedidos),
'total_a_recaudar' => number_format($this->totalARecaudar(),2),
'total_barrial' => number_format($this->totalBarrial(),2),
'total_devoluciones' => number_format($this->totalDevoluciones(),2),
'total_a_transferir' => number_format($this->totalATransferir(),2),
'total_transporte' => number_format($this->totalTransporte()),
'cantidad_transporte' => number_format($this->cantidadTransporte()),
];
}
}

View file

@ -2,7 +2,6 @@
namespace App\Http\Resources; namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
class ProductoResource extends JsonResource class ProductoResource extends JsonResource
@ -10,10 +9,10 @@ class ProductoResource extends JsonResource
/** /**
* Transform the resource into an array. * Transform the resource into an array.
* *
* @param Request $request * @param \Illuminate\Http\Request $request
* @return array * @return array
*/ */
public function toArray($request): array public function toArray($request)
{ {
return [ return [
'id' => $this->id, 'id' => $this->id,
@ -26,8 +25,7 @@ class ProductoResource extends JsonResource
'imagen' => optional($this->poster)->url(), 'imagen' => optional($this->poster)->url(),
'descripcion' => $this->descripcion, 'descripcion' => $this->descripcion,
'apto_veganxs' => $this->apto_veganxs, 'apto_veganxs' => $this->apto_veganxs,
'apto_celiacxs' => $this->apto_celiacxs, 'apto_celiacxs' => $this->apto_celiacxs
'requiere_notas' => $this->requiere_notas,
]; ];
} }
} }

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class SubpedidoResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'nombre' => $this->nombre,
'grupo_de_compra' => $this->grupoDeCompra,
'productos' => $this->productos,
'aprobado' => (bool) $this->aprobado,
'total' => number_format($this->total(),2),
'total_transporte' => number_format($this->totalTransporte()),
'cantidad_transporte' => number_format($this->cantidadTransporte()),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),
'devoluciones_total' => number_format($this->devoluciones_total,2),
'devoluciones_notas' => $this->devoluciones_notas
];
}
}

View file

@ -2,153 +2,35 @@
namespace App; namespace App;
use App\Filtros\FiltroDeProducto;
use App\Helpers\CsvHelper;
use App\Helpers\TransporteHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use App\Filtros\FiltroDeProducto;
use Illuminate\Support\Facades\DB;
class Producto extends Model class Producto extends Model
{ {
public $timestamps = false; public $timestamps = false;
protected $fillable = ["nombre", "precio", "presentacion", "stock", "categoria"]; protected $fillable = [ "nombre", "precio", "presentacion", "stock", "categoria" ];
static int $paginarPorDefecto = 10; static $paginarPorDefecto = 10;
public function subpedidos(): BelongsToMany public function subpedidos()
{ {
return $this->belongsToMany('App\Subpedido', 'productos_subpedidos')->withPivot(["cantidad", "notas"]); return $this->belongsToMany('App\Subpedido','productos_subpedidos')->withPivot(["cantidad"]);
} }
public function proveedor(): BelongsTo public function proveedor()
{ {
return $this->belongsTo('App\Proveedor'); return $this->belongsTo('App\Proveedor');
} }
//Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) //Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
public function scopeFiltrar($query, FiltroDeProducto $filtros): Builder public function scopeFiltrar($query, FiltroDeProducto $filtros)
{ {
return $filtros->aplicar($query); return $filtros->aplicar($query);
} }
public static function getPaginar(Request $request): int public static function getPaginar(Request $request)
{ {
return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::$paginarPorDefecto; return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::$paginarPorDefecto;
} }
public static function productosFilaID()
{
return Producto::pluck('id', 'fila')->all();
}
public static function productosIDFila()
{
return Producto::pluck('fila', 'id')->all();
}
public static function productosIDNombre()
{
return Producto::pluck('nombre', 'id')->all();
}
static public function cantidadesPorBarrio(): Collection
{
$barrios = GrupoDeCompra::barriosMenosPrueba()
->pluck('id', 'nombre');
$columnasBarrios = $barrios->map(function ($id, $nombre) {
return DB::raw("SUM(CASE WHEN subpedidos.grupo_de_compra_id = $id AND subpedidos.aprobado = 1 THEN producto_subpedido.cantidad ELSE 0 END) as `$nombre`");
})->toArray();
return DB::table('productos')
->where('productos.nombre', 'not like', '%barrial%')
->leftJoin('producto_subpedido', 'productos.id', '=', 'producto_subpedido.producto_id')
->leftJoin('subpedidos', 'subpedidos.id', '=', 'producto_subpedido.subpedido_id')
->select(array_merge(
['productos.fila as fila'],
['productos.nombre as producto'],
$columnasBarrios
))
->groupBy('productos.fila', 'productos.id', 'productos.nombre')
->orderBy('productos.fila')
->get();
}
static public function planillaTotales()
{
$headers = ['Producto'];
$barrios = GrupoDeCompra::barriosMenosPrueba()
->pluck('nombre')->toArray();
$headers = array_merge($headers, $barrios);
$cantidadesPorBarrio = self::cantidadesPorBarrio();
$transportePorBarrio = GrupoDeCompra::transportePorBarrio();
$planilla = [];
$ultimaFila = 1;
$filaTransporte = TransporteHelper::filaTransporte();
foreach ($cantidadesPorBarrio as $productoCantidades) {
$fila = $productoCantidades->fila;
while ($fila - $ultimaFila > 1) {
$ultimaFila++;
if ($ultimaFila == $filaTransporte) {
$planilla[$ultimaFila] = ['Bono de transporte'];
} else {
$planilla[$ultimaFila] = ['---'];
}
}
$planilla[$fila] = [$productoCantidades->producto];
foreach ($barrios as $barrio) {
$planilla[$fila][] = $productoCantidades->$barrio ?? 0;
}
$ultimaFila = $fila;
}
foreach ($transportePorBarrio as $key => $cantidad) {
$planilla[$filaTransporte][] = $cantidad;
}
CsvHelper::generarCsv('csv/exports/pedidos-por-barrio.csv', $planilla, $headers);
}
public static function notasPorBarrio(): Collection
{
return DB::table('productos')
->join('producto_subpedido', 'productos.id', '=', 'producto_subpedido.producto_id')
->join('subpedidos', 'producto_subpedido.subpedido_id', '=', 'subpedidos.id')
->join('grupos_de_compra', 'subpedidos.grupo_de_compra_id', '=', 'grupos_de_compra.id')
->where('productos.requiere_notas', 1)
->select(
'productos.nombre as producto',
'grupos_de_compra.nombre as barrio',
'producto_subpedido.notas'
)
->get()
->groupBy('producto');
}
static public function planillaNotas() {
$headers = ['Producto'];
$barrios = GrupoDeCompra::barriosMenosPrueba()
->pluck('nombre')->toArray();
$headers = array_merge($headers, $barrios);
$notasPorBarrio = self::notasPorBarrio();
$planilla = [];
foreach ($notasPorBarrio as $producto => $notasGrupo) {
$fila = [$producto];
foreach ($barrios as $barrio) {
$notas = $notasGrupo->where('barrio', $barrio)->pluck('notas')->implode('; ');
$fila[] = $notas ?: '';
}
$planilla[] = $fila;
}
CsvHelper::generarCsv('csv/exports/notas-por-barrio.csv', $planilla, $headers);
}
} }

View file

@ -3,7 +3,6 @@
namespace App; namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Proveedor extends Model class Proveedor extends Model
{ {
@ -11,7 +10,7 @@ class Proveedor extends Model
protected $fillable = [ "nombre","direccion","telefono","correo","comentario" ]; protected $fillable = [ "nombre","direccion","telefono","correo","comentario" ];
protected $table = 'proveedores'; protected $table = 'proveedores';
public function productos(): HasMany public function productos()
{ {
return $this->hasMany('App\Producto'); return $this->hasMany('App\Producto');
} }

View file

@ -2,130 +2,31 @@
namespace App; namespace App;
use App\Helpers\TransporteHelper; use League\Csv\Reader;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Log;
use App\Filtros\FiltroDeSubpedido; use App\Filtros\FiltroDeSubpedido;
class Subpedido extends Model class Subpedido extends Model
{ {
public $timestamps = false; public $timestamps = false;
protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre', 'devoluciones_total', 'devoluciones_notas']; protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre'];
public function productos(): BelongsToMany public function productos()
{ {
return $this->belongsToMany('App\Producto')->withPivot(["cantidad", "total", "notas"]); return $this->belongsToMany('App\Producto')->withPivot(["cantidad"]);
} }
public function grupoDeCompra(): BelongsTo public function grupoDeCompra()
{ {
return $this->belongsTo('App\GrupoDeCompra'); return $this->belongsTo('App\GrupoDeCompra');
} }
// Permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) //Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
public function scopeFiltrar($query, FiltroDeSubpedido $filtros): Builder public function scopeFiltrar($query, FiltroDeSubpedido $filtros)
{ {
return $filtros->aplicar($query); return $filtros->aplicar($query);
} }
public function total()
{
return $this->totalSinDevoluciones() - $this->devoluciones_total;
}
public function totalSinDevoluciones()
{
return $this->totalBarrial() + $this->totalCentral();
}
public function totalBarrial()
{
return DB::table('producto_subpedido')
->join('productos', 'producto_subpedido.producto_id', '=', 'productos.id')
->where('producto_subpedido.subpedido_id', $this->id)
->where('productos.nombre', 'like', '%barrial%')
->selectRaw('SUM(productos.precio * producto_subpedido.cantidad) as total')
->value('total');
}
public function totalCentral()
{
return $this->totalCentralesQueNoPaganTransporte() + $this->totalCentralesQuePaganTransporte() + $this->totalTransporte();
}
public function totalCentralesQueNoPaganTransporte()
{
return DB::table('producto_subpedido')
->join('productos', 'producto_subpedido.producto_id', '=', 'productos.id')
->where('producto_subpedido.subpedido_id', $this->id)
->where('productos.nombre', 'not like', '%barrial%')
->where(function ($query) {
$query->where('productos.categoria', 'like', '%SUBSIDIADO%')
->orWhere('productos.bono', true);
})
->selectRaw('SUM(productos.precio * producto_subpedido.cantidad) as total')
->value('total');
}
public function totalCentralesQuePaganTransporte()
{
return DB::table('producto_subpedido')
->join('productos', 'producto_subpedido.producto_id', '=', 'productos.id')
->where('producto_subpedido.subpedido_id', $this->id)
->where('productos.nombre', 'not like', '%barrial%')
->where('productos.bono', false)
->where('productos.categoria', 'not like', '%SUBSIDIADO%')
->selectRaw('SUM(productos.precio * producto_subpedido.cantidad) as total')
->value('total');
}
public function totalTransporte()
{
return TransporteHelper::totalTransporte($this->totalCentralesQuePaganTransporte());
}
public function cantidadTransporte()
{
return TransporteHelper::cantidadTransporte($this->totalCentralesQuePaganTransporte());
}
//Actualiza el pedido, agregando o quitando del subpedido según sea necesario. Debe ser llamado desde el controlador de subpedidos, luego de validar que los parámetros $producto y $cantidad son correctos. También calcula el subtotal por producto.
public function syncProducto(Producto $producto, int $cantidad, string $notas)
{
if ($cantidad) {
//si la cantidad es 1 o más se agrega el producto o actualiza la cantidad
$this->productos()->syncWithoutDetaching([
$producto->id => [
'cantidad' => $cantidad,
'total' => $cantidad * $producto->precio,
'notas' => $notas,
]
]);
} else {
//si la cantidad es 0, se elimina el producto del subpedido
$this->productos()->detach($producto->id);
}
}
public function toggleAprobacion(bool $aprobacion)
{
$this->aprobado = $aprobacion;
$this->save();
}
public function generarHTML()
{
$view = view("pdfgen.pedido_tabla", ["pedido" => $this]);
return $view->render();
}
public function syncDevoluciones(float $total, string $notas)
{
$this->devoluciones_total = $total;
$this->devoluciones_notas = $notas;
$this->save();
}
} }

View file

@ -2,7 +2,7 @@
namespace App; namespace App;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@ -38,7 +38,7 @@ class User extends Authenticatable
]; ];
public function grupoDeCompra(): BelongsTo public function grupoDeCompra()
{ {
return $this->belongsTo('App\GrupoDeCompra'); return $this->belongsTo('App\GrupoDeCompra');
} }

View file

@ -8,7 +8,7 @@
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.4", "php": "^7.2.5|^8.0",
"fideloper/proxy": "^4.4", "fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3.1|^7.0.1", "guzzlehttp/guzzle": "^6.3.1|^7.0.1",
@ -16,9 +16,7 @@
"laravel/sanctum": "^2.13", "laravel/sanctum": "^2.13",
"laravel/tinker": "^2.5", "laravel/tinker": "^2.5",
"laravel/ui": "*", "laravel/ui": "*",
"league/csv": "^9.8", "league/csv": "^9.8"
"mpdf/mpdf": "^8.1",
"prexview/prexview": "^1.1"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^2.0", "facade/ignition": "^2.0",
@ -54,13 +52,13 @@
"scripts": { "scripts": {
"post-autoload-dump": [ "post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php7.4 artisan package:discover --ansi" "@php artisan package:discover --ansi"
], ],
"post-root-package-install": [ "post-root-package-install": [
"@php7.4 -r \"file_exists('.env') || copy('.env.example', '.env');\"" "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php7.4 artisan key:generate --ansi" "@php artisan key:generate --ansi"
] ]
} }
} }

1848
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,9 @@
<?php <?php
/** @var Factory $factory */ /** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User; use App\User;
use Faker\Generator as Faker; use Faker\Generator as Faker;
use Illuminate\Database\Eloquent\Factory;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/* /*

View file

@ -19,8 +19,6 @@ class CreateUsersTable extends Migration
$table->string('email')->unique()->nullable(); $table->string('email')->unique()->nullable();
$table->timestamp('email_verified_at')->nullable(); $table->timestamp('email_verified_at')->nullable();
$table->foreignId('grupo_de_compra_id')->nullable(); $table->foreignId('grupo_de_compra_id')->nullable();
$table->boolean('is_admin');
$table->unique(['name', 'is_admin']);
$table->string('password'); $table->string('password');
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestamps();

View file

@ -17,7 +17,7 @@ class CreateSubpedidosTable extends Migration
$table->id(); $table->id();
$table->string('nombre'); $table->string('nombre');
$table->foreignId('grupo_de_compra_id'); $table->foreignId('grupo_de_compra_id');
$table->boolean('aprobado')->default(false); $table->boolean('aprobado')->nullable();
$table->timestamps(); $table->timestamps();
}); });

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarColumnaBonoATablaDeProductos extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->boolean('bono')->after('apto_celiacxs');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('productos', function (Blueprint $table) {
$table->dropColumn('bono');
});
}
}

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarColumnaTotalATablaDeProductoSubpedido extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('producto_subpedido', function (Blueprint $table) {
$table->double('total',10,2)->after('cantidad')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('producto_subpedido', function (Blueprint $table) {
$table->dropColumn('total');
});
}
}

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarColumnaFilaEnPlanillaATablaDeProductos extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->integer('fila')->after('id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('productos', function (Blueprint $table) {
$table->dropColumn('fila');
});
}
}

View file

@ -1,26 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
class CallCrearPedidosAprobados extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Artisan::call("view:CrearPedidosAprobadosView");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View file

@ -1,34 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DevolucionesPedido extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('subpedidos', function (Blueprint $table) {
$table->double('devoluciones_total', 10, 2)->default(0);
$table->string('devoluciones_notas')->default("");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('subpedidos', function (Blueprint $table) {
$table->dropColumn('devoluciones_total');
$table->dropColumn('devoluciones_notas');
});
}
}

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class HabilitarDevoluciones extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->boolean('devoluciones_habilitadas')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->dropColumn('devoluciones_habilitadas');
});
}
}

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarIsComprasAUser extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_compras')->after('is_admin')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_compras');
});
}
}

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class NotasProducto extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('producto_subpedido', function (Blueprint $table) {
$table->string('notas')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('producto_subpedido', function (Blueprint $table) {
$table->dropColumn('notas');
});
}
}

View file

@ -1,32 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProductoRequiereNotas extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->boolean('requiere_notas')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('productos', function (Blueprint $table) {
$table->dropColumn('requiere_notas');
});
}
}

View file

@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CrearCargaDeCanastas extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('carga_de_canastas', function (Blueprint $table) {
$table->id();
$table->string('path');
$table->string('descripcion');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('carga_de_canastas');
}
}

View file

@ -1,27 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Artisan;
class CallAgregarEsBonoAPedidosAprobados extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Artisan::call("command:AgregarEsBonoAPedidosAprobados");
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View file

@ -1,19 +0,0 @@
<?php
use App\Helpers\CanastaHelper;
use Illuminate\Database\Seeder;
class CanastaSeeder extends Seeder
{
const ARCHIVO_DEFAULT = 'csv/productos.csv';
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
CanastaHelper::cargarCanasta(self::ARCHIVO_DEFAULT);
}
}

View file

@ -12,8 +12,7 @@ class DatabaseSeeder extends Seeder
*/ */
public function run() public function run()
{ {
$this->call(CanastaSeeder::class);
$this->call(GrupoDeCompraSeeder::class); $this->call(GrupoDeCompraSeeder::class);
$this->call(UserSeeder::class); $this->call(ProductoSeeder::class);
} }
} }

View file

@ -1,9 +1,7 @@
<?php <?php
use App\Helpers\CsvHelper as CsvHelperAlias;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; use League\Csv\Reader;
use Illuminate\Support\Facades\Hash;
class GrupoDeCompraSeeder extends Seeder class GrupoDeCompraSeeder extends Seeder
{ {
@ -14,7 +12,11 @@ class GrupoDeCompraSeeder extends Seeder
*/ */
public function run() public function run()
{ {
$registros = CsvHelperAlias::getRecords('csv/barrios.csv'); $csv = Reader::createFromPath(resource_path('csv/barrios.csv'), 'r');
$csv->setDelimiter("|");
$csv->setEnclosure("'");
$csv->setHeaderOffset(0);
$registros = $csv->getRecords();
$gdcToInsert = []; $gdcToInsert = [];
$usersToInsert = []; $usersToInsert = [];
@ -29,15 +31,7 @@ class GrupoDeCompraSeeder extends Seeder
$usersToInsert[] = [ $usersToInsert[] = [
'name' => $registro['barrio'], 'name' => $registro['barrio'],
'password' => Hash::make("123"), 'password' => Hash::make($registro['barrio']),
"is_admin" => 0,
'grupo_de_compra_id' => $key
];
$usersToInsert[] = [
'name' => $registro['barrio'] . "_admin",
'password' => Hash::make("123"),
"is_admin" => 1,
'grupo_de_compra_id' => $key 'grupo_de_compra_id' => $key
]; ];
} }

View file

@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Seeder;
use League\Csv\Reader;
use App\Proveedor;
class ProductoSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$csv = Reader::createFromPath(resource_path('csv/productos.csv'), 'r');
$csv->setDelimiter("|");
$csv->setEnclosure("'");
$csv->setHeaderOffset(0);
$registros = $csv->getRecords();
$toInsert = [];
foreach($registros as $registro){
$toInsert[] = [
'categoria' => $registro['categoria'],
'nombre' => $registro['producto'],
'precio' => $registro['precio'],
'proveedor_id' => isset($registro['proveedor']) ? Proveedor::firstOrCreate([
'nombre' => $registro['proveedor']
])->id : null
];
}
foreach (array_chunk($toInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{
DB::table('productos')->insert($chunk);
}
}
}

View file

@ -1,31 +0,0 @@
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$usersToInsert = [];
$usersToInsert[] = [
'name' => 'compras',
'password' => Hash::make("123"),
'is_admin' => 0,
'is_compras' => 1
];
foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{
DB::table('users')->insert($chunk);
}
}
}

View file

@ -1,2 +0,0 @@
#!/usr/bin/bash
docker-compose up -d && docker-compose exec app npm run watch

View file

@ -1,15 +1,14 @@
version: '3.2' version: "3.7"
services: services:
app: app:
build: build:
args: args:
user: www user: www
uid: ${USERID} uid: 1000
context: ./ context: ./
dockerfile: Dockerfile dockerfile: Dockerfile
image: laravel-image image: laravel-image
container_name: pedi2-app container_name: laravel-app
restart: unless-stopped restart: unless-stopped
working_dir: /var/www/ working_dir: /var/www/
volumes: volumes:
@ -20,8 +19,10 @@ services:
db: db:
image: mysql:5.7 image: mysql:5.7
container_name: pedi2-db container_name: laravel-db
restart: unless-stopped restart: unless-stopped
ports:
- "3306:3306"
environment: environment:
MYSQL_DATABASE: ${DB_DATABASE} MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
@ -35,15 +36,13 @@ services:
- dbdata:/var/lib/mysql - dbdata:/var/lib/mysql
networks: networks:
- app-network - app-network
ports:
- ${DB_PORT_EXPOSED}:3306
nginx: nginx:
image: nginx:alpine image: nginx:alpine
container_name: pedi2-nginx container_name: laravel-nginx
restart: unless-stopped restart: unless-stopped
ports: ports:
- ${NGINX_PORT}:80 - 8000:80
volumes: volumes:
- ./:/var/www - ./:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/ - ./nginx/conf.d/:/etc/nginx/conf.d/

26581
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,9 +10,9 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js" "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --config=node_modules/laravel-mix/setup/webpack.config.js"
}, },
"devDependencies": { "devDependencies": {
"axios": "^0.19.2", "axios": "^0.19",
"bootstrap": "^4.0.0", "bootstrap": "^4.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0",
"jquery": "^3.2", "jquery": "^3.2",
"laravel-mix": "^5.0.1", "laravel-mix": "^5.0.1",
"lodash": "^4.17.19", "lodash": "^4.17.19",
@ -22,11 +22,5 @@
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"vue": "^2.5.17", "vue": "^2.5.17",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
},
"dependencies": {
"animate.css": "^4.1.1",
"bulma": "^0.9.4",
"bulma-switch": "^2.0.4",
"bulma-toast": "^2.4.1"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

8
public/css/app.css vendored Normal file
View file

@ -0,0 +1,8 @@
p.navbar-item:empty {
display: none;
}
.breadcrumb a {
color: #cc0f35;
}

4
public/css/productos.css vendored Normal file
View file

@ -0,0 +1,4 @@
figure.image.icono {
float: right;
margin: 4px;
}

73
public/js/app.js vendored Normal file
View file

@ -0,0 +1,73 @@
window.Event = new Vue();
Vue.component('nav-bar', {
template: `
<nav class="navbar is-danger" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://mps.org.uy">
<img src="/assets/logoMPS.png" height="28">
</a>
<p style="margin:0 auto" class="navbar-item"><slot name="subpedido"></slot></p>
<a role="button" class="navbar-burger" :class="{'is-active':isActive}" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample" @click="toggleState">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu" :class="{'is-active':isActive}">
<div class="navbar-start has-text-right-mobile">
<!-- Styles nombre del barrio-->
<p class="navbar-item"><slot name="gdc"></slot></p>
<a class="navbar-item"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Cerrar sesión
</a>
<slot name="logout-form"></slot>
</div>
</div>
</nav>`,
data() {
return {
isActive: false
}
},
methods: {
toggleState() {
this.isActive = !this.isActive;
}
}
});
Vue.component('nav-migas', {
data() {
return {
migas: []
}
},
computed: {
visible: function() {
return this.migas.length > 0
}
},
mounted() {
Event.$on('migas-setear-como-inicio', (miga) => {
this.migas = [];
this.migas.push(miga);
});
Event.$on('migas-agregar', (miga) => {
this.migas.push(miga);
});
Event.$on('migas-reset', () => {
this.migas = [];
});
Event.$on('migas-pop', () => {
this.migas.pop();
});
}
});
new Vue({
el: '#app'
});

114
public/js/login.js vendored Normal file
View file

@ -0,0 +1,114 @@
window.Event = new Vue();
Vue.component('region-select', {
template: `
<div class="block">
<div class="field">
<label class="label">Seleccioná tu región</label>
<div class="control">
<div class="select">
<select @change="onRegionSelected" v-model="region">
<option :disabled="isDefaultDisabled==1" value=null>Seleccionar</option>
<option v-for="region in regiones" v-text="region" :name="region"></option>
</select>
</div>
</div>
</div>
</div>`,
data() {
return {
regiones: [],
isDefaultDisabled: 0,
region: null
}
},
mounted() {
axios.get("/api/regiones").then(response => this.regiones = response.data);
},
methods: {
onRegionSelected() {
this.isDefaultDisabled = 1;
Event.$emit("region-seleccionada",this.region);
}
}
});
Vue.component('barrio-select', {
template: `
<div v-show="visible" class="block">
<div class="field">
<label class="label">Seleccioná tu barrio o grupo de compra</label>
<div class="control">
<div class="select">
<select @change="onGDCSelected" v-model="gdc" name="name">
<option :disabled="isDefaultDisabled==1" value=null>Seleccionar</option>
<option v-for="gdc in gdcs" v-text="gdc.nombre" :name="gdc.nombre"></option>
</select>
</div>
</div>
</div>
</div>`,
data() {
return {
visible: false,
region: null,
gdcs: [],
isDefaultDisabled: 0,
gdc: null
}
},
mounted() {
Event.$on('region-seleccionada', (region)=> {
this.region = region;
this.fillGDC(region);
this.visible = true;
});
},
methods : {
fillGDC(region) {
axios.get("/api/grupos-de-compra").then(response => {
this.gdcs = response.data[this.region];
});
},
onGDCSelected() {
this.isDefaultDisabled = 1;
Event.$emit("gdc-seleccionado",this.gdc);
}
}
});
Vue.component('login', {
template: `
<div v-show="visible" class="block">
<div class="field">
<label class="label">Contraseña del barrio</label>
<p class="control">
<input required class="input" type="password" name="password" placeholder="Contraseña del barrio">
</p>
<p class="help">Si no la sabés, consultá a tus compañerxs.</p>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-success" value="Ingresar">
</input>
</div>
</div>
</div>`,
data() {
return {
visible: false,
gdc: null
}
},
mounted() {
Event.$on('gdc-seleccionado', (gdc) => {
this.gdc = gdc;
this.visible = true;
});
}
});
new Vue({
el: '#root'
});

171
public/js/productos.js vendored Normal file
View file

@ -0,0 +1,171 @@
Vue.component('categorias-container', {
template: `
<div v-show="visible" class="container">
<div class="columns is-multiline is-mobile">
<div v-for="catego in categorias" class="block column is-one-quarter-desktop is-one-third-tablet is-half-mobile">
<div @click.capture="seleccionarCategoria(catego)" class="card" style="height:100%" >
<div class="card-content">
<div class="media">
<div class="media-content" style="overflow:hidden">
<p class="title is-6" v-text="catego"></p>
</div>
</div>
</div>
</div><!-- END CARD -->
</div><!-- END BLOCK COLUMN -->
</div><!-- END COLUMNS -->
</div><!-- END CONTAINER -->`,
data() {
return {
categorias: null,
visible: true,
miga: {
nombre: "Categorías",
href: "/productos"
}
}
},
mounted() {
axios.get("/api/categorias").then(response => {
this.categorias = response.data;
});
Event.$emit("migas-setear-como-inicio",this.miga);
},
methods: {
seleccionarCategoria(categoria) {
this.visible = false;
Event.$emit("categoria-seleccionada",categoria);
}
}
});
Vue.component('productos-container', {
template: `
<div v-show="visible" class="container">
<div class="columns is-multiline is-mobile">
<div v-for="producto in productos" class="block column is-one-quarter-desktop is-one-third-tablet is-half-mobile">
<div @click.capture="seleccionarProducto(producto)" class="card" style="height:100%">
<div class="card-image">
<figure class="image is-4by3">
<img v-bind:src="producto.imagen ? producto.imagen : 'https://bulma.io/images/placeholders/1280x960.png'">
</figure>
<figure v-show="producto.nacional" class="image icono is-32x32">
<img src="/assets/uruguay.png">
</figure>
<figure v-show="producto.economia_solidaria" class="image icono is-32x32">
<img src="/assets/solidaria.png">
</figure>
</div>
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-6" v-text="producto.nombre"></p>
<p class="subtitle is-7" v-text="producto.proveedor"></p>
<p class="subtitle is-7">$<span v-text="producto.precio"></span></p>
</div>
</div>
</div>
</div><!-- END CARD -->
</div><!-- END BLOCK COLUMN -->
</div><!-- END COLUMNS -->
</div><!-- END CONTAINER -->`,
data() {
return {
productos: [],
visible: false,
categoria: null,
paginar: 150
}
},
computed: {
miga: function(){
return {
nombre: this.categoria,
href: "#" + this.categoria
}
}
},
mounted() {
Event.$on('categoria-seleccionada', (categoria) => {
this.categoria = categoria;
axios.get("/api/productos", {
params: {
categoria: this.categoria,
paginar: this.paginar
}
}).then(response => {
this.productos = response.data.data;
});
this.visible = true;
Event.$emit("migas-agregar",this.miga);
});
},
methods: {
seleccionarProducto(producto) {
Event.$emit("producto-seleccionado",producto);
}
}
});
Vue.component('producto-container', {
template: `
<div v-bind:class="visible ? 'is-active modal' : 'modal'">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title" v-text="producto.nombre"></p>
<button class="delete" aria-label="close" @click.capture="cerrar"></button>
</header>
<section class="modal-card-body">
<div class="card-image">
<figure class="image is-4by3">
<img v-bind:src="producto.imagen ? producto.imagen : 'https://bulma.io/images/placeholders/1280x960.png'">
</figure>
<figure v-show="producto.nacional" class="image icono is-32x32">
<img src="/assets/uruguay.png">
</figure>
<figure v-show="producto.economia_solidaria" class="image icono is-32x32">
<img src="/assets/solidaria.png">
</figure>
</div>
<div class="media-content">
<p class="title is-4" v-text="producto.proveedor"></p>
<p class="subtitle is-4">$<span v-text="producto.precio"></span></p>
<p class="subtitle is-5"><span v-text="producto.descripcion"></span></p>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Agregar a la chismosa</button>
<button class="button" @click.capture="cerrar">Cancelar</button>
</footer>
</div>
</div>`,
data() {
return {
producto: null,
visible: false
}
},
computed: {
miga: function(){
return {
nombre: this.producto.nombre,
href: "#" + this.producto.nombre
}
}
},
methods: {
cerrar() {
this.visible = false;
Event.$emit("migas-pop");
}
},
mounted() {
Event.$on('producto-seleccionado', (producto) => {
this.producto = producto;
this.visible = true;
Event.$emit("migas-agregar",this.miga);
});
}
});

33
public/js/subpedidos-create.js vendored Normal file
View file

@ -0,0 +1,33 @@
Vue.component('subpedido-select', {
data() {
return {
subpedido: null,
subpedidosExistentes: []
}
},
props: ["gdcid"],
mounted() {
console.log("ready");
},
methods: {
onType() {
axios.get("/api/subpedidos?nombre=" + this.subpedido).then(response => {
this.subpedidosExistentes = response.data
});
},
submit() {
axios.post("/api/subpedidos", {
nombre: this.subpedido,
grupo_de_compra_id: this.gdcid
}).then(response => {
//se creo el subpedido, guardamos el subpedido en sesion
axios.post("/subpedidos/guardar_sesion", {
subpedido: response.data
}).then(response => {
window.location.href = 'productos';
});
});
}
}
});

View file

@ -1,4 +1,45 @@
barrio|region|referente|telefono|correo barrio|region|referente|telefono|correo
ENTREVERO|SUR||| BUCEO|ESTE|||
MALVIN NORTE|ESTE|||
PINAR|ESTE|||
UNION|ESTE|||
SANTO DOMINGO|NORTE|||
21 DE FEBRERO|NORTE|||
PIEDRAS BLANCAS|NORTE|||
BELLA ITALIA|NORTE|||
JARDINES|NORTE|||
LA SOCIALISTA|NORTE|||
VILLA GARCIA|NORTE|||
LAS ACACIAS|NORTE|||
MANGA|NORTE|||
CERRITO|OESTE|||
LEZICA|OESTE|||
PEÑAROL|OESTE|||
LAS PIEDRAS|OESTE|||
A.C.T.E.O|OESTE|||
NUEVO PARIS|OESTE|||
SANTA CATALINA|OESTE|||
BELVEDERE|OESTE|||
BATLLE BERRES|OESTE|||
CERRO PTI|OESTE|||
COLECTIVO UTU LAVALLEJA|OESTE|||
FOGONES|OESTE|||
LAVALLEJA|OESTE|||
REJUNTE|OESTE|||
CAPURRO|OESTE|||
PARQUE RODO|SUR|||
REDUCTO|SUR|||
AGUADA|SUR|||
CASA DEL VECINO|SUR|||
CIUDAD VIEJA|SUR|||
COOP EJIDO|SUR|||
COVIREUS|SUR|||
LA BLANQUEADA|SUR|||
NITEP|SUR|||
LA CURVA|SUR|||
PANADERIA VIDAL|SUR|||
SUA|SUR|||
TRES CRUCES|SUR||| TRES CRUCES|SUR|||
PRUEBA|SIN REGION||| VILLA ESPAÑOLA|SUR|||
AUDA|OTRA|||
SINTEP|OTRA|||

1 barrio region referente telefono correo
2 ENTREVERO BUCEO SUR ESTE
3 MALVIN NORTE ESTE
4 PINAR ESTE
5 UNION ESTE
6 SANTO DOMINGO NORTE
7 21 DE FEBRERO NORTE
8 PIEDRAS BLANCAS NORTE
9 BELLA ITALIA NORTE
10 JARDINES NORTE
11 LA SOCIALISTA NORTE
12 VILLA GARCIA NORTE
13 LAS ACACIAS NORTE
14 MANGA NORTE
15 CERRITO OESTE
16 LEZICA OESTE
17 PEÑAROL OESTE
18 LAS PIEDRAS OESTE
19 A.C.T.E.O OESTE
20 NUEVO PARIS OESTE
21 SANTA CATALINA OESTE
22 BELVEDERE OESTE
23 BATLLE BERRES OESTE
24 CERRO PTI OESTE
25 COLECTIVO UTU LAVALLEJA OESTE
26 FOGONES OESTE
27 LAVALLEJA OESTE
28 REJUNTE OESTE
29 CAPURRO OESTE
30 PARQUE RODO SUR
31 REDUCTO SUR
32 AGUADA SUR
33 CASA DEL VECINO SUR
34 CIUDAD VIEJA SUR
35 COOP EJIDO SUR
36 COVIREUS SUR
37 LA BLANQUEADA SUR
38 NITEP SUR
39 LA CURVA SUR
40 PANADERIA VIDAL SUR
41 SUA SUR
42 TRES CRUCES SUR
43 PRUEBA VILLA ESPAÑOLA SIN REGION SUR
44 AUDA OTRA
45 SINTEP OTRA

View file

View file

@ -1,381 +1,359 @@
Tipo|Producto|Precio categoria|producto|precio|proveedor
ALIMENTOS NO PERECEDEROS|| ALIMENTOS NO PERECEDEROS|Yerba Compuesta La Herboristería 1kg |140.3|
P|Yerba Compuesta La Herboristería 1kg|157.63 ALIMENTOS NO PERECEDEROS|Yerba Yusa tradicional 1kg |121.8|
P|Yerba Yusa tradicional 1kg|148.00 ALIMENTOS NO PERECEDEROS|Yerba Yusa tradicional 500grs|65|
P|Yerba Yusa tradicional 500grs|81.00 ALIMENTOS NO PERECEDEROS|Yerba Sara tradicional 1kg|129.7|
P|Yerba Sara tradicional 1kg (Sin TACC)|139.40 ALIMENTOS NO PERECEDEROS|Yerba Sara suave 1kg|132.2|
P|Yerba Sara suave 1kg (Sin TACC)|139.40 ALIMENTOS NO PERECEDEROS|Yerba Kiero mate 500grs|72.5|
P|*Harina Santa Unión 000 1kg|33.11 ALIMENTOS NO PERECEDEROS|*Harina Santa Unión 000 1kg|27.3|Santa Unión
P|*Harina Santa Unión 0000 1kg|38.70 ALIMENTOS NO PERECEDEROS|*Harina Santa Unión 0000 1kg|34.8|Santa Unión
P|*Harina de arroz Pasaná 1 kg (Puede contener gluten)|90.00 ALIMENTOS NO PERECEDEROS|*Harina de trigo integral Pasaná 1 kg|65|Pasaná
P|*Harina de Garbanzos Pasaná 1 kg (Puede contener gluten)|140.00 ALIMENTOS NO PERECEDEROS|*Harina de arroz Pasaná 1 kg |65|Pasaná
P|*Mezcla para faina Pasaná 1 kg (Puede contener gluten)|160.00 ALIMENTOS NO PERECEDEROS|*Harina de Garbanzos Pasaná 1 kg |140|Pasaná
P|*Fécula de Mandioca Pasaná 1 kg (Puede contener gluten)|140.00 ALIMENTOS NO PERECEDEROS|*Mezcla para faina Pasaná 1 kg |150|Pasaná
P|Fécula de Mandioca Hornex 800g (Sin TACC)|125.00 ALIMENTOS NO PERECEDEROS|*Fécula de Mandioca Pasaná 1 kg |85|Pasaná
P|Tres Harinas Hornex 800grs (Sin TAAC)|129.00 ALIMENTOS NO PERECEDEROS|Tres Harinas 500grs. |85|
P|*Harina Santa Unión 000 Bolsa 25kg|602.86 ALIMENTOS NO PERECEDEROS|*Harina Santa Unión 000 Bolsa 25kg|560.6|Santa Unión
P|*Polenta Santa Unión 450gr|16.34 ALIMENTOS NO PERECEDEROS|*Polenta Santa Unión 450gr|15.4|Santa Unión
P|*Mezcla de Fainá Santa Unión 5kg|431.29 ALIMENTOS NO PERECEDEROS|*Mezcla de Fainá Santa Unión 5kg|294.8|Santa Unión
P|*Harina de trigo intergal orgánica "La linda sauceña"|97.00 ALIMENTOS NO PERECEDEROS|*Fideos Caorsi Tirabuzón 1kg|59|Caorsi
P|*Fideos Caorsi Tirabuzón 1kg|73.00 ALIMENTOS NO PERECEDEROS|*Fideos Caorsi Tallarín 1kg|69|Caorsi
P|*Fideos Caorsi Tallarín 1kg|81.00 ALIMENTOS NO PERECEDEROS|*Fideos Caorsi Moñita 1kg|69|Caorsi
P|*Fideos Caorsi Moñita 1kg|81.00 ALIMENTOS NO PERECEDEROS|*Fideos Caorsi para sopa 1kg|59|Caorsi
P|*Fideos Caorsi Tirabuzón 5kg|353.00 ALIMENTOS NO PERECEDEROS|*Fideos Caorsi para sopa 5kg|268|Caorsi
P|Arroz Blue Patna 1kg|50.02 ALIMENTOS NO PERECEDEROS|*Fideos Caorsi Tirabuzón 5kg|268|Caorsi
P|Arroz Shiva 1kg|35.65 ALIMENTOS NO PERECEDEROS|Arroz Blue Patna 1kg|40.2|
P|*Arroz integral 1kg|72.00 ALIMENTOS NO PERECEDEROS|Arroz Shiva 1kg|27|
P|*Arroz integral 3kg|204.00 ALIMENTOS NO PERECEDEROS|Arroz integral 1kg |55|
P|Aceite Condesa de Soja 900 cc.|59.80 ALIMENTOS NO PERECEDEROS|Arroz integral 3kg|155|
P|Aceite Uruguay de Girasol 900 cc.|81.70 ALIMENTOS NO PERECEDEROS|Aceite Condesa de Soja 900 cc.|84|
P|Aceite Optimo canola 900 cc.|75.00 ALIMENTOS NO PERECEDEROS|Aceite Uruguay de Girasol 900 cc.|108|
P|Vinagre Uruguay 900ml|61.60 ALIMENTOS NO PERECEDEROS|Aceite Optimo canola 900 cc.|82.4|
P|*Salsa de soja La Posta 250 ml.|120.00 ALIMENTOS NO PERECEDEROS|*Aceite de oliva 500 ml. |195|
P|*Aceitunas verdes sin carozo en frasco La Posta 500 gr.|220.00 ALIMENTOS NO PERECEDEROS|Vinagre Uruguay 900ml|77.9|
P|*Aceitunas negras sin carozo en frasco La Posta 500 gr.|240.00 ALIMENTOS NO PERECEDEROS|Aceite de Oliva Cuatro Piedras 3 lt|1090|
P|Lata de atún Golden Fish desmenuzado al aceite 170g|33.35 ALIMENTOS NO PERECEDEROS|*Salsa de soja La Posta 250 ml. |95|
P|Lata de arvejas Campero 300g|18.28 ALIMENTOS NO PERECEDEROS|*Aceitunas verdes sin carozo en frasco La Posta 500 gr. |180|La Posta
P|Lata de choclo Cosecha 300g|32.29 ALIMENTOS NO PERECEDEROS|*Aceitunas negras sin carozo en frasco frasco La Posta 500 gr. |180|La Posta
P|Lata de jardinera Cosecha|30.34 ALIMENTOS NO PERECEDEROS|Lata atún Golden Fish desmenuzado al aceite 170g|31.1|
P|Lata de porotos negros Cosecha|37.06 ALIMENTOS NO PERECEDEROS|Lata de arvejas Campero 300g|18.3|
P|Lata de porotos de frutilla Cosecha|37.06 ALIMENTOS NO PERECEDEROS|Lata de choclo Cosecha 300g|22|
P|Lata de duraznos en almíbar Campero|73.07 ALIMENTOS NO PERECEDEROS|Lata de jardinera Cosecha|25|
P|Mayonesa Uruguay 500g|81.40 ALIMENTOS NO PERECEDEROS|Lata de porotos negros Cosecha|31.1|
P|Azúcar Bella Unión 1kg|44.90 ALIMENTOS NO PERECEDEROS|Lata de porotos de frutilla Cosecha|31.1|
P|Azúcar Mascabo 500g|102.00 ALIMENTOS NO PERECEDEROS|Lata de duraznos en almíbar Campero |66.6|
P|Azúcar impalpable Hornex 200 gr|39.00 ALIMENTOS NO PERECEDEROS|Mayonesa Uruguay 500g|77.9|
P|Almidón de maíz Hornex 1Kg|89.00 ALIMENTOS NO PERECEDEROS|Azúcar Azucarlito 25kg|1007.5|
P|Almidón de maíz Ilu wayra 1Kg|75.00 ALIMENTOS NO PERECEDEROS|Azúcar Bella Unión 1kg|39.8|
P|Polvo de Hornear Hornex 100 g + 20 g|38.00 ALIMENTOS NO PERECEDEROS|Azúcar impalpable Hornex 200 gr|31|
P|*Esencia de vainilla La Posta 100ml|100.00 ALIMENTOS NO PERECEDEROS|Azúcar impalpable Hornex 1kg|104|
P|*Bicarbonato de sodio La Posta 250 gr|60.00 ALIMENTOS NO PERECEDEROS|Almidón de maíz Hornex 1Kg|64.5|
P|Grasa Uruguay 400grs|45.80 ALIMENTOS NO PERECEDEROS|Polvo de Hornear Hornex 1 kg|122|
P|Levadura seca Hornex 125g|120.00 ALIMENTOS NO PERECEDEROS|Polvo de Hornear Hornex 100 g + 20 g|27.9|
P|Café Sorocabana glaseado p/máquina 500 grs|396.00 ALIMENTOS NO PERECEDEROS|*Esencia de vainilla La Posta 100ml|80|
P|Café Sorocabana natural p/máquina 500 grs|557.00 ALIMENTOS NO PERECEDEROS|Grasa Uruguay 400grs|50|
P|Café soluble Saint bollón 170 gr|232.50 ALIMENTOS NO PERECEDEROS|Levadura seca Hornex 125g|85.5|
P|Té Negro en hebras 90gr hornimans|41.12 ALIMENTOS NO PERECEDEROS|Café Sorocabana glaseado p/máquina 500 grs|232.8|
P|Galletas de arroz SIN SAL Natural Rice 120 gr (Sin TACC)|39.00 ALIMENTOS NO PERECEDEROS|Café Sorocabana natural p/máquina 500 grs |315.8|
P|Galletas de arroz comunes Natural Rice 120 gr (Sin TACC)|39.00 ALIMENTOS NO PERECEDEROS|Café Saint 170 gr Instantaneo|185.3|
P|Leche en polvo entera 250 g|105.00 ALIMENTOS NO PERECEDEROS|Té Negro en hebras 90gr hornimans|30.7|
P|Leche en polvo entera 1kg|315.00 ALIMENTOS NO PERECEDEROS|Galletas de arroz comunes Natural Rice 120 gr|34|
P|* Coco rallado 200gr|72.00 ALIMENTOS NO PERECEDEROS|* Leche en polvo 250grs|65|
P|* Coco rallado 1kg|246.00 ALIMENTOS NO PERECEDEROS|* Leche en polvo 1kg|220|
P|Cocoa Hornex 200gr|55.00 ALIMENTOS NO PERECEDEROS|* Coco rallado 200gr|70|
P|Postre de chocolate Hornex 8 porciones|44.00 ALIMENTOS NO PERECEDEROS|* Coco rallado 1kg|275|
P|Postre LIGHT de vainilla Hornex 8 porciones (aprobado por ADU)|68.00 ALIMENTOS NO PERECEDEROS|Cocoa Hornex 200gr|43|
P|Flan de vainilla Hornex 8 porciones|44.00 ALIMENTOS NO PERECEDEROS|Postre de chocolate Hornex 8 porciones|36.5|
P|Gelatina de frutilla Hornex 8 porciones|44.00 ALIMENTOS NO PERECEDEROS|Prostre LIGHT de vainilla Hornex 8 porciones (aprobado por ADU)|57|
P|Bizcochuelo de vainilla SIN GLUTEN 500gr Hornex|180.00 ALIMENTOS NO PERECEDEROS|Flan de vainilla Hornex 8 porciones|36.5|
P|Pizza SIN GLUTEN 320gr Hornex|170.00 ALIMENTOS NO PERECEDEROS|Gelatina de frutilla Hornex 8 porciones|36.5|
P|Pulpa de Tomate De Ley 1lt (S-G)|50.43 ALIMENTOS NO PERECEDEROS|Bizcochuelo de vainilla SIN GLUTEN 500gr Hornex|142|
P|Pure de papa instantaneo De Ley 125g|24.73 ALIMENTOS NO PERECEDEROS|Pizza SIN GLUTEN 320gr Hornex|137|
P|*Sal fina sin fluor Polenteados 500g|36.00 ALIMENTOS NO PERECEDEROS|Pulpa de Tomate De Ley 1lt|43.5|
P|*Sal gruesa sin fluor Polenteados 500g|36.00 ALIMENTOS NO PERECEDEROS|Pure de papa instantaneo De Ley 125g|21.3|
P|*Sal Rosa 250gr |80.00 ALIMENTOS NO PERECEDEROS|*Sal fina sin fluor Polenteados 500g|27|
P|*Salsa de tomate casera (puro tomate) 1 lt - (S-G) - azucar agregada: 1g/L - sal agregada: 0,25g/L|85.00 ALIMENTOS NO PERECEDEROS|*Sal gruesa sin fluor Polenteados 500g|27|
P|*Barras de cereales (maní, sésamo, lino, girasol, avena, copos de arroz, copos de máiz, azúcar rubia y miel) - Pack x2|80.00 ALIMENTOS NO PERECEDEROS|*Salsa de tomate casera (puro tomate) 1 lt|65|
P|*Barras de cereales bañadas en chocolate (maní, sésamo, lino, girasol, avena, copos de arroz, copos de máiz, azúcar rubia y miel) - Pack x2|90.00 ALIMENTOS NO PERECEDEROS|*Barras de cereales (maní, sésamo, lino, girasol, avena, copos de arroz, copos de máiz, azúcar rubia y miel) - Pack x2 |65|
P|*Granola artesanal 500 gr Ing: copos ,avena,miel, vainilla,coco rallado, chia,girasol,sésamo, lino,maní, almendras, castañas de caju, nueces, chips de chocolate.|250.00 ALIMENTOS NO PERECEDEROS|*Barras de cereales bañadas en chocolate (maní, sésamo, lino, girasol, avena, copos de arroz, copos de máiz, azúcar rubia y miel) - Pack x2|75|
P|*Granola simple (avena+ girasol+ pasaUva) 1kg|171.00 ALIMENTOS NO PERECEDEROS|*Granola artesanal 500 gr Ing: copos ,avena,miel, vainilla,coco rallado, chia,girasol,sésamo, lino,maní, almendras, castañas de caju, nueces, chips de chocolate.|210|
P|*Copos de maíz azucarados 500g|112.00 ALIMENTOS NO PERECEDEROS|*Granola simple (avena+ girasol+ pasaUva) 1kg|160|
P|*Copos de maíz naturales 500g|112.00 ALIMENTOS NO PERECEDEROS|*Copos de maíz azucarados 500g|89|
P|*Crema untable de maní 330 gr, envase de vidrio |225.00 ALIMENTOS NO PERECEDEROS|*Copos de maíz naturales 500g|89|
P|*Tableta artesanal chocolate semiamargo con frutos secos 100g |130.00 ALIMENTOS NO PERECEDEROS|*Crema untable de maní 250gr|89|
P|*Tableta artesanal chocolate semiamargo con pasas 100g |130.00 ALIMENTOS NO PERECEDEROS|*Galletas de coco, avena y maní 400gr|100|
P|*Tableta artesanal chocolate blanco con frutos secos 100g |130.00 ALIMENTOS NO PERECEDEROS|*Budin Panitep con cobertura de chocolate con almendras, maní, nueces y pasas 250grs|160|
P|*Tableta artesanal chocolate blanco con pasas 100g |130.00 ALIMENTOS NO PERECEDEROS|*Budin Panitep con cobertura de almíbar y coco con almendras, maní, nueces y pasas 250grs|140|
P|Alfajor de chocolate negro 80 g (S-G) |48.00 ALIMENTOS NO PERECEDEROS|*Budin Panitep de naranja 250grs|140|
P|Galletitas dulces de chispas de chocolate y avena 150 g (S-G)|116.00 ALIMENTOS NO PERECEDEROS|*Chia 1/4 kg |65|
P|Crackers saladas de sésamo girasol y chía 180 g (S-G)|116.00 ALIMENTOS NO PERECEDEROS|*Girasol 1/2 kg |85|
P|*Lino 1/4 kg|42.00 ALIMENTOS NO PERECEDEROS|*Lino 1/4 kg |30|
P|*Chía 1/4 kg|80.00 ALIMENTOS NO PERECEDEROS|*Sésamo 1/4 kg |55|
P|*Girasol 1/2 kg|140.00 ALIMENTOS NO PERECEDEROS|*Quinoa 1 kg |200|
P|*Sésamo 1/4 kg|65.00 ALIMENTOS NO PERECEDEROS|*Dátiles con carozo 500 gr|180|
P|*Quinoa 1 kg|210.00 ALIMENTOS NO PERECEDEROS|*Cacao en polvo 250 grs |100|
P|*Dátiles con carozo 500 gr|240.00 ALIMENTOS NO PERECEDEROS|*Almendra pelada (sin tostar) Polenteados 100g|80|Polenteados
P|*Cacao en polvo 250 grs (S-A)|125.00 ALIMENTOS NO PERECEDEROS|*Pasas de Uva Polenteados 250 gr|69|Polenteados
P|*Almendra pelada (sin tostar) Polenteados 100g|93.00 ALIMENTOS NO PERECEDEROS|*Nueces Polenteados 100g|79|Polenteados
P|*Pasas de Uva Polenteados 500 gr|149.00 ALIMENTOS NO PERECEDEROS|*Castañas tostadas SIN sal 100grs|76|Polenteados
P|*Nueces Polenteados 100g|89.00 ALIMENTOS NO PERECEDEROS|*Avena laminada instantánea Polenteados 500g|49|Polenteados
P|*Castañas tostadas SIN sal 100grs|81.00 ALIMENTOS NO PERECEDEROS|*Pan de molde Gigor Lacteado 550g|55|Gigor
P|*Avena laminada instantánea Polenteados 500g|69.00 ALIMENTOS NO PERECEDEROS|*Pan de molde Gigor Integral 550g|55|Gigor
P|*Pan de molde Gory Lacteado 550g|64.00 ALIMENTOS NO PERECEDEROS|*Galleta Malteada La Socialista 350g|56|La Socialista
P|*Pan de molde Integral Gory 550g|64.00 ALIMENTOS NO PERECEDEROS|*Galleta Malteada c/semillas (sésamo, chia, lino) La Socialista 380gr |74|La Socialista
P|*Galleta Malteada La Socialista 350g|84.00 ALIMENTOS NO PERECEDEROS|*Galleta Cara Sucia La Socialista 350g|63|La Socialista
P|*Galleta Malteada c/semillas (sésamo, chia, lino) La Socialista 380gr|103.00 ALIMENTOS NO PERECEDEROS|*Grisines La Socialista 350g|67|La Socialista
P|*Galleta Cara Sucia La Socialista 350g|92.00 ALIMENTOS NO PERECEDEROS|*Mezcla para panqueques La Socialista 2 x 250g|56|La Socialista
P|*Grisines La Socialista 350g|96.00 ALIMENTOS NO PERECEDEROS|*Mezcla para salsa blanca La Socialista 2 x 50g|46|La Socialista
P|*Maní pelado frito y salado Polenteados 500g|103.00 ALIMENTOS NO PERECEDEROS|*Maní pelado frito y salado Polenteados 500g|89|Polenteados
P|*Maní pelado sin sal Polenteados 500g|103.00 ALIMENTOS NO PERECEDEROS|*Maní pelado sin sal Polenteados 500g|89|Polenteados
P|*Garbanzo Polenteados 1kg|109.00 ALIMENTOS NO PERECEDEROS|*Garbanzo Polenteados 1kg|79|Polenteados
P|* Maiz para pop 500grs|54.00 ALIMENTOS NO PERECEDEROS|*Lentejas Polenteados 1kg|96|Polenteados
P|*Lentejas Polenteados 1kg|125.00 ALIMENTOS NO PERECEDEROS|*Porotos negros Polenteados 1kg|86|Polenteados
P|*Porotos negros Polenteados 1kg|125.00 ALIMENTOS NO PERECEDEROS|*Porotos de manteca Polenteados 1kg |105|Polenteados
P|*Porotos de manteca Polenteados 1kg|149.00 ALIMENTOS NO PERECEDEROS|*Proteína de SOJA texturizada 1kg |125|
P|*Porotos mung 1kg |150.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Tallarines frescos de yema Pastas Colon 1kg|155|Pastas Colón
P|*Arvejas 1kg |120.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Tallarines frescos de espinaca Pastas Colon 1kg|165|Pastas Colón
P|*Proteína de SOJA texturizada 1kg|139.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Tallarines frescos de morrón Pastas Colon 1kg|165|Pastas Colón
P|*Semillas Zapallo 100g.|60.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Salsa pomarola 300gr ex trabajadores de La Spezia|90|
CONDIMENTOS, PERECEDEROS Y BEBIDAS|| CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Fetuccine integral de zanahoria, apto veganos 1kg|190|
P|*Tallarines frescos de yema Pastas Colon 1kg|175.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Romanitos rellenos jamón y queso ex trabajadores de La Spezia 1kg|490|
P|*Tallarines frescos de espinaca Pastas Colon 1kg|185.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Romanitos vegetarianos ex trabajadores de La Spezia 1kg|490|
P|*Tallarines frescos de morrón Pastas Colon 1kg|190.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Sorrentinos jamón y queso 1Kg ex trabajadores de La Spezia|450|
P|*Prepizza 28cm ex trabajadores de La Spezia|90.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Sorrentinos Ricota y Nuez 1kg ex Trabajadores de La Spezia|450|
P|*Milanesas de carne 1kg|395.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Raviolón vegetariano 1Kg ex trabajadores de La Spezia|450|
P|*Milanesas de pollo 1kg|370.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Raviolón Caprese 1kg ex Trabajadores de La Spezia|450|
P|*Empanada de pollo x 6|265.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Prepizza de 28cm ex trabajadores de La Spezia|85|
P|*Empanada de carne x 6|265.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Milanesas de carne empanadas 1kg|365|
P|*Pan rallado 1kg|75.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Milanesas de pollo empanadas 1kg|365|
P|*Pan rallado saborizado 1Kg|95.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Milanesas de seitan x6 |300|
P|*Pimienta blanca 30g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Hamburguesas parrilleras de soja no transgénica, sal, harina de avena y adobo sin picante x6|300|
P|*Orégano 25g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Jamón vegano (gluten de trigo, salsa de tomate, sabor ahumado, sal) horma 250 g|200|
P|*Pimentón 30g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Hummus 200cc |140|
P|*Adobo 30g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pate de zanahoria 200cc |140|
P|*Ajo y Perejil 30g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pan rallado 1kg|55|
P|*Clavo de olor 15g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pan rallado saborizado 1Kg|75|
P|*Tomillo 25g|20.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pimienta blanca 30g|20|
P|*PACK "A" Curry / Nuez moscada / Ajo en polvo / Condimento verde / Comino|100.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Orégano 25g|20|
P|*PACK "B" Pimienta Negra polvo / Sal de ajo / Aji molido / Canela en polvo / Condimento para arroz|100.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pimentón 30g|20|
P|*PACK "C" Cebolla en polvo / Pimienta blanca en grano / Pimienta negra en grano / Ajo en escamas / Especias surtidas|100.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Adobo 30g|20|
P|*Pimentón 250grs|110.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Ajo y Perejil 30g|20|
P|*Orégano 250g|115.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*PACK "A" Curry, nuez moscada, ajo polvo, condimento verde, comino|80|
P|*Nuez moscada entera 2 unidades|28.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*PACK "B" Pimienta Negra polvo / sal de ajo, aji molido, canela polvo y chimichurry|80|
P|*Canela en rama 10g|25.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*PACK "C" Cebolla en escamas, tomillo, clavo de olor, pimienta blanca en grano, ajo en escamas|80|
P|*Cúrcuma 20g|25.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pimentón 250grs |75|
P|*Pack "Mix sabores"- Paprika, Albahaca, Mostaza en polvo.|65.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Orégano 250g|85|
P|*Pack "Pal mate"- Boldo, Cedron, Marcela.|120.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Nuez moscada entera 2 unidades |21|
P|*Pack "Relax"- Tilo, Malva, Té rojo.|120.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Canela en rama 10g|21|
P|*Pack "Power" - Ginseng, Carqueja, Ginkgo biloba.|120.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Cúrcuma 20g |21|
P|Atado de perejil|50.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pack "Sabores exoticos" - Paprika, fenogreco y cardamomo|57|
P|Mix de hierbas (ciboullete, tomillo, laurel y pasto lim[on)|50.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|'''''''*Pack "Pa''''''''l mate" - Manzanilla, cedrón y carqueja'''''''|105|
P|Atado de romero|50.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Pack "Medicinas de monte" - Tilo, malva y marcela|105|
P|Fernet 760 ml|405.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Pack "Té helado light" - Té rojo, stevia y lemon grass|105|
P|Vino Santero Marselán 1 lt.|235.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Vino Santero Marselán 1 lt.|198|
P|Frizzante de Maracuyá|246.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Vino Tannat-Cabernet Paso del Roble 1 lt.|90|
P|Frizzante de Frutos del bosque|246.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Vino Rosado dulce Paso del Roble 1 lt.|90|
P|*Cerveza artesanal APA Press 1L|170.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Frizzante de Maracuyá|205|
P|* Cerveza artesanal IPA Press 1L |180.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Frizzante de Frutos del bosque|205|
P|*Cerveza artesanal Negra Press 1L|180.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Cerveza artesanal Sudaka Blonde 500cc|100|
P|* Cerveza artesanal Rubia Lager 1L |160.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Cerveza artesanal Sudaka Scottish 500cc|100|
P|Jugo en polvo "Juguito" sabores surtidos|8.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Cerveza artesanal Punto Rojo Red Ipa 500cc|100|
P|Jugo Big C 200ml sabores surtidos|16.84 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Cerveza artesanal Punto Rojo Negra 500cc|100|
P|*Refresco U Naranja 2lt|94.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Cerveza artesanal APA Guillotina 1L|180|
P|*Refresco U Mandarina 2lt|94.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Fernet artesanal 780 ml|370|
P|*Refresco U Pomelo 2lt|94.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Jugo en polvo "Juguito" sabores surtidos|8.06|
P|*Refresco U Limonada 2lt|94.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Jugo Big C 200ml sabores surtidos|13.14|
P|*Agua 6 lts|98.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Refresco U Naranja 2lt|82|
P|*Queso Muzzarella 1/2 kg Unidad Cooperaria|172.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Refresco U Mandarina 2lt|82|
P|*Queso Magro s/sal 1/2 kg Unidad Cooperaria|191.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Refresco U Pomelo 2lt|82|
P|*Queso Magro c/sal 1/2 kg Unidad Cooperaria|191.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Refresco U Limonada 2lt|82|
P|*Queso Danbo 1/2 kg Unidad Cooperaria|188.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Agua 6 lts|84|
P|*Queso Sbrinz 1/2 kg Unidad Cooperaria|287.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso Muzzarella 1/2 kg Unidad Cooperaria|130|
P|*Queso Colonia 1/2 Kg Unidad Cooperaria|197.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso Magro s/sal 1/2 kg Unidad Cooperaria|147.5|
P|*Queso parrillero 350g Unidad Cooperaria|177.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso Magro c/sal 1/2 kg Uniddad Cooperaria|147.5|
P|*Queso semiduro 500grs (envasado al vacío) Productores Ismael Cortinas|258.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso Danbo 1/2 kg Unidad Cooperaria|137.5|
P|*Queso cuartirolo horma 1kg envasado al vacío|375.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso Sbrinz 1/2 kg Unidad Cooperaria|170|
P|*Queso rallado 200grs|155.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso Colonia 1/2 Kg Unidad Cooperaria|160|
P|*Dulce de Leche 1 Kg Unidad Cooperaria|269.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso parrillero 350g Unidad Cooperaria|165|
P|*Morrones en vinagre 330 gr|230.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso de mandioca en horma 400 gr|200|
P|*Berenjenas en vinagre 330 gr|230.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso saborizado con ciboulette 600grs (envasado al vacío)|230|
P|*Mermelada de morrones 250 gr|230.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso saborizado con albahaca 600grs (envasado al vacío)|230|
P|*Mermelada de frutilla, 450 grs.|145.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso saborizado con orégano 500grs (envasado al vacío)|250|
P|*Mermelada de durazno, 450 grs.|133.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso semiduro 500grs (envasado al vacío) Productores Ismael Cortinas|195|
P|*Mermelada de ciruela, 450 grs.|133.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso cuartirolo horma 1kg envasado al vacío|270|
P|*Mermelada de higo, 450 grs.|133.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso rallado 100grs|63|
P|*Mermelada de zapallo, 450 grs.|133.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Queso rallado 200grs|120|
P|*Mermelada de naranja, 450 grs.|134.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Dulce de Leche 1 Kg Unidad Cooperaria|165|
P|*Mermelada de arándanos, 450 grs.|159.50 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Dulce de leche de coco 360 gr|300|
P|*Dulce de membrillo, 900grs|123.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Morrones en vinagre 330 gr|140|
P|*Dulce de batata con chocolate 1kg|154.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Berenjenas en vinagre 330 gr|140|
P|*Dulce de zapallo 1kg|139.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de morrones 250 gr|140|
P|*Dulce de higo 1kg|134.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de frutilla, 450 grs. |123|
P|*Miel artesanal 500g|130.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de durazno, 450 grs.|110|
P|*Miel artesanal 1 kg|240.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de ciruela, 450 grs. |115|
P|*Canasta de frutas y verduras "34 Sur Productos Orgánicos"|630.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de higo, 450 grs. |110|
ARTÍCULOS PERSONALES Y DE LIMPIEZA|| CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de zapallo, 450 grs.|110|
P|Preservativos Prime ultrafinos x3|89.70 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de naranja, 450 grs. |115|
P|Preservativos Kamasutra x3|34.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Mermelada de arándanos, 450 grs. |145|
P|Tabaco Cerrito|125.67 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Dulce de membrillo, 900grs |105|
P|Hojilla JOB x30|28.36 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Dulce de batata con chocolate 1kg|115|
P|Shampoo Suave 930ml|123.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Dulce de zapallo 1kg|115|
P|Acondicionador Suave 930ml|123.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|'''''''*Dulce de higo 1kg'''|115|
P|Jabón de tocador IO, 80gs|16.60 CONDIMENTOS, PERECEDEROS Y BEBIDAS|Miel artesanal 1 kg|210|
P|Cepillo dental kolynos máster. |28.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Canasta de frutas y verduras "34 Sur Productos Orgánicos"|630|
P|Pasta Dental kolynos 180 grs. |49.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Romero 25grs, El Ombú PTIc|45|
P|Pastillas para mosquitos Fuyi x 12|98.30 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Apio 25grs, El Ombú PTIc|45|
P|*Pack x3 jabones glicerina vegetal Natura|380.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Menta 25grs, El Ombú PTIc|45|
P|*Shampoo artesanal pelo seco 250ml Natura|250.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Melissa 25grs, El Ombú Ptic|45|
P|*Desodorante ecológico apto veganos Natura|190.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Albahaca limón, El Ombú Ptic|45|
P|*Shampoo artesanal pelo graso 250ml Natura|250.00 CONDIMENTOS, PERECEDEROS Y BEBIDAS|*Llanten 25grs, El Ombú PTIc|45|
P|*Barrita quita manchas 75 gr Natura|100.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Preservativos Prudence clasico x3|59.09|
P|*Aromatizador ambiental fragancia floral de varilla Natura, 100ml |345.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Tabaco Cerrito|99|
P|*Aromatizador ambiental fragancia cítrica de varilla Natura, 100ml |345.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Hojilla JOB x30 |22|
P|*Talco pédico Natura, 200gr|235.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Shampoo Ainé Cuidado Total 500cc|230|
P|*Repelente Natura, 125ml|245.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Shampoo Ainé Brillo Extremo 500cc|230|
P|*Shampoo pediculosis, envase 250 ml|320.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Acondicionador Ainé Cuidado Total 500cc |230|
P|*Jabón en polvo Bonsai 800g|95.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Acondicionador Ainé Brillo Extremo 500cc |230|
P|*Jabón en polvo Bonsai 5kg|550.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Acondicionador Ainé Dos Minutos 500cc |230|
P|*Suavizante Bonsai 1lt|80.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Shampoo Suave 930ml |144.09|
P|*Jabon liquido para lavarropas 900 cc Bonsai|85.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Acondicionador Suave 930ml |144.09|
P|*Jabon liquido para lavarropas 3 lts Bonsai|270.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Jabón de tocador IO, 80gs |12|
P|*Jabon liquido de Manos 500 cc Bonsai|60.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Cepillo dental Introdento (medio)|29.74|
P|*Perfumador de telas 250 cc Bonsai|120.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pasta Dental Introdento menta 102 grs. |36.72|
P|*Limpiador desengarsante para cocinas y baños aroma cítrico 1 litro (no es para cañerías)|120.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Loción repelente de mosquitos, 200 ml|210|
P|*Limpiador cremoso 650 cc *|120.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Pack x3 jabones glicerina vegetal Natura|330|
P|*Entrebichitos - MEN 2lts. (graseras, pozos, cañerías, plantas)|180.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Desodorante ecológico apto veganos Natura|160|
P|*Entrebichitos - MEN Limpieza 1lt (suelos, mesadas, paredes, combate hongos, bacterias y virus)|100.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Pasta Dental Libre de Flúor Natura|165|
P|*Entrebichitos - Pastilla grasera|80.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Shampoo artesanal pelo graso 250ml Natura|235|
P|*Hipoclorito El Resistente 1800cc|76.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Shampoo artesanal pelo seco 250ml Natura|230|
P|*Limpiador perfumado El Resistente (perfumol) 1800cc|76.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Shampoo sólido cabello seco y normal 50gr |340|
P|*Detergente El Resistente 500ml|51.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Protector solar factor 65, 200 ml. NUEVO!!|550|
P|*KIT El Resistente (Hip./Perf./Det.)|188.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Shampoo sólido cabello graso 50gr |355|
P|Jabon en barra Primor x1|29.36 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Acondicionador sólido 50gr |350|
P|Rejilla de cocina 40 x 27.5 Tacuabé (ex Paylana) cm|36.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Cepillo dental de bambú 97% biodegradable (niños y adultos)|165|
P|Trapo de piso 53 x 53 Tacuabé (ex Paylana)|36.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Talco pédico 200gr |205|
P|Esponja de cocina|26.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Bálsamo labial Herencias de aquelarre (protege y repara) |180|
P|Esponja de acero inoxidable|29.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Detergente ecológico grupo Flores Silvestres 500 grs|90|
P|Repasador de algodón 43 x 65 cm|53.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Jabón en polvo Bonsai 800g|70|Bonsai
P|Franela 34 x 34|37.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Jabón en polvo Bonsai 5kg|350|Bonsai
P|Escoba|116.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Suavizante Bonsai 1lt|70|Bonsai
P|Pala con mango|99.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Jabon liquido para lavarropas 900 cc Bonsai|80|Bonsai
P|Balde 9 Lts|109.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Jabon liquido para lavarropas 3 lts Bonsai|220|Bonsai
P|Bolsa de residuos 50x55 30 unidades|63.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Jabon liquido de Manos 500 cc Bonsai|50|Bonsai
P|Lampazo|119.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Perfumador de telas 250 cc Bonsai|100|Bonsai
P|Rollitos de aluminio Griselda x12|38.30 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Limpiador desengarsante para cocinas y baños aroma cítrico 1 litro (no es para cañerías)|90|Bonsai
P|rollitos de aluminio jaspe x6|30.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Entrebichitos - MEN 2lts. (graseras, pozos, cañerías, plantas)|180|Entrebichitos
P|*Vela de apagón|8.40 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Entrebichitos - MEN Limpieza 1lt (suelos, mesadas, paredes, combate hongos, bacterias y virus)|100|Entrebichitos
P|*Vela de citronela 1 mecha|132.80 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Entrebichitos - Pastilla grasera|80|Entrebichitos
P|Toallita de bebé BabySec ultra 50un|83.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Hipoclorito El Resistente 1800cc|66|El Resistente
P|Papel Higienico: Higienol Texturado x4|42.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Limpiador perfumado El Resistente (perfumol) 1800cc|66|El Resistente
P|Papel de Cocina Sussex extra x 2 -120 paños-|72.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Detergente El Resistente 500ml|43|El Resistente
P|Pañales Babysec ULTRA (Celeste) XXG 24 unidades |448.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*KIT El Resistente (Hip./Perf./Det.)|161|El Resistente
P|Pañales Babysec ULTRA (Celeste) XG 24 unidades|448.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Jabon bulldog packx2|66.24|
P|Pañales Babysec ULTRA (Celeste) G 30 unidades|448.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Rejilla de cocina 40 x 27.5 Tacuabé (ex Paylana) cm|22|
P|Pañales Babysec ULTRA (Celeste) M 36 unidades|448.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Trapo de piso 55 x 55 Tacuabé (ex Paylana)|35|
P|Pañales Babysec ULTRA (Celeste) P 36 unidades|448.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Esponja de cocina|22|
P|Pañales Babysec PREMIUM (Violeta) XXG 48 unidades|790.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Esponja de acero inoxidable|23|
P|Pañales Babysec PREMIUM (Violeta) XG 48 unidades|790.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Repasador de algodón 43 x 65 cm|39|
P|Pañales Babysec PREMIUM (Violeta) G 60 unidades|790.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Franela 34 x 34|30|
P|Pañales Babysec PREMIUM (Violeta) M 68 unidades|790.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Escoba|97|
P|Pañales para Adultes INCOPROTECT TALLE M|579.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pala con mango|89|
P|Pañales para Adultes INCOPROTECT TALLE G |617.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Balde 9 Lts |99|
P|Pañales para Adultes INCOPROTECT TALLE EXTRA G |743.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Bolsa de residuos 50x55 30 unidades |49|
TEXTIL|| ARTÍCULOS PERSONALES Y DE LIMPIEZA|Lampazo |99|
PTC|*Calza licra de algodon talle S|900.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Guantes de latex talle M|80.1|
PTC|*Calza licra de algodon talle M|900.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Rollitos de alumnio x6 |27.3|
PTC|*Calza licra de algodon talle L|900.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Vela de apagón|8.37|
PTC|*Calza licra de algodon talle XL|900.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|*Vela de citronela 1 mecha|122.2|
PTC|*Biker licra de algodón - Talle S |650.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pastillas para mosquitos Sapolio 12 unidades |52.61|
PTC|*Biker licra de algodón - Talle M |650.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pañales Babysec ULTRA XXG 24 unidades|312|
PTC|*Biker licra de algodón - Talle L |650.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pañales Babysec ULTRA XG 24 unidades|312|
PTC|*Biker licra de algodón - Talle XL |650.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pañales Babysec ULTRA G 30 unidades|312|
PTC|*Biker licra de algodón - Talle 0 |350.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pañales Babysec ULTRA M 36 unidades|312|
PTC|*Biker licra de algodón - Talle 2 |350.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Pañales Babysec ULTRA P 36 unidades|312|
PTC|*Biker licra de algodón - Talle 4 |350.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Toallita de bebé BabySec ultra 50un|67|
PTC|*Biker licra de algodón - Talle 6 |400.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Papel Higienico: Higienol Texturado x4|47|
PTC|*Biker licra de algodón - Talle 8 |400.00 ARTÍCULOS PERSONALES Y DE LIMPIEZA|Papel de Cocina Sussex extra x 2 -120 paños-|57|
PTC|*Biker licra de algodón - Talle 10 |400.00 TEXTIL|*Calza licra de algodon talle S|750|
PTC|*Biker licra de algodón - Talle 12 |500.00 TEXTIL|*Calza licra de algodon talle M|750|
PTC|*Biker licra de algodón - Talle 14 |500.00 TEXTIL|*Calza licra de algodon talle L|750|
PTC|*Biker licra de algodón - Talle 16 |500.00 TEXTIL|*Calza licra de algodon talle XL|750|
P|*Conjunto primera muda 100% algodón (pack de pelele, bata y gorrito en bolsa de lienzo) color a elección sujeto a disponibilidad de tela|600.00 TEXTIL|*Calza licra de algodon talle 0|300|
PTC|*Calza licra de algodon talle 0|350.00 TEXTIL|*Calza licra de algodon talle 2 |300|
PTC|*Calza licra de algodon talle 2|350.00 TEXTIL|*Calza licra de algodon talle 4|300|
PTC|*Calza licra de algodon talle 4|350.00 TEXTIL|*Calza licra de algodon talle 6 |300|
PTC|*Calza licra de algodon talle 6|450.00 TEXTIL|*Calza licra de algodon talle 8 |300|
PTC|*Calza licra de algodon talle 8|450.00 TEXTIL|*Calza licra de algodon talle 10|300|
PTC|*Calza licra de algodon talle 10|450.00 TEXTIL|*Calza licra de algodon talle 12|300|
PTC|*Calza licra de algodon talle 12|550.00 TEXTIL|*Calza licra de algodon talle 14 |350|
PTC|*Calza licra de algodon talle 14|550.00 TEXTIL|*Calza licra de algodon talle 16 |350|
PTC|*Calza licra de algodon talle 16|550.00 TEXTIL|*Campera deportiva - Talle 0|350|
PTC|*Campera deportiva - Talle 0|400.00 TEXTIL|*Campera deportiva - Talle 2|350|
PTC|*Campera deportiva - Talle 2|400.00 TEXTIL|*Campera deportiva - Talle 4|350|
PTC|*Campera deportiva - Talle 4|400.00 TEXTIL|*Campera deportiva - Talle 6|400|
PTC|*Campera deportiva - Talle 6|450.00 TEXTIL|*Campera deportiva - Talle 8|400|
PTC|*Campera deportiva - Talle 8|450.00 TEXTIL|*Campera deportiva - Talle 10|400|
PTC|*Campera deportiva - Talle 10|450.00 TEXTIL|*Campera deportiva - Talle 12|450|
PTC|*Campera deportiva - Talle 12|500.00 TEXTIL|*Campera deportiva - Talle 14|450|
PTC|*Campera deportiva - Talle 14|500.00 TEXTIL|*Campera deportiva - Talle 16|450|
PTC|*Campera deportiva - Talle 16|500.00 TEXTIL|*Pantalón deportivo - Talle 0|250|
PTC|*Pantalón deportivo - Talle 0|300.00 TEXTIL|*Pantalón deportivo - Talle 2|250|
PTC|*Pantalón deportivo - Talle 2|300.00 TEXTIL|*Pantalón deportivo - Talle 4|250|
PTC|*Pantalón deportivo - Talle 4|300.00 TEXTIL|*Pantalón deportivo - Talle 6|300|
PTC|*Pantalón deportivo - Talle 6|350.00 TEXTIL|*Pantalón deportivo - Talle 8|300|
PTC|*Pantalón deportivo - Talle 8|350.00 TEXTIL|*Pantalón deportivo - Talle 10|300|
PTC|*Pantalón deportivo - Talle 10|350.00 TEXTIL|*Pantalón deportivo - Talle 12|350|
PTC|*Pantalón deportivo - Talle 12|400.00 TEXTIL|*Pantalón deportivo - Talle 14|350|
PTC|*Pantalón deportivo - Talle 14|400.00 TEXTIL|*Pantalón deportivo - Talle 16|350|
PTC|*Pantalón deportivo - Talle 16|400.00 TEXTIL|*Babucha deportiva en algodón - Talle 4|500|
PTC|*Buzo deportivo de verano para adultes - Talle S |1000.00 TEXTIL|*Babucha deportiva en algodón - Talle 6|500|
PTC|*Buzo deportivo de verano para adultes - Talle M |1000.00 TEXTIL|*Babucha deportiva en algodón - Talle 8 |500|
PTC|*Buzo deportivo de verano para adultes - Talle L |1000.00 TEXTIL|*Babucha deportiva en algodón - Talle 10 |600|
PTC|*Buzo deportivo de verano para adultes - Talle XL |1000.00 TEXTIL|*Babucha deportiva en algodón - Talle 12 |600|
PTC|*Pantalón deportivo de verano para adultes - Talle S |900.00 TEXTIL|*Babucha deportiva en algodón - Talle 14 |600|
PTC|*Pantalón deportivo de verano para adultes - Talle M |900.00 TEXTIL|*Babucha deportiva en algodón - Talle 16 |600|
PTC|*Pantalón deportivo de verano para adultes - Talle L |900.00 TEXTIL|*Babucha deportiva en algodón - Talle 18 |600|
PTC|*Pantalón deportivo de verano para adultes - Talle XL |900.00 TEXTIL|*Pantalón deportivo liso de algodón - Talle S|850|
P|*Juego de sábanas de algodón 1 plaza|1300.00 TEXTIL|*Pantalón deportivo liso de algodón - Talle M|850|
P|*Juego de sábanas de algodón 2 plazas (para sommier)|1500.00 TEXTIL|*Pantalón deportivo liso de algodón - Talle L|850|
P|*Materas de Lona.|450.00 TEXTIL|*Pantalón deportivo liso de algodón - Talle XL |850|
P|*Sábana sola con elástico, 2 plazas (para sommier)|950.00 TEXTIL|*Canguro deportivo liso con capucha de algodón - TalleS |1000|
P|*Juego de toallón y toalla de algodón|900.00 TEXTIL|*Canguro deportivo liso con capucha de algodón - Talle M |1000|
P|*Toallón|750.00 TEXTIL|*Canguro deportivo liso con capucha de algodón - Talle L |1000|
P|*Toalla de mano|350.00 TEXTIL|*Canguro deportivo liso con capucha de algodón - Talle XL|1000|
P|*Turbante toalla|450.00 TEXTIL|*Túnica niñe con cinto en espalda y tajo detrás- talles 6 a 16|480|
P|*Juego de Sábanas de poliéster 1 plaza |850.00 TEXTIL|*Túnica niñe con martingala, festón y pinzas talles 6 a 16|480|
P|*Juego de sábanas de poliéster 2 plazas |1000.00 TEXTIL|*Pintor verde - talles 2 a 8|300|
MADRES Y FAMILIARES|| TEXTIL|*Pintor azul - talles 2 a 8|300|
PTC|Camiseta EDICION ESPECIAL - talles S al XXL|450.00 TEXTIL|*Pintor rojo - talles 2 a 8|300|
PTC|Canguros Madres y Familiares |950.00 TEXTIL|*Pintor amarillo - talles 2 a 8|300|
P|Pack 1: 1 pin redondo + 1 lapicera + 1 Pañuelo + Pegotines y marcalibros|150.00 TEXTIL|*Túnicas adulto - talles 1 a 5|1100|
P|Pack 2: 1 Pin redondo + 1 Lapicera + Pegotines y marcalibros|100.00 TEXTIL|*Moña escolar satinada|50|
P|Pack 3: 1 Llavero + 1 Lapicera|100.00 TEXTIL|*Juego de sábanas de algodón 1 plaza|1000|
P|Libro "Desaparecidos"|50.00 TEXTIL|*Juego de sábanas de algodón 2 plazas (para sommier) |1200|
ARTÍCULOS DE LA COORDINADORA POR PALESTINA|| TEXTIL|*Sábana sola sin elástico, 2 plazas (para sommier) |700|
B|Bono colaboración|20.00 TEXTIL|*Sábana sola con elástico, 2 plazas (para sommier) |700|
P|Remera talle S - Palestina|450.00 TEXTIL|*Juego de toallón y toalla de algodón |650|
P|Remera talle M - Palestina|450.00 TEXTIL|*Toallón|500|
P|Remera talle L - Palestina|450.00 TEXTIL|*Toalla de mano|250|
P|Remera talle XL - Palestina|450.00 TEXTIL|*Turbante toalla|350|
P|Bandera 60x90 cm - Coordinadora por Palestina|450.00 TEXTIL|*Tapaboca de tela|50|
P|Pin Coordinadora por Palestina|40.00 ARTÍCULOS DE MADRES Y FAMILIARES|Pañuelo Madres y Familiares de Detenidos Desaparecidos|50|Madres y familiares
P|Balconera "Paremos el genocidio"|150.00 ARTÍCULOS DE MADRES Y FAMILIARES|Balconera Madres y Familiares de Detenidos Desaparecidos|100|Madres y familiares
P|Banderita para el auto |100.00 ARTÍCULOS DE MADRES Y FAMILIARES|Pack 2 - 1 Pin redondo + Lapicera + Pegotines y Marcalibros|100|Madres y familiares
P|Kit 5 pegotines - Coordinadora por Palestina|80.00 PRODUCTOS DEL MPS|Pack de 5 pegotines del MPS (2 logos y 3 consignas, todo a color).|40|Madres y familiares
P|Remera talle S - Palestina - Puño - único color Negro *NUEVO|500.00 BONOS Y FINANCIAMIENTO SORORO|Campaña solidaria MPS - apoyo a ollas y merenderos|20|MPS
P|Remera talle M - Palestina - Puño - único color Negro *NUEVO|500.00 BONOS Y FINANCIAMIENTO SORORO|Financiamiento sororo para copa menstrual|20|
P|Remera talle L - Palestina - Puño - único color Negro *NUEVO|500.00 BONOS Y FINANCIAMIENTO SORORO|Galpón de corrales|20|
P|Remera talle XL - Palestina - Puño - único color Negro *NUEVO|500.00 PRODUCTOS DE GESTIÓN MENSTRUAL|Copa menstrual de silicona, ecológica |750|
PRODUCTOS ESPECIALES Y DE FIN DE AÑO|| PRODUCTOS DE GESTIÓN MENSTRUAL|Copa menstrual de silicona, ecológica (financiamiento sororo)|0|
P|*Cuaderno artesanal 80 hojas rayado|100.00 PRODUCTOS DE GESTIÓN MENSTRUAL|Vaso esterilizador para copa menstrual|290|
P|*Cuaderno artesanal 80 hojas liso|100.00 PRODUCTOS DE GESTIÓN MENSTRUAL|*Toallita de tela Nocturna "Chúlin"|170|Chúlin
P|*Cuaderno artesanal 200 hojas rayado|180.00 PRODUCTOS DE GESTIÓN MENSTRUAL|*Toallita de tela para Colaless "Chúlin"|170|Chúlin
P|*Cuaderno artesanal 200 hojas liso|180.00 PRODUCTOS DE GESTIÓN MENSTRUAL|*Toallitas de tela para Bombacha "Chúlin"|200|Chúlin
P|*Agenda 2025 NUEVO!|420.00 PRODUCTOS DE GESTIÓN MENSTRUAL|*Protector Diario de tela "Chúlin"|160|Chúlin
P|Gorro del MPS - Ciudad Vieja NUEVO!|150.00 PRODUCTOS DE GESTIÓN MENSTRUAL|*Pack 1: 2 protectores diarios + 2 toallitas para bombacha "Chúlin"|610|Chúlin
P|*Papas con sal 230 gr NUEVO!|154.00 PRODUCTOS DE GESTIÓN MENSTRUAL|*Pack 2: 3 protectores diarios "Chúlin" |410|Chúlin
P|*Papas sin sal 250 gr NUEVO!|154.00 PRODUCTOS DE GESTIÓN MENSTRUAL|Ladysoft Clasicas 8un|20.3|
P|Turrón Portezuelo Blando 70g NUEVO!|39.67 PRODUCTOS DE GESTIÓN MENSTRUAL|Tampones Medianos Ladysoft 10un|84.7|
P|Turrón Portezuelo Bañado 70g NUEVO!|39.67 PRODUCTOS DE GESTIÓN MENSTRUAL|Tampones Grandes Ladysoft 10un|84.7|
P |*Budín chocolate 300g NUEVO!|200.00
P|*Budín vainilla/cítrico 300g NUEVO!|200.00
P|*Budín marmolado 300g NUEVO!|200.00
P|Pan dulce artesanal con frutas y pasas 500gms NUEVO!|210.00
P|Libro para infancias "Encendiendo Memorias" NUEVO!|350.00
TRANSPORTE, BONOS Y FINANCIAMIENTO SORORO||
T|Por cada $ 500 de consumo, abonar $ 15 para transporte y gastos operativos, ej:$520 son $30|15.00
B|Campaña solidaria MPS - apoyo a ollas y merenderos|30.00
B|Financiamiento sororo para copa menstrual|20.00
B|Bono especial - Apoyo a la familia del compañero Alberto Colman|20.00
B|Bono especial - Apoyo al fletero Ramón|20.00
PRODUCTOS SUBSIDIADOS POR FINANCIAMIENTO SORORO||
P|*Toallita de tela Nocturna "Chúlin"|210.00
P|*Toallita de tela para Colaless "Chúlin"|168.00
P|*Toallitas de tela para Bombacha "Chúlin"|175.00
P|*Protector Diario de tela "Chúlin"|126.00
P|*Pack 1: 2 protectores diarios + 2 toallitas para bombacha "Chúlin"|560.00
P|*Pack 2: 3 protectores diarios "Chúlin"|364.00
P|Ladysoft Clasicas 8un|27.00
P|Tampones algodón orgánico - Talle mini- caja 18 un. |208.00
P|Tampones algodón orgánico -Talle medio- caja 18un. |208.00
P|Tampones algodón orgánico -Talle super-caja 15 un. |208.00

Can't render this file because it contains an unexpected character in line 19 and column 39.

135
resources/js/app.js vendored
View file

@ -3,12 +3,10 @@
* includes Vue and other libraries. It is a great starting point when * includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel. * building robust, powerful web applications using Vue and Laravel.
*/ */
import axios from 'axios';
import Vue from 'vue'; require('./bootstrap');
window.Vue = require('vue'); window.Vue = require('vue');
window.Event = new Vue();
window.axios = axios;
window.bulmaToast = require('bulma-toast');
/** /**
* The following block of code may be used to automatically register your * The following block of code may be used to automatically register your
@ -17,133 +15,18 @@ window.bulmaToast = require('bulma-toast');
* *
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component> * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/ */
import './components';
/** // const files = require.context('./', true, /\.vue$/i)
* Constants // files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
*/
Vue.prototype.$rootMiga = { Vue.component('example-component', require('./components/ExampleComponent.vue').default);
nombre: "Categorías",
href: "/productos"
}
/**
* Global methods
*/
Vue.prototype.$settearProducto = function(cantidad, id) {
Event.$emit("sync-subpedido", this.cant, this.producto.id)
}
Vue.prototype.$toast = function(mensaje, duration = 2000) {
return window.bulmaToast.toast({
message: mensaje,
duration: duration,
type: 'is-danger',
position: 'bottom-center',
});
}
Vue.prototype.$limpiarFloat = function(unFloat) {
return parseFloat(unFloat.replace(/,/g, ''))
}
Vue.prototype.$limpiarInt = function(unInt) {
return parseInt(unInt.replace(/,/g, ''))
}
/** /**
* Next, we will create a fresh Vue application instance and attach it to * Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application * the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs. * or customize the JavaScript scaffolding to fit your unique needs.
*/ */
const app = new Vue({ const app = new Vue({
el: '#root', el: '#app',
data() {
return {
gdc: null,
pedido: null,
devoluciones: null,
}
},
computed: {
productos: function() {
return this.pedido.productos
}
},
methods: {
cantidad(producto) {
let pedido = this.productos.some(p => p.id == producto.id)
return pedido ? this.productos.find(p => p.id == producto.id).pivot.cantidad : 0
},
notas(producto) {
let pedido = this.productos.some(p => p.id == producto.id);
return pedido ? this.productos.find(p => p.id == producto.id).pivot.notas : "";
},
settearDevoluciones() {
axios.get(`/api/grupos-de-compra/${this.gdc}/devoluciones`)
.then(response => {
this.devoluciones = response.data.devoluciones;
});
}
},
mounted() {
Event.$on('obtener-sesion', () => {
axios.get('/subpedidos/obtener_sesion')
.then(response => {
if (response.data.subpedido.id) {
this.gdc = response.data.gdc;
this.settearDevoluciones();
this.pedido = response.data.subpedido.id;
axios.get('/api/subpedidos/' + this.pedido)
.then(response => {
this.pedido = response.data.data;
Event.$emit("pedido-actualizado");
});
} else {
axios.get('/admin/obtener_sesion')
.then(response => {
this.gdc = response.data.gdc
});
}
})
})
Event.$on('sync-subpedido', (cantidad, id, notas) => {
if (this.pedido.aprobado) {
this.$toast('No se puede modificar un pedido ya aprobado', 2000);
return;
}
axios.post("/api/subpedidos/" + this.pedido.id + "/sync", {
cantidad: cantidad,
producto_id: id,
notas: notas,
}).then((response) => {
this.pedido = response.data.data
this.$toast('Pedido actualizado exitosamente')
Event.$emit("pedido-actualizado");
});
});
// Actualizar monto y notas de devoluciones
Event.$on('sync-devoluciones', (total, notas) => {
if (this.pedido.aprobado) {
this.$toast('No se puede modificar un pedido ya aprobado', 2000);
return;
}
axios.post("api/subpedidos/" + this.pedido.id + "/sync_devoluciones", {
total: total,
notas: notas,
}).then((response) => {
this.pedido = response.data.data;
this.$toast('Pedido actualizado');
Event.$emit("pedido-actualizado");
});
});
Event.$on('aprobacion-subpedido', (subpedidoId, aprobado) => {
axios.post("/api/admin/subpedidos/" + subpedidoId + "/aprobacion", {
aprobacion: aprobado
}).then((response) => {
Event.$emit('sync-aprobacion', response.data.data);
this.$toast('Pedido ' + (aprobado ? 'aprobado' : 'desaprobado') + ' exitosamente')
})
})
Event.$emit('obtener-sesion')
},
}); });

View file

@ -1,25 +0,0 @@
import Vue from 'vue';
const requireComponent = require.context('./components', true, /\.vue$/);
// Registro automático de componentes:
// e.g. components/foo/bar/UnComponente.vue
// se registra como 'foo-bar-un-componente'
requireComponent.keys().forEach(fileName => {
// Get the component config
const componentConfig = requireComponent(fileName);
// Get the PascalCase name of the component
const componentName = fileName
.replace(/^\.\/(.*)\.\w+$/, '$1') // Remove "./" from the beginning and the file extension from the end
.replace(/\//g, '-') // Replace directories with hyphens
.replace(/([a-z])([A-Z])/g, '$1-$2') // Insert hyphen between camelCase words
.toLowerCase() // Convert to lowercase
// Globally register the component
Vue.component(
componentName,
// Look for the component options on `.default`, which will
// exist if the component was exported with `export default`,
// otherwise fall back to module's root.
componentConfig.default || componentConfig
);
});

View file

@ -0,0 +1,23 @@
<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Example Component</div>
<div class="card-body">
I'm an example component.
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
}
}
</script>

View file

@ -1,83 +0,0 @@
<template>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<comunes-tabs-secciones :tabs="tabs" :tabInicial="tabActiva"></comunes-tabs-secciones>
<div class="block" id="pedidos-seccion"
:class="seccionActiva === 'pedidos-seccion' ? 'is-active' : 'is-hidden'">
<div class="block pb-6" id="pedidos-tabla-y-dropdown" v-if="hayPedidos">
<admin-dropdown-descargar
:gdc_id="gdc.id">
</admin-dropdown-descargar>
<admin-tabla-pedidos
:gdc="this.gdc"
></admin-tabla-pedidos>
</div>
<p class="has-text-centered" v-else>
Todavía no hay ningún pedido para administrar.
</p>
</div>
<div class="block pb-6" id="caracteristicas-seccion"
:class="seccionActiva === 'caracteristicas-seccion' ? 'is-active' : 'is-hidden'">
<admin-caracteristicas-opcionales>
</admin-caracteristicas-opcionales>
</div>
</div>
</template>
<script>
import CaracteristicasOpcionales from "./CaracteristicasOpcionales.vue";
import TabsSecciones from "../comunes/TabsSecciones.vue";
import DropdownDescargar from "./DropdownDescargar.vue";
import TablaPedidos from "./TablaPedidos.vue";
import TablaBonos from "./TablaBonos.vue";
import axios from "axios";
export default {
components: {
CaracteristicasOpcionales,
TabsSecciones,
DropdownDescargar,
TablaPedidos,
TablaBonos,
},
data() {
return {
gdc: undefined,
tabs: [{ id: "pedidos", nombre: "Pedidos" },
{ id: "caracteristicas", nombre: "Caracteristicas opcionales" }],
tabActiva: "pedidos",
seccionActiva: "pedidos-seccion",
}
},
computed: {
hayPedidos: function() {
return this.gdc && this.gdc.pedidos.length !== 0
},
hayAprobados: function() {
return this.gdc && this.gdc.pedidos.filter(p => p.aprobado).length > 0
}
},
methods: {
setSeccionActiva(tabId) {
this.tabActiva = tabId;
this.seccionActiva = tabId + "-seccion";
},
actualizar() {
axios.get('/api/grupos-de-compra/' + this.$root.gdc)
.then(response => {
this.gdc = response.data.data;
console.log(this.gdc);
})
}
},
async mounted() {
Event.$on('sync-aprobacion', (_) => {
this.actualizar();
});
await new Promise(r => setTimeout(r, 1000));
this.actualizar();
},
}
</script>
<style>
</style>

View file

@ -1,12 +0,0 @@
<template>
<div class="buttons is-right">
<a class="button is-danger is-light is-small" href="/admin">
<span class="icon">
<i class="fa fa-solid fa-user-check"></i>
</span>
<span>
Admin
</span>
</a>
</div>
</template>

View file

@ -1,42 +0,0 @@
<script>
import FilaCaracteristica from "./FilaCaracteristica.vue";
export default {
components: { FilaCaracteristica },
data() {
return {
caracteristicas: [
{
id: "devoluciones",
nombre: "Devoluciones",
habilitada: false
},
]
}
},
}
</script>
<template>
<div class="block">
<table class="table is-fullwidth is-striped is-bordered">
<thead>
<tr>
<th> Característica </th>
<th> Habilitada </th>
</tr>
</thead>
<tbody>
<admin-fila-caracteristica
v-for="(c,i) in caracteristicas"
:key="i"
:caracteristica="c">
</admin-fila-caracteristica>
</tbody>
</table>
</div>
</template>
<style scoped>
</style>

View file

@ -1,56 +0,0 @@
<template>
<div class="buttons is-right">
<div class="dropdown" :class="{'is-active': dropdownActivo}" @mouseleave="dropdownActivo = false">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu" :disabled="!hayAprobados" @click="dropdownActivo = !dropdownActivo">
<span class="icon is-small">
<i class="fas fa-download"></i>
</span>
<span>Descargar pedido</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a :href="'/admin/exportar-pedido-a-csv/' + gdc_id" class="dropdown-item has-background-primary">
Planilla para central (CSV)
</a>
<a :href="'/admin/exportar-planillas-a-pdf/' + gdc_id" class="dropdown-item">
Planillas para armado (PDF)
</a>
<a :href="'/admin/exportar-pedido-con-nucleos-a-csv/' + gdc_id" class="dropdown-item">
Planilla completa de la canasta (CSV)
</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
gdc_id: {
type: Number,
required: true
},
},
data() {
return {
dropdownActivo: false
}
},
computed: {
hayAprobados: function() {
return this.$parent.hayAprobados;
}
},
}
</script>
<style>
</style>

View file

@ -1,69 +0,0 @@
<script>
import axios from "axios";
export default {
props: {
caracteristica: Object
},
data() {
return {
gdc: undefined
}
},
watch: {
'$root.gdc' : {
handler(newValue) {
if (newValue) {
this.gdc = newValue;
this.obtenerValor();
}
}
},
},
methods: {
toggleActivacion() {
const id = this.caracteristica.id;
axios.post(`/api/grupos-de-compra/${this.gdc}/${id}`)
.then(response => {
this.caracteristica.habilitada = response.data[id];
this.$root[id] = response.data[id];
});
},
obtenerValor() {
const id = this.caracteristica.id;
axios.get(`/api/grupos-de-compra/${this.gdc}/${id}`)
.then(response => {
this.caracteristica.habilitada = response.data[id];
this.$root[id] = response.data[id];
});
},
},
mounted() {
if (this.$root.gdc) {
this.gdc = this.$root.gdc;
this.obtenerValor();
}
}
}
</script>
<template>
<tr>
<td>{{ caracteristica.nombre }}</td>
<td>
<div class="field">
<input type="checkbox" class="switch is-rounded is-success"
:id="'switch-'+caracteristica.id"
:checked="caracteristica.habilitada"
@change="toggleActivacion(caracteristica)">
<label :for="'switch-'+caracteristica.id">
<span class="is-hidden-mobile">{{ caracteristica.habilitada ? 'Habilitada' : 'Deshabilitada' }}</span>
</label>
</div>
</td>
</tr>
</template>
<style scoped>
</style>

View file

@ -1,36 +0,0 @@
<template>
<tr>
<td>{{ pedido.nombre }}</td>
<td v-if="$root.devoluciones" class="has-text-right" >{{ pedido.total_sin_devoluciones }}</td>
<td v-if="$root.devoluciones" class="has-text-right" ><abbr :title="pedido.devoluciones_notas">-{{ pedido.devoluciones_total }}</abbr></td>
<td class="has-text-right" >{{ $root.devoluciones ? pedido.total : pedido.total_sin_devoluciones }}</td>
<td>
<admin-switch-aprobacion
:pedido="pedido">
</admin-switch-aprobacion>
</td>
</tr>
</template>
<script>
import SwitchAprobacion from "./SwitchAprobacion.vue";
export default {
components: {
SwitchAprobacion
},
props: {
pedido: Object
},
mounted() {
Event.$on('sync-aprobacion', (unPedido) => {
if (this.pedido.id === unPedido.id) {
this.pedido = unPedido
}
})
}
}
</script>
<style scoped>
</style>

View file

@ -1,56 +0,0 @@
<template>
<div v-show="visible" class="block">
<div class="field">
<label class="label has-text-white">Contraseña de administración del barrio</label>
<div class="field has-addons">
<div class="control">
<input required class="input" :type="this.passwordType" name="password" placeholder="Contraseña de admin del barrio">
</div>
<div class="control">
<a class="button is-warning" @click="togglePassword">
{{ (passwordVisible ? 'Ocultar' : 'Mostrar') + ' contraseña'}}
</a>
</div>
</div>
<p class="help has-text-white">Si no la sabés, consultá a la comisión informática.</p>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-warning" value="Ingresar"/>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
gdc: null,
passwordVisible: false,
passwordType: "password",
}
},
mounted() {
Event.$on('gdc-seleccionado', (gdc) => {
this.gdc = gdc;
this.visible = true;
});
},
methods: {
togglePassword() {
if (this.passwordVisible) this.passwordType = "password";
else this.passwordType = "text"
this.passwordVisible = !this.passwordVisible
}
}
}
</script>
<style>
.help {
font-size: 1rem;
}
</style>

View file

@ -1,46 +0,0 @@
<template>
<div class="field">
<input type="checkbox" name="switchRoundedSuccess" class="switch is-rounded is-success"
:id="'switch'+this.pedido.id"
:checked="pedido.aprobado"
@change="toggleAprobacion">
<label :for="'switch'+this.pedido.id">
<span class="is-hidden-mobile">{{ mensaje }}</span>
</label>
</div>
</template>
<script>
export default {
props: {
pedido: Object
},
data() {
return {
aprobado: this.pedido.aprobado
}
},
computed: {
mensaje: function () {
return this.aprobado ? "Pagado" : "No pagado"
}
},
methods: {
toggleAprobacion() {
Event.$emit('aprobacion-subpedido', this.pedido.id, !this.aprobado);
this.aprobado = !this.aprobado
}
},
mounted() {
Event.$on('sync-aprobacion', (unPedido) => {
if (this.pedido.id === unPedido.id) {
this.pedido = unPedido
}
})
}
}
</script>
<style scoped>
</style>

View file

@ -1,97 +0,0 @@
<template>
<div class="block">
<div class="block" v-show="!hayBonos">
<p class="has-text-centered">
Todavía no hay bonos pedidos.
</p>
</div>
<div class="block" v-show="hayBonos">
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th><abbr title="Núcleo">Núcleo</abbr></th>
<td v-for="(bono,i) in bonos" :key="i" class="is-1">
{{ bono.nombre }}
</td>
<th><abbr title="Total a Pagar">Total $</abbr></th>
</tr>
</thead>
<tbody>
<tr v-for="(bp, i) in bonosPorPedido" :key="i">
<td> {{ bp.nucleo }} </td>
<td v-for="(bono,j) in bp.bonos" :key="j" class="has-text-right">
{{ bono.cantidad }}
</td>
<td class="has-text-right"> {{ bp.total }} </td>
</tr>
</tbody>
<tfoot>
<tr>
<th></th>
<th :colspan="bonos.length">Total bonos</th>
<th class="has-text-right">$ {{ totalBonos }}</th>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script>
export default {
props: {
pedidos: {
type: Array,
required: true
}
},
data() {
return {
bonos: []
}
},
computed: {
pedidosAprobados: function() {
return this.pedidos.filter(p => p.aprobado)
},
hayBonos: function() {
return this.pedidosAprobados.filter(p => p.subtotal_bonos != 0).length !== 0
},
bonosPorPedido: function() {
let bonosPorPedido = this.pedidosAprobados.map(p => p = {"nucleo":p.nombre, "bonos":p.productos.filter(x => x.bono), "total":p.subtotal_bonos});
bonosPorPedido.forEach(bp => {
bp.bonos = bp.bonos.map(b => b = {"bono":b.nombre, "cantidad":b.pivot.cantidad, "total":b.pivot.total, "id":b.id})
})
return bonosPorPedido.map(bp => this.completarBonos(bp));
},
totalBonos: function() {
let total = 0
this.bonosPorPedido.map(bp => total += parseInt(bp.total))
return total
},
},
methods: {
completarBonos(bonosPedido) {
this.bonos.map(b => {
if (!bonosPedido.bonos.map(x => x.id).includes(b.id))
bonosPedido.bonos.push({"bono":b.nombre, "cantidad":0, "total":0, "id":b.id})
})
bonosPedido.bonos = bonosPedido.bonos.sort((b1,b2) => b1.id - b2.id)
return bonosPedido
}
},
beforeMount() {
axios.get("../api/productos", {
params: {
categoria:'TRANSPORTE, BONOS Y FINANCIAMIENTO SORORO',
}
}).then(response => {
this.bonos = response.data.data;
});
},
}
</script>
<style>
</style>

View file

@ -1,69 +0,0 @@
<template>
<div>
<table class="table is-fullwidth is-striped is-bordered">
<thead>
<tr>
<th>Núcleo</th>
<th v-if="$root.devoluciones"><abbr title="Total sin tomar en cuenta las devoluciones">Total parcial $</abbr></th>
<th v-if="$root.devoluciones"><abbr title="Devoluciones correspondientes al núcleo">Devoluciones $</abbr></th>
<th><abbr title="Total a Pagar por el núleo">{{ $root.devoluciones ? 'Total real' : 'Total' }} $</abbr></th>
<th class="is-1"><abbr title="Pagado">Pagado</abbr></th>
</tr>
</thead>
<tbody>
<admin-fila-pedido
v-for="pedido in gdc.pedidos"
:pedido="pedido" :key="pedido.id">
</admin-fila-pedido>
</tbody>
</table>
<table class="table is-striped is-bordered">
<tr>
<th colspan="2" class="has-background-black has-text-white has-text-centered">TOTALES</th>
</tr>
<tr>
<th>Total a recaudar:</th>
<td class="has-text-right">$ {{ gdc.total_a_recaudar }}</td>
</tr>
<tr>
<th>Total bonos barriales:</th>
<td class="has-text-right">$ {{ gdc.total_barrial }}</td>
</tr>
<tr v-if="$root.devoluciones">
<th>Total devoluciones:</th>
<td class="has-text-right">- $ {{ gdc.total_devoluciones }}</td>
</tr>
<tr>
<th>Cantidad bonos de transporte:</th>
<td class="has-text-right">{{ gdc.cantidad_transporte }}</td>
</tr>
<tr>
<th>Total bonos de transporte:</th>
<td class="has-text-right">$ {{ gdc.total_transporte }}</td>
</tr>
<tr>
<th>Total a depositar:</th>
<td class="has-text-right">$ {{ gdc.total_a_transferir }}</td>
</tr>
</table>
</div>
</template>
<script>
import FilaPedido from "./FilaPedido.vue";
export default {
components: {
FilaPedido
},
props: {
gdc: {
type: Object,
required: true
}
}
}
</script>
<style>
</style>

View file

@ -1,74 +0,0 @@
<template>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<comunes-tabs-secciones :tabs="tabs" :tabInicial="tabActiva"></comunes-tabs-secciones>
<div class="block pb-6" id="pedidos-compras-seccion"
:class="seccionActiva === 'pedidos-compras-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="pedidos-compras-tabla-y-dropdown">
<compras-dropdown-descargar>
</compras-dropdown-descargar>
</div>
</div>
<div class="block pb-6" id="canasta-compras-seccion"
:class="seccionActiva === 'canasta-compras-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="canasta-compras-seccion">
<article class="message is-warning">
<div class="message-header">
<p>Formato de la canasta</p>
</div>
<div class="message-body">
<div class="content">
La planilla de la canasta tiene que tener el siguiente formato para que la aplicación la lea correctamente:
<ul>
<li> Los precios deben usar punto y no coma decimal </li>
<li> El nombre de las columnas deben ser "Tipo", "Producto", y "Precio" respectivamente </li>
<li> Las celdas deben separarse con '|' </li>
<li> No puede haber "enters" en ninguna celda </li>
<li> El bono de transporte debe tener tipo 'T' </li>
</ul>
<a class="has-text-info" href="/compras/canasta/ejemplo">Planilla de ejemplo.</a>
<article class="message is-danger mt-2">
<div class="message-body">
<div class="content">
Cuidado! Cargar una nueva canasta elimina todos los pedidos de la aplicación.
</div>
</div>
</article>
</div>
</div>
</article>
<div class="buttons is-right">
<compras-canasta-input></compras-canasta-input>
</div>
</div>
</div>
</div>
</template>
<script>
import TabsSecciones from "../comunes/TabsSecciones.vue";
import DropdownDescargar from "./DropdownDescargar.vue";
import CanastaInput from "./CanastaInput.vue";
export default {
components: {
TabsSecciones,
DropdownDescargar,
CanastaInput,
},
data() {
return {
tabs: [{ id: "pedidos-compras", nombre: "Pedidos" },
{ id: "canasta-compras", nombre: "Canasta" }],
tabActiva: "pedidos-compras",
seccionActiva: "pedidos-compras-seccion",
archivo: undefined,
}
},
methods: {
setSeccionActiva(tabId) {
this.tabActiva = tabId;
this.seccionActiva = tabId + "-seccion";
},
}
}
</script>

View file

@ -1,69 +0,0 @@
<template>
<div class="block">
<div class="file has-name is-right">
<label class="file-label">
<input
class="file-input"
type="file"
name="canasta"
@change="archivoSubido"
/>
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-cloud-upload-alt"></i>
</span>
<span class="file-label">Subir canasta</span>
</span>
<span class="file-name" v-if="archivo">
{{ 'Cargando ' + archivo.nombre }}
</span>
</label>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "CanastaInput",
data() {
return {
archivo: null,
cargando: false,
};
},
methods: {
async archivoSubido(event) {
const archivo = event.target.files[0];
if (archivo && archivo.type === "text/csv") {
this.archivo = {data: archivo, nombre: archivo.name};
const formData = new FormData();
formData.append("data", this.archivo.data);
try {
this.cargando = true;
const response = await axios.post("/compras/canasta", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
this.$root.$toast(response.data.message || "Canasta cargada exitosamente");
} catch (error) {
this.$root.$toast(error.response?.data?.message || "Hubo errores.");
} finally {
this.cargando = false;
this.archivo = null;
}
} else {
this.$root.$toast("La canasta debe ser .CSV")
this.archivo = null;
}
},
},
};
</script>
<style scoped>
</style>

View file

@ -1,44 +0,0 @@
<template>
<div class="buttons is-right">
<div class="dropdown" :class="{'is-active': dropdownActivo}" @mouseleave="dropdownActivo = false">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu" @click="dropdownActivo = !dropdownActivo">
<span class="icon is-small">
<i class="fas fa-download"></i>
</span>
<span>Descargar planillas</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="/compras/pedidos/descargar" class="dropdown-item">
Pedidos por barrio
</a>
<a href="/compras/pedidos/notas" class="dropdown-item">
Notas por barrio
</a>
<a href="/compras/pedidos/pdf" class="dropdown-item">
Pedidos por barrio en pdf
</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
dropdownActivo: false
}
},
}
</script>
<style>
</style>

View file

@ -1,52 +0,0 @@
<template>
<div class="block">
<div class="field">
<label class="label">Usuario</label>
<div class="field">
<div class="control">
<input required class="input" type="text" name="name" placeholder="Usuario">
</div>
</div>
</div>
<div class="field">
<label class="label">Contraseña</label>
<div class="field has-addons">
<div class="control">
<input required class="input" :type="this.passwordType" name="password" placeholder="Contraseña">
</div>
<div class="control">
<a class="button is-dark" @click="togglePassword">
{{ (passwordVisible ? 'Ocultar' : 'Mostrar') + ' contraseña'}}
</a>
</div>
</div>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-dark" value="Ingresar"/>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LoginAdmin",
data() {
return {
passwordVisible: false,
passwordType: "password",
}
},
methods: {
togglePassword() {
if (this.passwordVisible) this.passwordType = "password";
else this.passwordType = "text"
this.passwordVisible = !this.passwordVisible
}
}
}
</script>
<style>
</style>

View file

@ -1,51 +0,0 @@
<template>
<div v-show="visible" class="block">
<div class="field">
<label class="label" :class="isAdmin ? 'has-text-white' : ''">Seleccioná tu barrio o grupo de compra</label>
<div class="control">
<div class="select">
<select @change="onGDCSelected" v-model="gdc" name="name">
<option :disabled="isDefaultDisabled==1" value=null>Seleccionar</option>
<option v-for="(gdc, index) in gdcs" :key="index" v-text="gdc.nombre + (isAdmin ? '_admin' : '')"
:name="gdc.nombre + (isAdmin ? '_admin' : '')">
</option>
</select>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
region: null,
gdcs: [],
isDefaultDisabled: 0,
gdc: null,
isAdmin: this.admin == null ? false : this.admin
}
},
mounted() {
Event.$on('region-seleccionada', (region)=> {
this.region = region;
this.fillGDC(region);
this.visible = true;
});
},
methods : {
fillGDC(region) {
axios.get("/api/grupos-de-compra").then(response => {
this.gdcs = response.data[this.region];
});
},
onGDCSelected() {
this.isDefaultDisabled = 1;
Event.$emit("gdc-seleccionado",this.gdc);
}
},
props: {'admin': Boolean}
}
</script>

View file

@ -1,91 +0,0 @@
<template>
<nav id="nav-bar" class="navbar is-danger is-fixed-top" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://mps.org.uy">
<img src="/assets/logoMPS.png" height="28">
</a>
<!-- Styles nombre del barrio-->
<p class="navbar-item hide-below-1024">
<slot name="gdc"></slot>
</p>
<p class="navbar-item">
<slot name="subpedido"></slot>
</p>
<pedidos-chismosa-dropdown v-if="this.$root.pedido != null" class="hide-above-1023" id="mobile"></pedidos-chismosa-dropdown>
<a role="button" class="navbar-burger" :class="{'is-active':burgerActiva}" aria-label="menu" aria-expanded="false" data-target="nav-bar" @click="toggleBurger">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div class="navbar-menu" :class="{'is-active':burgerActiva}">
<div class="navbar-end">
<div v-if="this.$root.pedido != null" class="navbar-item field has-addons mt-2 mr-3">
<a class="button is-small has-text-dark-grey" @click.capture="buscar">
<span class="icon">
<i class="fas fa-search"></i>
</span>
</a>
<input class="input is-small" type="text" placeholder="Harina" v-model="searchString" @keyup.enter="buscar" >
</div>
<pedidos-chismosa-dropdown v-if="this.$root.pedido != null" class="hide-below-1024" id="wide"></pedidos-chismosa-dropdown>
<div class="block navbar-item">
<a onclick="event.preventDefault(); document.getElementById('logout-form').submit();" class="text-a">
Cerrar sesión
</a>
<slot name="logout-form"></slot>
</div>
</div>
</div>
</nav>
</template>
<script>
import ChismosaDropdown from '../pedidos/ChismosaDropdown.vue';
export default {
components: { ChismosaDropdown },
data() {
return {
burgerActiva: false,
searchString: "",
}
},
methods: {
toggleBurger() {
this.burgerActiva = !this.burgerActiva
},
buscar() {
if (this.burgerActiva) this.toggleBurger()
Event.$emit("migas-setear-como-inicio", this.$rootMiga)
Event.$emit("filtrar-productos",'nombre',this.searchString)
}
},
};
</script>
<style>
p.navbar-item:empty {
display: none;
}
#nav-bar {
z-index: 10;
}
.text-a {
color: inherit;
}
@media (max-width: 1023px) {
.hide-below-1024 {
display: none !important;
}
}
@media (min-width: 1024px) {
.hide-above-1023 {
display: none !important;
}
}
</style>

View file

@ -1,38 +0,0 @@
<template>
<div class="block">
<div class="field">
<label class="label" :class="whiteText ? 'has-text-white' : ''">Seleccioná tu región</label>
<div class="control">
<div class="select">
<select @change="onRegionSelected" v-model="region">
<option :disabled="isDefaultDisabled===1" value=null>Seleccionar</option>
<option v-for="(region, index) in regiones" :key="index" v-text="region" :name="region"></option>
</select>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
regiones: [],
isDefaultDisabled: 0,
region: null,
whiteText: this.admin == null ? false : this.admin
}
},
mounted() {
axios.get("/api/regiones").then(response => this.regiones = response.data);
},
methods: {
onRegionSelected() {
this.isDefaultDisabled = 1;
Event.$emit("region-seleccionada",this.region);
}
},
props: {'admin': Boolean}
}
</script>

View file

@ -1,55 +0,0 @@
<template>
<div class="block">
<div class="tabs is-boxed" id="tabs">
<ul class="has-bottom-line">
<li v-for="(tab, index) in tabs" class="is-size-6"
:key="index"
:id="tab.id + '-tab'"
:class="{'is-active': tab.id === tabActiva}">
<a @click="setTabActiva(tab.id)">
<span>
{{ tab.nombre }}
</span>
</a>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: {
tabs: Array,
tabInicial: String,
},
data() {
return {
tabActiva: this.tabInicial,
}
},
methods: {
setTabActiva(tabId) {
this.$parent.setSeccionActiva(tabId);
this.tabActiva = tabId
}
}
}
</script>
<style lang="scss" scoped>
@import '../../../../node_modules/bulma';
hr {
border: none;
height: 1px;
}
.has-bottom-line {
border-bottom-color: #dbdbdb !important;
border-bottom-style: solid !important;
border-bottom-width: 1px !important;
}
.tabs li.is-active a {
border-bottom-color: #e3342f;
color: #e3342f;
}
</style>

View file

@ -1,22 +0,0 @@
<template>
<div class="columns ml-3 mr-3">
<pedidos-categorias-container :class="chismosaActiva ? 'hide-below-1024' : ''"></pedidos-categorias-container>
<pedidos-productos-container :class="chismosaActiva ? 'hide-below-1024' : ''"></pedidos-productos-container>
<pedidos-chismosa v-show="chismosaActiva"></pedidos-chismosa>
</div>
</template>
<script>
export default {
data() {
return {
chismosaActiva: false,
}
},
mounted() {
Event.$on('toggle-chismosa', (activa) => {
this.chismosaActiva = activa;
});
},
}
</script>

View file

@ -1,32 +0,0 @@
<template>
<div v-show="aprobado" class="notification is-warning has-text-centered">
Tu pedido fue <strong>aprobado</strong>, por lo que no puede ser modificado
</div>
</template>
<script>
export default {
data() {
return {
aprobado: false,
}
},
mounted() {
Event.$on('pedido-actualizado', this.actualizarEstado);
if (this.$root.pedido != null) {
this.actualizarEstado();
}
},
methods: {
pedidoAprobado: function() {
return this.$root.pedido.aprobado;
},
actualizarEstado: function() {
this.aprobado = this.pedidoAprobado();
},
},
}
</script>
<style>
</style>

View file

@ -1,41 +0,0 @@
<template>
<div v-show="visible" class="column">
<div class="columns is-multiline is-mobile">
<div v-for="(categoria,i) in categorias" :key="i" class="block column is-one-quarter-desktop is-one-third-tablet is-half-mobile">
<div @click.capture="seleccionarCategoria(categoria)" class="card" style="height:100%" >
<div class="card-content">
<div class="media">
<div class="media-content" style="overflow:hidden">
<p class="title is-6" v-text="categoria"></p>
</div>
</div>
</div>
</div><!-- END CARD -->
</div><!-- END BLOCK COLUMN -->
</div><!-- END COLUMNS -->
</div><!-- END CONTAINER -->
</template>
<script>
export default {
data() {
return {
categorias: null,
visible: true
}
},
mounted() {
axios.get("/api/categorias").then(response => {
this.categorias = response.data;
});
Event.$emit("migas-setear-como-inicio", this.$rootMiga);
Event.$on("filtrar-productos", (_) => this.visible = false)
},
methods: {
seleccionarCategoria(categoria) {
this.visible = false;
Event.$emit("filtrar-productos",'categoria',categoria);
}
}
}
</script>

View file

@ -1,102 +0,0 @@
<template>
<div class="column is-one-third">
<div class="fixed-right">
<table v-show="mostrar_tabla" class="table is-striped is-bordered tabla-chismosa is-narrow">
<thead>
<tr>
<th>Producto</th>
<th>Cantidad</th>
<th><abbr title="Precio Total">$</abbr></th>
</tr>
</thead>
<tfoot>
<tr>
<th><abbr title="Bonos de Transporte">B. Transporte</abbr></th>
<th class="has-text-right">{{ cantidad_bonos_transporte }}</th>
<th class="has-text-right">{{ total_bonos_transporte }}</th>
</tr>
<tr v-if="this.$root.devoluciones">
<th><p>Devoluciones</p></th>
<td>
<abbr :title="notas_devoluciones">{{ notas_devoluciones_abbr }}</abbr>
<button @click.capture="modificarDevoluciones()" class="button is-warning is-small">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
</td>
<th class="has-text-right">-{{ devoluciones }}</th>
</tr>
<tr>
<th>Total total</th>
<th></th>
<th class="has-text-right">{{ total }}</th>
</tr>
</tfoot>
<tbody>
<pedidos-producto-row v-for="producto in productos" :producto="producto" :key="producto.id"></pedidos-producto-row>
</tbody>
</table>
<p class="has-text-centered" v-show="!mostrar_tabla">
Compa, todavía no agregaste nada a la chismosa.
</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
mostrar_tabla: false,
cantidad_bonos_transporte: 0,
total_bonos_transporte: 0,
devoluciones: 0,
notas_devoluciones: "",
notas_devoluciones_abbr: "",
total: 0,
productos: [],
}
},
mounted() {
Event.$on('pedido-actualizado', this.pedidoActualizado);
Event.$on('toggle-chismosa', this.pedidoActualizado);
},
methods: {
pedidoActualizado: function() {
this.mostrar_tabla = this.$root.productos.length > 0;
this.cantidad_bonos_transporte = this.cantidadBonosDeTransporte();
this.total_bonos_transporte = this.totalBonosDeTransporte();
this.devoluciones = this.$root.pedido.devoluciones_total;
this.notas_devoluciones = this.$root.pedido.devoluciones_notas;
this.notas_devoluciones_abbr = this.notas_devoluciones.substring(0, 15);
if (this.notas_devoluciones.length > 15) {
this.notas_devoluciones_abbr += "...";
}
this.total = this.$root.pedido.total;
this.productos = this.$root.productos;
},
modificarDevoluciones: function() {
Event.$emit("modificar-devoluciones");
},
cantidadBonosDeTransporte: function() {
return this.$root.pedido.cantidad_transporte;
},
totalBonosDeTransporte: function() {
return this.$root.pedido.total_transporte
},
},
}
</script>
<style>
.tabla-chismosa {
width: 100%;
}
.fixed-right {
position: fixed;
overflow-y: auto;
max-height: 81vh;
margin-right: 20px;
}
</style>

View file

@ -1,45 +0,0 @@
<template>
<div class="dropdown is-right navbar-item" :class="{'is-active':activa}">
<div class="dropdown-trigger">
<a class="text-a" aria-haspopup="true" :aria-controls="id" @click.capture="toggle">
<span class="icon is-small mr-1">
<img src="/assets/chismosa.png">
</span>
<span v-text="'$' + total"></span>
</a>
</div>
</div>
</template>
<script>
import Chismosa from './Chismosa.vue'
export default {
components: {
Chismosa
},
props: {
id: {
type: String,
required: true
}
},
data() {
return {
activa: false,
total: 0,
}
},
mounted() {
Event.$on('pedido-actualizado', this.actualizar);
},
methods: {
toggle() {
this.activa = !this.activa;
Event.$emit("toggle-chismosa", this.activa);
},
actualizar() {
this.total = this.$root.pedido.total;
},
},
}
</script>

View file

@ -1,67 +0,0 @@
<template>
<div v-bind:class="visible ? 'is-active modal' : 'modal'">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Devoluciones</p>
<button class="delete" aria-label="close" @click.capture="cerrar"></button>
</header>
<section class="modal-card-body">
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Total:
<input id="total" class="input" type="number" v-model="total" style="text-align: center">
</p>
</div>
<div class="field has-addons is-centered is-thin-centered">
<p class="control">
Notas:
<input id="notas" class="input" type="text" v-model.text="notas">
</p>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-success" @click="modificar">Aceptar</button>
<button class="button" @click.capture="cerrar">Cancelar</button>
</footer>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
total: 0,
notas: "",
}
},
computed: {
miga: function() {
return {
nombre: "Devoluciones",
href: "#devoluciones",
}
},
},
methods: {
cerrar() {
this.visible = false;
Event.$emit("migas-pop");
},
modificar() {
Event.$emit('sync-devoluciones', this.total, this.notas);
this.cerrar();
}
},
mounted() {
Event.$on('modificar-devoluciones', () => {
this.visible = true;
this.total = this.$root.pedido.devoluciones_total;
this.notas = this.$root.pedido.devoluciones_notas;
Event.$emit("migas-agregar", this.miga);
});
},
}
</script>

View file

@ -1,55 +0,0 @@
<template>
<div v-show="visible" class="block">
<div class="field">
<label class="label">Contraseña del barrio</label>
<div class="field has-addons">
<div class="control">
<input required class="input" :type="this.passwordType" name="password" placeholder="Contraseña del barrio">
</div>
<div class="control">
<a class="button is-info" @click="togglePassword">
{{ (passwordVisible ? 'Ocultar' : 'Mostrar') + ' contraseña'}}
</a>
</div>
</div>
<p class="help">Si no la sabés, consultá a tus compañerxs.</p>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-success" value="Ingresar"/>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
gdc: this.$root.gdc,
passwordVisible: false,
passwordType: "password",
}
},
mounted() {
Event.$on('gdc-seleccionado', (gdc) => {
this.$root.gdc = gdc;
this.visible = true;
});
},
methods: {
togglePassword() {
if (this.passwordVisible) this.passwordType = "password";
else this.passwordType = "text"
this.passwordVisible = !this.passwordVisible
}
}
}
</script>
<style>
.help {
font-size: 1rem;
}
</style>

View file

@ -1,54 +0,0 @@
<template>
<nav class="breadcrumb is-centered has-background-danger-light is-fixed-top" aria-label="breadcrumbs" v-show="visible">
<ul class="mt-4">
<li v-for="(miga, i) in migas" :key="i" :class="{'is-active': i == migaActiva}">
<a :href="miga.href" v-text="miga.nombre"
:class="{'has-text-danger': i != migaActiva}"></a>
</li>
</ul>
</nav>
</template>
<script>
export default {
data() {
return {
migas: []
}
},
computed: {
visible: function() {
return this.migas.length > 0
},
migaActiva: function() {
return this.migas.length-1
}
},
mounted() {
Event.$on('migas-setear-como-inicio', (miga) => {
this.migas = [];
this.migas.push(miga);
});
Event.$on('migas-agregar', (miga) => {
this.migas.push(miga);
});
Event.$on('migas-reset', () => {
this.migas = [];
});
Event.$on('migas-pop', () => {
this.migas.pop();
});
}
}
</script>
<style>
nav.breadcrumb.is-fixed-top {
position: fixed;
left: 0;
right: 0;
top: 3.25rem;
height: 2.75rem;
z-index: 5;
}
</style>

Some files were not shown because too many files have changed in this diff Show more