Compare commits

..

1 commit

Author SHA1 Message Date
nat
95ba0733ab WIP 2022-02-22 10:47:06 -03:00
104 changed files with 1732 additions and 30826 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
USERID=
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT_EXPOSED=3306
DB_PORT=3306
DB_DATABASE=pedi2
DB_USERNAME=pedi2
DB_PASSWORD=pedi2
@ -49,5 +47,4 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
WEB_CLIENT_EMAIL=informaticamps@buzon.uy
WEB_CLIENT_NAME=web
WEB_CLIENT_PASS=pass
NGINX_PORT=8000
WEB_CLIENT_PASS=pass

6
.gitignore vendored
View file

@ -10,9 +10,3 @@ Homestead.json
Homestead.yaml
npm-debug.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 \
libxml2-dev \
zip \
unzip \
npm
unzip
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

View file

@ -1,6 +1,6 @@
# 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.
@ -17,39 +17,41 @@ Se utilizan los siguientes servicios, separadamente:
## 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`).
2. Levantá los contenedores, construyendo la imagen de la app primero
2. Construí la imagen de la app
```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.
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
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
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
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.
@ -59,11 +61,6 @@ Podés usar el comando `logs` para ver los logs generados por tus servicios:
docker-compose logs nginx
```
8. Ejecuta npm para compilar el js y css
```bash
docker-compose exec app npm run prod
```
## Services description
### Dockerfile
@ -123,4 +120,4 @@ DB_PASSWORD=password
- https://docs.docker.com/
- https://docs.docker.com/compose/
- https://github.com/dyarleniber/laravel7-docker
- https://laravel.com/docs/7.x/installation
- https://laravel.com/docs/7.x/installation

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,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()
{
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

@ -2,319 +2,17 @@
namespace App;
use App\Helpers\TransporteHelper;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use League\Csv\CannotInsertRecord;
use League\Csv\Reader;
use League\Csv\Writer;
use Mpdf\Mpdf;
class GrupoDeCompra extends Model
{
public $timestamps = false;
protected $fillable = ["nombre", "region", "telefono", "correo", "referente_finanzas", "cantidad_de_nucleos", "fila", "devoluciones_habilitadas"];
protected $table = 'grupos_de_compra';
protected $hidden = ['password'];
public $timestamps = false;
protected $fillable = [ "nombre","region","telefono","correo","referente_finanzas","cantidad_de_nucleos"];
protected $table = 'grupos_de_compra';
protected $hidden = ['password'];
public function subpedidos()
{
return $this->hasMany('App\Subpedido');
}
public function toggleDevoluciones()
{
$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());
}
public function cantidadTransporte()
{
return TransporteHelper::cantidadTransporte($this->totalCentralesQuePaganTransporte());
}
public function exportarPlanillasAPdf()
{
$subpedidos = $this->pedidosAprobados();
//generar pdf
$mpdf = new Mpdf();
foreach ($subpedidos as $subpedido) {
$tabla = $subpedido->generarHTML();
// agregar la tabla al pdf en una nueva página
$mpdf->WriteHTML($tabla);
$mpdf->AddPage();
}
$filename = $this->nombre . '.pdf';
// imprimir el pdf
$mpdf->Output($filename, "D");
}
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)
{
$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[GrupoDeCompra::obtenerFilaDeBonoTransporte()] = GrupoDeCompra::filaVacia("Bonos de transporte", $columns);
return $template;
}
private static function obtenerFilaDeBonoTransporte()
{
$csv = Reader::createFromPath(resource_path('csv/productos.csv'), 'r');
$csv->setDelimiter("|");
$csv->setEnclosure("'");
$registros = $csv->getRecords();
foreach ($registros as $key => $registro)
if ($registro[0] == 'T') return $key;
throw new Exception('No hay bono de transporte');
}
private function totalPedidosSinBonos()
{
$total = 0;
foreach ($this->pedidosAprobados() as $pedido) {
$total += ceil($pedido->totalSinBonos());
}
return $total;
}
public function calcularCantidadBDT()
{
$total = 0;
foreach ($this->pedidosAprobados() as $pedido) {
$total += $pedido->totalParaTransporte();
}
return ceil($total / 500);
}
public function totalBonosBarriales()
{
$total = 0;
$bonoBarrial = Producto::where('nombre', 'LIKE', '%barrial%')->first();
if ($bonoBarrial) {
$pedidos = $this->pedidosAprobados();
foreach ($pedidos as $pedido) {
$bonoPedido = $pedido->productos()->find($bonoBarrial["id"]);
if ($bonoPedido) {
$total += $bonoPedido["pivot"]["total"];
}
}
}
return $total;
}
public function exportarPedidoEnCSV()
{
$records = $this->generarColumnaCantidades();
try {
$writer = Writer::createFromPath(resource_path('csv/exports/' . $this->nombre . '.csv'), 'w');
$writer->insertAll($records);
} catch (CannotInsertRecord $e) {
var_export($e->getRecords());
}
}
public function generarColumnaCantidades()
{
$productos_en_pedido = DB::table('pedidos_aprobados')->where('grupo_de_compra_id', $this->id)->get()->keyBy('producto_id');
//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[$this->obtenerFilaDeBonoTransporte()][1] = $this->calcularCantidadBDT();
return $records;
}
public function exportarPedidoConNucleosEnCSV()
{
$productos_en_pedido = DB::table('pedidos_aprobados')->where('grupo_de_compra_id', $this->id)->get()->keyBy('producto_id');
// 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));
// Guardar en un archivo .csv
try {
$writer = Writer::createFromPath(resource_path('csv/exports/' . $this->nombre . '-completo.csv'), 'w');
$writer->insertAll($records);
} catch (CannotInsertRecord $e) {
var_export($e->getRecords());
}
}
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);
}
static public function totalesParaTransportePorBarrio() {
return DB::table('grupos_de_compra')
->leftJoin('subpedidos', 'grupos_de_compra.id', '=', 'subpedidos.grupo_de_compra_id')
->leftJoin('producto_subpedido', 'subpedidos.id', '=', 'producto_subpedido.subpedido_id')
->leftJoin('productos', 'producto_subpedido.producto_id', '=', 'productos.id')
->where(function ($query) {
$query->whereNull('productos.categoria')
->orWhere('productos.categoria', 'not like', '%SUBSIDIADO%');
})
->where(function ($query) {
$query->whereNull('productos.bono')
->orWhere('productos.bono', 0);
})
->where(function ($query) {
$query->whereNull('subpedidos.aprobado')
->orWhere('subpedidos.aprobado', 1);
})
->select(
'grupos_de_compra.id as id',
'grupos_de_compra.nombre as barrio',
DB::raw('COALESCE(SUM(producto_subpedido.cantidad * productos.precio), 0) as total')
)
->groupBy('grupos_de_compra.id')
->get();
}
static public function planillaTransporte() {
$totalesPorBarrio = self::totalesParaTransportePorBarrio();
$barrios = [];
$bonosDeTransporte = [];
foreach ($totalesPorBarrio as $totalBarrio) {
$barrios[] = $totalBarrio->barrio;
$bonosDeTransporte[] = ceil($totalBarrio->total / 500);
}
$planilla = [];
$planilla[] = array_merge(['Barrio'], $barrios);
$planilla[] = array_merge(['Cant. bonos de transporte'], $bonosDeTransporte);
try {
$writer = Writer::createFromPath(resource_path('csv/exports/transporte-por-barrio.csv'), 'w');
$writer->insertAll($planilla);
} catch (CannotInsertRecord $e) {
var_export($e->getRecords());
}
}
public function subpedidos() {
return $this->hasMany('App\Subpedido');
}
}

View file

@ -1,151 +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;
use League\Csv\Reader;
class CanastaHelper
{
const FILA_HEADER = "Tipo";
const ULTIMA_FILA = "TOTAL";
const ARCHIVO_SUBIDO = 'Archivo subido';
const CANASTA_CARGADA = 'Canasta cargada';
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();
$csv = Reader::createFromPath(resource_path($archivo), 'r');
$csv->setDelimiter("|");
$iHeader = self::obtenerIndiceDeHeader($csv);
$csv->setHeaderOffset($iHeader);
$registros = $csv->getRecords();
$toInsert = [];
$categoria = '';
foreach($registros as $i => $registro){
//filas que están arriba del header
if ($i <= $iHeader){
continue;
}
//finalizar
if ($registro[self::FILA_HEADER] == self::ULTIMA_FILA) {
break;
}
//filas que no tienen tipo
if (!Arr::has($registro,self::FILA_HEADER)|| trim($registro[self::FILA_HEADER]) == ''){
var_dump("no hay tipo en la fila " . $i);
continue;
}
//saltear bono de transporte
if ($registro[self::FILA_HEADER] == "T"){
continue;
}
//obtener categoria
if ($registro['Producto'] == '') {
//es la pregunta de la copa?
if (Str::contains($registro[self::FILA_HEADER],"¿")) { continue; }
$categoria = $registro[self::FILA_HEADER];
continue;
}
//completar producto
$toInsert[] = [
'fila' => $i,
'categoria' => $categoria,
'nombre' => trim(str_replace('*', ' ',$registro['Producto'])),
'precio' => $registro['Precio'],
'proveedor_id' => self::obtenerProveedor($registro['Producto']),
'bono' => $registro[self::FILA_HEADER] == "B",
'requiere_notas'=> $registro[self::FILA_HEADER] =="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 obtenerIndiceDeHeader($csv){
$registros = $csv->getRecords();
$iheader = 0;
foreach ($registros as $i => $registro){
if (strtolower($registro[0]) == strtolower(self::FILA_HEADER)) {
$iheader = $i;
break;
}
}
return $iheader;
}
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,
]);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Helpers;
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;
}
}

View file

@ -2,34 +2,9 @@
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use Illuminate\Http\Request;
use Response;
class AdminController extends Controller
{
public function show()
{
return view('auth/admin_login');
}
public function index() {
return view('auth/admin_subpedidos');
}
public function exportarPlanillasAPdf(GrupoDeCompra $gdc) {
return $gdc->exportarPlanillasAPdf();
}
public function exportarPedidoACSV(GrupoDeCompra $gdc) {
$gdc->exportarPedidoEnCSV();
$file = resource_path('csv/exports/'.$gdc->nombre.'.csv');
return response()->download($file);
}
public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc) {
$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

@ -3,12 +3,11 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Producto;
use Illuminate\Http\Request;
use App\Filtros\FiltroDeSubpedido;
use App\Subpedido;
use App\GrupoDeCompra;
use App\Http\Resources\SubpedidoResource;
use App\Producto;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpKernel\Exception\HttpException;
@ -27,13 +26,8 @@ class SubpedidoController extends Controller
return Subpedido::filtrar($filtros)->get();
}
public function indexResources(FiltroDeSubpedido $filtros, Request $request)
{
return SubpedidoResource::collection(Subpedido::filtrar($filtros)->get());
}
/**
* Store a newly created resource in storage.
* Guardar un nuevo registro en el almacenamiento.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
@ -51,6 +45,28 @@ class SubpedidoController extends Controller
return $s;
}
/**
* 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([
'nombre' => 'required|max:255',
@ -61,55 +77,9 @@ class SubpedidoController extends Controller
]);
}
/**
* Display the specified resource.
*
* @param \App\Subpedido $subpedido
* @return \Illuminate\Http\Response
*/
public function show(Subpedido $subpedido)
{
return new SubpedidoResource($subpedido);
}
// 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')),
]
protected function validateActualizacionDeProducto(){
return request()->validate([
'cantidad' => 'required|min:0'
]);
$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\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
@ -29,18 +28,6 @@ class LoginController extends Controller
*/
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.
*

View file

@ -1,59 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Helpers\CanastaHelper;
use App\Producto;
use Illuminate\Http\Request;
class ComprasController
{
const CANASTAS_PATH = 'csv/canastas/';
public function indexPedidos() {
return view('compras_pedidos');
}
public function descargarPedidos() {
Producto::planillaTotales();
$file = resource_path('csv/exports/pedidos-por-barrio.csv');
return response()->download($file);
}
public function descargarNotas() {
Producto::planillaNotas();
$file = resource_path('csv/exports/notas-por-barrio.csv');
return response()->download($file);
}
public function descargarTransporte() {
GrupoDeCompra::planillaTransporte();
$file = resource_path('csv/exports/transporte-por-barrio.csv');
return response()->download($file);
}
public function show()
{
return view('auth/compras_login');
}
public function cargarCanasta(Request $request)
{
$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',
], 200);
}
public function descargarCanastaEjemplo() {
$file = resource_path('csv/productos.csv');
return response()->download($file);
}
}

View file

@ -56,8 +56,6 @@ class Kernel extends HttpKernel
*/
protected $routeMiddleware = [
'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,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View file

@ -1,26 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Auth;
class Admin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($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

@ -1,30 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GrupoDeCompraResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
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(),0),
'cantidad_transporte' => number_format($this->cantidadTransporte(),0),
];
}
}

View file

@ -25,8 +25,7 @@ class ProductoResource extends JsonResource
'imagen' => optional($this->poster)->url(),
'descripcion' => $this->descripcion,
'apto_veganxs' => $this->apto_veganxs,
'apto_celiacxs' => $this->apto_celiacxs,
'requiere_notas' => $this->requiere_notas,
'apto_celiacxs' => $this->apto_celiacxs
];
}
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SubpedidoResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
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(),0),
'cantidad_transporte' => number_format($this->cantidadTransporte(),0),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),
'devoluciones_total' => number_format($this->devoluciones_total,2),
'devoluciones_notas' => $this->devoluciones_notas
];
}
}

View file

@ -2,160 +2,35 @@
namespace App;
use App\Filtros\FiltroDeProducto;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use League\Csv\CannotInsertRecord;
use League\Csv\Reader;
use League\Csv\Writer;
use App\Filtros\FiltroDeProducto;
class Producto extends Model
{
public $timestamps = false;
protected $fillable = ["nombre", "precio", "presentacion", "stock", "categoria"];
static $paginarPorDefecto = 10;
public $timestamps = false;
protected $fillable = [ "nombre", "precio", "presentacion", "stock", "categoria" ];
static $paginarPorDefecto = 10;
public function subpedidos()
{
return $this->belongsToMany('App\Subpedido', 'productos_subpedidos')->withPivot(["cantidad", "notas"]);
}
public function subpedidos()
{
return $this->belongsToMany('App\Subpedido','productos_subpedidos')->withPivot(["cantidad"]);
}
public function proveedor()
{
return $this->belongsTo('App\Proveedor');
}
public function proveedor()
{
return $this->belongsTo('App\Proveedor');
}
//Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
public function scopeFiltrar($query, FiltroDeProducto $filtros)
{
return $filtros->aplicar($query);
}
public function pagaTransporte()
{
return !($this->bono || Str::contains($this->categoria, 'SUBSIDIADO'));
}
//Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
public function scopeFiltrar($query, FiltroDeProducto $filtros)
{
return $filtros->aplicar($query);
}
public static function getPaginar(Request $request)
{
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()
{
$barrios = DB::table('grupos_de_compra')
->where('nombre', '<>', 'PRUEBA')
->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 = DB::table('grupos_de_compra')
->where('nombre', '<>', 'PRUEBA')
->pluck('nombre')->toArray();
$headers = array_merge($headers, $barrios);
$cantidadesPorBarrio = self::cantidadesPorBarrio();
$planilla = [];
$ultimaFila = 1;
foreach ($cantidadesPorBarrio as $productoCantidades) {
$fila = $productoCantidades->fila;
while ($fila - $ultimaFila > 1) {
$ultimaFila++;
$planilla[$ultimaFila] = ['---'];
}
$planilla[$fila] = [$productoCantidades->producto];
foreach ($barrios as $barrio) {
$planilla[$fila][] = $productoCantidades->$barrio ?? 0;
}
$ultimaFila = $fila;
}
try {
$writer = Writer::createFromPath(resource_path('csv/exports/pedidos-por-barrio.csv'), 'w');
$writer->insertOne($headers);
$writer->insertAll($planilla);
} catch (CannotInsertRecord $e) {
var_export($e->getRecords());
}
}
public static function notasPorBarrio(): \Illuminate\Support\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 = DB::table('grupos_de_compra')
->where('nombre', '<>', 'PRUEBA')
->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;
}
try {
$writer = Writer::createFromPath(resource_path('csv/exports/notas-por-barrio.csv'), 'w');
$writer->insertOne($headers);
$writer->insertAll($planilla);
} catch (CannotInsertRecord $e) {
var_export($e->getRecords());
}
}
public static function getPaginar(Request $request)
{
return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::$paginarPorDefecto;
}
}

View file

@ -2,7 +2,7 @@
namespace App;
use App\Helpers\TransporteHelper;
use League\Csv\Reader;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Log;
@ -10,24 +10,12 @@ use App\Filtros\FiltroDeSubpedido;
class Subpedido extends Model
{
const COSTO_TRANSPORTE = 15;
public $timestamps = false;
protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre', 'devoluciones_total', 'devoluciones_notas'];
public $timestamps = false;
protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre'];
public function productos()
{
return $this->belongsToMany('App\Producto')->withPivot(["cantidad","total", "notas"]);
}
//Bonos del MPS, Sororo, etc. NO devuelve bonos de transporte
private function bonos()
{
return $this->productos()->where('bono',1);
}
public function productosSinBonos()
{
return $this->productos()->where('bono',false);
return $this->belongsToMany('App\Producto')->withPivot(["cantidad"]);
}
public function grupoDeCompra()
@ -35,103 +23,10 @@ class Subpedido extends Model
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)
{
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.subpedido_tabla", ["subpedido" => $this]);
return $view->render();
}
public function syncDevoluciones(float $total, string $notas) {
$this->devoluciones_total = $total;
$this->devoluciones_notas = $notas;
$this->save();
}
}

View file

@ -8,7 +8,7 @@
],
"license": "MIT",
"require": {
"php": "^7.4",
"php": "^7.2.5|^8.0",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3.1|^7.0.1",
@ -16,9 +16,7 @@
"laravel/sanctum": "^2.13",
"laravel/tinker": "^2.5",
"laravel/ui": "*",
"league/csv": "^9.8",
"mpdf/mpdf": "^8.1",
"prexview/prexview": "^1.1"
"league/csv": "^9.8"
},
"require-dev": {
"facade/ignition": "^2.0",
@ -54,13 +52,13 @@
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php7.4 artisan package:discover --ansi"
"@php artisan package:discover --ansi"
],
"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": [
"@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

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

View file

@ -17,7 +17,7 @@ class CreateSubpedidosTable extends Migration
$table->id();
$table->string('nombre');
$table->foreignId('grupo_de_compra_id');
$table->boolean('aprobado')->default(false);
$table->boolean('aprobado')->nullable();
$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,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
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,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,7 +12,7 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
$this->call(CanastaSeeder::class);
$this->call(GrupoDeCompraSeeder::class);
$this->call(ProductoSeeder::class);
}
}

View file

@ -31,25 +31,17 @@ class GrupoDeCompraSeeder extends Seeder
$usersToInsert[] = [
'name' => $registro['barrio'],
'password' => Hash::make("asd"),
"is_admin" => 0,
'grupo_de_compra_id' => $key
];
$usersToInsert[] = [
'name' => $registro['barrio'] . "_admin",
'password' => Hash::make("asd"),
"is_admin" => 1,
'password' => Hash::make($registro['barrio']),
'grupo_de_compra_id' => $key
];
}
foreach (array_chunk($gdcToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
foreach (array_chunk($gdcToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{
DB::table('grupos_de_compra')->insert($chunk);
DB::table('grupos_de_compra')->insert($chunk);
}
foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{
DB::table('users')->insert($chunk);
}

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,2 +0,0 @@
#!/usr/bin/bash
docker-compose up -d && docker-compose exec app npm run watch

View file

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

25435
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"
},
"devDependencies": {
"axios": "^0.19.2",
"axios": "^0.19",
"bootstrap": "^4.0.0",
"cross-env": "^7.0.3",
"cross-env": "^7.0",
"jquery": "^3.2",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
@ -22,11 +22,5 @@
"sass-loader": "^8.0.0",
"vue": "^2.5.17",
"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
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|||
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

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

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
* building robust, powerful web applications using Vue and Laravel.
*/
import axios from 'axios';
import Vue from 'vue';
require('./bootstrap');
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
@ -17,133 +15,18 @@ window.bulmaToast = require('bulma-toast');
*
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/
import './components';
/**
* Constants
*/
Vue.prototype.$rootMiga = {
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, ''))
}
// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#root',
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')
},
el: '#app',
});

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,75 +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 la columna de precios debe ser "Precio" </li>
<li> Las celdas deben separarse con '|' </li>
<li> No puede haber "enters" en ninguna celda </li>
<li> Todos los bonos deben tener tipo 'B' para evitar que paguen transporte </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/transporte" class="dropdown-item">
Transporte por barrio
</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>

View file

@ -1,140 +0,0 @@
<template>
<div>
<div class="field has-addons contador">
<div class="control">
<button class="button is-small" @click.capture="decrementar();">
<i class="fa fa-solid fa-minus"></i>
</button>
</div>
<div class="control">
<input id="cantidad" v-model="cantidad" class="input is-small" type="number" style="text-align: center">
</div>
<div class="control" @click="incrementar();">
<button class="button is-small">
<i class="fa fa-solid fa-plus"></i>
</button>
</div>
<button :disabled="disableConfirm()" class="button is-small is-success ml-1" @click="confirmar()">
<span class="icon">
<i class="fas fa-check"></i>
</span>
</button>
<button :disabled="!puedeBorrar()" class="button is-small is-danger ml-1" @click="borrar()">
<span class="icon">
<i class="fas fa-trash-alt"></i>
</span>
</button>
</div>
<div v-if="producto.requiere_notas" v-bind:class="{'has-icons-right': notas_warning_visible}" class="control is-full-width has-icons-left">
<span class="icon is-small is-left">
<i class="fas fa-sticky-note"></i>
</span>
<input v-model="notas" v-bind:class="{'is-danger': notas_warning_visible}" id="notas" class="input" type="text" placeholder="Talle o color" />
<span v-if="notas_warning_visible" class="icon is-small is-right">
<i class="fas fa-exclamation-triangle"></i>
</span>
<article v-if="notas_warning_visible" class="message is-danger is-small">
<div class="message-body">
No se puede dejar este campo vac&iacute;o
</div>
</article>
</div>
</div>
</template>
<script>
export default {
props: {
producto: Object
},
data() {
return {
cantidad: this.cantidadEnChismosa(),
notas: this.notasEnChismosa(),
notas_warning_visible: false,
}
},
mounted() {
Event.$on('sync-subpedido', (cantidad, productoId, notas) => {
if (this.producto.id === productoId)
this.sincronizar(cantidad, notas);
});
},
methods: {
notasEnChismosa() {
return this.producto.pivot !== undefined ? this.producto.pivot.notas : "";
},
cantidadEnChismosa() {
return this.producto.pivot !== undefined ? this.producto.pivot.cantidad : 0;
},
decrementar() {
this.cantidad -= 1;
},
incrementar() {
this.cantidad += 1;
},
confirmar() {
if (this.warningNotas()) {
this.notas_warning_visible = true;
return;
}
console.log("Emit sync " + this.cantidad + " " + this.notas);
Event.$emit('sync-subpedido', this.cantidad, this.producto.id, this.notas);
},
borrar() {
this.cantidad = 0;
this.confirmar();
},
sincronizar(cantidad, notas) {
this.notas_warning_visible = false;
this.notas = notas;
this.cantidad = cantidad;
if (this.producto.pivot !== undefined) {
this.producto.pivot.cantidad = cantidad;
this.producto.pivot.notas = notas;
}
},
hayCambios() {
if (this.cantidad != this.cantidadEnChismosa()) return true;
return this.cantidad > 0 && this.notas != this.notasEnChismosa();
},
puedeBorrar() {
return this.cantidadEnChismosa() > 0;
},
warningNotas() {
return this.producto.requiere_notas && this.cantidad > 0 && !this.notas;
},
disableConfirm() {
return !this.hayCambios();
},
}
}
</script>
<style>
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
appearance: textfield;
-moz-appearance: textfield;
}
.contador {
min-width: 178px;
}
.is-danger {
background-color: #fca697;
}
.is-danger::placeholder {
color: #fff;
opacity: 1; /* Firefox */
}
</style>

View file

@ -1,113 +0,0 @@
<script>
export default {
name: "ProductoCard",
props: {
producto: Object
},
data() {
return {
cantidad: this.producto.cantidad,
enChismosa: this.producto.cantidad,
notas: this.producto.notas,
}
},
mounted() {
Event.$on('sync-subpedido', (cantidad, productoId, notas) => {
if (this.producto.id === productoId)
this.sincronizar(cantidad, notas);
});
},
methods: {
decrementar() {
this.cantidad -= 1;
},
incrementar() {
this.cantidad += 1;
},
confirmar() {
Event.$emit('sync-subpedido', this.cantidad, this.producto.id, this.notas);
},
borrar() {
this.cantidad = 0;
this.confirmar();
},
sincronizar(cantidad, notas) {
this.cantidad = cantidad;
this.producto.cantidad = cantidad;
this.enChismosa = cantidad;
this.notas = notas;
this.producto.notas = notas;
},
hayCambios() {
return this.cantidad !== this.enChismosa || this.notas !== this.producto.notas;
},
puedeBorrar() {
return this.enChismosa > 0;
},
}
}
</script>
<template>
<div class="block column is-one-quarter-desktop is-full-mobile is-half-tablet min-width-from-desktop">
<div class="box" style="height:100%">
<div class="columns">
<div class="column">
<p class="title is-6">
{{ producto.nombre }}
</p>
<p class="subtitle is-7" v-text="producto.proveedor"></p>
<span class="subtitle is-7 hidden-from-tablet" v-if="enChismosa !== 0">{{ enChismosa }} en chismosa</span>
</div>
<div class="column is-one-quarter has-text-right">
<p class="has-text-weight-bold has-text-primary">
<span class="is-left-mobile">
<img v-show="producto.economia_solidaria" height="30px" width="30px" src="/assets/solidaria.png" alt="proveedor de economía solidaria">
<img v-show="producto.nacional" height="30px" width="30px" src="/assets/uruguay.png" alt="proveedor nacional"/>
</span>
$<span v-text="producto.precio"></span>
</p>
</div>
</div>
<footer class="columns">
<div class="column is-three-quarters">
<pedidos-producto-cantidad :producto="producto"></pedidos-producto-cantidad>
</div>
<div class="column">
<p class="subtitle is-7 is-hidden-mobile" v-if="enChismosa > 0">{{ enChismosa }} en chismosa</p>
</div>
</footer>
</div><!-- END BOX -->
</div>
</template>
<style lang="scss" scoped>
@use "../../../../node_modules/bulma/sass/utilities/mixins";
@include mixins.until(mixins.$desktop) {
.hidden-until-desktop {
display: none;
}
}
@include mixins.from(mixins.$desktop) {
.min-width-from-desktop {
min-width: 25rem;
}
.hidden-from-desktop {
display: none;
}
}
@include mixins.from(mixins.$tablet) {
.hidden-from-tablet {
display: none;
}
}
@include mixins.mobile() {
.is-left-mobile {
float: left;
}
}
</style>

View file

@ -1,16 +0,0 @@
<template>
<tr>
<td>{{ this.producto.nombre }}</td>
<td class="has-text-right">
<pedidos-producto-cantidad :producto="producto"></pedidos-producto-cantidad>
</td>
<td class="has-text-right">{{ Math.ceil(this.producto.pivot.total) }}</td>
</tr>
</template>
<script>
export default {
props: {
producto: Object
},
}
</script>

View file

@ -1,55 +0,0 @@
<template>
<div v-show="visible" class="column">
<div class="columns is-multiline is-mobile">
<pedidos-producto-card v-for="(producto,i) in productos" :key="i" :producto="producto">
</pedidos-producto-card><!-- END BLOCK COLUMN -->
</div><!-- END COLUMNS -->
</div><!-- END CONTAINER -->
</template>
<script>
export default {
data() {
return {
productos: [],
visible: false,
paginar: 150,
valor: null,
filtro: null
}
},
computed: {
miga: function(){
return {
nombre: this.valor,
href: "#" + this.valor
}
}
},
mounted() {
Event.$on('filtrar-productos', (filtro,valor) => {
this.filtro = filtro
this.valor = valor
axios.get("/api/productos", {
params: this.params(filtro,valor)
}).then(response => {
this.productos = response.data.data;
this.productos.forEach(p => {
p.pivot = {};
p.pivot.cantidad = this.$root.cantidad(p);
p.pivot.notas = this.$root.notas(p);
});
});
this.visible = true;
Event.$emit("migas-agregar",this.miga);
});
},
methods: {
params(filtro,valor) {
let params = { paginar: this.paginar }
params[filtro] = valor
return params
},
}
}
</script>

View file

@ -1,91 +0,0 @@
<template>
<div>
<label class="label">Escribí el nombre de tu familia o grupo de convivencia</label>
<div class="columns">
<div class="column is-two-thirds">
<div class="field">
<div class="control">
<input class="input" @input="onType" v-model="subpedido"/>
</div>
<p class="help">Debe ser claro para que tus compas del barrio te identifiquen.</p>
</div>
</div>
<div class="column is-one-third buttons">
<button class="button is-danger" v-show="!botonCrearDesabilitado" @click="submit">Crear nuevo pedido</button>
</div>
</div>
<div v-if="subpedidosExistentes.length" class="block">
<label class="label">Si ya comenzaste a hacer tu pedido este mes, elegilo en esta lista:</label>
<p class="help">Podés seguir escribiendo en el campo de arriba para refinar la búsqueda.</p>
<div class="columns is-mobile" v-for="(subpedidoExistente, index) in subpedidosExistentes" :class="{'has-background-grey-lighter': index % 2}" :key="index">
<div class="column is-half-mobile is-two-thirds-desktop is-two-thirds-tablet">
<p style="padding-top: calc(.5em - 1px); margin-bottom: .5rem" v-text="subpedidoExistente.nombre"></p>
</div>
<div class="buttons column is-half-mobile is-one-third-desktop is-one-third-tablet">
<button class="button is-danger" @click="elegirSubpedido(subpedidoExistente)">Continuar pedido</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
subpedido: null,
subpedidosExistentes: []
}
},
computed: {
nombresDeSubpedidos: function() {
return this.subpedidosExistentes.map(a => a.nombre.toLowerCase())
},
botonCrearDesabilitado : function() {
return !this.subpedido || this.nombresDeSubpedidos.includes(this.subpedido.toLowerCase())
}
},
props: ["gdcid"],
mounted() {
console.log("ready");
},
methods: {
onType() {
if (!this.subpedido){
this.subpedidosExistentes = [];
return;
}
axios.get("/api/subpedidos", {
params: {
nombre: this.subpedido,
grupo_de_compra: this.gdcid
}
}).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
this.elegirSubpedido(response.data);
});
},
elegirSubpedido(subpedido){
//lo guardamos en sesion
this.guardarSubpedidoEnSesion(subpedido);
},
guardarSubpedidoEnSesion(subpedido) {
axios.post("/subpedidos/guardar_sesion", {
subpedido: subpedido,
grupo_de_compra_id: this.gdcid
}).then(_ => {
Event.$emit('obtener-sesion')
window.location.href = 'productos';
});
}
}
}
</script>

View file

@ -4,56 +4,5 @@
// Variables
@import 'variables';
@import 'bulma';
@import '~bulma-switch';
main.has-top-padding {
padding-top: 4.5rem !important;
}
table.table td {
vertical-align: middle;
}
.has-text-centered {
text-align: center;
margin: 0 1em;
}
.is-fixed-top {
position: fixed;
z-index: 30;
}
#main {
height: 100%;
}
.container {
max-height: 100% !important;
}
/*
Author: Aseem Lalfakawma <alalfakawma.github.io>
This SCSS mixin will allow sizing of table columns in Bulma CSS Framework.
The Bulma framework does not support this yet, this small code snippet fixes this.
USAGE:
* Should be applied on TH element, it controls all the columns under it *
The class should be "is-#",
is-1 will give the first widthamount, is-2 will give the second.
Eg. The code below shows the widthAmounts as (1, 2.5, 3, 4 , 5, 6, 7)
When typing <th class="is-2">, the width will be 2.5em, as the second value in widthAmounts array is 2.5
*/
$widthAmounts: (15); // Just add the numbers here, you can use points too.
$widthUnit: "em"; // Add the unit here (rem|em|px|%)
@each $width in $widthAmounts {
$i: index($widthAmounts, $width);
@media only screen and (min-width: 768px) {
.table thead th.is-#{$i} {
width: #{$width}#{$widthUnit} !important;
}
}
}
// Bootstrap
@import '~bootstrap/scss/bootstrap';

View file

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html class="has-background-danger" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Pedidos del MPS') }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
<section class="section">
<div id="root" class="container">
<h1 class="title has-text-white">
Administración de Pedidos MPS
</h1>
<p class="subtitle has-text-white">
Bienvenidx a la administración de pedidos del <strong class="has-text-white">Mercado Popular de Subsistencia</strong>
</p>
@error('name')
<div class="notification is-warning">
Contraseña incorrecta, intentalo nuevamente.
</div>
@enderror
<comunes-region-select v-bind:admin="true"></comunes-region-select>
<form method="post" action="login">
@csrf
<comunes-barrio-select v-bind:admin="true"></comunes-barrio-select>
<admin-login></admin-login>
</form>
</div>
</section>
<script src="{{ mix('js/app.js') }}" defer></script>
</body>
</html>

View file

@ -1,5 +0,0 @@
@extends('layouts.app')
@section('content')
<admin-body></admin-body>
@endsection

View file

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html class="has-background-warning" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Pedidos del MPS') }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script>
</head>
<body>
<section class="section">
<div id="root" class="container">
<h1 class="title">
Pedidos MPS
</h1>
<p class="subtitle">
Bienvenidx a la sección de compras de la aplicación del <strong>Mercado Popular de Subsistencia</strong>
</p>
@error('name')
<div class="notification is-danger">
Contraseña incorrecta, intentalo nuevamente.
</div>
@enderror
<form method="post" action="login">
@csrf
<compras-login></compras-login>
</form>
</div>
</section>
<script src="{{ mix('js/app.js') }}" defer></script>
</body>
</html>

View file

@ -3,34 +3,33 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Pedidos del MPS') }}</title>
<title>{{ config('app.name', 'Compras del MPS') }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script>
</head>
<body>
<section class="section">
<div id="root" class="container">
<admin-boton-login></admin-boton-login>
<h1 class="title">
Pedidos MPS
Compras MPS
</h1>
<p class="subtitle">
Bienvenidx a la aplicación de pedidos del <strong>Mercado Popular de Subsistencia</strong>
Bienvenidx a la aplicación de compras del <strong>Mercado Popular de Subsistencia</strong>
</p>
@error('name')
<div class="notification is-danger">
Contraseña incorrecta, intentalo nuevamente.
</div>
@enderror
<comunes-region-select></comunes-region-select>
<region-select></region-select>
<form method="post" action="login">
@csrf
<comunes-barrio-select></comunes-barrio-select>
<pedidos-login></pedidos-login>
<barrio-select></barrio-select>
<login></login>
</form>
</div>
</section>
<script src="{{ mix('js/app.js') }}" defer></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="{{ asset('js/login.js') }}" defer></script>
</body>
</html>

View file

@ -1,5 +0,0 @@
@extends('layouts.app')
@section('content')
<compras-body></compras-body>
@endsection

View file

@ -7,19 +7,20 @@
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ session("subpedido_nombre") ? "Pedido de " . session("subpedido_nombre") . " - " . config('app.name', 'Pedidos del MPS') : config('app.name', 'Pedidos del MPS')}}</title>
<link rel="icon" type="image/x-icon" href="/assets/favicon.png">
<title>{{ session("subpedido_nombre") ? "Pedido de " . session("subpedido_nombre") . " - " . config('app.name', 'Compras del MPS') : config('app.name', 'Compras del MPS')}}</title>
<!-- Fonts -->
<script src="https://kit.fontawesome.com/9235d1c676.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
<!-- Styles -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
@yield('stylesheets')
</head>
<body class="has-navbar-fixed-top">
<div id="root">
<comunes-nav-bar>
<body>
<div id="app">
<nav-bar>
<template slot="subpedido">{{ session('subpedido_nombre') ? 'Pedido de '. session('subpedido_nombre') : Auth::user()->name }}</template>
<template slot="gdc">{{ session('subpedido_nombre') ? Auth::user()->name : "" }}</template>
<template slot="logout-form">
@ -27,16 +28,23 @@
@csrf
</form>
</template>
</comunes-nav-bar>
<pedidos-nav-migas></pedidos-nav-migas>
</nav-bar>
<nav-migas inline-template>
<nav class="breadcrumb is-centered has-background-danger-light" aria-label="breadcrumbs" v-show="visible">
<ul>
<li v-for="(miga, i) in migas" v-bind:class="i==migas.length-1 ? 'is-active' : ''"><a :href="miga.href" v-text="miga.nombre"></a></li>
</ul>
</nav>
</nav-migas>
<main id="main" class="py-4 has-top-padding">
<pedidos-cartel-pedido-aprobado></pedidos-cartel-pedido-aprobado>
<main class="py-4">
@yield('content')
</main>
</div>
<!-- Scripts -->
<script src="{{ mix('js/app.js') }}" defer></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="{{ asset('js/app.js') }}" defer></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
@yield('scripts')
</body>
</html>
</html>

View file

@ -1,44 +0,0 @@
<!doctype html>
<style>
tr:nth-child(even) {
background: #CCC
}
</style>
<h3>{{$subpedido->nombre}}</h3>
<table style="width: 100%">
<tr>
<th>
Producto
</th>
<th>
Cantidad
</th>
<th>
Control 1
</th>
<th>
Control 2
</th>
<th>
Control 3
</th>
</tr>
@foreach($subpedido->productos as $producto)
@if(!$producto->bono)
<tr>
<td>
{{ $producto->nombre }}
@if($producto->pivot->notas)
<br /><b>Talle/Color:</b> {{ $producto->pivot->notas }}
@endif
</td>
<td style="text-align: center">
{{ $producto->pivot->cantidad }}
</td>
</tr>
@endif
@endforeach
</table>

View file

@ -1,6 +1,15 @@
@extends('layouts.app')
@section('content')
<pedidos-body></pedidos-body>
<pedidos-devoluciones-modal></pedidos-devoluciones-modal>
@section('stylesheets')
<link rel="stylesheet" href="{{ asset('css/productos.css') }}">
@endsection
@section('content')
<categorias-container></categorias-container>
<productos-container></productos-container>
<producto-container></producto-container>
@endsection
@section('scripts')
<script src="{{ asset('js/productos.js') }}"></script>
@endsection

View file

@ -4,15 +4,38 @@
<section class="section">
<div id="root" class="container">
<h1 class="title">
Pedidos MPS
Compras MPS
</h1>
<p class="subtitle">
Bienvenidx a la aplicación de pedidos del <strong>Mercado Popular de Subsistencia</strong>
Bienvenidx a la aplicación de compras del <strong>Mercado Popular de Subsistencia</strong>
</p>
<pedidos-subpedido-select gdcid="{{Auth::user()->grupoDeCompra->id}}"></pedidos-subpedido-select>
<subpedido-select inline-template gdcid="{{Auth::user()->grupoDeCompra->id}}">
<div class="block">
<div class="field">
<label class="label">Escribí el nombre de tu familia o grupo de convivencia</label>
<div class="control">
<input class="input" @input="onType" v-model="subpedido"></input>
</div>
<p class="help">Intentá que sea claro como para que tus compas del barrio te identifiquen.</p>
</div>
<div class="buttons">
<button class="button is-primary" @click="submit">Crear nuevo pedido</button>
</div>
<div class="table-container">
<table class="table is-hoverable">
<tbody>
<tr v-for="subpedidoExistente in subpedidosExistentes" >
<td><a v-text="subpedidoExistente.nombre" :href="subpedidoExistente.nombre"></a></td>
</tr>
</tbody>
</table>
</div>
</div>
</subpedido-select>
</div>
</section>
@endsection
@section('scripts')
@endsection
<script src="{{ asset('js/subpedidos-create.js') }}"></script>
@endsection

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