Compare commits

...

11 Commits

192 changed files with 26425 additions and 831 deletions
+16 -3
View File
@@ -4,9 +4,9 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_LOCALE=es
APP_FALLBACK_LOCALE=es
APP_FAKER_LOCALE=es_AR
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
@@ -62,4 +62,17 @@ AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
BACKUP_DESTINATION_DISK=local
BACKUP_MAIL_TO=admin@example.com
BACKUP_ARCHIVE_PASSWORD=
BACKUP_INCLUDE_ENV=false
BACKUP_MAX_AGE_DAYS=7
BACKUP_MAX_STORAGE_MB=5000
BACKUP_KEEP_ALL_DAYS=7
BACKUP_KEEP_DAILY_DAYS=16
BACKUP_KEEP_WEEKLY_WEEKS=8
BACKUP_KEEP_MONTHLY_MONTHS=4
BACKUP_KEEP_YEARLY_YEARS=2
BACKUP_MAX_STORAGE_LIMIT_MB=5000
VITE_APP_NAME="${APP_NAME}"
View File
+2 -2
View File
@@ -191,7 +191,7 @@ class Agenda {
+ GuardarTurno(T: Turno, INotificador : INotificadorTurnos) : bool
- Disponible(date) : bool //para asignar manualmente
- VerificarDisponibilidad(f: Formulario) : datetime //Para asignacion automatica
+ GuardarDiaDeAtencion(DiaAtencion) : void
+ crearDiaDeAtencion(diaid, horarioComienzo, horarioFin, tipo) : void
+ GuardarModoVacaciones(ModoVacaciones) : void
+ GuardarFeriado(Feriado) : void
}
@@ -199,7 +199,7 @@ class Agenda {
class DiaDeAtencion {
- Dia_ID: integer
+ DiaDeAtencion(ID_Dia : integer) //Los id los voy a hardcodear
+ DiaDeAtencion(ID_Dia : integer, comienzo : integer, fin : integer, tipo : string) //tipo = 'AM', 'PM', 'INDISTINTO'
+ GuardarHorarioAtencion(HorarioAtencion) : void
+ GuardarHorarioReceso(HorarioReceso) : void
+4 -4
View File
@@ -41,10 +41,10 @@ Formulario -Si Cli_DNI es null, entonces ese formulario lo envió
_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
LogSeguridad -Responsable_ID: Vendría a ser el DNI de la persona que ejecutó esa accion.
_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
HorariosAtenciones -TIPO = Corresponde a AM o PM
_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
Baja -Su funcion es habilitar o deshabilitar credenciales. Si el Cliente/profesional esta "Activo" puede iniciar sesión. Si está de "Baja", no podrá iniciar sesión. (El administrador decide quien esta de alta o baja)
_________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
-48
View File
@@ -1,48 +0,0 @@
----- Módulo Recuperación -----
Recuperacion Profesionales/Administrador
1) Llama al método RecuperarProf(rol, usuario, email) de la class AuthController. ROL = Profesional o Administrador
2) Se verifica si existe ese usuario con ExisteUsuario() de la class AuthController
3) Si existe, se verifica con CorreoPertenece(ROL, ID_Credencial) de la class AuthController que el email pasado en RecuperarProf() esté asociado a ese usuario
4) Si es verdadero, se genera un token de recuperación y se guarda en la BD con GenerarToken(ROL, usuario) de la class AuthController
5) Se notifica al correo ingresado en RecuperarProf() el token de recuperacion
6) Se verifica el token ingresado por el usuario con VerificarToken(ROL, token, ID_Credencial) de la class Credencial
7) Se actualiza la contraseña con CambiarContrasenia(ID_Credencial) de la class Credencial
Recuperacion de Administrador sin Usuario
1) Se llama al método RecuperarAdmin(DNI, correo, CUIL)
2) Se verifica si los datos son correctos con ExisteAdmin(DNI, correo, CUIL)
3) Si es verdadero, se genera el token de recuperacion y se almacena en la BD
4) Se envía el correo de recuperacion al correo ingresado en RecuperacionAdmin
5) Se verifica el token ingresado por el usuario con VerificarToken() de la class Credencial
6) Se actualiza la contraseña con CambiarContraseña(ID_Credencial, password, ROL) de la class Credencial
Recuperacion Clientes
1) Llama al método RecuperarCli(usuario, email) de la class AuthController.
2) Se verifica si existe ese usuario con ExisteUsuario(ROL, usuario) de la class AuthController. ROL = Cliente
3) Si existe, se verifica con CorreoPertenece(ROL, ID_Credencial) de la class AuthController que el email pasado en RecuperarCli() esté asociado a ese usuario
4) Si es verdadero, se genera un token de recuperación y se guarda en la BD con GenerarToken(ROL, usuario) de la class AuthController
5) Se notifica al correo ingresado en RecuperarCli() el token de recuperacion
6) Se verifica el token ingresado por el usuario con VerificarToken(ROL, token, ID_Credencial) de la class Credencial
7) Se actualiza la contraseña con CambiarContrasenia(ID_Credencial, password, ROL) de la class Credencial
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
Version 1.0.0
Sistema funcionando sin fallas hasta el momento.
+163 -39
View File
@@ -1,59 +1,183 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
# Abogadas Litoral
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
Aplicacion web para gestion de turnos de un estudio juridico, desarrollada con Laravel y Vite.
## About Laravel
## Tabla de contenido
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Tecnologias y versiones](#tecnologias-y-versiones)
- [Requisitos previos](#requisitos-previos)
- [Instalacion](#instalacion)
- [Comandos utiles](#comandos-utiles)
- [Testing](#testing)
- [Flujo de trabajo con Git](#flujo-de-trabajo-con-git)
- [Estructura principal](#estructura-principal)
- [Notas](#notas)
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
## Tecnologias y versiones
Laravel is accessible, powerful, and provides tools required for large, robust applications.
Versiones tomadas del proyecto actual:
## Learning Laravel
- PHP: `^8.2`
- Laravel Framework: `^12.0`
- PHPUnit: `^11.5.3`
- Node.js: recomendado `>=20`
- npm: recomendado `>=10`
- Vite: `^7.0.7`
- Bootstrap: `^5.3.8`
- FullCalendar: `^6.1.20`
- Spatie Laravel Backup: `^9.3`
- DomPDF (barryvdh/laravel-dompdf): `^3.1`
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
Archivos fuente de versionado:
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
- `composer.json`
- `package.json`
## Laravel Sponsors
## Requisitos previos
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
- PHP 8.2 o superior
- Composer 2.x
- Node.js y npm
- Base de datos (segun entorno):
- En `.env.example` la conexion por defecto es `sqlite`
- En `config/database.php` el fallback de Laravel esta en `mysql`
### Premium Partners
## Instalacion
- **[Vehikl](https://vehikl.com)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Redberry](https://redberry.international/laravel-development)**
- **[Active Logic](https://activelogic.com)**
### Opcion rapida (script del proyecto)
## Contributing
```bash
composer run setup
```
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
Este script ejecuta:
## Code of Conduct
1. `composer install`
2. Creacion de `.env` desde `.env.example` (si no existe)
3. `php artisan key:generate`
4. `php artisan migrate --force`
5. `npm install`
6. `npm run build`
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
### Opcion manual
## Security Vulnerabilities
```bash
composer install
copy .env.example .env
php artisan key:generate
php artisan migrate
npm install
npm run build
```
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
Si usas Linux/Mac, reemplaza `copy` por:
## License
```bash
cp .env.example .env
```
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
## Comandos utiles
Levantar entorno de desarrollo completo (servidor, cola, logs y Vite):
```bash
composer run dev
```
Levantar solo backend:
```bash
php artisan serve
```
Levantar solo frontend:
```bash
npm run dev
```
Compilar assets para produccion:
```bash
npm run build
```
## Testing
Ejecutar tests:
```bash
composer run test
```
o
```bash
php artisan test
```
## Flujo de trabajo con Git
### 1) Crear rama de trabajo
```bash
git checkout main
git pull origin main
git checkout -b feature/nombre-cambio
```
### 2) Commits pequenos y descriptivos
Formato recomendado:
- `feat: agrega agenda semanal`
- `fix: corrige validacion de telefono`
- `docs: actualiza readme de instalacion`
### 3) Subir rama y abrir Pull Request
```bash
git add .
git commit -m "feat: descripcion breve"
git push -u origin feature/nombre-cambio
```
### 4) Actualizar tu rama con cambios de main
```bash
git checkout main
git pull origin main
git checkout feature/nombre-cambio
git merge main
```
## Estructura principal
- `app/`: modelos, controladores, middleware y logica principal
- `resources/views/`: vistas Blade
- `resources/js/` y `resources/css/`: frontend
- `routes/`: rutas web, api y consola
- `database/migrations/`: migraciones
- `tests/`: pruebas unitarias y feature
- `scripts/`: utilidades de scheduler en Windows
## Datos de acceso (Entorno de desarrollo)
Las credenciales de administrador se generan automáticamente al ejecutar las migraciones con seeders:
```bash
php artisan migrate:fresh --seed
```
O si prefieres solo los seeders sin resetear migraciones:
```bash
php artisan db:seed
```
Consulta `database/seeders/` para ver los datos que se cargan.
## Notas
- No subir archivos sensibles al repositorio (`.env`, claves, tokens).
- Mantener este README actualizado ante cambios de version o arquitectura.
+73
View File
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
use App\Models\Administrador;
use App\Models\CredencialProfesional;
use App\Models\Foto;
use App\Models\Persona;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
require __DIR__ . '/vendor/autoload.php';
$app = require __DIR__ . '/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();
$usuario = env('ADMIN_USUARIO', 'admin');
$passwordPlano = env('ADMIN_PASSWORD', 'admin1234');
$correo = env('ADMIN_CORREO', 'admin@abogadaslitoral.com');
$dni = env('ADMIN_DNI', '1');
$nombre = env('ADMIN_NOMBRE', 'Usuario');
$apellido = env('ADMIN_APELLIDO', 'Administrador');
$cuil = env('ADMIN_CUIL', '20-1-0');
$fechaNac = env('ADMIN_FECHANAC', '2026-01-01');
try {
DB::transaction(function () use ($usuario, $passwordPlano, $correo, $dni, $nombre, $apellido, $cuil, $fechaNac): void {
$foto = Foto::firstOrCreate(
['ruta' => 'avatars/admin-default.png'],
[
'extension' => 'png',
'nombre' => 'admin-default',
'mime_type' => 'image/png',
'tamanio_bytes' => 0,
]
);
$persona = Persona::updateOrCreate(
['dni' => $dni],
[
'nombre' => $nombre,
'apellido' => $apellido,
'cuil' => $cuil,
'fechanac' => $fechaNac,
'foto_id' => $foto->id,
]
);
$credencial = CredencialProfesional::updateOrCreate(
['usuario' => $usuario],
[
'contra' => Hash::make($passwordPlano),
'rol' => 'ADMIN',
]
);
Administrador::updateOrCreate(
['dni' => $dni, 'correo' => $correo],
[
'persona_id' => $persona->id,
'credencialprofesional_id' => $credencial->id,
]
);
});
echo "Administrador creado/actualizado correctamente." . PHP_EOL;
echo "Usuario: {$usuario}" . PHP_EOL;
echo "Correo: {$correo}" . PHP_EOL;
} catch (Throwable $e) {
fwrite(STDERR, 'Error al crear administrador: ' . $e->getMessage() . PHP_EOL);
exit(1);
}
@@ -76,4 +76,5 @@ class AgendaController extends Controller
'message' => 'Registro eliminado correctamente',
], 200);
}
}
+463
View File
@@ -0,0 +1,463 @@
<?php
namespace App\Http\Controllers;
use App\Models\AccionLog;
use App\Models\CredencialCliente;
use App\Models\CredencialProfesional;
use App\Models\LogSeguridad;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
class AuthController extends Controller
{
public function loginClienteWeb(Request $request): RedirectResponse
{
$request->validate([
'correo' => ['required', 'string'],
'contra' => ['required', 'string'],
'website' => ['nullable', 'string', 'max:255'],
]);
if (!$this->honeypotValido($request)) {
return back()
->withInput($request->except(['contra', 'website']))
->withErrors(['login_error' => 'No se pudo procesar el inicio de sesión.']);
}
$correo = trim((string) $request->input('correo'));
$contra = (string) $request->input('contra');
$credencial = CredencialCliente::with('cliente.persona.telefonos')->where('correo', $correo)->first();
if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) {
return back()
->withInput($request->except('contra'))
->with('login_error', 'Usuario o contraseña incorrectos.');
}
if ((int) ($credencial->cliente?->baja_id ?? 0) !== 1) {
return back()
->withInput($request->except('contra'))
->with('login_error', 'No es posible iniciar sesion. Comuníquese con un profesional de Abogadas del Litoral');
}
$token = Str::random(64);
$credencial->token = $token;
$credencial->fecha_hora = now();
$credencial->save();
$personaCliente = $credencial->cliente?->persona;
$nombreCliente = trim((string) ($personaCliente?->nombre ?? ''));
$apellidoCliente = trim((string) ($personaCliente?->apellido ?? ''));
$celularCliente = trim((string) ($personaCliente?->telefonos?->first()?->telefono ?? ''));
$request->session()->regenerate();
$request->session()->regenerateToken();
$request->session()->put([
'cliente_auth' => true,
'cliente_id' => (int) ($credencial->cliente?->id ?? 0),
'cliente_token' => $token,
'cliente_correo' => (string) $credencial->correo,
'cliente_nombre' => $nombreCliente !== '' ? $nombreCliente : (string) $credencial->correo,
'cliente_apellido' => $apellidoCliente,
'cliente_celular' => $celularCliente,
]);
$this->registrarAccionSeguridad(
(int) ($credencial->cliente?->persona_id ?? 0),
'CLIENTE',
(string) $request->ip(),
'Inició sesión',
'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' inició sesión.'
);
$this->limpiarThrottle($request, 'login-cliente-web');
return redirect('/cliente/dashboard')->with('login_success', 'Login exitoso.');
}
public function loginPersonalWeb(Request $request): RedirectResponse
{
$request->validate([
'usuario' => ['required', 'string'],
'contra' => ['required', 'string'],
'website' => ['nullable', 'string', 'max:255'],
]);
if (!$this->honeypotValido($request)) {
return back()
->withInput($request->except(['contra', 'website']))
->withErrors(['login_error' => 'No se pudo procesar el inicio de sesión.']);
}
$usuario = trim((string) $request->input('usuario'));
$contra = (string) $request->input('contra');
$credencial = CredencialProfesional::with(['administrador.persona', 'profesional.persona'])
->where('usuario', $usuario)
->first();
if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) {
return back()
->withInput($request->except('contra'))
->with('login_error', 'Usuario o contraseña incorrectos.');
}
if (strtoupper((string) $credencial->rol) !== 'ADMIN' && (int) ($credencial->profesional?->baja_id ?? 0) !== 1) {
return back()
->withInput($request->except('contra'))
->with('login_error', 'Usted está dado de baja. Comuníquese con el administrador');
}
$token = Str::random(64);
$credencial->token = $token;
$credencial->fecha_hora = now();
$credencial->save();
$rol = strtoupper((string) $credencial->rol);
$personaProfesional = $credencial->profesional?->persona;
$nombreProfesional = trim((string) ($personaProfesional?->nombre ?? '') . ' ' . (string) ($personaProfesional?->apellido ?? ''));
$personaAdmin = $credencial->administrador?->persona;
$nombreAdmin = trim((string) ($personaAdmin?->nombre ?? '') . ' ' . (string) ($personaAdmin?->apellido ?? ''));
$nombreSesion = $rol === 'ADMIN' ? $nombreAdmin : $nombreProfesional;
$request->session()->regenerate();
$request->session()->regenerateToken();
$request->session()->put([
'personal_auth' => true,
'personal_token' => $token,
'personal_usuario' => (string) $credencial->usuario,
'personal_nombre' => $nombreSesion !== '' ? $nombreSesion : (string) $credencial->usuario,
'personal_apellido' => $rol === 'ADMIN'
? ($personaAdmin?->apellido ?? '')
: ($personaProfesional?->apellido ?? ''),
'personal_rol' => $rol,
'personal_credencial_id' => (int) $credencial->id,
]);
$this->registrarAccionSeguridad(
$rol === 'ADMIN'
? (int) ($credencial->administrador?->persona_id ?? 0)
: (int) ($credencial->profesional?->persona_id ?? 0),
$rol === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL',
(string) $request->ip(),
'Inició sesión',
($rol === 'ADMIN' ? 'La administradora ID ' . (int) ($credencial->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencial->profesional?->id ?? 0)) . ' inició sesión.'
);
$this->limpiarThrottle($request, 'login-personal-web');
$redirectPath = $rol === 'ADMIN' ? '/administrador/dashboard' : '/profesional/dashboard';
return redirect($redirectPath)->with('login_success', 'Login exitoso.');
}
public function loginCliente(Request $request): JsonResponse
{
$request->validate([
'correo' => ['required', 'string'],
'contra' => ['required', 'string'],
]);
$correo = trim((string) $request->input('correo'));
$contra = (string) $request->input('contra');
$credencial = CredencialCliente::with('cliente')->where('correo', $correo)->first();
if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) {
return response()->json([
'success' => false,
'message' => 'Credenciales invalidas',
], 401);
}
if ((int) ($credencial->cliente?->baja_id ?? 0) !== 1) {
return response()->json([
'success' => false,
'message' => 'No es posible iniciar sesion. Comuníquese con un profesional de Abogadas del Litoral',
], 403);
}
$token = Str::random(64);
$credencial->token = $token;
$credencial->fecha_hora = now();
$credencial->save();
$this->registrarAccionSeguridad(
(int) ($credencial->cliente?->persona_id ?? 0),
'CLIENTE',
(string) $request->ip(),
'Inició sesión',
'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' inició sesión.'
);
$this->limpiarThrottle($request, 'login-cliente-api');
return response()->json([
'success' => true,
'data' => [
'tipo' => 'cliente',
'id_credencial' => $credencial->id,
'token' => $token,
],
'message' => 'Login de cliente exitoso',
], 200);
}
public function loginPersonal(Request $request): JsonResponse
{
$request->validate([
'usuario' => ['required', 'string'],
'contra' => ['required', 'string'],
]);
$usuario = trim((string) $request->input('usuario'));
$contra = (string) $request->input('contra');
$credencial = CredencialProfesional::with(['administrador', 'profesional'])->where('usuario', $usuario)->first();
if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) {
return response()->json([
'success' => false,
'message' => 'Credenciales invalidas',
], 401);
}
if (strtoupper((string) $credencial->rol) !== 'ADMIN' && (int) ($credencial->profesional?->baja_id ?? 0) !== 1) {
return response()->json([
'success' => false,
'message' => 'Usted está dado de baja. Comuníquese con el administrador',
], 403);
}
$token = Str::random(64);
$credencial->token = $token;
$credencial->fecha_hora = now();
$credencial->save();
$rol = strtoupper((string) $credencial->rol);
$this->registrarAccionSeguridad(
$rol === 'ADMIN'
? (int) ($credencial->administrador?->persona_id ?? 0)
: (int) ($credencial->profesional?->persona_id ?? 0),
$rol === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL',
(string) $request->ip(),
'Inició sesión',
($rol === 'ADMIN' ? 'La administradora ID ' . (int) ($credencial->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencial->profesional?->id ?? 0)) . ' inició sesión.'
);
$this->limpiarThrottle($request, 'login-personal-api');
return response()->json([
'success' => true,
'data' => [
'tipo' => 'personal',
'rol' => $credencial->rol,
'id_credencial' => $credencial->id,
'token' => $token,
],
'message' => 'Login de personal exitoso',
], 200);
}
public function login(Request $request): JsonResponse
{
$request->validate([
'identificador' => ['required', 'string'],
'contra' => ['required', 'string'],
'tipo' => ['nullable', 'in:cliente,profesional'],
]);
$identificador = trim((string) $request->input('identificador'));
$contra = (string) $request->input('contra');
$tipo = $request->input('tipo');
$credencial = null;
$tipoDetectado = null;
if ($tipo === 'cliente') {
$credencial = CredencialCliente::with('cliente')->where('correo', $identificador)->first();
$tipoDetectado = 'cliente';
} elseif ($tipo === 'profesional') {
$credencial = CredencialProfesional::where('usuario', $identificador)->first();
$tipoDetectado = 'personal';
} else {
$credencial = CredencialCliente::with('cliente')->where('correo', $identificador)->first();
$tipoDetectado = $credencial ? 'cliente' : null;
if (!$credencial) {
$credencial = CredencialProfesional::where('usuario', $identificador)->first();
$tipoDetectado = $credencial ? 'personal' : null;
}
}
if (!$credencial || !$this->credencialValida($contra, (string) $credencial->contra)) {
return response()->json([
'success' => false,
'message' => 'Credenciales invalidas',
], 401);
}
if ($credencial instanceof CredencialCliente
&& (int) ($credencial->cliente?->baja_id ?? 0) !== 1) {
return response()->json([
'success' => false,
'message' => 'No es posible iniciar sesion. Comuníquese con un profesional de Abogadas del Litoral',
], 403);
}
if ($credencial instanceof CredencialProfesional
&& strtoupper((string) $credencial->rol) !== 'ADMIN'
&& (int) ($credencial->profesional?->baja_id ?? 0) !== 1) {
return response()->json([
'success' => false,
'message' => 'Usted está dado de baja. Comuníquese con el administrador',
], 403);
}
$token = Str::random(64);
$credencial->token = $token;
$credencial->fecha_hora = now();
$credencial->save();
if ($credencial instanceof CredencialCliente) {
$this->registrarAccionSeguridad(
(int) ($credencial->cliente?->persona_id ?? 0),
'CLIENTE',
(string) $request->ip(),
'Inició sesión',
'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' inició sesión.'
);
} elseif ($credencial instanceof CredencialProfesional) {
$rolDescripcion = strtoupper((string) $credencial->rol) === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL';
$this->registrarAccionSeguridad(
$rolDescripcion === 'ADMIN'
? (int) ($credencial->administrador?->persona_id ?? 0)
: (int) ($credencial->profesional?->persona_id ?? 0),
$rolDescripcion,
(string) $request->ip(),
'Inició sesión',
($rolDescripcion === 'ADMIN' ? 'La administradora ID ' . (int) ($credencial->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencial->profesional?->id ?? 0)) . ' inició sesión.'
);
}
$this->limpiarThrottle($request, 'login-api-general');
return response()->json([
'success' => true,
'data' => [
'tipo' => $tipoDetectado,
'rol' => $credencial instanceof CredencialProfesional ? $credencial->rol : null,
'id_credencial' => $credencial->id,
'token' => $token,
],
'message' => 'Login exitoso',
], 200);
}
private function honeypotValido(Request $request): bool
{
return trim((string) $request->input('website', '')) === '';
}
public function logout(Request $request): JsonResponse
{
$token = (string) $request->input('token', '');
if ($token === '') {
return response()->json([
'success' => false,
'message' => 'Token requerido',
], 422);
}
$credencialCliente = CredencialCliente::with('cliente')->where('token', $token)->first();
if ($credencialCliente) {
$this->registrarAccionSeguridad(
(int) ($credencialCliente->cliente?->persona_id ?? 0),
'CLIENTE',
(string) $request->ip(),
'Cerró sesión',
'El cliente ID ' . (int) ($credencialCliente->cliente?->id ?? 0) . ' cerró sesión.'
);
$credencialCliente->token = null;
$credencialCliente->fecha_hora = now();
$credencialCliente->save();
return response()->json([
'success' => true,
'message' => 'Logout exitoso',
], 200);
}
$credencialProfesional = CredencialProfesional::with(['administrador', 'profesional'])->where('token', $token)->first();
if ($credencialProfesional) {
$rol = strtoupper((string) $credencialProfesional->rol);
$this->registrarAccionSeguridad(
$rol === 'ADMIN'
? (int) ($credencialProfesional->administrador?->persona_id ?? 0)
: (int) ($credencialProfesional->profesional?->persona_id ?? 0),
$rol === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL',
(string) $request->ip(),
'Cerró sesión',
($rol === 'ADMIN' ? 'La administradora ID ' . (int) ($credencialProfesional->administrador?->id ?? 0) : 'El profesional ID ' . (int) ($credencialProfesional->profesional?->id ?? 0)) . ' cerró sesión.'
);
$credencialProfesional->token = null;
$credencialProfesional->fecha_hora = now();
$credencialProfesional->save();
return response()->json([
'success' => true,
'message' => 'Logout exitoso',
], 200);
}
return response()->json([
'success' => false,
'message' => 'Token invalido',
], 401);
}
private function credencialValida(string $contraIngresada, string $contraGuardada): bool
{
if ($contraIngresada === $contraGuardada) {
return true;
}
return Hash::check($contraIngresada, $contraGuardada);
}
private function limpiarThrottle(Request $request, string $prefijo): void
{
RateLimiter::clear(md5($prefijo . $request->ip()));
}
private function registrarAccionSeguridad(?int $personaId, string $rol, string $ipOrigen, string $accionDescripcion, string $descripcion): void
{
if (!$personaId) {
return;
}
$accion = AccionLog::query()->firstWhere('descripcion', $accionDescripcion);
if (!$accion) {
return;
}
LogSeguridad::create([
'descripcion' => $descripcion,
'fechahora' => now(),
'IPorigen' => $ipOrigen,
'rol' => $rol,
'persona_id' => (int) $personaId,
'accion_id' => (int) $accion->id,
]);
}
}
@@ -1,79 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\EstadoProfesional;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EstadoProfesionalController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(): JsonResponse
{
$items = EstadoProfesional::all();
return response()->json([
'success' => true,
'data' => $items,
'message' => 'Registros obtenidos correctamente',
], 200);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
{
$payload = $request->only((new EstadoProfesional())->getFillable());
$estadoProfesional = EstadoProfesional::create($payload);
return response()->json([
'success' => true,
'data' => $estadoProfesional,
'message' => 'Registro creado correctamente',
], 201);
}
/**
* Display the specified resource.
*/
public function show(EstadoProfesional $estadoProfesional): JsonResponse
{
return response()->json([
'success' => true,
'data' => $estadoProfesional,
'message' => 'Registro obtenido correctamente',
], 200);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, EstadoProfesional $estadoProfesional): JsonResponse
{
$payload = $request->only((new EstadoProfesional())->getFillable());
$estadoProfesional->update($payload);
return response()->json([
'success' => true,
'data' => $estadoProfesional,
'message' => 'Registro actualizado correctamente',
], 200);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(EstadoProfesional $estadoProfesional): JsonResponse
{
$estadoProfesional->delete();
return response()->json([
'success' => true,
'message' => 'Registro eliminado correctamente',
], 200);
}
}
+23 -20
View File
@@ -3,7 +3,6 @@
namespace App\Http\Controllers;
use App\Models\EstadoTurno;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EstadoTurnoController extends Controller
@@ -11,41 +10,43 @@ class EstadoTurnoController extends Controller
/**
* Display a listing of the resource.
*/
public function index(): JsonResponse
public function index() : JsonResponse
{
$items = EstadoTurno::all();
$estadoTurnos = EstadoTurno::all();
return response()->json([
'success' => true,
'data' => $items,
'message' => 'Registros obtenidos correctamente',
'data' => $estadoTurnos,
'message' => 'Estados de turno obtenidos correctamente'
], 200);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
public function store(Request $request) : JsonResponse
{
$payload = $request->only((new EstadoTurno())->getFillable());
$estadoTurno = EstadoTurno::create($payload);
$validated = $request->validate([
'descripcion' => 'required|string|max:255|unique:estado_turnos,descripcion',
]);
$estadoTurno = EstadoTurno::create($validated);
return response()->json([
'success' => true,
'data' => $estadoTurno,
'message' => 'Registro creado correctamente',
'message' => 'Estado de turno creado correctamente'
], 201);
}
/**
* Display the specified resource.
*/
public function show(EstadoTurno $estadoTurno): JsonResponse
public function show(EstadoTurno $estadoTurno) : JsonResponse
{
return response()->json([
'success' => true,
'data' => $estadoTurno,
'message' => 'Registro obtenido correctamente',
'message' => 'Estado de turno obtenido correctamente'
], 200);
}
@@ -54,26 +55,28 @@ class EstadoTurnoController extends Controller
*/
public function update(Request $request, EstadoTurno $estadoTurno): JsonResponse
{
$payload = $request->only((new EstadoTurno())->getFillable());
$estadoTurno->update($payload);
$validated = $request->validate([
'descripcion' => 'required|string|max:255|unique:estado_turnos,descripcion,' . $estadoTurno->id,
]);
$estadoTurno->update($validated);
return response()->json([
'success' => true,
'data' => $estadoTurno,
'message' => 'Registro actualizado correctamente',
], 200);
'message' => 'Estado de turno actualizado correctamente'
], 200);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(EstadoTurno $estadoTurno): JsonResponse
public function destroy(EstadoTurno $estadoTurno) : JsonResponse
{
$estadoTurno->delete();
return response()->json([
'success' => true,
'message' => 'Registro eliminado correctamente',
], 200);
'message' => 'Estado de turno eliminado correctamente'
}
}
}
+53 -8
View File
@@ -6,6 +6,8 @@ use App\Models\Profesional;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Http\Controllers\LogSeguridadController;
class ProfesionalController extends Controller
{
/**
@@ -22,21 +24,54 @@ class ProfesionalController extends Controller
], 200);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request): JsonResponse
{
$payload = $request->only((new Profesional())->getFillable());
$profesional = Profesional::create($payload);
public function store(Request $request): JsonResponse
{
// 1. Validamos los datos de entrada
$validated = $request->validate([
'inicio' => 'required|date_format:Y-m-d H:i:s',
'correo' => 'nullable|email|max:255',
'nombrecompleto' => 'nullable|string|max:255',
'descripcion' => 'required|string',
'cliente_id' => 'nullable|exists:clientes,id',
'estadoturno_id' => 'required|exists:estadosturnos,id',
'agenda_id' => 'required|exists:agendas,id',
'profesional_id' => 'required|exists:profesionales,id',
'servicio_id' => 'required|exists:servicios,id',
'modalidad_id' => 'required|exists:modalidades,id',
]);
// 2. Verificamos si el profesional ya tiene un turno en ese "inicio"
$existeTurno = Turno::where('profesional_id', $validated['profesional_id'])
->where('inicio', $validated['inicio'])
->whereIn('estadoturno_id', [1, 2]) // 1: Pendiente, 2: Confirmado
->exists();
if ($existeTurno) {
return response()->json([
'success' => true,
'data' => $profesional,
'message' => 'Registro creado correctamente',
], 201);
'success' => false,
'message' => 'Horario no disponible.',
], 422); // Error de validación lógica
}
$turno = Turno::create($validated);
// 3. Registramos el evento en el log de seguridad
$personaId = auth()->check() ? auth()->user()->persona->id : null;
LogSeguridadController::registrarAccion('Creacion de turno ID: ' . $turno->id, 'Profesional', 17, $personaId);
return response()->json([
'success' => true,
'data' => $turno,
'message' => 'Turno agendado correctamente para el ' . $turno->inicio->format('d/m/Y H:i'),
], 201);
}
/**
* Display the specified resource.
*/
@@ -76,4 +111,14 @@ class ProfesionalController extends Controller
'message' => 'Registro eliminado correctamente',
], 200);
}
public function aceptarFormulario(Formulario $formulario): JsonResponse
{
$turno->update(['estadoturno_id' => 2]);
return response()->json([
'success' => true,
'message' => 'Turno aceptado correctamente',
], 200);
}
}
+55
View File
@@ -76,4 +76,59 @@ class TurnoController extends Controller
'message' => 'Registro eliminado correctamente',
], 200);
}
public function confirmar(Turno $turno): JsonResponse
{
$turno->confirmar();
return response()->json([
'success' => true,
'data' => $turno,
'message' => 'Turno confirmado correctamente',
], 200);
}
public function cancelar(Turno $turno): JsonResponse
{
$turno->cancelar();
return response()->json([
'success' => true,
'data' => $turno,
'message' => 'Turno cancelado correctamente',
], 200);
}
public function reprogramar(Turno $turno): JsonResponse
{
$turno->reprogramar();
return response()->json([
'success' => true,
'data' => $turno,
'message' => 'Turno reprogramado correctamente',
], 200);
}
public function clienteAusente(Turno $turno): JsonResponse
{
$turno->clienteAusente();
return response()->json([
'success' => true,
'data' => $turno,
'message' => 'Turno marcado como cliente ausente',
], 200);
}
public function clientePresente(Turno $turno): JsonResponse
{
$turno->clientePresente();
return response()->json([
'success' => true,
'data' => $turno,
'message' => 'Turno marcado como cliente presente',
], 200);
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$entornoLocal = app()->environment(['local', 'development']);
$sesionAutenticada = (bool) $request->session()->get('cliente_auth', false)
|| (bool) $request->session()->get('personal_auth', false);
$styleSrc = $entornoLocal
? "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net http:;"
: "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;";
$scriptSrc = $entornoLocal
? "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net http:;"
: "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;";
$connectSrc = $entornoLocal
? "connect-src 'self' http: https: ws: wss:;"
: "connect-src 'self';";
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; img-src 'self' data: https:; {$styleSrc} {$scriptSrc} font-src 'self' data: https:; {$connectSrc} frame-src 'self' https://maps.google.com https://www.google.com;"
);
if ($request->isSecure()) {
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
if ($sesionAutenticada) {
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0, private');
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Expires', 'Thu, 01 Jan 1970 00:00:00 GMT');
}
return $response;
}
}
+2
View File
@@ -14,6 +14,8 @@ class Administrador extends Model
'persona_id',
'dni',
'correo',
'pregunta_secreta_hash',
'respuesta_secreta_hash',
'credencialprofesional_id',
];
+506
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\DB;
class Agenda extends Model
{
@@ -40,4 +41,509 @@ class Agenda extends Model
{
return $this->hasMany(ModoVacaciones::class, 'agenda_id', 'id');
}
public function formularios()
{
return $this->belongsToMany(
\App\Models\Formulario::class,
'profesionales_formularios',
'profesional_id',
'formulario_id',
'profesional_id',
'id'
)->withPivot('estadoformulario')->withTimestamps();
}
public function estaDisponible($fecha, $hora, $agendaId = null) // Verificar si la agenda está disponible para una fecha y hora específicas
{
$agenda = $agendaId ? self::find($agendaId) : $this;
if (!$agenda) {
return false;
}
// Verificar si la fecha es un feriado
if ($agenda->feriado()->where('fecha', $fecha)->exists()) {
return false;
}
// Verificar si la fecha está dentro de un período de vacaciones
if ($agenda->modoVacaciones()->where('inicio', '<=', $fecha)->where('fin', '>=', $fecha)->exists()) {
return false;
}
// Verificar si el día de atención corresponde al día de la semana de la fecha
$diaSemana = date('N', strtotime($fecha)); // 1 (lunes) a 7 (domingo)
$diaAtencion = $agenda->diaDeAtencion()->where('dia_id', $diaSemana)->first();
if (!$diaAtencion) {
return false;
}
// Verificar si la hora está dentro del horario de atención
if (!$diaAtencion->horariosAtenciones()->where('horariocomienzo', '<=', $hora)->where('horariofin', '>=', $hora)->exists()) {
return false;
}
// Verificar si la hora no está dentro de un horario de receso
if ($diaAtencion->horariosRecesos()->where('comienzo', '<=', $hora)->where('fin', '>=', $hora)->exists()) {
return false;
}
// Verificar si ya existe un turno para esa fecha y hora
if ($agenda->turno()->where('inicio', $fecha . ' ' . $hora)->exists()) {
return false;
}
return true;
}
public function obtenerTurnoDisponible($idProfesional, $tipopreferencia = 'INDISTINTO', $diasPreferencia = []) //Devuelve el turno disponible más cercano según preferencias
{
// Inicializa estructuras de salida con valores por defecto.
$DiasDeAtenciones = array_fill(0, 7, array_fill(0, 5, null));
$tipo = null;
$recesos = [];
$vacaciones = [];
$feriados = [];
$turnoMasCercano = null;
// Busca la agenda asociada al profesional recibido por parámetro.
$agendaId = self::where('profesional_id', $idProfesional)->value('id');
// Si no existe agenda para ese profesional, devuelve la estructura vacía.
if (!$agendaId) {
return null;
}
// Obtiene días de atención y sus horarios AM/PM para la agenda encontrada.
// [fila][0]=dia_id, [fila][1]=inicio AM, [fila][2]=fin AM, [fila][3]=inicio PM, [fila][4]=fin PM
$filasAtencion = DB::table('diasdeatenciones as d')
->leftJoin('horariosatenciones as h', 'h.diadeatencion_id', '=', 'd.id')
->where('d.agenda_id', $agendaId)
->select('d.dia_id', 'h.horariocomienzo', 'h.horariofin', 'h.tipo')
->orderBy('d.dia_id')
->get();
// Recorre cada fila y la ubica en la matriz de 7x5 según día y tipo de horario.
foreach ($filasAtencion as $filaAtencion) {
$fila = (int) $filaAtencion->dia_id - 1;
// Ignora valores fuera del rango esperado de días (1 a 7).
if ($fila < 0 || $fila > 6) {
continue;
}
// Guarda el identificador del día en la primera columna.
$DiasDeAtenciones[$fila][0] = $filaAtencion->dia_id;
// Si no hay tipo de horario, salta al siguiente registro.
if ($filaAtencion->tipo === null) {
continue;
}
// Normaliza y guarda el tipo actual (AM o PM).
$tipoActual = strtoupper(trim((string) $filaAtencion->tipo));
$tipo = $tipoActual;
// Completa columnas de mañana (inicio y fin).
if ($tipoActual === 'AM') {
$DiasDeAtenciones[$fila][1] = $filaAtencion->horariocomienzo;
$DiasDeAtenciones[$fila][2] = $filaAtencion->horariofin;
}
// Completa columnas de tarde (inicio y fin).
if ($tipoActual === 'PM') {
$DiasDeAtenciones[$fila][3] = $filaAtencion->horariocomienzo;
$DiasDeAtenciones[$fila][4] = $filaAtencion->horariofin;
}
}
// Trae los recesos de la agenda y los transforma a una matriz [dia_id, comienzo, fin].
$recesos = DB::table('horariosrecesos as r')
->join('diasdeatenciones as d', 'd.id', '=', 'r.diadeatencion_id')
->where('d.agenda_id', $agendaId)
->select('d.dia_id', 'r.comienzo', 'r.fin')
->orderBy('d.dia_id')
->get()
->map(function ($receso) {
return [
$receso->dia_id,
$receso->comienzo,
$receso->fin,
];
})
->values()
->all();
// Trae períodos de vacaciones y los transforma a una matriz [inicio, fin].
$vacaciones = DB::table('modosvacaciones')
->where('agenda_id', $agendaId)
->select('inicio', 'fin')
->orderBy('inicio')
->get()
->map(function ($vacacion) {
return [
$vacacion->inicio,
$vacacion->fin,
];
})
->values()
->all();
// Trae todos los feriados de la agenda y los transforma en una matriz de fechas.
$feriados = DB::table('feriados')
->where('agenda_id', $agendaId)
->select('fecha')
->orderBy('fecha')
->pluck('fecha')
->values()
->all();
// Normaliza preferencia horaria (AM, PM o INDISTINTO).
$tipopreferencia = strtoupper(trim((string) $tipopreferencia));
if (!in_array($tipopreferencia, ['AM', 'PM', 'INDISTINTO'], true)) {
$tipopreferencia = 'INDISTINTO';
}
// Convierte los días preferidos en texto a ids de 1 (lunes) a 7 (domingo).
$mapaDias = [
'lunes' => 1,
'martes' => 2,
'miercoles' => 3,
'jueves' => 4,
'viernes' => 5,
'sabado' => 6,
'domingo' => 7,
];
$diasPreferidosIds = [];
foreach ((array) $diasPreferencia as $diaPreferido) {
$diaNormalizado = strtolower(trim((string) $diaPreferido));
$diaNormalizado = strtr($diaNormalizado, [
'á' => 'a',
'é' => 'e',
'í' => 'i',
'ó' => 'o',
'ú' => 'u',
]);
if (isset($mapaDias[$diaNormalizado])) {
$diasPreferidosIds[] = $mapaDias[$diaNormalizado];
}
}
$diasPreferidosIds = array_values(array_unique($diasPreferidosIds));
// Toma la duración de turno de la agenda; si no existe, usa 30 minutos.
$duracionTurno = (int) (self::where('id', $agendaId)->value('duracionturno') ?? 30);
if ($duracionTurno <= 0) {
$duracionTurno = 30;
}
// Define el punto de inicio de la búsqueda desde el día siguiente (para evitar asignar turnos inmediatos del día actual).
$baseTimestamp = strtotime('tomorrow');
// Crea índices rápidos para validar fechas bloqueadas y horarios ocupados.
$feriadosLookup = array_flip($feriados);
$recesosPorDia = [];
foreach ($recesos as $receso) {
$diaId = (int) $receso[0];
if (!isset($recesosPorDia[$diaId])) {
$recesosPorDia[$diaId] = [];
}
$recesosPorDia[$diaId][] = [$receso[1], $receso[2]];
}
$turnosOcupadosLookup = [];
$turnosOcupados = DB::table('turnos')
->where('agenda_id', $agendaId)
->where('inicio', '>=', date('Y-m-d', $baseTimestamp))
->select('inicio')
->get();
foreach ($turnosOcupados as $turnoOcupado) {
$tsTurno = strtotime((string) $turnoOcupado->inicio);
if ($tsTurno === false) {
continue;
}
$turnosOcupadosLookup[date('Y-m-d H:i:s', $tsTurno)] = true;
}
// Busca el primer turno disponible respetando tipo, días preferidos y reglas de agenda.
for ($offsetDias = 0; $offsetDias <= 120; $offsetDias++) {
$tsDia = strtotime(date('Y-m-d', $baseTimestamp) . ' +' . $offsetDias . ' day');
if ($tsDia === false) {
continue;
}
$fechaIterada = date('Y-m-d', $tsDia);
$diaId = (int) date('N', $tsDia);
if (!empty($diasPreferidosIds) && !in_array($diaId, $diasPreferidosIds, true)) {
continue;
}
if (isset($feriadosLookup[$fechaIterada])) {
continue;
}
$enVacaciones = false;
foreach ($vacaciones as $vacacion) {
if ($fechaIterada >= $vacacion[0] && $fechaIterada <= $vacacion[1]) {
$enVacaciones = true;
break;
}
}
if ($enVacaciones) {
continue;
}
$filaDia = $DiasDeAtenciones[$diaId - 1] ?? null;
if (!$filaDia || $filaDia[0] === null) {
continue;
}
$ventanas = [];
if (($tipopreferencia === 'AM' || $tipopreferencia === 'INDISTINTO') && $filaDia[1] !== null && $filaDia[2] !== null) {
$ventanas[] = ['tipo' => 'AM', 'inicio' => $filaDia[1], 'fin' => $filaDia[2]];
}
if (($tipopreferencia === 'PM' || $tipopreferencia === 'INDISTINTO') && $filaDia[3] !== null && $filaDia[4] !== null) {
$ventanas[] = ['tipo' => 'PM', 'inicio' => $filaDia[3], 'fin' => $filaDia[4]];
}
foreach ($ventanas as $ventana) {
$inicioVentanaTs = strtotime($fechaIterada . ' ' . $ventana['inicio']);
$finVentanaTs = strtotime($fechaIterada . ' ' . $ventana['fin']);
if ($inicioVentanaTs === false || $finVentanaTs === false || $inicioVentanaTs >= $finVentanaTs) {
continue;
}
for ($slotTs = $inicioVentanaTs; ($slotTs + ($duracionTurno * 60)) <= $finVentanaTs; $slotTs += ($duracionTurno * 60)) {
$slotFecha = date('Y-m-d', $slotTs);
$slotHora = date('H:i:s', $slotTs);
$slotDateTime = $slotFecha . ' ' . $slotHora;
$slotFinTs = $slotTs + ($duracionTurno * 60);
if (isset($turnosOcupadosLookup[$slotDateTime])) {
continue;
}
$solapaReceso = false;
foreach ($recesosPorDia[$diaId] ?? [] as $recesoDia) {
$recesoInicioTs = strtotime($slotFecha . ' ' . $recesoDia[0]);
$recesoFinTs = strtotime($slotFecha . ' ' . $recesoDia[1]);
if ($recesoInicioTs === false || $recesoFinTs === false) {
continue;
}
if ($slotTs < $recesoFinTs && $slotFinTs > $recesoInicioTs) {
$solapaReceso = true;
break;
}
}
if ($solapaReceso) {
continue;
}
if (!$this->estaDisponible($slotFecha, $slotHora, $agendaId)) {
continue;
}
$turnoMasCercano = [
'fecha' => $slotFecha,
'hora' => $slotHora,
'fechaHora' => $slotDateTime,
'tipo' => $ventana['tipo'],
'duracionMinutos' => $duracionTurno,
];
break 3;
}
}
}
// Devuelve solo la fecha/hora del turno más cercano o null si no hay disponibilidad.
return $turnoMasCercano['fechaHora'] ?? null;
}
public function crearDiaDeAtencion($diaId, $horarioComienzo, $horarioFin, $tipo)
{
$tipoNormalizado = strtoupper(trim((string) $tipo));
if (!in_array($tipoNormalizado, ['AM', 'PM'], true)) {
throw new \InvalidArgumentException('El tipo debe ser AM o PM.');
}
$horarioComienzoNormalizado = $this->normalizarHora($horarioComienzo, 'horarioComienzo');
$horarioFinNormalizado = $this->normalizarHora($horarioFin, 'horarioFin');
if (strtotime('1970-01-01 ' . $horarioComienzoNormalizado) >= strtotime('1970-01-01 ' . $horarioFinNormalizado)) {
throw new \InvalidArgumentException('horarioComienzo debe ser menor que horarioFin.');
}
return DB::transaction(function () use ($diaId, $tipoNormalizado, $horarioComienzoNormalizado, $horarioFinNormalizado) {
$diaAtencion = DiaDeAtencion::firstOrCreate(
['agenda_id' => $this->id, 'dia_id' => $diaId],
['descripcion' => 'Dia de atencion']
);
HorarioDeAtencion::updateOrCreate(
[
'diadeatencion_id' => $diaAtencion->id,
'tipo' => $tipoNormalizado,
],
[
'horariocomienzo' => $horarioComienzoNormalizado,
'horariofin' => $horarioFinNormalizado,
]
);
return $diaAtencion;
});
}
public function eliminarDiaDeAtencion($diaId)
{
return DB::transaction(function () use ($diaId) {
$diaAtencion = DiaDeAtencion::where('agenda_id', $this->id)->where('dia_id', $diaId)->first();
if ($diaAtencion) {
HorarioDeAtencion::where('diadeatencion_id', $diaAtencion->id)->delete();
HorarioReceso::where('diadeatencion_id', $diaAtencion->id)->delete();
$diaAtencion->delete();
}
});
}
public function crearModoVacaciones($inicio, $fin, $descripcion = null)
{
return ModoVacaciones::create([
'agenda_id' => $this->id,
'inicio' => $inicio,
'fin' => $fin,
'descripcion' => $descripcion,
]);
}
public function eliminarModoVacaciones($id)
{
$modoVacaciones = ModoVacaciones::where('agenda_id', $this->id)->where('id', $id)->first();
if ($modoVacaciones) {
$modoVacaciones->delete();
}
}
public function crearFeriado($fecha, $descripcion = null)
{
return Feriado::create([
'agenda_id' => $this->id,
'fecha' => $fecha,
'descripcion' => $descripcion,
]);
}
public function eliminarFeriado($id)
{
$feriado = Feriado::where('agenda_id', $this->id)->where('id', $id)->first();
if ($feriado) {
$feriado->delete();
}
}
public function guardarTurno($inicio, $correo = null, $nombrecompleto = null, $descripcion = null, $clienteId = null, $estadoturnoId = null, $profesionalId = null, $servicioId = null, $modalidadId = null, $agendaId = null)
{
$inicioNormalizado = $this->normalizarDatetime($inicio, 'inicio');
$turno = Turno::create([
'agenda_id' => $agendaId ?? $this->id,
'correo' => $correo,
'nombrecompleto' => $nombrecompleto,
'descripcion' => $descripcion,
'cliente_id' => $clienteId,
'estadoturno_id' => $estadoturnoId,
'profesional_id' => $profesionalId,
'servicio_id' => $servicioId,
'modalidad_id' => $modalidadId,
'inicio' => $inicioNormalizado,
]);
return $turno;
}
private function normalizarHora($valor, $campo)
{
if ($valor instanceof \DateTimeInterface) {
return $valor->format('H:i:s');
}
$timestamp = strtotime(trim((string) $valor));
if ($timestamp === false) {
throw new \InvalidArgumentException('El campo ' . $campo . ' debe ser una hora valida.');
}
return date('H:i:s', $timestamp);
}
private function normalizarDatetime($valor, $campo)
{
if ($valor instanceof \DateTimeInterface) {
return $valor->format('Y-m-d H:i:s');
}
$timestamp = strtotime(trim((string) $valor));
if ($timestamp === false) {
throw new \InvalidArgumentException('El campo ' . $campo . ' debe ser una fecha y hora valida.');
}
return date('Y-m-d H:i:s', $timestamp);
}
public function confirmarTurno($id)
{
$turno = $this->turno()->find($id);
if (!$turno) {
return null;
}
return $turno->confirmar();
}
public function cancelarTurno($id)
{
$turno = $this->turno()->find($id);
if (!$turno) {
return null;
}
return $turno->cancelar();
}
public function reprogramarTurno($id)
{
$turno = $this->turno()->find($id);
if (!$turno) {
return null;
}
return $turno->reprogramar();
}
public function marcarClienteAusente($id)
{
$turno = $this->turno()->find($id);
if (!$turno) {
return null;
}
return $turno->clienteAusente();
}
public function marcarClientePresente($id)
{
$turno = $this->turno()->find($id);
if (!$turno) {
return null;
}
return $turno->clientePresente();
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AsistenteSinRespuesta extends Model
{
public $timestamps = false;
protected $table = 'asistente_sin_respuesta';
protected $fillable = [
'consulta',
'revisado',
];
protected $casts = [
'revisado' => 'boolean',
'created_at' => 'datetime',
];
}
+1 -1
View File
@@ -11,7 +11,7 @@ class Baja extends Model
protected $table = 'bajas';
protected $fillable = [
'motivo',
'descripcion',
];
//tiene una
+1 -1
View File
@@ -44,7 +44,7 @@ class Cliente extends Model
public function profesionales()
{
return $this->belongsToMany(Profesional::class, 'profesional_cliente','cliente_id', 'profesional_id')
return $this->belongsToMany(Profesional::class, 'profesionales_clientes','cliente_id', 'profesional_id')
->withPivot('estadorelacion')
->withTimestamps();
}
+2
View File
@@ -15,6 +15,8 @@ class CredencialCliente extends Model
'correo',
'token',
'fecha_hora',
'reset_token',
'reset_expira_en',
];
//tiene un
+2
View File
@@ -16,6 +16,8 @@ class CredencialProfesional extends Model
'rol',
'token',
'fecha_hora',
'reset_token',
'reset_expira_en',
];
public function profesional()
+1 -2
View File
@@ -12,7 +12,6 @@ use HasFactory;
protected $table = 'diasdeatenciones';
protected $fillable = [
'descripcion',
'agenda_id',
'dia_id',
];
@@ -33,7 +32,7 @@ use HasFactory;
}
public function horariosAtenciones()
{
return $this->hasMany(HorarioAtencion::class, 'diadeatencion_id', 'id');
return $this->hasMany(HorarioDeAtencion::class, 'diadeatencion_id', 'id');
}
-22
View File
@@ -1,22 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class EstadoProfesional extends Model
{
use HasFactory;
protected $table = 'estadosprofesionales';
protected $fillable = [
'descripcion',
];
public function profesional()
{
return $this->hasMany(Profesional::class, 'estadoprofesional_id', 'id');
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class FaqAsistente extends Model
{
use HasFactory;
protected $table = 'faq_asistentes';
protected $fillable = [
'intencion',
'palabras_clave',
'respuesta',
'orden',
'activo',
];
protected $casts = [
'palabras_clave' => 'array',
'activo' => 'boolean',
];
}
+2 -1
View File
@@ -15,6 +15,7 @@ class Formulario extends Model
'nombrecompleto',
'correo',
'celular',
'ip_origen',
'estado',
'profesion_id',
'servicio_id',
@@ -56,7 +57,7 @@ class Formulario extends Model
return $this->belongsToMany(DiaPreferencia::class, 'formularios_diaspreferidos', 'formulario_id', 'diapreferencia_id');
}
public function horariopreferido()
public function horariosPreferidos()
{
return $this->belongsToMany(HorarioPreferencia::class, 'formularios_horariospreferidos', 'formulario_id', 'horariopreferencia_id');
}
+1
View File
@@ -13,6 +13,7 @@ class HorarioDeAtencion extends Model
protected $fillable = [
'horariocomienzo',
'horariofin',
'tipo',
'diadeatencion_id',
];
+3
View File
@@ -10,6 +10,7 @@ class LogSeguridad extends Model
use HasFactory;
protected $table = 'logseguridades';
public $timestamps = false;
protected $fillable = [
'descripcion',
@@ -18,6 +19,8 @@ class LogSeguridad extends Model
'rol',
'persona_id',
'accion_id',
'accion_descripcion',
'responsable_nombre',
];
//pertenece a
+5
View File
@@ -19,4 +19,9 @@ class Modalidad extends Model
return $this->hasMany(Formulario::class, 'modalidad_id', 'id');
}
public function turnos()
{
return $this->hasMany(Turno::class, 'modalidad_id', 'id');
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Notificacion extends Model
{
use HasFactory;
protected $table = 'notificaciones';
protected $primaryKey = 'notificacion_id';
public $timestamps = false;
protected $fillable = [
'tipo',
'mensaje_inicio',
'mensaje_final',
];
}
+5
View File
@@ -20,4 +20,9 @@ class Profesion extends Model
{
return $this->hasMany(Profesional::class, 'profesion_id', 'id');
}
public function profesionalesAsociados()
{
return $this->belongsToMany(Profesional::class, 'profesionales_profesiones', 'profesion_id', 'profesional_id');
}
}
+6 -7
View File
@@ -16,7 +16,6 @@ class Profesional extends Model
'correo',
'dni',
'credencialprofesional_id',
'estadoprofesional_id',
'persona_id',
'baja_id',
'profesion_id',
@@ -30,16 +29,16 @@ class Profesional extends Model
return $this->belongsTo(Profesion::class, 'profesion_id', 'id');
}
public function profesiones()
{
return $this->belongsToMany(Profesion::class, 'profesionales_profesiones', 'profesional_id', 'profesion_id');
}
public function credencialProfesional()
{
return $this->belongsTo(CredencialProfesional::class, 'credencialprofesional_id', 'id');
}
public function estadoProfesional()
{
return $this->belongsTo(EstadoProfesional::class, 'estadoprofesional_id', 'id');
}
public function persona()
{
return $this->belongsTo(Persona::class, 'persona_id');
@@ -86,7 +85,7 @@ class Profesional extends Model
public function clientes()
{
return $this->belongsToMany(Cliente::class, 'profesionales_cliente', 'profesional_id', 'cliente_id')
return $this->belongsToMany(Cliente::class, 'profesionales_clientes', 'profesional_id', 'cliente_id')
->withPivot('estadorelacion')
->withTimestamps();
}
+1
View File
@@ -15,6 +15,7 @@ class Servicio extends Model
'titulo',
'estado',
'descripcion',
'visibleenweb',
'contenidoweb_id',
'profesion_id',
'foto_id',
+47
View File
@@ -13,6 +13,7 @@ class Turno extends Model
protected $fillable = [
'inicio',
'correo',
'celular',
'nombrecompleto',
'descripcion',
'cliente_id',
@@ -20,6 +21,11 @@ class Turno extends Model
'agenda_id',
'profesional_id',
'servicio_id',
'modalidad_id',
];
protected $casts = [
'inicio' => 'datetime',
];
public function estadoTurno()
@@ -45,4 +51,45 @@ class Turno extends Model
{
return $this->belongsTo(Servicio::class, 'servicio_id', 'id');
}
public function modalidad()
{
return $this->belongsTo(Modalidad::class, 'modalidad_id', 'id');
}
public function confirmar()
{
$this->estadoturno_id = 1; // ID del estado Confirmado (dato maestro)
$this->save();
return $this;
}
public function cancelar()
{
$this->estadoturno_id = 2; // ID del estado Cancelado (dato maestro)
$this->save();
return $this;
}
public function reprogramar()
{
$this->estadoturno_id = 3; // ID del estado Reprogramado (dato maestro)
$this->save();
return $this;
}
public function clienteAusente()
{
$this->estadoturno_id = 4; // ID del estado Cliente Ausente (dato maestro)
$this->save();
return $this;
}
public function clientePresente()
{
$this->estadoturno_id = 5; // ID del estado Cliente Presente (dato maestro)
$this->save();
return $this;
}
}
+110 -1
View File
@@ -2,7 +2,15 @@
namespace App\Providers;
use App\Models\Bug;
use App\Models\Profesional;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
class AppServiceProvider extends ServiceProvider
{
@@ -19,6 +27,107 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
//
Paginator::useBootstrapFive();
RateLimiter::for('login-cliente-web', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('login-personal-web', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('login-cliente-api', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('login-personal-api', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('login-api-general', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('recuperar-cliente', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('recuperar-personal', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('recuperar-admin', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('recuperar-admin-pregunta', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('reportar-bugs', fn (Request $request) => Limit::perHour(5)->by($request->ip()));
RateLimiter::for('asistente-chat', fn (Request $request) => Limit::perMinute(20)->by($request->ip()));
View::composer('profesional.*', function ($view): void {
$credencialId = (int) session('personal_credencial_id', 0);
$profesionalId = 0;
if ($credencialId > 0) {
$profesionalId = (int) Profesional::query()
->where('credencialprofesional_id', $credencialId)
->value('id');
}
$formulariosPendientesCount = 0;
if ($profesionalId > 0) {
$formulariosPendientesCount = DB::table('profesionales_formularios as pf')
->join('formularios as f', 'f.id', '=', 'pf.formulario_id')
->where('pf.profesional_id', $profesionalId)
->whereRaw("LOWER(TRIM(pf.estado)) = 'pendiente'")
->whereRaw("LOWER(TRIM(f.estado)) = 'pendiente'")
->count();
}
$view->with('formulariosPendientesCount', $formulariosPendientesCount);
$notificacionesClaves = [];
if ($profesionalId > 0) {
$hoy = now()->toDateString();
$manana = now()->addDay()->toDateString();
$turnosHoy = DB::table('turnos as t')
->where('t.profesional_id', $profesionalId)
->whereDate('t.inicio', $hoy)
->select('t.nombrecompleto', 't.inicio')
->get();
foreach ($turnosHoy as $turno) {
$nombre = trim((string) ($turno->nombrecompleto ?? 'Sin nombre'));
$hora = $turno->inicio ? \Illuminate\Support\Carbon::parse((string) $turno->inicio)->format('H:i') : '-';
$titulo = 'Turno hoy a las ' . $hora . ' — ' . $nombre;
$fecha = 'Hoy ' . $hora;
$notificacionesClaves[] = base64_encode('turno_hoy|' . $titulo . '|' . $fecha);
}
$turnosManana = DB::table('turnos as t')
->where('t.profesional_id', $profesionalId)
->whereDate('t.inicio', $manana)
->select('t.nombrecompleto', 't.inicio')
->get();
foreach ($turnosManana as $turno) {
$nombre = trim((string) ($turno->nombrecompleto ?? 'Sin nombre'));
$hora = $turno->inicio ? \Illuminate\Support\Carbon::parse((string) $turno->inicio)->format('H:i') : '-';
$titulo = 'Turno mañana a las ' . $hora . ' — ' . $nombre;
$fecha = 'Mañana ' . $hora;
$notificacionesClaves[] = base64_encode('turno_manana|' . $titulo . '|' . $fecha);
}
$estadoCanceladoId = (int) DB::table('estadosturnos')->whereRaw('LOWER(TRIM(descripcion)) = ?', ['cancelado'])->value('id');
if ($estadoCanceladoId > 0) {
$turnosCancelados = DB::table('turnos as t')
->where('t.profesional_id', $profesionalId)
->where('t.estadoturno_id', $estadoCanceladoId)
->where('t.updated_at', '>=', now()->subDays(7))
->select('t.nombrecompleto', 't.inicio', 't.updated_at')
->get();
foreach ($turnosCancelados as $turno) {
$nombre = trim((string) ($turno->nombrecompleto ?? 'Sin nombre'));
$fecha = $turno->updated_at
? \Illuminate\Support\Carbon::parse((string) $turno->updated_at)->format('d/m/Y')
: '-';
$titulo = 'Turno cancelado — ' . $nombre;
$notificacionesClaves[] = base64_encode('turno_cancelado|' . $titulo . '|' . $fecha);
}
}
}
$notificacionesCount = count($notificacionesClaves);
$view->with('notificacionesCount', $notificacionesCount);
$view->with('notificacionesClaves', $notificacionesClaves);
});
View::composer('administrador.*', function ($view): void {
$bugsPendientesCount = Bug::query()
->whereRaw("LOWER(TRIM(estado)) = 'pendiente'")
->count();
$view->with('bugsPendientesCount', $bugsPendientesCount);
});
}
}
+97 -2
View File
@@ -1,8 +1,15 @@
<?php
use App\Models\Error;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Http\Request;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
@@ -11,8 +18,96 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
$middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//
$exceptions->report(function (Throwable $exception): void {
if ($exception instanceof ThrottleRequestsException
|| $exception instanceof ValidationException
|| $exception instanceof HttpResponseException) {
return;
}
if ($exception instanceof HttpExceptionInterface
&& in_array($exception->getStatusCode(), [401, 403, 404, 419, 422, 429], true)) {
return;
}
try {
$codigo = (string) $exception->getCode();
if ($codigo === '') {
$codigo = class_basename($exception);
}
$url = app()->runningInConsole()
? 'console'
: (string) request()?->fullUrl();
Error::query()->create([
'codigo' => mb_substr($codigo, 0, 255),
'mensaje' => mb_substr((string) ($exception->getMessage() ?: class_basename($exception)), 0, 65000),
'track_trace' => mb_substr($exception->getTraceAsString(), 0, 65000),
'url' => mb_substr($url !== '' ? $url : 'desconocida', 0, 255),
'fecha_hora' => now(),
]);
} catch (Throwable $loggingException) {
// Evita que un error al registrar el error rompa el flujo original.
}
});
$exceptions->render(function (ThrottleRequestsException $exception, Request $request) {
$retryAfter = (int) ($exception->getHeaders()['Retry-After'] ?? 60);
$retryAfter = max($retryAfter, 1);
$minutos = intdiv($retryAfter, 60);
$segundos = $retryAfter % 60;
$partesTiempo = [];
if ($minutos > 0) {
$partesTiempo[] = $minutos . ' minuto' . ($minutos === 1 ? '' : 's');
}
if ($segundos > 0 || empty($partesTiempo)) {
$partesTiempo[] = $segundos . ' segundo' . ($segundos === 1 ? '' : 's');
}
$mensaje = 'Demasiados intentos. Esperá ' . implode(' y ', $partesTiempo) . ' antes de volver a intentar.';
if ($request->expectsJson()) {
return response()->json([
'message' => $mensaje,
'retry_after' => $retryAfter,
], 429, $exception->getHeaders());
}
$sessionKey = $request->is('login/*') ? 'login_error' : 'recuperar_error';
return back()
->withInput($request->except([
'contra',
'contra_confirmation',
'contra_actual',
'contra_actual_secreta',
]))
->with($sessionKey, $mensaje);
});
$exceptions->render(function (TokenMismatchException $exception, Request $request) {
$mensaje = 'La sesión cambió en otra pestaña o expiró. Recargá la página y volvé a intentar.';
if ($request->expectsJson()) {
return response()->json([
'message' => $mensaje,
], 419);
}
return back()
->withInput($request->except([
'contra',
'contra_confirmation',
'contra_actual',
'contra_actual_secreta',
]))
->withErrors(['csrf' => $mensaje]);
});
})->create();
+3 -1
View File
@@ -7,8 +7,10 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
"laravel/tinker": "^2.10.1",
"spatie/laravel-backup": "^9.3"
},
"require-dev": {
"fakerphp/faker": "^1.23",
Generated
+883 -1
View File
@@ -4,8 +4,85 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c514d8f7b9fc5970bdd94287905ef584",
"content-hash": "6a842c6b26c34979722aa85fd18609e1",
"packages": [
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12|^13.0",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9.16|^10|^11.0",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2026-02-21T08:51:10+00:00"
},
{
"name": "brick/math",
"version": "0.14.8",
@@ -377,6 +454,161 @@
],
"time": "2024-02-05T11:56:58+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.5"
},
"time": "2026-03-03T13:54:37+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
},
"time": "2026-01-20T14:10:26+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4 || ^9.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
},
"time": "2026-01-02T16:01:13+00:00"
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.6.0",
@@ -2019,6 +2251,73 @@
],
"time": "2026-01-15T06:54:53+00:00"
},
{
"name": "masterminds/html5",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
},
"time": "2025-07-25T09:04:22+00:00"
},
{
"name": "monolog/monolog",
"version": "3.10.0",
@@ -3294,6 +3593,446 @@
},
"time": "2025-12-14T04:43:48+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "v9.3.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "1.12.32 || 2.1.32",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
"phpunit/phpunit": "8.5.52",
"rawr/phpunit-data-provider": "3.3.1",
"rector/rector": "1.2.10 || 2.2.8",
"rector/type-perfect": "1.0.0 || 2.1.0",
"squizlabs/php_codesniffer": "4.0.1",
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.4.x-dev"
}
},
"autoload": {
"files": [
"src/Rule/Rule.php",
"src/RuleSet/RuleContainer.php"
],
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0"
},
"time": "2026-03-03T17:31:43+00:00"
},
{
"name": "spatie/db-dumper",
"version": "3.8.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/db-dumper.git",
"reference": "eac3221fbe27fac51f388600d27b67b1b079406e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/db-dumper/zipball/eac3221fbe27fac51f388600d27b67b1b079406e",
"reference": "eac3221fbe27fac51f388600d27b67b1b079406e",
"shasum": ""
},
"require": {
"php": "^8.0",
"symfony/process": "^5.0|^6.0|^7.0|^8.0"
},
"require-dev": {
"pestphp/pest": "^1.22"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\DbDumper\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Dump databases",
"homepage": "https://github.com/spatie/db-dumper",
"keywords": [
"database",
"db-dumper",
"dump",
"mysqldump",
"spatie"
],
"support": {
"source": "https://github.com/spatie/db-dumper/tree/3.8.3"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
},
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2026-01-05T16:26:03+00:00"
},
{
"name": "spatie/laravel-backup",
"version": "9.3.6",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-backup.git",
"reference": "d378a07b580aa8bf440b50decdbab7b5d6f63c46"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-backup/zipball/d378a07b580aa8bf440b50decdbab7b5d6f63c46",
"reference": "d378a07b580aa8bf440b50decdbab7b5d6f63c46",
"shasum": ""
},
"require": {
"ext-zip": "^1.14.0",
"illuminate/console": "^10.10.0|^11.0|^12.0",
"illuminate/contracts": "^10.10.0|^11.0|^12.0",
"illuminate/events": "^10.10.0|^11.0|^12.0",
"illuminate/filesystem": "^10.10.0|^11.0|^12.0",
"illuminate/notifications": "^10.10.0|^11.0|^12.0",
"illuminate/support": "^10.10.0|^11.0|^12.0",
"league/flysystem": "^3.0",
"php": "^8.2",
"spatie/db-dumper": "^3.8",
"spatie/laravel-package-tools": "^1.6.2",
"spatie/laravel-signal-aware-command": "^1.2|^2.0",
"spatie/temporary-directory": "^2.0",
"symfony/console": "^6.0|^7.0",
"symfony/finder": "^6.0|^7.0"
},
"require-dev": {
"composer-runtime-api": "^2.0",
"ext-pcntl": "*",
"larastan/larastan": "^2.7.0|^3.0",
"laravel/slack-notification-channel": "^2.5|^3.0",
"league/flysystem-aws-s3-v3": "^2.0|^3.0",
"mockery/mockery": "^1.4",
"orchestra/testbench": "^8.0|^9.0|^10.0",
"pestphp/pest": "^1.20|^2.0|^3.0|^4.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.1",
"rector/rector": "^1.1"
},
"suggest": {
"laravel/slack-notification-channel": "Required for sending notifications via Slack"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Backup\\BackupServiceProvider"
]
}
},
"autoload": {
"files": [
"src/Helpers/functions.php"
],
"psr-4": {
"Spatie\\Backup\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "A Laravel package to backup your application",
"homepage": "https://github.com/spatie/laravel-backup",
"keywords": [
"backup",
"database",
"laravel-backup",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-backup/issues",
"source": "https://github.com/spatie/laravel-backup/tree/9.3.6"
},
"funding": [
{
"url": "https://github.com/sponsors/spatie",
"type": "github"
},
{
"url": "https://spatie.be/open-source/support-us",
"type": "other"
}
],
"time": "2025-11-05T11:25:01+00:00"
},
{
"name": "spatie/laravel-package-tools",
"version": "1.93.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-package-tools.git",
"reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7",
"reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7",
"shasum": ""
},
"require": {
"illuminate/contracts": "^10.0|^11.0|^12.0|^13.0",
"php": "^8.1"
},
"require-dev": {
"mockery/mockery": "^1.5",
"orchestra/testbench": "^8.0|^9.2|^10.0|^11.0",
"pestphp/pest": "^2.1|^3.1|^4.0",
"phpunit/php-code-coverage": "^10.0|^11.0|^12.0",
"phpunit/phpunit": "^10.5|^11.5|^12.5",
"spatie/pest-plugin-test-time": "^2.2|^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\LaravelPackageTools\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"role": "Developer"
}
],
"description": "Tools for creating Laravel packages",
"homepage": "https://github.com/spatie/laravel-package-tools",
"keywords": [
"laravel-package-tools",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-package-tools/issues",
"source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2026-02-21T12:49:54+00:00"
},
{
"name": "spatie/laravel-signal-aware-command",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-signal-aware-command.git",
"reference": "54dcc1efd152bfb3eb0faf56a5fc28569b864b5d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-signal-aware-command/zipball/54dcc1efd152bfb3eb0faf56a5fc28569b864b5d",
"reference": "54dcc1efd152bfb3eb0faf56a5fc28569b864b5d",
"shasum": ""
},
"require": {
"illuminate/contracts": "^11.0|^12.0|^13.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.4.3",
"symfony/console": "^7.0|^8.0"
},
"require-dev": {
"brianium/paratest": "^6.2|^7.0",
"ext-pcntl": "*",
"nunomaduro/collision": "^5.3|^6.0|^7.0|^8.0",
"orchestra/testbench": "^9.0|^10.0",
"pestphp/pest-plugin-laravel": "^1.3|^2.0|^3.0",
"phpunit/phpunit": "^9.5|^10|^11",
"spatie/laravel-ray": "^1.17"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Signal": "Spatie\\SignalAwareCommand\\Facades\\Signal"
},
"providers": [
"Spatie\\SignalAwareCommand\\SignalAwareCommandServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\SignalAwareCommand\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"role": "Developer"
}
],
"description": "Handle signals in artisan commands",
"homepage": "https://github.com/spatie/laravel-signal-aware-command",
"keywords": [
"laravel",
"laravel-signal-aware-command",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-signal-aware-command/issues",
"source": "https://github.com/spatie/laravel-signal-aware-command/tree/2.1.2"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2026-02-22T08:16:31+00:00"
},
{
"name": "spatie/temporary-directory",
"version": "2.3.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/temporary-directory.git",
"reference": "662e481d6ec07ef29fd05010433428851a42cd07"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/temporary-directory/zipball/662e481d6ec07ef29fd05010433428851a42cd07",
"reference": "662e481d6ec07ef29fd05010433428851a42cd07",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\TemporaryDirectory\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alex Vanderbist",
"email": "alex@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Easily create, use and destroy temporary directories",
"homepage": "https://github.com/spatie/temporary-directory",
"keywords": [
"php",
"spatie",
"temporary-directory"
],
"support": {
"issues": "https://github.com/spatie/temporary-directory/issues",
"source": "https://github.com/spatie/temporary-directory/tree/2.3.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
},
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2026-01-12T07:42:22+00:00"
},
{
"name": "symfony/clock",
"version": "v7.4.0",
@@ -5795,6 +6534,149 @@
],
"time": "2026-02-15T10:53:20+00:00"
},
{
"name": "thecodingmachine/safe",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^10",
"squizlabs/php_codesniffer": "^3.2"
},
"type": "library",
"autoload": {
"files": [
"lib/special_cases.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gettext.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/mysql.php",
"generated/mysqli.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rnp.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"classmap": [
"lib/DateTime.php",
"lib/DateTimeImmutable.php",
"lib/Exceptions/",
"generated/Exceptions/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
},
"funding": [
{
"url": "https://github.com/OskarStark",
"type": "github"
},
{
"url": "https://github.com/shish",
"type": "github"
},
{
"url": "https://github.com/silasjoisten",
"type": "github"
},
{
"url": "https://github.com/staabm",
"type": "github"
}
],
"time": "2026-02-04T18:08:13+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.4.0",
+4 -4
View File
@@ -65,7 +65,7 @@ return [
|
*/
'timezone' => 'UTC',
'timezone' => 'America/Argentina/Buenos_Aires',
/*
|--------------------------------------------------------------------------
@@ -78,11 +78,11 @@ return [
|
*/
'locale' => env('APP_LOCALE', 'en'),
'locale' => env('APP_LOCALE', 'es'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'es'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
'faker_locale' => env('APP_FAKER_LOCALE', 'es_AR'),
/*
|--------------------------------------------------------------------------
+344
View File
@@ -0,0 +1,344 @@
<?php
return [
'backup' => [
/*
* The name of this application. You can use this name to monitor
* the backups.
*/
'name' => env('APP_NAME', 'AbogadasDelLitoral'),
'source' => [
'files' => [
/*
* The list of directories and files that will be included in the backup.
*/
'include' => array_values(array_filter([
storage_path('app/public/fotos'),
storage_path('app/private/documentacion-clientes'),
public_path('images/profesionales'),
public_path('images/servicios'),
public_path('images/bugs'),
public_path('uploads/documentacion-clientes'),
filter_var(env('BACKUP_INCLUDE_ENV', false), FILTER_VALIDATE_BOOLEAN) ? base_path('.env') : null,
], static fn (mixed $path): bool => is_string($path) && (is_dir($path) || is_file($path)))),
/*
* These directories and files will be excluded from the backup.
*
* Directories used by the backup process will automatically be excluded.
*/
'exclude' => [],
/*
* Determines if symlinks should be followed.
*/
'follow_links' => false,
/*
* Determines if it should avoid unreadable folders.
*/
'ignore_unreadable_directories' => false,
/*
* This path is used to make directories in resulting zip-file relative
* Set to `null` to include complete absolute path
* Example: base_path()
*/
'relative_path' => base_path(),
],
/*
* The names of the connections to the databases that should be backed up
* MySQL, PostgreSQL, SQLite and Mongo databases are supported.
*
* The content of the database dump may be customized for each connection
* by adding a 'dump' key to the connection settings in config/database.php.
* E.g.
* 'mysql' => [
* ...
* 'dump' => [
* 'exclude_tables' => [
* 'table_to_exclude_from_backup',
* 'another_table_to_exclude'
* ]
* ],
* ],
*
* If you are using only InnoDB tables on a MySQL server, you can
* also supply the useSingleTransaction option to avoid table locking.
*
* E.g.
* 'mysql' => [
* ...
* 'dump' => [
* 'useSingleTransaction' => true,
* ],
* ],
*
* For a complete list of available customization options, see https://github.com/spatie/db-dumper
*/
'databases' => [
env('DB_CONNECTION', 'mysql'),
],
],
/*
* The database dump can be compressed to decrease disk space usage.
*
* Out of the box Laravel-backup supplies
* Spatie\DbDumper\Compressors\GzipCompressor::class.
*
* You can also create custom compressor. More info on that here:
* https://github.com/spatie/db-dumper#using-compression
*
* If you do not want any compressor at all, set it to null.
*/
'database_dump_compressor' => null,
/*
* If specified, the database dumped file name will contain a timestamp (e.g.: 'Y-m-d-H-i-s').
*/
'database_dump_file_timestamp_format' => null,
/*
* The base of the dump filename, either 'database' or 'connection'
*
* If 'database' (default), the dumped filename will contain the database name.
* If 'connection', the dumped filename will contain the connection name.
*/
'database_dump_filename_base' => 'database',
/*
* The file extension used for the database dump files.
*
* If not specified, the file extension will be .archive for MongoDB and .sql for all other databases
* The file extension should be specified without a leading .
*/
'database_dump_file_extension' => '',
'destination' => [
/*
* The compression algorithm to be used for creating the zip archive.
*
* If backing up only database, you may choose gzip compression for db dump and no compression at zip.
*
* Some common algorithms are listed below:
* ZipArchive::CM_STORE (no compression at all; set 0 as compression level)
* ZipArchive::CM_DEFAULT
* ZipArchive::CM_DEFLATE
* ZipArchive::CM_BZIP2
* ZipArchive::CM_XZ
*
* For more check https://www.php.net/manual/zip.constants.php and confirm it's supported by your system.
*/
'compression_method' => ZipArchive::CM_DEFAULT,
/*
* The compression level corresponding to the used algorithm; an integer between 0 and 9.
*
* Check supported levels for the chosen algorithm, usually 1 means the fastest and weakest compression,
* while 9 the slowest and strongest one.
*
* Setting of 0 for some algorithms may switch to the strongest compression.
*/
'compression_level' => 9,
/*
* The filename prefix used for the backup zip file.
*/
'filename_prefix' => '',
/*
* The disk names on which the backups will be stored.
*/
'disks' => [
...array_values(array_filter(array_map('trim', explode(',', env('BACKUP_DESTINATION_DISK', 'local'))))),
],
],
/*
* The directory where the temporary files will be stored.
*/
'temporary_directory' => storage_path('app/backup-temp'),
/*
* The password to be used for archive encryption.
* Set to `null` to disable encryption.
*/
'password' => env('BACKUP_ARCHIVE_PASSWORD') ?: null,
/*
* The encryption algorithm to be used for archive encryption.
* You can set it to `null` or `false` to disable encryption.
*
* When set to 'default', we'll use ZipArchive::EM_AES_256 if it is
* available on your system.
*/
'encryption' => env('BACKUP_ARCHIVE_PASSWORD') ? 'default' : false,
/*
* The number of attempts, in case the backup command encounters an exception
*/
'tries' => 1,
/*
* The number of seconds to wait before attempting a new backup if the previous try failed
* Set to `0` for none
*/
'retry_delay' => 0,
],
/*
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
* For Slack you need to install laravel/slack-notification-channel.
*
* You can also use your own notification classes, just make sure the class is named after one of
* the `Spatie\Backup\Notifications\Notifications` classes.
*/
'notifications' => [
'notifications' => [
\Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => [],
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class => [],
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class => [],
],
/*
* Here you can specify the notifiable to which the notifications should be sent. The default
* notifiable will use the variables specified in this config file.
*/
'notifiable' => \Spatie\Backup\Notifications\Notifiable::class,
'mail' => [
'to' => env('BACKUP_MAIL_TO', env('MAIL_FROM_ADDRESS', 'admin@example.com')),
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
],
'slack' => [
'webhook_url' => '',
/*
* If this is set to null the default channel of the webhook will be used.
*/
'channel' => null,
'username' => null,
'icon' => null,
],
'discord' => [
'webhook_url' => '',
/*
* If this is an empty string, the name field on the webhook will be used.
*/
'username' => '',
/*
* If this is an empty string, the avatar on the webhook will be used.
*/
'avatar_url' => '',
],
],
/*
* Here you can specify which backups should be monitored.
* If a backup does not meet the specified requirements the
* UnHealthyBackupWasFound event will be fired.
*/
'monitor_backups' => [
[
'name' => env('APP_NAME', 'laravel-backup'),
'disks' => array_values(array_filter(array_map('trim', explode(',', env('BACKUP_DESTINATION_DISK', 'local'))))),
'health_checks' => [
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => (int) env('BACKUP_MAX_AGE_DAYS', 7),
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => (int) env('BACKUP_MAX_STORAGE_MB', 5000),
],
],
/*
[
'name' => 'name of the second app',
'disks' => ['local', 's3'],
'health_checks' => [
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
],
],
*/
],
'cleanup' => [
/*
* The strategy that will be used to cleanup old backups. The default strategy
* will keep all backups for a certain amount of days. After that period only
* a daily backup will be kept. After that period only weekly backups will
* be kept and so on.
*
* No matter how you configure it the default strategy will never
* delete the newest backup.
*/
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
'default_strategy' => [
/*
* The number of days for which backups must be kept.
*/
'keep_all_backups_for_days' => (int) env('BACKUP_KEEP_ALL_DAYS', 7),
/*
* After the "keep_all_backups_for_days" period is over, the most recent backup
* of that day will be kept. Older backups within the same day will be removed.
* If you create backups only once a day, no backups will be removed yet.
*/
'keep_daily_backups_for_days' => (int) env('BACKUP_KEEP_DAILY_DAYS', 16),
/*
* After the "keep_daily_backups_for_days" period is over, the most recent backup
* of that week will be kept. Older backups within the same week will be removed.
* If you create backups only once a week, no backups will be removed yet.
*/
'keep_weekly_backups_for_weeks' => (int) env('BACKUP_KEEP_WEEKLY_WEEKS', 8),
/*
* After the "keep_weekly_backups_for_weeks" period is over, the most recent backup
* of that month will be kept. Older backups within the same month will be removed.
*/
'keep_monthly_backups_for_months' => (int) env('BACKUP_KEEP_MONTHLY_MONTHS', 4),
/*
* After the "keep_monthly_backups_for_months" period is over, the most recent backup
* of that year will be kept. Older backups within the same year will be removed.
*/
'keep_yearly_backups_for_years' => (int) env('BACKUP_KEEP_YEARLY_YEARS', 2),
/*
* After cleaning up the backups remove the oldest backup until
* this amount of megabytes has been reached.
* Set null for unlimited size.
*/
'delete_oldest_backups_when_using_more_megabytes_than' => (int) env('BACKUP_MAX_STORAGE_LIMIT_MB', 5000),
],
/*
* The number of attempts, in case the cleanup command encounters an exception
*/
'tries' => 1,
/*
* The number of seconds to wait before attempting a new cleanup if the previous try failed
* Set to `0` for none
*/
'retry_delay' => 0,
],
];
+8 -1
View File
@@ -16,7 +16,7 @@ return [
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
@@ -60,7 +60,13 @@ return [
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci',
]) : [],
'dump' => [
'dump_binary_path' => env('DB_DUMP_BINARY_PATH', PHP_OS_FAMILY === 'Windows' ? 'C:/xampp/mysql/bin/' : '/usr/bin/'),
'use_single_transaction' => true,
'timeout' => 60 * 5, // 5 minutos de tiempo límite
],
],
'mariadb' => [
@@ -80,6 +86,7 @@ return [
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci',
]) : [],
],
+13 -1
View File
@@ -6,6 +6,7 @@ use App\Models\Cliente;
use App\Models\CredencialCliente;
use App\Models\Persona;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Cliente>
@@ -30,8 +31,19 @@ class ClienteFactory extends Factory
'dni' => (string) $this->faker->unique()->numberBetween(20000000, 45000000),
'correo' => $this->faker->unique()->safeEmail(),
'persona_id' => Persona::factory(),
'baja_id' => null,
'baja_id' => 1,
'credencialcliente_id' => CredencialCliente::factory(),
];
}
public function configure(): static
{
return $this->afterCreating(function (Cliente $cliente) {
CredencialCliente::where('id', $cliente->credencialcliente_id)
->update([
'correo' => $cliente->correo,
'contra' => Hash::make($cliente->dni),
]);
});
}
}
@@ -28,7 +28,7 @@ class CredencialProfesionalFactory extends Factory
return [
'usuario' => $this->faker->unique()->userName(),
'contra' => Hash::make('password'),
'rol' => $this->faker->randomElement(['Administrador', 'Profesional']),
'rol' => 'Profesional',
'token' => null,
'fecha_hora' => null,
];
+11 -1
View File
@@ -29,13 +29,23 @@ class PersonaFactory extends Factory
$cuilPrefix = $this->faker->randomElement(['20', '27']);
$cuilSuffix = $this->faker->numberBetween(0, 9);
$fotoDefault = Foto::firstOrCreate(
['ruta' => 'images/avatar_default.png'],
[
'extension' => 'png',
'tamanio_bytes' => 136788,
'nombre' => 'avatar_default',
'mime_type' => 'image/png',
]
);
return [
'dni' => (string) $dni,
'nombre' => $this->faker->firstName(),
'apellido' => $this->faker->lastName(),
'cuil' => "{$cuilPrefix}{$dni}{$cuilSuffix}",
'fechanac' => $this->faker->dateTimeBetween('-60 years', '-18 years')->format('Y-m-d'),
'foto_id' => Foto::factory(),
'foto_id' => $fotoDefault->id,
];
}
}
+9 -3
View File
@@ -3,7 +3,6 @@
namespace Database\Factories;
use App\Models\CredencialProfesional;
use App\Models\EstadoProfesional;
use App\Models\Persona;
use App\Models\Profesional;
use App\Models\Profesion;
@@ -35,8 +34,15 @@ class ProfesionalFactory extends Factory
'persona_id' => Persona::factory(),
'profesion_id' => Profesion::factory(),
'credencialprofesional_id' => CredencialProfesional::factory(),
'estadoprofesional_id' => EstadoProfesional::factory(),
'baja_id' => null,
'baja_id' => 1,
];
}
public function configure(): static
{
return $this->afterCreating(function (Profesional $profesional) {
CredencialProfesional::where('id', $profesional->credencialprofesional_id)
->update(['usuario' => $profesional->dni . '-' . $profesional->profesion_id]);
});
}
}
+20 -2
View File
@@ -2,8 +2,11 @@
namespace Database\Factories;
use App\Models\Foto;
use App\Models\Servicio;
use App\Models\Profesion;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\DB;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Servicio>
@@ -24,9 +27,24 @@ class ServicioFactory extends Factory
*/
public function definition(): array
{
$fotoDefault = Foto::firstOrCreate(
['ruta' => 'images/Servicio.jpg'],
[
'extension' => 'jpg',
'tamanio_bytes' => 0,
'nombre' => 'Servicio',
'mime_type' => 'image/jpeg',
]
);
return [
'nombre' => $this->faker->words(3, true),
'descripcion' => $this->faker->sentence(12),
'titulo' => $this->faker->unique()->bs(),
'estado' => 'activo',
'descripcion' => $this->faker->sentence(12),
'visibleenweb' => 'si',
'contenidoweb_id' => DB::table('contenidoswebs')->value('id'),
'profesion_id' => Profesion::factory(),
'foto_id' => $fotoDefault->id,
];
}
}
@@ -14,7 +14,7 @@ return new class extends Migration
Schema::create('bajas', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->text('motivo')->nullable();
$table->text('descripcion');
});
}
@@ -25,15 +25,12 @@ return new class extends Migration
$table->foreignId('credencialprofesional_id')
->constrained('credencialesprofesionales');
$table->foreignId('estadoprofesional_id')
->constrained('estadosprofesionales');
$table->foreignId('persona_id')
->constrained('personas');
$table->foreignId('baja_id')
->nullable()
->constrained('bajas');
->default(1)
->constrained('bajas');
$table->unique(['profesion_id', 'matricula']);
});
@@ -26,8 +26,9 @@ return new class extends Migration
->constrained('personas')
->onDelete('cascade');
$table->foreignId('baja_id')->nullable()
->constrained('bajas')
$table->foreignId('baja_id')
->default(1)
->constrained('bajas')
->onDelete('cascade');
$table->unique(['dni','correo']);
@@ -14,7 +14,6 @@ return new class extends Migration
Schema::create('diasdeatenciones', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('descripcion');
$table->foreignId('agenda_id')
->constrained('agendas')
->onDelete('cascade');
@@ -16,6 +16,7 @@ return new class extends Migration
$table->timestamps();
$table->time('horariocomienzo');
$table->time('horariofin');
$table->string('tipo'); // AM o PM
$table->foreignId('diadeatencion_id')
->constrained('diasdeatenciones');
});
@@ -38,6 +38,10 @@ return new class extends Migration
$table->foreignId('servicio_id')
->constrained('servicios')
->onDelete('cascade');
$table->foreignId('modalidad_id')
->constrained('modalidades')
->onDelete('cascade');
});
}
@@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('bajas')->updateOrInsert(
['id' => 1],
[
'descripcion' => 'Activo',
'created_at' => now(),
'updated_at' => now(),
]
);
DB::table('bajas')->updateOrInsert(
['id' => 2],
[
'descripcion' => 'Baja',
'created_at' => now(),
'updated_at' => now(),
]
);
DB::statement('ALTER TABLE profesionales MODIFY baja_id BIGINT UNSIGNED NOT NULL DEFAULT 1');
DB::statement('ALTER TABLE clientes MODIFY baja_id BIGINT UNSIGNED NOT NULL DEFAULT 1');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::statement('ALTER TABLE profesionales MODIFY baja_id BIGINT UNSIGNED NOT NULL');
DB::statement('ALTER TABLE clientes MODIFY baja_id BIGINT UNSIGNED NOT NULL');
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasColumn('servicios', 'visibleenweb')) {
Schema::table('servicios', function (Blueprint $table) {
$table->text('visibleenweb')->default('si');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasColumn('servicios', 'visibleenweb')) {
Schema::table('servicios', function (Blueprint $table) {
$table->dropColumn('visibleenweb');
});
}
}
};
@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('profesionales_profesiones', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('profesional_id')
->constrained('profesionales')
->onDelete('cascade');
$table->foreignId('profesion_id')
->constrained('profesiones')
->onDelete('cascade');
$table->unique(['profesional_id', 'profesion_id'], 'profesionales_profesiones_unique');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('profesionales_profesiones');
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('faq_asistentes', function (Blueprint $table) {
$table->id();
$table->string('intencion', 100)->nullable();
$table->json('palabras_clave')->nullable();
$table->text('respuesta');
$table->unsignedSmallInteger('orden')->default(0);
$table->boolean('activo')->default(true);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('faq_asistentes');
}
};
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('contenidoswebs', function (Blueprint $table) {
$table->string('version')->default('1.0.0')->after('quienessomos');
});
DB::table('contenidoswebs')->update([
'version' => '1.0.0',
]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('contenidoswebs', function (Blueprint $table) {
$table->dropColumn('version');
});
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (Schema::hasColumn('diasdeatenciones', 'descripcion')) {
Schema::table('diasdeatenciones', function (Blueprint $table) {
$table->dropColumn('descripcion');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (!Schema::hasColumn('diasdeatenciones', 'descripcion')) {
Schema::table('diasdeatenciones', function (Blueprint $table) {
$table->string('descripcion')->nullable();
});
}
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasColumn('turnos', 'celular')) {
Schema::table('turnos', function (Blueprint $table) {
$table->string('celular')->nullable()->after('correo');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasColumn('turnos', 'celular')) {
Schema::table('turnos', function (Blueprint $table) {
$table->dropColumn('celular');
});
}
}
};
@@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (Schema::hasTable('credencialesclientes') && !Schema::hasColumn('credencialesclientes', 'session_id')) {
Schema::table('credencialesclientes', function (Blueprint $table) {
$table->string('session_id')->nullable()->after('token');
});
}
if (Schema::hasTable('credencialesprofesionales') && !Schema::hasColumn('credencialesprofesionales', 'session_id')) {
Schema::table('credencialesprofesionales', function (Blueprint $table) {
$table->string('session_id')->nullable()->after('token');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasTable('credencialesclientes') && Schema::hasColumn('credencialesclientes', 'session_id')) {
Schema::table('credencialesclientes', function (Blueprint $table) {
$table->dropColumn('session_id');
});
}
if (Schema::hasTable('credencialesprofesionales') && Schema::hasColumn('credencialesprofesionales', 'session_id')) {
Schema::table('credencialesprofesionales', function (Blueprint $table) {
$table->dropColumn('session_id');
});
}
}
};
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('notificaciones', function (Blueprint $table) {
$table->bigIncrements('notificacion_id');
$table->string('tipo');
$table->text('mensaje_inicio');
$table->text('mensaje_final');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('notificaciones');
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('credencialesclientes', function (Blueprint $table) {
$table->string('reset_token')->nullable()->after('token');
$table->datetime('reset_expira_en')->nullable()->after('reset_token');
});
}
public function down(): void
{
Schema::table('credencialesclientes', function (Blueprint $table) {
$table->dropColumn(['reset_token', 'reset_expira_en']);
});
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('credencialesprofesionales', function (Blueprint $table) {
$table->string('reset_token')->nullable()->after('token');
$table->datetime('reset_expira_en')->nullable()->after('reset_token');
});
}
public function down(): void
{
Schema::table('credencialesprofesionales', function (Blueprint $table) {
$table->dropColumn(['reset_token', 'reset_expira_en']);
});
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('administradores', function (Blueprint $table) {
$table->string('pregunta_secreta_hash')->nullable()->after('correo');
$table->string('respuesta_secreta_hash')->nullable()->after('pregunta_secreta_hash');
});
}
public function down(): void
{
Schema::table('administradores', function (Blueprint $table) {
$table->dropColumn(['pregunta_secreta_hash', 'respuesta_secreta_hash']);
});
}
};
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('formularios', function (Blueprint $table) {
$table->string('ip_origen', 45)->nullable()->after('celular');
});
}
public function down(): void
{
Schema::table('formularios', function (Blueprint $table) {
$table->dropColumn('ip_origen');
});
}
};
@@ -11,10 +11,8 @@ return new class extends Migration
*/
public function up(): void
{
Schema::create('estadosprofesionales', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('descripcion')->default('Activo');
Schema::table('servicios', function (Blueprint $table) {
$table->unsignedBigInteger('foto_id')->nullable()->change();
});
}
@@ -23,6 +21,8 @@ return new class extends Migration
*/
public function down(): void
{
Schema::dropIfExists('estadosprofesionales');
Schema::table('servicios', function (Blueprint $table) {
$table->unsignedBigInteger('foto_id')->nullable(false)->change();
});
}
};
@@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('logseguridades', function (Blueprint $table) {
$table->string('accion_descripcion')->nullable()->after('accion_id');
$table->string('responsable_nombre')->nullable()->after('persona_id');
});
DB::statement(<<<'SQL'
UPDATE logseguridades AS logs
LEFT JOIN accioneslogs AS acciones ON acciones.id = logs.accion_id
LEFT JOIN personas AS personas ON personas.id = logs.persona_id
SET
logs.accion_descripcion = COALESCE(logs.accion_descripcion, acciones.descripcion),
logs.responsable_nombre = COALESCE(
logs.responsable_nombre,
NULLIF(TRIM(CONCAT(COALESCE(personas.nombre, ''), ' ', COALESCE(personas.apellido, ''))), '')
)
SQL);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('logseguridades', function (Blueprint $table) {
$table->dropColumn(['accion_descripcion', 'responsable_nombre']);
});
}
};
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('accioneslogs')->updateOrInsert(
['descripcion' => 'Edito los datos del administrador'],
[
'created_at' => now(),
'updated_at' => now(),
]
);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
DB::table('accioneslogs')
->where('descripcion', 'Edito los datos del administrador')
->delete();
}
};
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('asistente_sin_respuesta', function (Blueprint $table) {
$table->id();
$table->text('consulta');
$table->boolean('revisado')->default(false);
$table->timestamp('created_at')->useCurrent();
});
}
public function down(): void
{
Schema::dropIfExists('asistente_sin_respuesta');
}
};
+48 -33
View File
@@ -13,50 +13,65 @@ class AccionLogSeeder extends Seeder
*/
public function run(): void
{
//IMPORTANTE: El orden de las acciones no se debe modificar, ya que se asocian a los logs por su ID. Agregar nuevas acciones al final del array.
$acciones = [
['descripcion' => 'Creación nuevo profesional'],
['descripcion' => 'Baja profesional'],
['descripcion' => 'Alta profesional'],
['descripcion' => 'Edición datos profesional'],
['descripcion' => 'Creación nuevo profesional'], //
['descripcion' => 'Baja profesional'], //
['descripcion' => 'Alta profesional'], //
['descripcion' => 'Edición datos profesional'], //
['descripcion' => 'Creación nuevo servicio'],
['descripcion' => 'Baja servicio'],
['descripcion' => 'Alta servicio'],
['descripcion' => 'Edición datos servicio'],
['descripcion' => 'Creación nuevo servicio'], //
['descripcion' => 'Baja servicio'], //
['descripcion' => 'Alta servicio'], //
['descripcion' => 'Edición datos servicio'], //
['descripcion' => 'Creación nueva profesion'],
['descripcion' => 'Baja profesion'],
['descripcion' => 'Alta profesion'],
['descripcion' => 'Edición datos profesion'],
['descripcion' => 'Creación nueva profesion'], //
['descripcion' => 'Baja profesion'], //
['descripcion' => 'Alta profesion'], //
['descripcion' => 'Edición datos profesion'], //
['descripcion'=> 'Creación nuevo cliente'],
['descripcion'=> 'Edición datos cliente'],
['descripcion'=> 'Agregó documentación cliente'],
['descripcion'=> 'Dar de baja cliente'],
['descripcion'=> 'Creación nuevo cliente'], //
['descripcion'=> 'Edición datos cliente'], //
['descripcion'=> 'Agregó documentación cliente'], //
['descripcion'=> 'Dar de baja cliente'], //
['descripcion'=> 'Asignó un turno'],
['descripcion'=> 'Canceló un turno'],
['descripcion'=> 'Reprogramó un turno'],
['descripcion'=> 'Asignó un turno'], //
['descripcion'=> 'Canceló un turno'], //
['descripcion'=> 'Reprogramó un turno'], //
['descripcion'=> 'Aceptó un caso'],
['descripcion'=> 'Rechazó un caso'],
['descripcion'=> 'Devolvió un caso'],
['descripcion'=> 'Aceptó un caso'], //
['descripcion'=> 'Rechazó un caso'], //
['descripcion'=> 'Devolvió un caso'],
['descripcion'=> 'Inició sesión'],
['descripcion'=> 'Cerró sesión'],
['descripcion'=> 'Inició sesión'], //
['descripcion'=> 'Cerró sesión'], //
['descripcion'=> 'Solicitud cambio de contraseña'],
['descripcion'=> 'Cambio de contraseña exitoso'],
['descripcion'=> 'Solicitud cambio de contraseña'], //
['descripcion'=> 'Cambio de contraseña exitoso'], //
['descripcion'=> 'Cambio de contraseña frustrado'],
//agregados despues
['descripcion'=> 'Dar de alta cliente'], //
['descripcion'=> 'Eliminó documentación cliente'], //
['descripcion'=> 'dio de baja relacion con cliente'], //
['descripcion'=> 'dio de alta relacion con cliente'], //
['descripcion'=> 'Cambio de DNI Cliente'], //
['descripcion'=> 'Cambio de DNI Profesional'], //
['descripcion'=> 'Edito los datos del administrador'], //
];
foreach($acciones as $accion)
{
DB::table('accioneslogs')->insert([
'descripcion' => $accion['descripcion'],
'created_at' => now(),
'updated_at' => now(),
]);
foreach ($acciones as $accion) {
DB::table('accioneslogs')->updateOrInsert(
['descripcion' => $accion['descripcion']],
[
'updated_at' => now(),
'created_at' => now(),
]
);
}
}
}
+85 -9
View File
@@ -2,9 +2,14 @@
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use App\Models\Administrador;
use App\Models\CredencialProfesional;
use App\Models\Foto;
use App\Models\Persona;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class AdministradorSeeder extends Seeder
{
/**
@@ -12,13 +17,84 @@ class AdministradorSeeder extends Seeder
*/
public function run(): void
{
$administrador = [
'persona_id' => 1,
'dni' => '40563707',
'correo' => 'CamyBelini@gmail.com',
'credencialprofesional_id' => 1,
];
$usuario = env('ADMIN_USUARIO', 'admin');
$passwordPlano = env('ADMIN_PASSWORD', 'admin1234');
$correo = env('ADMIN_CORREO', 'admin@abogadaslitoral.com');
$dni = env('ADMIN_DNI', '30000000');
$nombre = env('ADMIN_NOMBRE', 'Usuario');
$apellido = env('ADMIN_APELLIDO', 'Administrador');
$cuil = env('ADMIN_CUIL', '20-30000000-0');
$fechaNac = env('ADMIN_FECHANAC', '2026-01-01');
$preguntaSecreta = mb_strtolower(trim((string) env('ADMIN_PREGUNTA_SECRETA', 'Cual es el nombre de tu primera mascota')));
$respuestaSecreta = mb_strtolower(trim((string) env('ADMIN_RESPUESTA_SECRETA', 'admin')));
DB::table('administradores')->insert($administrador);
DB::transaction(function () use (
$apellido,
$correo,
$cuil,
$dni,
$fechaNac,
$nombre,
$passwordPlano,
$preguntaSecreta,
$respuestaSecreta,
$usuario
): void {
$foto = Foto::query()->firstOrCreate(
['ruta' => 'images/avatar_default.png'],
[
'extension' => 'png',
'nombre' => 'avatar_default',
'mime_type' => 'image/png',
'tamanio_bytes' => 136788,
]
);
$persona = Persona::query()->updateOrCreate(
['dni' => $dni],
[
'nombre' => $nombre,
'apellido' => $apellido,
'cuil' => $cuil,
'fechanac' => $fechaNac,
'foto_id' => $foto->id,
]
);
$credencial = CredencialProfesional::query()->updateOrCreate(
['usuario' => $usuario],
[
'contra' => Hash::make($passwordPlano),
'rol' => 'ADMIN',
]
);
$administrador = Administrador::query()
->where('persona_id', $persona->id)
->orWhere('dni', $dni)
->first();
if ($administrador) {
$administrador->update([
'persona_id' => $persona->id,
'dni' => $dni,
'correo' => $correo,
'pregunta_secreta_hash' => $preguntaSecreta,
'respuesta_secreta_hash' => Hash::make($respuestaSecreta),
'credencialprofesional_id' => $credencial->id,
]);
return;
}
Administrador::query()->create([
'persona_id' => $persona->id,
'dni' => $dni,
'correo' => $correo,
'pregunta_secreta_hash' => $preguntaSecreta,
'respuesta_secreta_hash' => Hash::make($respuestaSecreta),
'credencialprofesional_id' => $credencial->id,
]);
});
}
}
}
+15 -10
View File
@@ -12,17 +12,22 @@ class BajaSeeder extends Seeder
*/
public function run(): void
{
$bajas = [
['motivo' => 'Cliente no responde'],
['motivo' => 'Servicio cancelado'],
];
foreach ($bajas as $baja) {
DB::table('bajas')->insert([
'motivo' => $baja['motivo'],
DB::table('bajas')->updateOrInsert(
['id' => 1],
[
'descripcion' => 'Activo',
'created_at' => now(),
'updated_at' => now(),
]);
}
]
);
DB::table('bajas')->updateOrInsert(
['id' => 2],
[
'descripcion' => 'Baja',
'created_at' => now(),
'updated_at' => now(),
]
);
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace Database\Seeders;
use App\Models\Cliente;
use App\Models\Profesional;
use App\Models\Profesion;
use Illuminate\Database\Seeder;
use App\Models\Servicio;
use Illuminate\Support\Facades\DB;
class BulkDataSeeder extends Seeder
{
/**
* Seed large batches of synthetic data.
*/
public function run(): void
{
$clientes = max(0, (int) config('bulk_seed.clientes', (int) env('SEED_CLIENTES', 1000)));
$profesionales = max(0, (int) config('bulk_seed.profesionales', (int) env('SEED_PROFESIONALES', 100)));
$servicios = max(0, (int) config('bulk_seed.servicios', (int) env('SEED_SERVICIOS', 30)));
$profesiones = max(0, (int) config('bulk_seed.profesiones', (int) env('SEED_PROFESIONES', 4)));
// Profesiones nuevas
if ($profesiones > 0) {
Profesion::factory()->count($profesiones)->create();
}
$profesionIds = Profesion::query()->pluck('id')->all();
// Servicios
if ($servicios > 0 && count($profesionIds) > 0) {
$contenidoWebId = DB::table('contenidoswebs')->value('id');
Servicio::factory()
->count($servicios)
->state(fn () => [
'profesion_id' => $profesionIds[array_rand($profesionIds)],
'contenidoweb_id' => $contenidoWebId,
])
->create();
}
// Profesionales
$profesionalesCreados = collect();
if ($profesionales > 0 && count($profesionIds) > 0) {
$profesionalesCreados = Profesional::factory()
->count($profesionales)
->state(fn () => [
'profesion_id' => $profesionIds[array_rand($profesionIds)],
])
->create();
}
// Clientes
$clientesCreados = collect();
if ($clientes > 0) {
$clientesCreados = Cliente::factory()->count($clientes)->create();
}
// Relacion cliente-profesional: cada cliente queda asociado a 1..3 profesionales
if ($clientesCreados->isNotEmpty() && $profesionalesCreados->isNotEmpty()) {
$idsProfesionales = $profesionalesCreados->pluck('id')->values()->all();
$maxAsignables = min(3, count($idsProfesionales));
foreach ($clientesCreados as $cliente) {
$cantidadAsignaciones = random_int(1, $maxAsignables);
$idsSeleccionados = collect($idsProfesionales)
->shuffle()
->take($cantidadAsignaciones)
->values()
->all();
$payload = [];
foreach ($idsSeleccionados as $profesionalId) {
$payload[$profesionalId] = ['estadorelacion' => 'Activo'];
}
$cliente->profesionales()->syncWithoutDetaching($payload);
}
}
}
}
+11 -9
View File
@@ -2,21 +2,23 @@
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ContenidoWebSeeder extends Seeder
{
/**
* Run the database seeds.
*/
/**
* Run the database seeds.
*/
public function run(): void
{
$contenido = [
'quienessomos' => 'Somos un grupo de abogadas recibidas de la Universidad Nacional del Litoral que decidimos trabajar en conjunto para resolver cualquier consulta que caiga en nuestras manos',
];
DB::table('contenidoswebs')->insert('$contenido');
DB::table('contenidoswebs')->updateOrInsert(
['id' => 1],
[
'quienessomos' => 'Somos un grupo de abogadas recibidas de la Universidad Nacional del Litoral comprometidas con cada caso que llega a nuestro estudio jurídico. Desde Abogadas del Litoral, nuestra prioridad es siempre la satisfacción de nuestros clientes. No dudes en enviar un formulario con tu consulta y nosotros te asignaremos un turno para que puedas ser atendido o atendida.',
'created_at' => now(),
'updated_at' => now(),
]
);
}
}
@@ -1,22 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class CredencialClienteSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$credencial = [
'contra' => bcrypt('contraseña'),
'correo' => 'ficticio@gmail.com',
];
DB::table('credencialesclientes')->insert($credencial);
}
}
@@ -1,40 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class CredencialProfesionalSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$credenciales = [[
'usuario' => 'Administrador-Camila',
'contra' => bcrypt('AbogadasDelLitoral-2026'),
'rol' => 'Administrador',
],
[
'usuario' => '43293244-2', //'DNI de la persona'+'-'+'codigo de profesion'
'contra' => bcrypt('contraseñaluciano'),
'rol' => 'Profesional',
],
[
'usuario' => '40563707-1', //'DNI de la persona'+'-'+'codigo de profesion'
'contra' => bcrypt('contraseñacamila'),
'rol' => 'Profesional',
]];
foreach($credenciales as $credencial){
DB::table('credencialesprofesionales')->insert([
'usuario' => $credencial['usuario'],
'contra' => $credencial['contra'],
'rol' => $credencial['rol'],
'created_at' => now(),
'updated_at' => now(),
]);
};
}
}
+14 -5
View File
@@ -23,19 +23,28 @@ class DatabaseSeeder extends Seeder
]);
$this->call([
ContenidoWebSeeder::class,
ProfesionSeeder::class,
EstadoTurnoSeeder::class,
ModalidadSeeder::class,
HorarioPreferenciaSeeder::class,
DiaPreferenciaSeeder::class,
AccionLogSeeder::class,
FaqAsistenteSeeder::class,
FotoSeeder::class,
PersonaSeeder::class,
EstadoProfesionalSeeder::class,
CredencialProfesionalSeeder::class,
ProfesionalSeeder::class,
AdministradorSeeder::class,
CredencialClienteSeeder::class,
ServicioSeeder::class,
BajaSeeder::class,
DiasSeeder::class,
]);
$bulkEnabled = (bool) config(
'bulk_seed.enabled',
filter_var(env('SEED_BULK_DATA', false), FILTER_VALIDATE_BOOLEAN)
);
if ($bulkEnabled) {
$this->call(BulkDataSeeder::class);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DiaPreferenciaSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$dias = [
['descripcion' => 'Lunes'],
['descripcion' => 'Martes'],
['descripcion' => 'Miercoles'],
['descripcion' => 'Jueves'],
['descripcion' => 'Viernes'],
['descripcion' => 'Sabado'],
['descripcion' => 'Domingo'],
['descripcion' => 'Indistinto'],
];
foreach ($dias as $dia) {
DB::table('diaspreferencias')->insert([
'descripcion' => $dia['descripcion'],
'formulario_id' => null,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
@@ -1,28 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class EstadoProfesionalSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$estados = [
['descripcion' => 'Activo'],
['descripcion' => 'Baja'],
];
foreach ($estados as $estado){
DB::table('estadosprofesionales')->insert([
'descripcion'=>$estado['descripcion'],
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
+3 -3
View File
@@ -14,11 +14,11 @@ class EstadoTurnoSeeder extends Seeder
public function run(): void
{
$estados = [
['descripcion' => 'Pendiente'],
['descripcion' => 'Confirmado'],
['descripcion' => 'Rechazado'],
['descripcion' => 'Cancelado'],
['descripcion' => 'Reprogramado']
['descripcion' => 'Reprogramado'],
['descripcion' => 'Cliente Ausente'],
['descripcion' => 'Cliente Presente']
];
foreach ($estados as $estado){
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class FaqAsistenteSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$items = [
[
'intencion' => 'ui_burbuja',
'palabras_clave' => json_encode([]),
'respuesta' => '¡Hola soy Clara!, ¿te puedo ayudar en algo?',
'orden' => 1,
'activo' => true,
],
[
'intencion' => 'ui_panel_inicio',
'palabras_clave' => json_encode([]),
'respuesta' => 'Hola, soy Clara, la asistente virtual de Abogadas del Litoral. Escribí una palabra clave con el tema con el que necesites ayuda.',
'orden' => 2,
'activo' => true,
],
[
'intencion' => 'ui_error',
'palabras_clave' => json_encode([]),
'respuesta' => 'Lo siento, no tengo una respuesta exacta para eso. Si tenes dudas, podes enviar un formulario para solicitar un turno con algún profesional o contactarnos por las redes',
'orden' => 3,
'activo' => true,
],
[
'intencion' => 'ui_nombre',
'palabras_clave' => json_encode([]),
'respuesta' => 'Clara',
'orden' => 4,
'activo' => true,
],
[
'intencion' => 'servicios',
'palabras_clave' => json_encode(['servicio', 'servicios']),
'respuesta' => 'Actualmente hay {cantidad_servicios} servicios activos en la web. En la sección Servicios podes ver el detalle de cada uno.',
'orden' => 10,
'activo' => true,
],
[
'intencion' => 'profesionales',
'palabras_clave' => json_encode(['profesional', 'profesionales', 'abogada', 'abogadas', 'equipo']),
'respuesta' => 'En este momento hay {cantidad_profesionales} profesionales activos. Los podes ver en la sección Equipo.',
'orden' => 20,
'activo' => true,
],
[
'intencion' => 'turnos',
'palabras_clave' => json_encode(['turno', 'turnos', 'consulta', 'formulario', 'reserva']),
'respuesta' => 'Para solicitar un turno, completá el formulario al final de esta página. Si ya tenes cuenta, también podés iniciar sesión para ver tus turnos. Recordá que solo podes iniciar sesión si sos cliente del estudio.',
'orden' => 30,
'activo' => true,
],
[
'intencion' => 'ubicacion',
'palabras_clave' => json_encode(['ubicacion', 'ubicación', 'direccion', 'dirección', 'donde', 'dónde']),
'respuesta' => 'La oficina está en Dr. Luis Pasteur 141, Paraná, Entre Ríos, Argentina.',
'orden' => 40,
'activo' => true,
],
[
'intencion' => 'horarios',
'palabras_clave' => json_encode(['horario', 'horarios', 'dias', 'días', 'dia', 'día']),
'respuesta' => 'Al cargar el formulario podés elegir días y horario de preferencia (AM, PM o indistinto).',
'orden' => 50,
'activo' => true,
],
[
'intencion' => 'contacto',
'palabras_clave' => json_encode(['contacto', 'instagram', 'redes']),
'respuesta' => 'Podés contactarte por Instagram desde el enlace en el pie de página o enviarnos el formulario de consulta.',
'orden' => 60,
'activo' => true,
],
[
'intencion' => 'login',
'palabras_clave' => json_encode(['login', 'sesion', 'sesión', 'ingresar', 'iniciar sesion', 'registro', 'registrarse']),
'respuesta' => 'Para ingresar, usá el botón Iniciar Sesión en la parte superior. Si sos cliente, entrás por login de cliente.',
'orden' => 70,
'activo' => true,
],
[
'intencion' => 'honorarios',
'palabras_clave' => json_encode(['honorarios', 'costo', 'precio', 'tarifa', 'honorario', 'costos', 'precios', 'tarifas']),
'respuesta' => 'Los honorarios son los estipulados por el Colegio de abogados de la provincia de Entre Ríos, consultanos a través de nuestros medios de contacto el monto actualizado.',
'orden' => 15,
'activo' => true,
],
];
foreach ($items as $item) {
DB::table('faq_asistentes')->updateOrInsert(
['intencion' => $item['intencion']],
[
'palabras_clave' => $item['palabras_clave'],
'respuesta' => $item['respuesta'],
'orden' => $item['orden'],
'activo' => $item['activo'],
'updated_at' => now(),
'created_at' => now(),
]
);
}
}
}
+11 -13
View File
@@ -13,18 +13,16 @@ class FotoSeeder extends Seeder
*/
public function run(): void
{
$fotos = [
['extension' => 'png', 'tamanio_bytes' => 136788, 'nombre' => 'default', 'mime_type' => 'image/png', 'ruta' => 'fotos/default.png'],
];
DB::table('fotos')->insert([
'extension' => $fotos[0]['extension'],
'tamanio_bytes' => $fotos[0]['tamanio_bytes'],
'nombre' => $fotos[0]['nombre'],
'mime_type' => $fotos[0]['mime_type'],
'ruta' => $fotos[0]['ruta'],
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('fotos')->updateOrInsert(
['ruta' => 'images/avatar_default.png'],
[
'extension' => 'png',
'tamanio_bytes' => 136788,
'nombre' => 'avatar_default',
'mime_type' => 'image/png',
'created_at' => now(),
'updated_at' => now(),
]
);
}
}
-52
View File
@@ -1,52 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class PersonaSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$personas = [[
'dni' => '40563707',
'nombre' => 'Camila Rosario',
'apellido' => 'Belini',
'cuil' => '27405637077',
'fechanac' => '1997-08-21',
'foto_id' => 1,
],
[
'dni' => '43293244',
'nombre' => 'Luciano Luca',
'apellido' => 'Belini',
'cuil' => '20432932444',
'fechanac' => '2001-04-05',
'foto_id' => 1,
],
[
'dni' => '40987654',
'nombre' => 'Cliente',
'apellido' => 'Ficticio',
'cuil' => '20409876544',
'fechanac' => '2000-01-01',
'foto_id' => 1,
]];
foreach($personas as $persona){
DB::table('personas')->insert([
'dni' => $persona['dni'],
'nombre' => $persona['nombre'],
'apellido' => $persona['apellido'],
'cuil' => $persona['cuil'],
'fechanac' => $persona['fechanac'],
'foto_id' => $persona['foto_id'],
'created_at' => now(),
'updated_at' => now(),
]);
}
}
}
-1
View File
@@ -15,7 +15,6 @@ class ProfesionSeeder extends Seeder
{
$profesiones = [
['titulo' => 'Abogacía', 'visible_en_formulario' => true],
['titulo' => 'Informático', 'visible_en_formulario' => false]
];
foreach ($profesiones as $profesion){
-40
View File
@@ -1,40 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ProfesionalSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$profesional = [[
'profesion_id' => 1,
'matricula' => '5678',
'correo' => 'CamyBelini@gmail.com',
'dni' => '40563707',
'persona_id' => 1,
'estadoprofesional_id' => 1,
'credencialprofesional_id' => 2,
'created_at' => now(),
'updated_at' => now(),
],
[
'profesion_id' => 2,
'matricula' => '1234',
'correo' => 'lucianobelini2015@gmail.com',
'dni' => '43293244',
'persona_id' => 2,
'estadoprofesional_id' => 1,
'credencialprofesional_id' => 3,
'created_at' => now(),
'updated_at' => now(),
]];
DB::table('profesionales')->insert($profesional);
}
}
+48 -14
View File
@@ -2,8 +2,8 @@
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ServicioSeeder extends Seeder
{
@@ -12,21 +12,55 @@ class ServicioSeeder extends Seeder
*/
public function run(): void
{
$contenidoWebId = (int) DB::table('contenidoswebs')->value('id');
DB::table('fotos')->updateOrInsert(
['ruta' => 'images/Servicio.jpg'],
[
'extension' => 'jpg',
'tamanio_bytes' => 0,
'nombre' => 'Servicio',
'mime_type' => 'image/jpeg',
'created_at' => now(),
'updated_at' => now(),
]
);
$fotoId = (int) DB::table('fotos')->where('ruta', 'images/Servicio.jpg')->value('id');
$profesionAbogaciaId = (int) DB::table('profesiones')->where('titulo', 'Abogacía')->value('id');
if ($contenidoWebId <= 0 || $fotoId <= 0 || $profesionAbogaciaId <= 0) {
return;
}
$servicios = [
['titulo' => 'Consulta Jurídica', 'estado' => 'Activo', 'descripcion' => 'Consiste en una reunión con el profesional en la cual se podrá hacer una consulta sobre un tema particular'],
['titulo' => 'Sucesión', 'estado' => 'Activo', 'descripcion' => 'El profesional asesorará al cliente en cuanto a sucesiones de bienes inmuebles de personas fallecidas'],
['titulo' => 'Penal', 'estado' => 'Baja', 'descripcion' => 'El profesional asesorará al cliente acusado de cometer un delito de caracter penal. O en caso de que el cliente quiera acusar a otra persona por cometer un delito de caracter penal'],
['titulo' => 'Servicio técnico/Informático', 'estado' => 'Alta', 'descripcion' => 'Servicio ofrecido por el personal de informática'],
[
'titulo' => 'Consulta jurídica',
'estado' => 'activo',
'descripcion' => 'Entrevista para evaluar el caso, orientar al cliente y definir los próximos pasos.',
'visibleenweb' => 'si',
'contenidoweb_id' => $contenidoWebId,
'profesion_id' => $profesionAbogaciaId,
'foto_id' => $fotoId,
],
];
foreach($servicios as $servicio)
{
DB::table('servicios')->insert([
'titulo'=>$profesion['titulo'],
'estado'=>$profesion['estado'],
'descripcion'=>$profesion['descripcion'],
]);
};
foreach ($servicios as $servicio) {
DB::table('servicios')->updateOrInsert(
[
'titulo' => $servicio['titulo'],
'profesion_id' => $servicio['profesion_id'],
],
[
'estado' => $servicio['estado'],
'descripcion' => $servicio['descripcion'],
'visibleenweb' => $servicio['visibleenweb'],
'contenidoweb_id' => $servicio['contenidoweb_id'],
'foto_id' => $servicio['foto_id'],
'created_at' => now(),
'updated_at' => now(),
]
);
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<?php
return [
'failed' => 'Las credenciales ingresadas no son correctas.',
'password' => 'La contraseña ingresada no es correcta.',
'throttle' => 'Demasiados intentos. Intentá nuevamente en :seconds segundos.',
];
+135
View File
@@ -0,0 +1,135 @@
<?php
return [
'accepted' => 'Debes aceptar :attribute.',
'accepted_if' => 'Debes aceptar :attribute cuando :other sea :value.',
'active_url' => 'El campo :attribute no es una URL válida.',
'after' => 'El campo :attribute debe ser una fecha posterior a :date.',
'after_or_equal' => 'El campo :attribute debe ser una fecha posterior o igual a :date.',
'alpha' => 'El campo :attribute solo puede contener letras.',
'alpha_dash' => 'El campo :attribute solo puede contener letras, números, guiones y guiones bajos.',
'alpha_num' => 'El campo :attribute solo puede contener letras y números.',
'array' => 'El campo :attribute debe ser un conjunto de valores.',
'before' => 'El campo :attribute debe ser una fecha anterior a :date.',
'before_or_equal' => 'El campo :attribute debe ser una fecha anterior o igual a :date.',
'between' => [
'array' => 'El campo :attribute debe tener entre :min y :max elementos.',
'file' => 'El archivo :attribute debe pesar entre :min y :max kilobytes.',
'numeric' => 'El campo :attribute debe estar entre :min y :max.',
'string' => 'El campo :attribute debe tener entre :min y :max caracteres.',
],
'boolean' => 'El campo :attribute debe ser verdadero o falso.',
'confirmed' => 'La confirmación de :attribute no coincide.',
'date' => 'El campo :attribute no es una fecha válida.',
'date_equals' => 'El campo :attribute debe ser una fecha igual a :date.',
'date_format' => 'El campo :attribute no coincide con el formato :format.',
'different' => 'El campo :attribute debe ser diferente de :other.',
'digits' => 'El campo :attribute debe tener :digits dígitos.',
'digits_between' => 'El campo :attribute debe tener entre :min y :max dígitos.',
'email' => 'El campo :attribute debe ser un correo válido.',
'exists' => 'El valor seleccionado para :attribute no es válido.',
'file' => 'El campo :attribute debe ser un archivo.',
'filled' => 'El campo :attribute es obligatorio.',
'gt' => [
'array' => 'El campo :attribute debe tener más de :value elementos.',
'file' => 'El archivo :attribute debe pesar más de :value kilobytes.',
'numeric' => 'El campo :attribute debe ser mayor que :value.',
'string' => 'El campo :attribute debe tener más de :value caracteres.',
],
'gte' => [
'array' => 'El campo :attribute debe tener :value elementos o más.',
'file' => 'El archivo :attribute debe pesar :value kilobytes o más.',
'numeric' => 'El campo :attribute debe ser mayor o igual a :value.',
'string' => 'El campo :attribute debe tener :value caracteres o más.',
],
'image' => 'El archivo :attribute debe ser una imagen.',
'in' => 'El valor seleccionado para :attribute no es válido.',
'integer' => 'El campo :attribute debe ser un número entero.',
'lt' => [
'array' => 'El campo :attribute debe tener menos de :value elementos.',
'file' => 'El archivo :attribute debe pesar menos de :value kilobytes.',
'numeric' => 'El campo :attribute debe ser menor que :value.',
'string' => 'El campo :attribute debe tener menos de :value caracteres.',
],
'lte' => [
'array' => 'El campo :attribute no debe tener más de :value elementos.',
'file' => 'El archivo :attribute no debe pesar más de :value kilobytes.',
'numeric' => 'El campo :attribute debe ser menor o igual a :value.',
'string' => 'El campo :attribute no debe tener más de :value caracteres.',
],
'max' => [
'array' => 'El campo :attribute no debe tener más de :max elementos.',
'file' => 'El archivo :attribute no debe pesar más de :max kilobytes.',
'numeric' => 'El campo :attribute no debe ser mayor que :max.',
'string' => 'El campo :attribute no debe tener más de :max caracteres.',
],
'mimes' => 'El archivo :attribute debe ser de tipo: :values.',
'mimetypes' => 'El archivo :attribute debe ser de tipo: :values.',
'min' => [
'array' => 'El campo :attribute debe tener al menos :min elementos.',
'file' => 'El archivo :attribute debe pesar al menos :min kilobytes.',
'numeric' => 'El campo :attribute debe ser como mínimo :min.',
'string' => 'El campo :attribute debe tener al menos :min caracteres.',
],
'multiple_of' => 'El campo :attribute debe ser múltiplo de :value.',
'not_in' => 'El valor seleccionado para :attribute no es válido.',
'numeric' => 'El campo :attribute debe ser numérico.',
'present' => 'El campo :attribute debe estar presente.',
'regex' => 'El formato del campo :attribute no es válido.',
'required' => 'El campo :attribute es obligatorio.',
'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.',
'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.',
'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.',
'required_with_all' => 'El campo :attribute es obligatorio cuando :values están presentes.',
'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.',
'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de :values está presente.',
'same' => 'El campo :attribute debe coincidir con :other.',
'size' => [
'array' => 'El campo :attribute debe contener :size elementos.',
'file' => 'El archivo :attribute debe pesar :size kilobytes.',
'numeric' => 'El campo :attribute debe ser :size.',
'string' => 'El campo :attribute debe tener :size caracteres.',
],
'string' => 'El campo :attribute debe ser una cadena de texto.',
'timezone' => 'El campo :attribute debe ser una zona horaria válida.',
'unique' => 'El campo :attribute ya está en uso.',
'uploaded' => 'No se pudo subir :attribute.',
'url' => 'El campo :attribute debe ser una URL válida.',
'uuid' => 'El campo :attribute debe ser un UUID válido.',
'custom' => [
'dni' => [
'unique' => 'El DNI ingresado ya pertenece a otra persona.',
'regex' => 'El DNI debe contener entre 7 y 20 caracteres alfanuméricos.',
],
'matricula' => [
'unique' => 'La matrícula ingresada ya existe para esa profesión.',
],
],
'attributes' => [
'dni' => 'DNI',
'cuil' => 'CUIL',
'correo' => 'correo',
'nombre' => 'nombre',
'apellido' => 'apellido',
'telefono' => 'teléfono',
'celular' => 'celular',
'matricula' => 'matrícula',
'fechanac' => 'fecha de nacimiento',
'contra' => 'contraseña',
'profesion_id' => 'profesión',
'servicio_id' => 'servicio',
'servicio_ids' => 'servicios',
'servicio_ids.*' => 'servicio',
'modalidad_id' => 'modalidad',
'fecha_turno' => 'fecha del turno',
'hora_turno' => 'hora del turno',
'descripcion' => 'descripción',
'titulo' => 'título',
'foto' => 'foto',
'visibleenweb' => 'visibilidad web',
'estado' => 'estado',
'usuario' => 'usuario',
],
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'رسالة استثناء: :message',
'exception_trace' => 'تتبع الإستثناء: :trace',
'exception_message_title' => 'رسالة استثناء',
'exception_trace_title' => 'تتبع الإستثناء',
'backup_failed_subject' => 'أخفق النسخ الاحتياطي لل :application_name',
'backup_failed_body' => 'مهم: حدث خطأ أثناء النسخ الاحتياطي :application_name',
'backup_successful_subject' => 'نسخ احتياطي جديد ناجح ل :application_name',
'backup_successful_subject_title' => 'نجاح النسخ الاحتياطي الجديد!',
'backup_successful_body' => 'أخبار عظيمة، نسخة احتياطية جديدة ل :application_name تم إنشاؤها بنجاح على القرص المسمى :disk_name.',
'cleanup_failed_subject' => 'فشل تنظيف النسخ الاحتياطي للتطبيق :application_name .',
'cleanup_failed_body' => 'حدث خطأ أثناء تنظيف النسخ الاحتياطية ل :application_name',
'cleanup_successful_subject' => 'تنظيف النسخ الاحتياطية ل :application_name تمت بنجاح',
'cleanup_successful_subject_title' => 'تنظيف النسخ الاحتياطية تم بنجاح!',
'cleanup_successful_body' => 'تنظيف النسخ الاحتياطية ل :application_name على القرص المسمى :disk_name تم بنجاح.',
'healthy_backup_found_subject' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name صحية',
'healthy_backup_found_subject_title' => 'النسخ الاحتياطية ل :application_name صحية',
'healthy_backup_found_body' => 'تعتبر النسخ الاحتياطية ل :application_name صحية. عمل جيد!',
'unhealthy_backup_found_subject' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية',
'unhealthy_backup_found_subject_title' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية. :problem',
'unhealthy_backup_found_body' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name غير صحية.',
'unhealthy_backup_found_not_reachable' => 'لا يمكن الوصول إلى وجهة النسخ الاحتياطي. :error',
'unhealthy_backup_found_empty' => 'لا توجد نسخ احتياطية لهذا التطبيق على الإطلاق.',
'unhealthy_backup_found_old' => 'تم إنشاء أحدث النسخ الاحتياطية في :date وتعتبر قديمة جدا.',
'unhealthy_backup_found_unknown' => 'عذرا، لا يمكن تحديد سبب دقيق.',
'unhealthy_backup_found_full' => 'النسخ الاحتياطية تستخدم الكثير من التخزين. الاستخدام الحالي هو :disk_usage وهو أعلى من الحد المسموح به من :disk_limit.',
'no_backups_info' => 'لم يتم عمل نسخ احتياطية حتى الآن',
'application_name' => 'اسم التطبيق',
'backup_name' => 'اسم النسخ الاحتياطي',
'disk' => 'القرص',
'newest_backup_size' => 'أحدث حجم للنسخ الاحتياطي',
'number_of_backups' => 'عدد النسخ الاحتياطية',
'total_storage_used' => 'إجمالي مساحة التخزين المستخدمة',
'newest_backup_date' => 'أحدث تاريخ النسخ الاحتياطي',
'oldest_backup_date' => 'أقدم تاريخ نسخ احتياطي',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Съобщение за изключение: :message',
'exception_trace' => 'Проследяване на изключение: :trace',
'exception_message_title' => 'Съобщение за изключение',
'exception_trace_title' => 'Проследяване на изключение',
'backup_failed_subject' => 'Неуспешно резервно копие на :application_name',
'backup_failed_body' => 'Важно: Възникна грешка при архивиране на :application_name',
'backup_successful_subject' => 'Успешно ново резервно копие на :application_name',
'backup_successful_subject_title' => 'Успешно ново резервно копие!',
'backup_successful_body' => 'Чудесни новини, ново резервно копие на :application_name беше успешно създадено на диска с име :disk_name.',
'cleanup_failed_subject' => 'Почистването на резервните копия на :application_name не бе успешно.',
'cleanup_failed_body' => 'Възникна грешка при почистването на резервните копия на :application_name',
'cleanup_successful_subject' => 'Почистването на архивите на :application_name е успешно',
'cleanup_successful_subject_title' => 'Почистването на резервните копия е успешно!',
'cleanup_successful_body' => 'Почистването на резервни копия на :application_name на диска с име :disk_name беше успешно.',
'healthy_backup_found_subject' => 'Резервните копия за :application_name на диск :disk_name са здрави',
'healthy_backup_found_subject_title' => 'Резервните копия за :application_name са здрави',
'healthy_backup_found_body' => 'Резервните копия за :application_name се считат за здрави. Добра работа!',
'unhealthy_backup_found_subject' => 'Важно: Резервните копия за :application_name не са здрави',
'unhealthy_backup_found_subject_title' => 'Важно: Резервните копия за :application_name не са здрави. :проблем',
'unhealthy_backup_found_body' => 'Резервните копия за :application_name на диск :disk_name не са здрави.',
'unhealthy_backup_found_not_reachable' => 'Дестинацията за резервни копия не може да бъде достигната. :грешка',
'unhealthy_backup_found_empty' => 'Изобщо няма резервни копия на това приложение.',
'unhealthy_backup_found_old' => 'Последното резервно копие, направено на :date, се счита за твърде старо.',
'unhealthy_backup_found_unknown' => 'За съжаление не може да се определи точна причина.',
'unhealthy_backup_found_full' => 'Резервните копия използват твърде много място за съхранение. Текущото използване е :disk_usage, което е по-високо от разрешеното ограничение на :disk_limit.',
'no_backups_info' => 'Все още не са правени резервни копия',
'application_name' => 'Име на приложението',
'backup_name' => 'Име на резервно копие',
'disk' => 'Диск',
'newest_backup_size' => 'Най-новият размер на резервно копие',
'number_of_backups' => 'Брой резервни копия',
'total_storage_used' => 'Общо използвано дисково пространство',
'newest_backup_date' => 'Най-нова дата на резервно копие',
'oldest_backup_date' => 'Най-старата дата на резервно копие',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'এক্সসেপশন বার্তা: :message',
'exception_trace' => 'এক্সসেপশন ট্রেস: :trace',
'exception_message_title' => 'এক্সসেপশন message',
'exception_trace_title' => 'এক্সসেপশন ট্রেস',
'backup_failed_subject' => ':application_name এর ব্যাকআপ ব্যর্থ হয়েছে।',
'backup_failed_body' => 'গুরুত্বপূর্ণঃ :application_name ব্যাক আপ করার সময় একটি ত্রুটি ঘটেছে।',
'backup_successful_subject' => ':application_name এর নতুন ব্যাকআপ সফল হয়েছে।',
'backup_successful_subject_title' => 'নতুন ব্যাকআপ সফল হয়েছে!',
'backup_successful_body' => 'খুশির খবর, :application_name এর নতুন ব্যাকআপ :disk_name ডিস্কে সফলভাবে তৈরি হয়েছে।',
'cleanup_failed_subject' => ':application_name ব্যাকআপগুলি সাফ করতে ব্যর্থ হয়েছে।',
'cleanup_failed_body' => ':application_name ব্যাকআপগুলি সাফ করার সময় একটি ত্রুটি ঘটেছে।',
'cleanup_successful_subject' => ':application_name এর ব্যাকআপগুলি সফলভাবে সাফ করা হয়েছে।',
'cleanup_successful_subject_title' => 'ব্যাকআপগুলি সফলভাবে সাফ করা হয়েছে!',
'cleanup_successful_body' => ':application_name এর ব্যাকআপগুলি :disk_name ডিস্ক থেকে সফলভাবে সাফ করা হয়েছে।',
'healthy_backup_found_subject' => ':application_name এর ব্যাকআপগুলি :disk_name ডিস্কে স্বাস্থ্যকর অবস্থায় আছে।',
'healthy_backup_found_subject_title' => ':application_name এর ব্যাকআপগুলি স্বাস্থ্যকর অবস্থায় আছে।',
'healthy_backup_found_body' => ':application_name এর ব্যাকআপগুলি স্বাস্থ্যকর বিবেচনা করা হচ্ছে। Good job!',
'unhealthy_backup_found_subject' => 'গুরুত্বপূর্ণঃ :application_name এর ব্যাকআপগুলি অস্বাস্থ্যকর অবস্থায় আছে।',
'unhealthy_backup_found_subject_title' => 'গুরুত্বপূর্ণঃ :application_name এর ব্যাকআপগুলি অস্বাস্থ্যকর অবস্থায় আছে। :problem',
'unhealthy_backup_found_body' => ':disk_name ডিস্কের :application_name এর ব্যাকআপগুলি অস্বাস্থ্যকর অবস্থায় আছে।',
'unhealthy_backup_found_not_reachable' => 'ব্যাকআপ গন্তব্যে পৌঁছানো যায় নি। :error',
'unhealthy_backup_found_empty' => 'এই অ্যাপ্লিকেশনটির কোনও ব্যাকআপ নেই।',
'unhealthy_backup_found_old' => 'সর্বশেষ ব্যাকআপ যেটি :date এই তারিখে করা হয়েছে, সেটি খুব পুরানো।',
'unhealthy_backup_found_unknown' => 'দুঃখিত, সঠিক কারণ নির্ধারণ করা সম্ভব হয়নি।',
'unhealthy_backup_found_full' => 'ব্যাকআপগুলি অতিরিক্ত স্টোরেজ ব্যবহার করছে। বর্তমান ব্যবহারের পরিমান :disk_usage যা অনুমোদিত সীমা :disk_limit এর বেশি।',
'no_backups_info' => 'কোনো ব্যাকআপ এখনও তৈরি হয়নি',
'application_name' => 'আবেদনের নাম',
'backup_name' => 'ব্যাকআপের নাম',
'disk' => 'ডিস্ক',
'newest_backup_size' => 'নতুন ব্যাকআপ আকার',
'number_of_backups' => 'ব্যাকআপের সংখ্যা',
'total_storage_used' => 'ব্যবহৃত মোট সঞ্চয়স্থান',
'newest_backup_date' => 'নতুন ব্যাকআপের তারিখ',
'oldest_backup_date' => 'পুরানো ব্যাকআপের তারিখ',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Zpráva výjimky: :message',
'exception_trace' => 'Stopa výjimky: :trace',
'exception_message_title' => 'Zpráva výjimky',
'exception_trace_title' => 'Stopa výjimky',
'backup_failed_subject' => 'Záloha :application_name neuspěla',
'backup_failed_body' => 'Důležité: Při záloze :application_name se vyskytla chyba',
'backup_successful_subject' => 'Úspěšná nová záloha :application_name',
'backup_successful_subject_title' => 'Úspěšná nová záloha!',
'backup_successful_body' => 'Dobrá zpráva, na disku jménem :disk_name byla úspěšně vytvořena nová záloha :application_name.',
'cleanup_failed_subject' => 'Vyčištění záloh :application_name neuspělo.',
'cleanup_failed_body' => 'Při čištění záloh :application_name se vyskytla chyba',
'cleanup_successful_subject' => 'Vyčištění záloh :application_name úspěšné',
'cleanup_successful_subject_title' => 'Vyčištění záloh bylo úspěšné!',
'cleanup_successful_body' => 'Vyčištění záloh :application_name na disku jménem :disk_name bylo úspěšné.',
'healthy_backup_found_subject' => 'Zálohy pro :application_name na disku :disk_name jsou zdravé',
'healthy_backup_found_subject_title' => 'Zálohy pro :application_name jsou zdravé',
'healthy_backup_found_body' => 'Zálohy pro :application_name jsou považovány za zdravé. Dobrá práce!',
'unhealthy_backup_found_subject' => 'Důležité: Zálohy pro :application_name jsou nezdravé',
'unhealthy_backup_found_subject_title' => 'Důležité: Zálohy pro :application_name jsou nezdravé. :problem',
'unhealthy_backup_found_body' => 'Zálohy pro :application_name na disku :disk_name jsou nezdravé.',
'unhealthy_backup_found_not_reachable' => 'Nelze se dostat k cíli zálohy. :error',
'unhealthy_backup_found_empty' => 'Tato aplikace nemá vůbec žádné zálohy.',
'unhealthy_backup_found_old' => 'Poslední záloha vytvořená dne :date je považována za příliš starou.',
'unhealthy_backup_found_unknown' => 'Omlouváme se, nemůžeme určit přesný důvod.',
'unhealthy_backup_found_full' => 'Zálohy zabírají příliš mnoho místa na disku. Aktuální využití disku je :disk_usage, což je vyšší než povolený limit :disk_limit.',
'no_backups_info' => 'Zatím nebyly vytvořeny žádné zálohy',
'application_name' => 'Název aplikace',
'backup_name' => 'Název zálohy',
'disk' => 'Disk',
'newest_backup_size' => 'Velikost nejnovější zálohy',
'number_of_backups' => 'Počet záloh',
'total_storage_used' => 'Celková využitá kapacita úložiště',
'newest_backup_date' => 'Datum nejnovější zálohy',
'oldest_backup_date' => 'Datum nejstarší zálohy',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Fejlbesked: :message',
'exception_trace' => 'Fejl trace: :trace',
'exception_message_title' => 'Fejlbesked',
'exception_trace_title' => 'Fejl trace',
'backup_failed_subject' => 'Backup af :application_name fejlede',
'backup_failed_body' => 'Vigtigt: Der skete en fejl under backup af :application_name',
'backup_successful_subject' => 'Ny backup af :application_name oprettet',
'backup_successful_subject_title' => 'Ny backup!',
'backup_successful_body' => 'Gode nyheder - der blev oprettet en ny backup af :application_name på disken :disk_name.',
'cleanup_failed_subject' => 'Oprydning af backups for :application_name fejlede.',
'cleanup_failed_body' => 'Der skete en fejl under oprydning af backups for :application_name',
'cleanup_successful_subject' => 'Oprydning af backups for :application_name gennemført',
'cleanup_successful_subject_title' => 'Backup oprydning gennemført!',
'cleanup_successful_body' => 'Oprydningen af backups for :application_name på disken :disk_name er gennemført.',
'healthy_backup_found_subject' => 'Alle backups for :application_name på disken :disk_name er OK',
'healthy_backup_found_subject_title' => 'Alle backups for :application_name er OK',
'healthy_backup_found_body' => 'Alle backups for :application_name er ok. Godt gået!',
'unhealthy_backup_found_subject' => 'Vigtigt: Backups for :application_name fejlbehæftede',
'unhealthy_backup_found_subject_title' => 'Vigtigt: Backups for :application_name er fejlbehæftede. :problem',
'unhealthy_backup_found_body' => 'Backups for :application_name på disken :disk_name er fejlbehæftede.',
'unhealthy_backup_found_not_reachable' => 'Backup destinationen kunne ikke findes. :error',
'unhealthy_backup_found_empty' => 'Denne applikation har ingen backups overhovedet.',
'unhealthy_backup_found_old' => 'Den seneste backup fra :date er for gammel.',
'unhealthy_backup_found_unknown' => 'Beklager, en præcis årsag kunne ikke findes.',
'unhealthy_backup_found_full' => 'Backups bruger for meget plads. Nuværende disk forbrug er :disk_usage, hvilket er mere end den tilladte grænse på :disk_limit.',
'no_backups_info' => 'Der blev ikke foretaget nogen sikkerhedskopier endnu',
'application_name' => 'Applikationens navn',
'backup_name' => 'Backup navn',
'disk' => 'Disk',
'newest_backup_size' => 'Nyeste backup-størrelse',
'number_of_backups' => 'Antal sikkerhedskopier',
'total_storage_used' => 'Samlet lagerplads brugt',
'newest_backup_date' => 'Nyeste backup-størrelse',
'oldest_backup_date' => 'Ældste backup-størrelse',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Fehlermeldung: :message',
'exception_trace' => 'Fehlerverfolgung: :trace',
'exception_message_title' => 'Fehlermeldung',
'exception_trace_title' => 'Fehlerverfolgung',
'backup_failed_subject' => 'Backup von :application_name konnte nicht erstellt werden',
'backup_failed_body' => 'Wichtig: Beim Backup von :application_name ist ein Fehler aufgetreten',
'backup_successful_subject' => 'Erfolgreiches neues Backup von :application_name',
'backup_successful_subject_title' => 'Erfolgreiches neues Backup!',
'backup_successful_body' => 'Gute Nachrichten, ein neues Backup von :application_name wurde erfolgreich erstellt und in :disk_name gepeichert.',
'cleanup_failed_subject' => 'Aufräumen der Backups von :application_name schlug fehl.',
'cleanup_failed_body' => 'Beim aufräumen der Backups von :application_name ist ein Fehler aufgetreten',
'cleanup_successful_subject' => 'Aufräumen der Backups von :application_name backups erfolgreich',
'cleanup_successful_subject_title' => 'Aufräumen der Backups erfolgreich!',
'cleanup_successful_body' => 'Aufräumen der Backups von :application_name in :disk_name war erfolgreich.',
'healthy_backup_found_subject' => 'Die Backups von :application_name in :disk_name sind gesund',
'healthy_backup_found_subject_title' => 'Die Backups von :application_name sind Gesund',
'healthy_backup_found_body' => 'Die Backups von :application_name wurden als gesund eingestuft. Gute Arbeit!',
'unhealthy_backup_found_subject' => 'Wichtig: Die Backups für :application_name sind nicht gesund',
'unhealthy_backup_found_subject_title' => 'Wichtig: Die Backups für :application_name sind ungesund. :problem',
'unhealthy_backup_found_body' => 'Die Backups für :application_name in :disk_name sind ungesund.',
'unhealthy_backup_found_not_reachable' => 'Das Backup Ziel konnte nicht erreicht werden. :error',
'unhealthy_backup_found_empty' => 'Es gibt für die Anwendung noch gar keine Backups.',
'unhealthy_backup_found_old' => 'Das letzte Backup am :date ist zu lange her.',
'unhealthy_backup_found_unknown' => 'Sorry, ein genauer Grund konnte nicht gefunden werden.',
'unhealthy_backup_found_full' => 'Die Backups verbrauchen zu viel Platz. Aktuell wird :disk_usage belegt, dass ist höher als das erlaubte Limit von :disk_limit.',
'no_backups_info' => 'Bisher keine Backups vorhanden',
'application_name' => 'Applikationsname',
'backup_name' => 'Backup Name',
'disk' => 'Speicherort',
'newest_backup_size' => 'Neuste Backup-Größe',
'number_of_backups' => 'Anzahl Backups',
'total_storage_used' => 'Gesamter genutzter Speicherplatz',
'newest_backup_date' => 'Neustes Backup',
'oldest_backup_date' => 'Ältestes Backup',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Exception message: :message',
'exception_trace' => 'Exception trace: :trace',
'exception_message_title' => 'Exception message',
'exception_trace_title' => 'Exception trace',
'backup_failed_subject' => 'Failed backup of :application_name',
'backup_failed_body' => 'Important: An error occurred while backing up :application_name',
'backup_successful_subject' => 'Successful new backup of :application_name',
'backup_successful_subject_title' => 'Successful new backup!',
'backup_successful_body' => 'Great news, a new backup of :application_name was successfully created on the disk named :disk_name.',
'cleanup_failed_subject' => 'Cleaning up the backups of :application_name failed.',
'cleanup_failed_body' => 'An error occurred while cleaning up the backups of :application_name',
'cleanup_successful_subject' => 'Clean up of :application_name backups successful',
'cleanup_successful_subject_title' => 'Clean up of backups successful!',
'cleanup_successful_body' => 'The clean up of the :application_name backups on the disk named :disk_name was successful.',
'healthy_backup_found_subject' => 'The backups for :application_name on disk :disk_name are healthy',
'healthy_backup_found_subject_title' => 'The backups for :application_name are healthy',
'healthy_backup_found_body' => 'The backups for :application_name are considered healthy. Good job!',
'unhealthy_backup_found_subject' => 'Important: The backups for :application_name are unhealthy',
'unhealthy_backup_found_subject_title' => 'Important: The backups for :application_name are unhealthy. :problem',
'unhealthy_backup_found_body' => 'The backups for :application_name on disk :disk_name are unhealthy.',
'unhealthy_backup_found_not_reachable' => 'The backup destination cannot be reached. :error',
'unhealthy_backup_found_empty' => 'There are no backups of this application at all.',
'unhealthy_backup_found_old' => 'The latest backup made on :date is considered too old.',
'unhealthy_backup_found_unknown' => 'Sorry, an exact reason cannot be determined.',
'unhealthy_backup_found_full' => 'The backups are using too much storage. Current usage is :disk_usage which is higher than the allowed limit of :disk_limit.',
'no_backups_info' => 'No backups were made yet',
'application_name' => 'Application name',
'backup_name' => 'Backup name',
'disk' => 'Disk',
'newest_backup_size' => 'Newest backup size',
'number_of_backups' => 'Number of backups',
'total_storage_used' => 'Total storage used',
'newest_backup_date' => 'Newest backup date',
'oldest_backup_date' => 'Oldest backup date',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Mensaje de la excepción: :message',
'exception_trace' => 'Traza de la excepción: :trace',
'exception_message_title' => 'Mensaje de la excepción',
'exception_trace_title' => 'Traza de la excepción',
'backup_failed_subject' => 'Copia de seguridad de :application_name fallida',
'backup_failed_body' => 'Importante: Ocurrió un error al realizar la copia de seguridad de :application_name',
'backup_successful_subject' => 'Se completó con éxito la copia de seguridad de :application_name',
'backup_successful_subject_title' => '¡Nueva copia de seguridad creada con éxito!',
'backup_successful_body' => 'Buenas noticias, una nueva copia de seguridad de :application_name fue creada con éxito en el disco llamado :disk_name.',
'cleanup_failed_subject' => 'La limpieza de copias de seguridad de :application_name falló.',
'cleanup_failed_body' => 'Ocurrió un error mientras se realizaba la limpieza de copias de seguridad de :application_name',
'cleanup_successful_subject' => 'La limpieza de copias de seguridad de :application_name se completó con éxito',
'cleanup_successful_subject_title' => '!Limpieza de copias de seguridad completada con éxito!',
'cleanup_successful_body' => 'La limpieza de copias de seguridad de :application_name en el disco llamado :disk_name se completo con éxito.',
'healthy_backup_found_subject' => 'Las copias de seguridad de :application_name en el disco :disk_name están en buen estado',
'healthy_backup_found_subject_title' => 'Las copias de seguridad de :application_name están en buen estado',
'healthy_backup_found_body' => 'Las copias de seguridad de :application_name se consideran en buen estado. ¡Buen trabajo!',
'unhealthy_backup_found_subject' => 'Importante: Las copias de seguridad de :application_name están en mal estado',
'unhealthy_backup_found_subject_title' => 'Importante: Las copias de seguridad de :application_name están en mal estado. :problem',
'unhealthy_backup_found_body' => 'Las copias de seguridad de :application_name en el disco :disk_name están en mal estado.',
'unhealthy_backup_found_not_reachable' => 'No se puede acceder al destino de la copia de seguridad. :error',
'unhealthy_backup_found_empty' => 'No existe ninguna copia de seguridad de esta aplicación.',
'unhealthy_backup_found_old' => 'La última copia de seguriad hecha en :date es demasiado antigua.',
'unhealthy_backup_found_unknown' => 'Lo siento, no es posible determinar la razón exacta.',
'unhealthy_backup_found_full' => 'Las copias de seguridad están ocupando demasiado espacio. El espacio utilizado actualmente es :disk_usage el cual es mayor que el límite permitido de :disk_limit.',
'no_backups_info' => 'Aún no se hicieron copias de seguridad',
'application_name' => 'Nombre de la aplicación',
'backup_name' => 'Nombre de la copia de seguridad',
'disk' => 'Disco',
'newest_backup_size' => 'Tamaño de copia de seguridad más reciente',
'number_of_backups' => 'Número de copias de seguridad',
'total_storage_used' => 'Almacenamiento total utilizado',
'newest_backup_date' => 'Fecha de la copia de seguridad más reciente',
'oldest_backup_date' => 'Fecha de la copia de seguridad más antigua',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'پیغام خطا: :message',
'exception_trace' => 'جزییات خطا: :trace',
'exception_message_title' => 'پیغام خطا',
'exception_trace_title' => 'جزییات خطا',
'backup_failed_subject' => 'پشتیبان‌گیری :application_name با خطا مواجه شد.',
'backup_failed_body' => 'پیغام مهم: هنگام پشتیبان‌گیری از :application_name خطایی رخ داده است. ',
'backup_successful_subject' => 'نسخه پشتیبان جدید :application_name با موفقیت ساخته شد.',
'backup_successful_subject_title' => 'پشتیبان‌گیری موفق!',
'backup_successful_body' => 'خبر خوب، به تازگی نسخه پشتیبان :application_name روی دیسک :disk_name با موفقیت ساخته شد. ',
'cleanup_failed_subject' => 'پاک‌‌سازی نسخه پشتیبان :application_name انجام نشد.',
'cleanup_failed_body' => 'هنگام پاک‌سازی نسخه پشتیبان :application_name خطایی رخ داده است.',
'cleanup_successful_subject' => 'پاک‌سازی نسخه پشتیبان :application_name با موفقیت انجام شد.',
'cleanup_successful_subject_title' => 'پاک‌سازی نسخه پشتیبان!',
'cleanup_successful_body' => 'پاک‌سازی نسخه پشتیبان :application_name روی دیسک :disk_name با موفقیت انجام شد.',
'healthy_backup_found_subject' => 'نسخه پشتیبان :application_name روی دیسک :disk_name سالم بود.',
'healthy_backup_found_subject_title' => 'نسخه پشتیبان :application_name سالم بود.',
'healthy_backup_found_body' => 'نسخه پشتیبان :application_name به نظر سالم میاد. دمت گرم!',
'unhealthy_backup_found_subject' => 'خبر مهم: نسخه پشتیبان :application_name سالم نبود.',
'unhealthy_backup_found_subject_title' => 'خبر مهم: نسخه پشتیبان :application_name سالم نبود. :problem',
'unhealthy_backup_found_body' => 'نسخه پشتیبان :application_name روی دیسک :disk_name سالم نبود.',
'unhealthy_backup_found_not_reachable' => 'مقصد پشتیبان‌گیری در دسترس نبود. :error',
'unhealthy_backup_found_empty' => 'برای این برنامه هیچ نسخه پشتیبانی وجود ندارد.',
'unhealthy_backup_found_old' => 'آخرین نسخه پشتیبان برای تاریخ :date است، که به نظر خیلی قدیمی میاد. ',
'unhealthy_backup_found_unknown' => 'متاسفانه دلیل دقیقی قابل تعیین نیست.',
'unhealthy_backup_found_full' => 'نسخه‌های پشتیبان حجم زیادی اشغال کرده‌اند. میزان دیسک استفاده‌شده :disk_usage است که از میزان مجاز :disk_limit فراتر رفته است. ',
'no_backups_info' => 'هنوز نسخه پشتیبان تهیه نشده است',
'application_name' => 'نام نرم‌افزار',
'backup_name' => 'نام نسخه پشتیبان',
'disk' => 'دیسک',
'newest_backup_size' => 'اندازه جدیدترین نسخه پشتیبان',
'number_of_backups' => 'تعداد نسخه‌های پشتیبان',
'total_storage_used' => 'کل فضای ذخیره‌سازی استفاده‌شده',
'newest_backup_date' => 'تاریخ جدیدترین نسخه پشتیبان',
'oldest_backup_date' => 'تاریخ قدیمی‌ترین نسخه پشتیبان',
];
+45
View File
@@ -0,0 +1,45 @@
<?php
return [
'exception_message' => 'Virheilmoitus: :message',
'exception_trace' => 'Virhe, jäljitys: :trace',
'exception_message_title' => 'Virheilmoitus',
'exception_trace_title' => 'Virheen jäljitys',
'backup_failed_subject' => ':application_name varmuuskopiointi epäonnistui',
'backup_failed_body' => 'HUOM!: :application_name varmuuskoipionnissa tapahtui virhe',
'backup_successful_subject' => ':application_name varmuuskopioitu onnistuneesti',
'backup_successful_subject_title' => 'Uusi varmuuskopio!',
'backup_successful_body' => 'Hyviä uutisia! :application_name on varmuuskopioitu levylle :disk_name.',
'cleanup_failed_subject' => ':application_name varmuuskopioiden poistaminen epäonnistui.',
'cleanup_failed_body' => ':application_name varmuuskopioiden poistamisessa tapahtui virhe.',
'cleanup_successful_subject' => ':application_name varmuuskopiot poistettu onnistuneesti',
'cleanup_successful_subject_title' => 'Varmuuskopiot poistettu onnistuneesti!',
'cleanup_successful_body' => ':application_name varmuuskopiot poistettu onnistuneesti levyltä :disk_name.',
'healthy_backup_found_subject' => ':application_name varmuuskopiot levyllä :disk_name ovat kunnossa',
'healthy_backup_found_subject_title' => ':application_name varmuuskopiot ovat kunnossa',
'healthy_backup_found_body' => ':application_name varmuuskopiot ovat kunnossa. Hieno homma!',
'unhealthy_backup_found_subject' => 'HUOM!: :application_name varmuuskopiot ovat vialliset',
'unhealthy_backup_found_subject_title' => 'HUOM!: :application_name varmuuskopiot ovat vialliset. :problem',
'unhealthy_backup_found_body' => ':application_name varmuuskopiot levyllä :disk_name ovat vialliset.',
'unhealthy_backup_found_not_reachable' => 'Varmuuskopioiden kohdekansio ei ole saatavilla. :error',
'unhealthy_backup_found_empty' => 'Tästä sovelluksesta ei ole varmuuskopioita.',
'unhealthy_backup_found_old' => 'Viimeisin varmuuskopio, luotu :date, on liian vanha.',
'unhealthy_backup_found_unknown' => 'Virhe, tarkempaa tietoa syystä ei valitettavasti ole saatavilla.',
'unhealthy_backup_found_full' => 'Varmuuskopiot vievät liikaa levytilaa. Tällä hetkellä käytössä :disk_usage, mikä on suurempi kuin sallittu tilavuus (:disk_limit).',
'no_backups_info' => 'Varmuuskopioita ei vielä tehty',
'application_name' => 'Sovelluksen nimi',
'backup_name' => 'Varmuuskopion nimi',
'disk' => 'Levy',
'newest_backup_size' => 'Uusin varmuuskopion koko',
'number_of_backups' => 'Varmuuskopioiden määrä',
'total_storage_used' => 'Käytetty tallennustila yhteensä',
'newest_backup_date' => 'Uusin varmuuskopion koko',
'oldest_backup_date' => 'Vanhin varmuuskopion koko',
];

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